Browse Source
feat(repairSkill): 新增维修技能卡管理和技能内容维护功能
feat(repairSkill): 新增维修技能卡管理和技能内容维护功能
- 创建维修技能卡CRUD接口及人员选择、打分管理功能 - 实现技能卡列表展示、统计信息和筛选查询功能 - 开发技能卡明细查看、打分标准展示和批量打分功能 - 新增选择维修模板功能,支持批量添加技能明细 - 实现技能内容库管理,包含技能编码、名称和分值设置 - 添加技能内容的分数标准配置和状态管理功能 - 更新依赖包版本,调整sass相关配置以解决构建问题master
7 changed files with 1192 additions and 2 deletions
-
4package.json
-
17src/api/repairSkill/maintenanceSkillCard.js
-
11src/api/repairSkill/repairSkillContent.js
-
10src/api/repairSkill/repairSkillTemplate.js
-
587src/views/modules/repairSkill/maintenanceSkillCard.vue
-
261src/views/modules/repairSkill/repairSkillContent.vue
-
304src/views/modules/repairSkill/repairSkillTemplate.vue
@ -0,0 +1,17 @@ |
|||
import { createAPI } from '@/utils/httpRequest.js' |
|||
|
|||
// 技能卡 CRUD
|
|||
export const searchCardList = data => createAPI('/repairSkill/card/search', 'POST', data) |
|||
export const createCard = data => createAPI('/repairSkill/card/save', 'POST', data) |
|||
export const deleteCard = data => createAPI('/repairSkill/card/delete', 'POST', data) |
|||
|
|||
// 卡片技能明细(技能类型)
|
|||
export const getCardDetails = data => createAPI('/repairSkill/card/detail/search', 'POST', data) |
|||
export const addCardDetails = data => createAPI('/repairSkill/card/detail/save', 'POST', data) |
|||
export const removeCardDetail = data => createAPI('/repairSkill/card/detail/remove', 'POST', data) |
|||
|
|||
// 打分 / 更新掌握情况(批量:传入明细数组,后端同一事务处理,逐行保存传单元素数组)
|
|||
export const scoreCardDetail = data => createAPI('/repairSkill/card/score', 'POST', data) |
|||
|
|||
// 人员选择(本模块自建接口,直查 sys_user;一次返回 rows=人员 + roles=角色下拉)
|
|||
export const searchMachineAdmin = data => createAPI('/repairSkill/card/adminSearch', 'POST', data) |
|||
@ -0,0 +1,11 @@ |
|||
import { createAPI } from '@/utils/httpRequest.js' |
|||
|
|||
// 技能内容(技能类型库)CRUD
|
|||
export const searchContentList = data => createAPI('/repairSkill/content/search', 'POST', data) |
|||
export const createContent = data => createAPI('/repairSkill/content/save', 'POST', data) |
|||
export const updateContent = data => createAPI('/repairSkill/content/edit', 'POST', data) |
|||
export const deleteContent = data => createAPI('/repairSkill/content/delete', 'POST', data) |
|||
|
|||
// 分数标准(一个技能多条)
|
|||
export const getCriteriaList = data => createAPI('/repairSkill/content/criteria/search', 'POST', data) |
|||
export const saveCriteriaList = data => createAPI('/repairSkill/content/criteria/save', 'POST', data) |
|||
@ -0,0 +1,10 @@ |
|||
import { createAPI } from '@/utils/httpRequest.js' |
|||
|
|||
// 维修技能模板 CRUD
|
|||
export const searchTemplateList = data => createAPI('/repairSkill/template/search', 'POST', data) |
|||
export const createTemplate = data => createAPI('/repairSkill/template/save', 'POST', data) |
|||
export const updateTemplate = data => createAPI('/repairSkill/template/edit', 'POST', data) |
|||
export const deleteTemplate = data => createAPI('/repairSkill/template/delete', 'POST', data) |
|||
|
|||
// 模板明细(模板关联的维修明细项)
|
|||
export const getTemplateDetails = data => createAPI('/repairSkill/template/detail/search', 'POST', data) |
|||
@ -0,0 +1,587 @@ |
|||
<template> |
|||
<div class="card-container" v-loading="pageLoading"> |
|||
<!-- 页面标题和统计 --> |
|||
<div class="page-header"> |
|||
<div class="header-left"> |
|||
<h2 class="page-title"><i class="el-icon-postcard"></i> 维修技能卡</h2> |
|||
<p class="page-subtitle">机修人员技能掌握与打分管理</p> |
|||
</div> |
|||
<div class="header-right"> |
|||
<div class="stat-cards"> |
|||
<div class="stat-card stat-total"> |
|||
<div class="stat-icon"><i class="el-icon-user"></i></div> |
|||
<div class="stat-content"> |
|||
<div class="stat-value">{{ totalPage }}</div> |
|||
<div class="stat-label">技能卡总数</div> |
|||
</div> |
|||
</div> |
|||
<div class="stat-card stat-score"> |
|||
<div class="stat-icon"><i class="el-icon-trophy"></i></div> |
|||
<div class="stat-content"> |
|||
<div class="stat-value">{{ avgScore }}</div> |
|||
<div class="stat-label">平均总分</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 筛选条件 --> |
|||
<div class="search-section"> |
|||
<el-collapse class="no-arrow" v-model="searchExpanded"> |
|||
<el-collapse-item name="1"> |
|||
<template slot="title"> |
|||
<i class="el-icon-search"></i> |
|||
<span style="margin-left: 8px; font-weight: 500;">筛选条件</span> |
|||
<el-button v-if="canCardEdit" type="primary" size="mini" round icon="el-icon-plus" style="margin-left: 12px" @click.stop="openAdminDialog">新增技能卡</el-button> |
|||
</template> |
|||
<el-form :inline="true" label-position="top" class="search-form"> |
|||
<el-form-item label="人员姓名"> |
|||
<el-input v-model="searchData.adminName" placeholder="支持模糊查询" clearable style="width: 160px"></el-input> |
|||
</el-form-item> |
|||
<el-form-item label=" "> |
|||
<el-button @click="getDataList('Y')" type="primary" icon="el-icon-search">查询</el-button> |
|||
<el-button @click="resetQuery()" type="default" icon="el-icon-refresh-left">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</el-collapse-item> |
|||
</el-collapse> |
|||
</div> |
|||
|
|||
<!-- 卡片列表 --> |
|||
<div class="cards-container" v-loading="dataListLoading"> |
|||
<transition-group name="card-list" tag="div" class="cards-grid"> |
|||
<div v-for="item in dataList" :key="item.adminId" class="skill-card" @click="openDetailDialog(item)"> |
|||
<div class="card-header"> |
|||
<div class="card-title-area"> |
|||
<i class="el-icon-user-solid head-icon"></i> |
|||
<span class="admin-name">{{ item.adminName }}</span> |
|||
</div> |
|||
<div class="card-tags"> |
|||
<el-tag size="small" :type="(item.skillCount || 0) === 0 ? 'info' : ((item.unmasteredCount || 0) > 0 ? 'warning' : 'success')" effect="dark">{{ (item.skillCount || 0) === 0 ? '暂无技能' : ((item.unmasteredCount || 0) > 0 ? ('待掌握 ' + item.unmasteredCount) : '已全部掌握') }}</el-tag> |
|||
<el-tag size="medium" effect="dark" class="score-tag">总分 {{ formatScore(item.totalScore) }}</el-tag> |
|||
</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="detail-row"> |
|||
<span class="detail-label"><i class="el-icon-postcard"></i>人员编号</span> |
|||
<span class="detail-value">{{ item.adminId }}</span> |
|||
</div> |
|||
<div class="detail-row"> |
|||
<span class="detail-label"><i class="el-icon-circle-check"></i>技能项数</span> |
|||
<span class="detail-value">{{ (item.detailList && item.detailList.length) || item.skillCount || 0 }}</span> |
|||
</div> |
|||
</div> |
|||
<div class="progress-block"> |
|||
<div class="progress-text"> |
|||
<span><i class="el-icon-data-line"></i> 掌握进度</span> |
|||
<span>{{ item.masteredCount || 0 }} / {{ item.skillCount || 0 }}</span> |
|||
</div> |
|||
<el-progress :percentage="masteryPercent(item)" :color="masteryPercent(item) === 100 ? '#67C23A' : '#409EFF'" :stroke-width="10" /> |
|||
</div> |
|||
<div class="card-footer"> |
|||
<div class="current-step"><i class="el-icon-time"></i>{{ item.createDate }}</div> |
|||
<div class="action-buttons"> |
|||
<el-button type="primary" size="mini" plain icon="el-icon-view" @click.stop="openDetailDialog(item)">查看/打分</el-button> |
|||
<el-button v-if="canCardEdit" type="danger" size="mini" plain icon="el-icon-delete" @click.stop="removeCard(item)">删除</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</transition-group> |
|||
|
|||
<div v-if="!dataListLoading && dataList.length === 0" class="empty-state"> |
|||
<i class="el-icon-postcard empty-icon"></i> |
|||
<p class="empty-text">暂无技能卡</p> |
|||
<p class="empty-subtext" v-if="canCardEdit">点击“新增技能卡”按钮,为人员创建技能卡</p> |
|||
<p class="empty-subtext" v-else>暂无技能卡数据</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-wrapper" v-if="dataList.length > 0"> |
|||
<el-pagination |
|||
@size-change="sizeChangeHandle" |
|||
@current-change="currentChangeHandle" |
|||
:current-page="pageIndex" |
|||
:page-sizes="[12, 24, 48]" |
|||
:page-size="pageSize" |
|||
:total="totalPage" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
background> |
|||
</el-pagination> |
|||
</div> |
|||
|
|||
<!-- 技能卡明细弹窗 --> |
|||
<el-dialog :title="detailTitle" :visible.sync="detailVisible" width="1100px" top="5vh" :close-on-click-modal="false" v-drag> |
|||
<el-descriptions :column="3" border size="small" style="margin-bottom:10px"> |
|||
<el-descriptions-item label="人员姓名">{{ currentCard.adminName }}</el-descriptions-item> |
|||
<el-descriptions-item label="总分"> |
|||
<el-tag type="warning">{{ formatScore(currentCard.totalScore) }}</el-tag> |
|||
</el-descriptions-item> |
|||
</el-descriptions> |
|||
|
|||
<div class="detail-toolbar" v-if="canCardEdit"> |
|||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openTemplatePicker">选择维修模板</el-button> |
|||
</div> |
|||
|
|||
<el-table :data="details" class="editable-table" border size="mini" max-height="440" v-loading="detailLoading" highlight-current-row @row-click="onDetailRowClick"> |
|||
<el-table-column type="index" label="#" width="45" align="center" /> |
|||
<el-table-column prop="skillName" label="技能名称" min-width="140" show-overflow-tooltip header-align="center" align="left" /> |
|||
<el-table-column prop="scoreDesc" label="分值描述" min-width="160" show-overflow-tooltip header-align="center" align="left" /> |
|||
<el-table-column label="掌握情况" width="130" header-align="center" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-select v-if="canScore" v-model="scope.row.masteryStatus" style="width:110px" @change="onMasteryChange(scope.row)"> |
|||
<el-option label="是" value="Y" /> |
|||
<el-option label="否" value="N" /> |
|||
</el-select> |
|||
<span v-else>{{ masteryText(scope.row.masteryStatus) }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="分数" width="100" header-align="center" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-input-number v-if="canScore" v-model="scope.row.score" :controls="false" :min="0" :max="scope.row.maxScore || 100000" style="width:80px" /> |
|||
<span v-else>{{ scope.row.score == null ? '-' : scope.row.score }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="掌握时间" width="150" header-align="center" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-date-picker v-if="canScore" v-model="scope.row.masterDate" type="date" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择日期" style="width:135px" /> |
|||
<span v-else>{{ formatDate(scope.row.masterDate) }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column v-if="canCardEdit" label="操作" width="80" header-align="center" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-button type="text" size="mini" style="color:#F56C6C" @click="removeDetail(scope.row)">删除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<div style="margin-top:12px"> |
|||
<div style="font-weight:600;margin-bottom:6px"> |
|||
<i class="el-icon-notebook-2"></i> 打分标准<span v-if="selectedSkillName"> - {{ selectedSkillName }}</span> |
|||
<span v-else style="color:#909399;font-weight:400;font-size:12px">(点击上方明细行查看对应标准)</span> |
|||
</div> |
|||
<el-table :data="criteriaList" size="mini" border max-height="200" v-loading="criteriaLoading"> |
|||
<el-table-column type="index" label="#" width="45" align="center" /> |
|||
<el-table-column prop="criteriaDesc" label="标准描述" min-width="360" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="score" label="分数" width="100" header-align="center" align="center" /> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button v-if="canScore" type="primary" :loading="detailSaving" @click="saveAllDetails">保存</el-button> |
|||
<el-button @click="detailVisible=false">关闭</el-button> |
|||
</span> |
|||
</el-dialog> |
|||
|
|||
<!-- 选择机修人员 建卡 --> |
|||
<el-dialog title="选择人员" :visible.sync="adminVisible" width="800px" top="8vh" :close-on-click-modal="false" v-drag> |
|||
<el-form :inline="true" label-position="top"> |
|||
<el-form-item label="所属角色"> |
|||
<el-select v-model="adminQuery.roleId" placeholder="请选择" clearable style="width:160px" @change="loadAdmins"> |
|||
<el-option v-for="r in roleList" :key="r.roleId" :label="r.roleName" :value="r.roleId" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="姓名"> |
|||
<el-input v-model="adminQuery.adminName" clearable style="width:160px" @keyup.enter.native="loadAdmins" /> |
|||
</el-form-item> |
|||
<el-form-item label=" "> |
|||
<el-button type="primary" icon="el-icon-search" @click="loadAdmins">查询</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
<el-table :data="adminList" height="400" size="mini" border v-loading="adminLoading" @row-click="pickAdmin" highlight-current-row> |
|||
<el-table-column prop="adminID" label="账号" width="160" header-align="center" align="left" /> |
|||
<el-table-column prop="adminName" label="姓名" width="160" header-align="center" align="left" /> |
|||
<el-table-column prop="roleName" label="角色" min-width="200" header-align="center" align="left" show-overflow-tooltip /> |
|||
</el-table> |
|||
<div style="margin-top:6px;color:#909399;font-size:12px">提示:点击行选择人员并创建技能卡(一人一卡)</div> |
|||
</el-dialog> |
|||
|
|||
<!-- 选择维修模板 --> |
|||
<el-dialog title="选择维修模板" :visible.sync="tplPickerVisible" width="820px" top="8vh" :close-on-click-modal="false" v-drag> |
|||
<el-form :inline="true" label-position="top"> |
|||
<el-form-item label="模板名称"> |
|||
<el-input v-model="tplQuery.templateName" clearable style="width:180px" @keyup.enter.native="loadTemplates" /> |
|||
</el-form-item> |
|||
<el-form-item label=" "> |
|||
<el-button type="primary" icon="el-icon-search" @click="loadTemplates">查询</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
<el-table :data="templateList" height="420" size="mini" border v-loading="tplLoading" @row-click="confirmAddTemplate" highlight-current-row> |
|||
<el-table-column prop="templateCode" label="模板编码" width="150" header-align="center" align="left" /> |
|||
<el-table-column prop="templateName" label="模板名称" min-width="180" header-align="center" align="left" show-overflow-tooltip /> |
|||
</el-table> |
|||
<div style="margin-top:6px;color:#909399;font-size:12px">提示:点击模板行,将该模板下的所有维修明细加入当前技能卡</div> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
searchCardList, |
|||
createCard, |
|||
deleteCard, |
|||
getCardDetails, |
|||
addCardDetails, |
|||
removeCardDetail, |
|||
scoreCardDetail, |
|||
searchMachineAdmin |
|||
} from '@/api/repairSkill/maintenanceSkillCard.js' |
|||
import { searchTemplateList } from '@/api/repairSkill/repairSkillTemplate.js' |
|||
import { getCriteriaList } from '@/api/repairSkill/repairSkillContent.js' |
|||
|
|||
const MASTERY_TEXT = { Y: '是', N: '否' } |
|||
|
|||
export default { |
|||
name: 'MaintenanceSkillCard', |
|||
data () { |
|||
return { |
|||
pageLoading: false, |
|||
dataListLoading: false, |
|||
searchExpanded: ['0'], |
|||
dataList: [], |
|||
pageIndex: 1, |
|||
pageSize: 12, |
|||
totalPage: 0, |
|||
searchData: { |
|||
site: this.$store.state.user.site, |
|||
adminName: '' |
|||
}, |
|||
// 明细弹窗 |
|||
detailVisible: false, |
|||
detailLoading: false, |
|||
detailSaving: false, |
|||
currentCard: {}, |
|||
details: [], |
|||
// 打分标准(点击明细行联动) |
|||
criteriaList: [], |
|||
criteriaLoading: false, |
|||
selectedSkillName: '', |
|||
// 建卡 |
|||
adminVisible: false, |
|||
adminLoading: false, |
|||
adminList: [], |
|||
roleList: [], |
|||
adminQuery: { adminName: '', roleId: '' }, |
|||
// 选择模板 |
|||
tplPickerVisible: false, |
|||
tplLoading: false, |
|||
templateList: [], |
|||
tplQuery: { templateName: '' } |
|||
} |
|||
}, |
|||
computed: { |
|||
// 特殊权限:拥有 repairSkill:card:edit 才能编辑/打分,否则只读 |
|||
canCardEdit () { return this.isAuth('repairSkill:card:edit') }, |
|||
canScore () { return this.isAuth('repairSkill:card:edit') }, |
|||
detailTitle () { return '技能卡明细 - ' + (this.currentCard.adminName || '') }, |
|||
avgScore () { |
|||
if (!this.dataList.length) return '0' |
|||
const sum = this.dataList.reduce((s, i) => s + (Number(i.totalScore) || 0), 0) |
|||
return (sum / this.dataList.length).toFixed(1) |
|||
} |
|||
}, |
|||
activated () { this.getDataList() }, |
|||
mounted () { this.getDataList() }, |
|||
methods: { |
|||
formatScore (v) { return (Number(v) || 0).toFixed(0) }, |
|||
formatDate (v) { return v ? String(v).substring(0, 10) : '-' }, |
|||
masteryText (v) { return MASTERY_TEXT[v] || (v || '-') }, |
|||
masteryPercent (item) { |
|||
const total = Number(item.skillCount) || 0 |
|||
if (!total) return 0 |
|||
return Math.round((Number(item.masteredCount) || 0) / total * 100) |
|||
}, |
|||
todayStr () { |
|||
const d = new Date() |
|||
const p = n => (n < 10 ? '0' + n : '' + n) |
|||
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' 00:00:00' |
|||
}, |
|||
onMasteryChange (row) { |
|||
// 选择“是”时,若未填掌握时间则默认今天 |
|||
if (row.masteryStatus === 'Y' && !row.masterDate) { |
|||
row.masterDate = this.todayStr() |
|||
} |
|||
}, |
|||
getDataList (flag) { |
|||
if (flag === 'Y') this.pageIndex = 1 |
|||
this.dataListLoading = true |
|||
const params = { ...this.searchData, page: this.pageIndex, limit: this.pageSize } |
|||
searchCardList(params).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0') && data.page) { |
|||
this.dataList = data.page.list || [] |
|||
this.totalPage = data.page.totalCount || 0 |
|||
} else { |
|||
this.dataList = [] |
|||
this.totalPage = 0 |
|||
} |
|||
}).catch(() => { this.dataList = []; this.totalPage = 0 }) |
|||
.finally(() => { this.dataListLoading = false }) |
|||
}, |
|||
resetQuery () { |
|||
this.searchData.adminName = '' |
|||
this.getDataList('Y') |
|||
}, |
|||
sizeChangeHandle (v) { this.pageSize = v; this.pageIndex = 1; this.getDataList() }, |
|||
currentChangeHandle (v) { this.pageIndex = v; this.getDataList() }, |
|||
|
|||
// ===== 明细 ===== |
|||
openDetailDialog (card) { |
|||
this.currentCard = { ...card } |
|||
this.details = [] |
|||
this.criteriaList = [] |
|||
this.selectedSkillName = '' |
|||
this.detailVisible = true |
|||
this.loadDetails() |
|||
}, |
|||
// 点击打分明细行,联动加载该技能对应的打分标准 |
|||
onDetailRowClick (row) { |
|||
if (!row) return |
|||
this.selectedSkillName = row.skillName || '' |
|||
this.criteriaList = [] |
|||
const skillCode = row.skillContentId |
|||
if (!skillCode) { return } |
|||
this.criteriaLoading = true |
|||
getCriteriaList({ skillCode, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.criteriaList = data.obj || [] |
|||
} else { |
|||
this.criteriaList = [] |
|||
} |
|||
}).catch(() => { this.criteriaList = [] }) |
|||
.finally(() => { this.criteriaLoading = false }) |
|||
}, |
|||
loadDetails () { |
|||
this.detailLoading = true |
|||
getCardDetails({ cardId: this.currentCard.adminId, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.details = data.obj || [] |
|||
} else { |
|||
this.details = [] |
|||
} |
|||
}).catch(() => { this.details = [] }) |
|||
.finally(() => { this.detailLoading = false }) |
|||
}, |
|||
saveAllDetails () { |
|||
if (!this.details || !this.details.length) { this.$message.info('没有可保存的明细'); return } |
|||
this.detailSaving = true |
|||
this.submitScore(this.details).finally(() => { this.detailSaving = false }) |
|||
}, |
|||
// 统一提交打分:一次请求(后端同一事务,仅重算一次总分) |
|||
submitScore (rows) { |
|||
const site = this.$store.state.user.site |
|||
const payload = rows.map(row => ({ |
|||
site, |
|||
cardId: row.cardId, |
|||
skillContentId: row.skillContentId, |
|||
masteryStatus: row.masteryStatus, |
|||
score: row.score, |
|||
masterDate: row.masterDate, |
|||
remarks: row.remarks, |
|||
scoreBy: this.$store.state.user.name |
|||
})) |
|||
return scoreCardDetail(payload).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('保存成功') |
|||
this.loadDetails() |
|||
this.refreshCurrentTotal() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '保存失败') |
|||
} |
|||
}).catch(e => this.$message.error('保存失败: ' + (e && e.message ? e.message : e))) |
|||
}, |
|||
refreshCurrentTotal () { |
|||
// 重新拉取当前卡片总分 |
|||
searchCardList({ site: this.$store.state.user.site, adminId: this.currentCard.adminId, page: 1, limit: 1 }).then(({ data }) => { |
|||
if (data && data.page && data.page.list && data.page.list.length) { |
|||
const c = data.page.list[0] |
|||
this.currentCard = { ...this.currentCard, totalScore: c.totalScore, skillCount: c.skillCount, masteredCount: c.masteredCount, unmasteredCount: c.unmasteredCount } |
|||
const idx = this.dataList.findIndex(x => x.adminId === c.adminId) |
|||
if (idx > -1) this.$set(this.dataList, idx, { ...this.dataList[idx], ...c }) |
|||
} |
|||
}).catch(() => {}) |
|||
}, |
|||
removeDetail (row) { |
|||
this.$confirm('确认从技能卡删除该技能?', '提示', { type: 'warning' }).then(() => { |
|||
removeCardDetail({ site: this.$store.state.user.site, cardId: row.cardId, skillContentId: row.skillContentId }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('删除成功') |
|||
this.loadDetails() |
|||
this.refreshCurrentTotal() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '删除失败') |
|||
} |
|||
}).catch(e => this.$message.error('删除失败: ' + e.message)) |
|||
}).catch(() => {}) |
|||
}, |
|||
|
|||
// ===== 建卡 ===== |
|||
openAdminDialog () { |
|||
this.adminVisible = true |
|||
this.adminQuery = { adminName: '', roleId: '' } |
|||
this.roleList = [] |
|||
this.adminList = [] |
|||
// 一次 adminSearch 同时拿角色+人员(均来自 sys_user),再默认选中“机修” |
|||
this.fetchAdminData(true).then(() => { |
|||
const machine = this.roleList.find(r => r.roleName === '机修') || |
|||
this.roleList.find(r => r.roleName && r.roleName.indexOf('机修') !== -1) |
|||
if (machine) { |
|||
this.adminQuery.roleId = machine.roleId |
|||
this.fetchAdminData(false) |
|||
} |
|||
}) |
|||
}, |
|||
// withRoles=true 时同时刷新角色下拉(仅初始化);否则只刷新人员列表 |
|||
fetchAdminData (withRoles) { |
|||
this.adminLoading = true |
|||
const params = { site: this.$store.state.user.site, adminName: this.adminQuery.adminName, roleId: this.adminQuery.roleId } |
|||
return searchMachineAdmin(params).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
if (withRoles) this.roleList = data.roles || [] |
|||
this.adminList = data.rows || [] |
|||
} else { |
|||
if (withRoles) this.roleList = [] |
|||
this.adminList = [] |
|||
} |
|||
}).catch(() => { this.adminList = [] }) |
|||
.finally(() => { this.adminLoading = false }) |
|||
}, |
|||
loadAdmins () { |
|||
this.fetchAdminData(false) |
|||
}, |
|||
pickAdmin (row) { |
|||
if (this._pickLock) return |
|||
this._pickLock = true |
|||
// 同时触发 loading 遮罩,彻底阻断后续点击 |
|||
this.adminLoading = true |
|||
const payload = { |
|||
site: this.$store.state.user.site, |
|||
adminId: row.adminID, |
|||
adminName: row.adminName, |
|||
createBy: this.$store.state.user.name |
|||
} |
|||
createCard(payload).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('创建成功') |
|||
this.adminVisible = false |
|||
this.getDataList('Y') |
|||
} else { |
|||
this.$message.error((data && data.msg) || '创建失败') |
|||
this.adminLoading = false |
|||
} |
|||
}).catch(e => { |
|||
this.$message.error('创建失败: ' + e.message) |
|||
this.adminLoading = false |
|||
}).finally(() => { this._pickLock = false }) |
|||
}, |
|||
removeCard (card) { |
|||
this.$confirm('确认删除 ' + card.adminName + ' 的技能卡?将同时删除其技能明细。', '提示', { type: 'warning' }).then(() => { |
|||
deleteCard({ adminId: card.adminId, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('删除成功') |
|||
this.getDataList() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '删除失败') |
|||
} |
|||
}).catch(e => this.$message.error('删除失败: ' + e.message)) |
|||
}).catch(() => {}) |
|||
}, |
|||
|
|||
// ===== 选择维修模板 ===== |
|||
openTemplatePicker () { |
|||
this.tplPickerVisible = true |
|||
this.tplQuery = { templateName: '' } |
|||
this.loadTemplates() |
|||
}, |
|||
loadTemplates () { |
|||
this.tplLoading = true |
|||
const params = { site: this.$store.state.user.site, templateName: this.tplQuery.templateName, status: 'Y', page: 1, limit: 1000 } |
|||
searchTemplateList(params).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0') && data.page) { |
|||
this.templateList = data.page.list || [] |
|||
} else { |
|||
this.templateList = [] |
|||
} |
|||
}).catch(() => { this.templateList = [] }) |
|||
.finally(() => { this.tplLoading = false }) |
|||
}, |
|||
confirmAddTemplate (row) { |
|||
if (this._tplLock) return |
|||
this._tplLock = true |
|||
this.tplLoading = true |
|||
const payload = { |
|||
site: this.$store.state.user.site, |
|||
cardId: this.currentCard.adminId, |
|||
templateId: row.templateCode, |
|||
createBy: this.$store.state.user.name |
|||
} |
|||
addCardDetails(payload).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success(data.msg || '添加成功') |
|||
this.tplPickerVisible = false |
|||
this.loadDetails() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '添加失败') |
|||
this.tplLoading = false |
|||
} |
|||
}).catch(e => { |
|||
this.$message.error('添加失败: ' + e.message) |
|||
this.tplLoading = false |
|||
}).finally(() => { this._tplLock = false }) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.card-container { padding: 15px; background: #f5f7fa; min-height: calc(100vh - 80px); } |
|||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding: 5px 20px; background: #fff; border-radius: 4px; border: 1px solid #EBEEF5; } |
|||
.page-title { margin: 0; font-size: 20px; font-weight: 600; color: #303133; display: flex; align-items: center; gap: 10px; } |
|||
.page-title i { font-size: 22px; color: #409EFF; } |
|||
.page-subtitle { margin: 6px 0 0 0; font-size: 13px; color: #909399; } |
|||
.header-right { display: flex; align-items: center; gap: 12px; } |
|||
.stat-cards { display: flex; gap: 12px; } |
|||
.stat-card { display: flex; align-items: center; gap: 12px; padding: 12px 20px; background: #fff; border-radius: 4px; border: 1px solid #EBEEF5; min-width: 130px; } |
|||
.stat-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 4px; font-size: 20px; color: #fff; } |
|||
.stat-total .stat-icon { background: #409EFF; } |
|||
.stat-score .stat-icon { background: #E6A23C; } |
|||
.stat-content { display: flex; flex-direction: column; } |
|||
.stat-value { font-size: 24px; font-weight: 600; color: #303133; line-height: 1; } |
|||
.stat-label { font-size: 12px; color: #909399; margin-top: 4px; } |
|||
.search-section { margin-bottom: 15px; background: #fff; border-radius: 4px; border: 1px solid #EBEEF5; overflow: hidden; } |
|||
.search-section >>> .el-collapse-item__header { padding: 0 15px; height: 45px; line-height: 45px; background: #fff; border-bottom: 1px solid #ebeef5; font-size: 13px; color: #303133; font-weight: 500; } |
|||
.search-section >>> .el-collapse-item__content { padding: 15px; } |
|||
.search-form >>> .el-form-item { margin-bottom: 10px; } |
|||
.cards-container { min-height: 300px; position: relative; } |
|||
.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 15px; margin-bottom: 15px; } |
|||
.skill-card { background: #fff; border-radius: 4px; padding: 16px; border: 1px solid #EBEEF5; transition: all .3s ease; cursor: pointer; position: relative; } |
|||
.skill-card:hover { border-color: #409EFF; box-shadow: 0 4px 12px rgba(64,158,255,.15); transform: translateY(-2px); } |
|||
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid #EBEEF5; } |
|||
.card-title-area { display: flex; align-items: center; gap: 8px; } |
|||
.head-icon { font-size: 20px; color: #409EFF; } |
|||
.admin-name { font-size: 16px; font-weight: 600; color: #303133; } |
|||
.score-tag { font-weight: 600; } |
|||
.card-tags { display: flex; align-items: center; gap: 8px; } |
|||
.progress-block { margin-bottom: 12px; } |
|||
.progress-text { display: flex; justify-content: space-between; font-size: 12px; color: #909399; margin-bottom: 4px; } |
|||
.progress-text i { color: #409EFF; margin-right: 3px; } |
|||
.card-body { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; } |
|||
.detail-row { display: flex; justify-content: space-between; align-items: center; font-size: 13px; padding: 6px 10px; background: #F5F7FA; border-radius: 3px; } |
|||
.detail-label { color: #909399; display: flex; align-items: center; gap: 5px; font-weight: 500; } |
|||
.detail-label i { color: #409EFF; font-size: 14px; } |
|||
.detail-value { color: #606266; font-weight: 500; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
|||
.card-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 10px; border-top: 1px solid #EBEEF5; } |
|||
.current-step { font-size: 12px; color: #909399; display: flex; align-items: center; gap: 5px; } |
|||
.current-step i { color: #409EFF; } |
|||
.action-buttons { display: flex; gap: 8px; } |
|||
.detail-toolbar { margin-bottom: 8px; } |
|||
.empty-state { text-align: center; padding: 80px 20px; background: #fff; border-radius: 4px; border: 1px solid #EBEEF5; } |
|||
.empty-icon { font-size: 64px; color: #c0c4cc; margin-bottom: 12px; } |
|||
.empty-text { font-size: 15px; color: #606266; margin: 0 0 6px 0; font-weight: 500; } |
|||
.empty-subtext { font-size: 13px; color: #909399; margin: 0; } |
|||
.pagination-wrapper { display: flex; justify-content: center; padding: 15px; background: #fff; border-radius: 4px; border: 1px solid #EBEEF5; } |
|||
/deep/ .no-arrow .el-collapse-item__header .el-collapse-item__arrow { display: none !important; } |
|||
/* 覆盖全局 .el-table .cell 固定高度,避免单元格内下拉/数字框/日期控件被裁切 */ |
|||
.editable-table /deep/ .cell { height: auto; line-height: 1.5; overflow: visible; } |
|||
.editable-table /deep/ td { padding: 4px 0; } |
|||
</style> |
|||
@ -0,0 +1,261 @@ |
|||
<template> |
|||
<div class="customer-css"> |
|||
<!-- 查询条件 --> |
|||
<el-form :inline="true" label-position="top" class="rs-search-form"> |
|||
<el-form-item label="技能编码"> |
|||
<el-input v-model="searchData.skillCode" style="width:150px" clearable @keyup.enter.native="getList" /> |
|||
</el-form-item> |
|||
<el-form-item label="技能名称"> |
|||
<el-input v-model="searchData.skillName" style="width:200px" clearable @keyup.enter.native="getList" /> |
|||
</el-form-item> |
|||
<el-form-item label=" "> |
|||
<el-button type="primary" class="customer-bun-min" icon="el-icon-search" @click="getList">查询</el-button> |
|||
<el-button v-if="canEdit" type="success" class="customer-bun-min" icon="el-icon-plus" @click="openDialog()">新增</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<!-- 技能内容列表 --> |
|||
<el-table :data="dataList" border highlight-current-row v-loading="loading" style="width:100%;margin-bottom:8px"> |
|||
<el-table-column v-if="canEdit" label="操作" header-align="center" align="center" width="120"> |
|||
<template slot-scope="scope"> |
|||
<a class="customer-a" @click.stop="openDialog(scope.row)">编辑 |</a> |
|||
<a class="customer-a" @click.stop="removeRow(scope.row)">删除</a> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column type="index" label="#" width="50" align="center" /> |
|||
<el-table-column prop="skillCode" label="技能编码" min-width="120" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="skillName" label="技能名称" min-width="180" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="scoreDesc" label="分值描述" min-width="260" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="maxScore" label="满分" width="80" header-align="center" align="right" /> |
|||
<el-table-column prop="status" label="状态" width="80" class-name="status-col" header-align="center" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag :type="scope.row.status === 'Y' ? 'success' : 'info'" size="mini">{{ scope.row.status === 'Y' ? '启用' : '停用' }}</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="createBy" label="创建者" width="110" header-align="center" align="left" /> |
|||
<el-table-column prop="createDate" label="创建时间" width="150" header-align="center" align="center" /> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<el-pagination style="margin-top:0" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" |
|||
:current-page="pageIndex" :page-sizes="[20,50,100,200]" :page-size="pageSize" :total="totalPage" |
|||
layout="total, sizes, prev, pager, next, jumper" /> |
|||
|
|||
<!-- 新增/编辑 技能内容(含分数标准) --> |
|||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" top="6vh" :close-on-click-modal="false" v-drag> |
|||
<el-form :model="form" label-position="top" class="rs-form"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="8"> |
|||
<el-form-item label="技能编码" :required="true"> |
|||
<el-input v-model="form.skillCode" :disabled="editMode" maxlength="50" /> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="8"> |
|||
<el-form-item label="技能名称" :required="true"> |
|||
<el-input v-model="form.skillName" maxlength="120" /> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="8"> |
|||
<el-form-item label="满分"> |
|||
<el-input-number v-model="form.maxScore" :controls="false" :min="0" :max="100000" style="width:100%" /> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="20"> |
|||
<el-form-item label="分值相关描述"> |
|||
<el-input v-model="form.scoreDesc" type="textarea" :rows="2" maxlength="1000" show-word-limit /> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="4"> |
|||
<el-form-item label="状态"> |
|||
<el-select v-model="form.status" style="width:100%"> |
|||
<el-option label="启用" value="Y" /> |
|||
<el-option label="停用" value="N" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
|
|||
<div class="criteria-header"> |
|||
<span>分数标准(评分参考)</span> |
|||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addCriteriaRow">新增标准</el-button> |
|||
</div> |
|||
<el-table :data="form.criteriaList" class="criteria-table" size="mini" border max-height="300"> |
|||
<el-table-column prop="criteriaDesc" label="标准描述" min-width="360" header-align="center" align="left"> |
|||
<template slot-scope="scope"> |
|||
<el-input v-model="scope.row.criteriaDesc" size="mini" maxlength="500" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="score" label="对应分数" width="120" align="right" header-align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-input-number v-model="scope.row.score" :controls="false" :min="0" :max="100000" style="width:100px" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" width="80" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-button type="text" size="mini" @click="removeCriteria(scope.$index)">删除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button type="primary" :loading="submitLoading" @click="submitForm">保存</el-button> |
|||
<el-button @click="dialogVisible=false">取消</el-button> |
|||
</span> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
searchContentList, |
|||
createContent, |
|||
updateContent, |
|||
deleteContent, |
|||
getCriteriaList |
|||
} from '@/api/repairSkill/repairSkillContent.js' |
|||
|
|||
export default { |
|||
name: 'RepairSkillContent', |
|||
data () { |
|||
return { |
|||
loading: false, |
|||
submitLoading: false, |
|||
dataList: [], |
|||
pageIndex: 1, |
|||
pageSize: 20, |
|||
totalPage: 0, |
|||
searchData: { |
|||
site: this.$store.state.user.site, |
|||
skillCode: '', |
|||
skillName: '' |
|||
}, |
|||
dialogVisible: false, |
|||
dialogTitle: '新增技能内容', |
|||
editMode: false, |
|||
form: this.emptyForm() |
|||
} |
|||
}, |
|||
computed: { |
|||
canEdit () { |
|||
// TODO 特殊权限:暂时放开,联调后恢复为 this.isAuth('repairSkill:content:edit') |
|||
return true |
|||
} |
|||
}, |
|||
mounted () { |
|||
this.getList() |
|||
}, |
|||
methods: { |
|||
emptyForm () { |
|||
return { |
|||
site: this.$store.state.user.site, |
|||
skillCode: '', |
|||
skillName: '', |
|||
scoreDesc: '', |
|||
maxScore: null, |
|||
status: 'Y', |
|||
createBy: this.$store.state.user.name, |
|||
updateBy: '', |
|||
criteriaList: [] |
|||
} |
|||
}, |
|||
getList () { |
|||
this.loading = true |
|||
const params = { ...this.searchData, page: this.pageIndex, limit: this.pageSize } |
|||
searchContentList(params).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0') && data.page) { |
|||
this.dataList = data.page.list || [] |
|||
this.totalPage = data.page.totalCount || 0 |
|||
} else { |
|||
this.dataList = [] |
|||
this.totalPage = 0 |
|||
} |
|||
}).catch(() => { this.dataList = []; this.totalPage = 0 }) |
|||
.finally(() => { this.loading = false }) |
|||
}, |
|||
sizeChangeHandle (v) { this.pageSize = v; this.pageIndex = 1; this.getList() }, |
|||
currentChangeHandle (v) { this.pageIndex = v; this.getList() }, |
|||
openDialog (row) { |
|||
if (row) { |
|||
this.dialogTitle = '编辑技能内容' |
|||
this.editMode = true |
|||
this.form = { ...row, updateBy: this.$store.state.user.name, criteriaList: [] } |
|||
this.loadCriteria(row.skillCode) |
|||
} else { |
|||
this.dialogTitle = '新增技能内容' |
|||
this.editMode = false |
|||
this.form = this.emptyForm() |
|||
} |
|||
this.dialogVisible = true |
|||
}, |
|||
loadCriteria (skillCode) { |
|||
getCriteriaList({ skillCode, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.form.criteriaList = data.obj || [] |
|||
} else { |
|||
this.form.criteriaList = [] |
|||
} |
|||
}).catch(() => { this.form.criteriaList = [] }) |
|||
}, |
|||
addCriteriaRow () { |
|||
this.form.criteriaList.push({ criteriaDesc: '', score: 0, seq: this.form.criteriaList.length + 1 }) |
|||
}, |
|||
removeCriteria (i) { this.form.criteriaList.splice(i, 1) }, |
|||
validate () { |
|||
if (!this.form.skillCode) { this.$message.warning('技能编码不能为空'); return false } |
|||
if (!this.form.skillName) { this.$message.warning('技能名称不能为空'); return false } |
|||
for (const c of this.form.criteriaList) { |
|||
if (!c.criteriaDesc) { this.$message.warning('存在空的分数标准描述'); return false } |
|||
} |
|||
return true |
|||
}, |
|||
submitForm () { |
|||
if (!this.validate()) return |
|||
this.submitLoading = true |
|||
const payload = { ...this.form, site: this.$store.state.user.site } |
|||
payload.criteriaList = (this.form.criteriaList || []).map((c, idx) => ({ |
|||
criteriaDesc: c.criteriaDesc, |
|||
score: c.score || 0, |
|||
seq: c.seq || idx + 1 |
|||
})) |
|||
const api = this.editMode ? updateContent : createContent |
|||
api(payload).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('保存成功') |
|||
this.dialogVisible = false |
|||
this.getList() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '保存失败') |
|||
} |
|||
}).catch(e => this.$message.error('保存失败: ' + e.message)) |
|||
.finally(() => { this.submitLoading = false }) |
|||
}, |
|||
removeRow (row) { |
|||
this.$confirm('确认删除技能: ' + row.skillName + ' ?', '提示', { type: 'warning' }).then(() => { |
|||
deleteContent({ skillCode: row.skillCode, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('删除成功') |
|||
this.getList() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '删除失败') |
|||
} |
|||
}).catch(e => this.$message.error('删除失败: ' + e.message)) |
|||
}).catch(() => {}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.rs-search-form { margin-top: 0; } |
|||
.rs-form { margin-top: -5px; } |
|||
.criteria-header { display: flex; align-items: center; justify-content: space-between; font-weight: 600; padding: 6px; margin-top: 50px; background: #f5f7fa; border: 1px solid #ebeef5; border-bottom: none; } |
|||
.criteria-header button { margin-left: 8px; } |
|||
/* 覆盖全局 .el-table .cell 固定高度,避免弹窗内输入框/按钮被裁切 */ |
|||
.criteria-table /deep/ .cell { height: auto; line-height: 1.5; overflow: visible; } |
|||
.criteria-table /deep/ td.el-table__cell, .criteria-table /deep/ td { padding: 4px 0; } |
|||
/* 列表状态列:覆盖全局固定高度,避免 el-tag 被裁切 */ |
|||
.el-table /deep/ .status-col .cell { height: auto; line-height: 1.5; overflow: visible; } |
|||
</style> |
|||
@ -0,0 +1,304 @@ |
|||
<template> |
|||
<div class="customer-css"> |
|||
<!-- 查询条件 --> |
|||
<el-form :inline="true" label-position="top" class="rs-search-form"> |
|||
<el-form-item label="模板编码"> |
|||
<el-input v-model="searchData.templateCode" style="width:150px" clearable @keyup.enter.native="getList" /> |
|||
</el-form-item> |
|||
<el-form-item label="模板名称"> |
|||
<el-input v-model="searchData.templateName" style="width:200px" clearable @keyup.enter.native="getList" /> |
|||
</el-form-item> |
|||
<el-form-item label=" "> |
|||
<el-button type="primary" class="customer-bun-min" icon="el-icon-search" @click="getList">查询</el-button> |
|||
<el-button v-if="canEdit" type="success" class="customer-bun-min" icon="el-icon-plus" @click="openDialog()">新增</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<!-- 模板列表 --> |
|||
<el-table :data="dataList" border highlight-current-row v-loading="loading" style="width:100%;margin-bottom:8px"> |
|||
<el-table-column v-if="canEdit" label="操作" header-align="center" align="center" width="120"> |
|||
<template slot-scope="scope"> |
|||
<a class="customer-a" @click.stop="openDialog(scope.row)">编辑 |</a> |
|||
<a class="customer-a" @click.stop="removeRow(scope.row)">删除</a> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column type="index" label="#" width="50" align="center" /> |
|||
<el-table-column prop="templateCode" label="模板编码" min-width="120" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="templateName" label="模板名称" min-width="180" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="remarks" label="备注" min-width="200" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="status" label="状态" width="80" class-name="status-col" header-align="center" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-tag :type="scope.row.status === 'Y' ? 'success' : 'info'" size="mini">{{ scope.row.status === 'Y' ? '启用' : '停用' }}</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="createBy" label="创建者" width="110" header-align="center" align="left" /> |
|||
<el-table-column prop="createDate" label="创建时间" width="150" header-align="center" align="center" /> |
|||
</el-table> |
|||
|
|||
<!-- 分页 --> |
|||
<el-pagination style="margin-top:0" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" |
|||
:current-page="pageIndex" :page-sizes="[20,50,100,200]" :page-size="pageSize" :total="totalPage" |
|||
layout="total, sizes, prev, pager, next, jumper" /> |
|||
|
|||
<!-- 新增/编辑 模板(含明细配置) --> |
|||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" top="6vh" :close-on-click-modal="false" v-drag> |
|||
<el-form :model="form" label-position="top" class="rs-form"> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="8"> |
|||
<el-form-item label="模板编码" :required="true"> |
|||
<el-input v-model="form.templateCode" :disabled="editMode" maxlength="50" /> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="8"> |
|||
<el-form-item label="模板名称" :required="true"> |
|||
<el-input v-model="form.templateName" maxlength="120" /> |
|||
</el-form-item> |
|||
</el-col> |
|||
<el-col :span="8"> |
|||
<el-form-item label="状态"> |
|||
<el-select v-model="form.status" style="width:100%"> |
|||
<el-option label="启用" value="Y" /> |
|||
<el-option label="停用" value="N" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
<el-row :gutter="20"> |
|||
<el-col :span="24"> |
|||
<el-form-item label="备注"> |
|||
<el-input v-model="form.remarks" type="textarea" :rows="2" maxlength="500" show-word-limit /> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
|
|||
<div class="criteria-header"> |
|||
<span>维修明细(模板包含的明细项)</span> |
|||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openPicker">添加明细</el-button> |
|||
</div> |
|||
<el-table :data="form.detailList" class="detail-table" size="mini" border max-height="300"> |
|||
<el-table-column type="index" label="#" width="45" align="center" /> |
|||
<el-table-column prop="skillCode" label="明细编码" width="130" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="skillName" label="明细名称" min-width="180" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="maxScore" label="满分" width="80" header-align="center" align="right" /> |
|||
<el-table-column label="操作" width="80" align="center"> |
|||
<template slot-scope="scope"> |
|||
<el-button type="text" size="mini" @click="removeDetail(scope.$index)">移除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button type="primary" :loading="submitLoading" @click="submitForm">保存</el-button> |
|||
<el-button @click="dialogVisible=false">取消</el-button> |
|||
</span> |
|||
</el-dialog> |
|||
|
|||
<!-- 从维修明细库选择 --> |
|||
<el-dialog title="选择维修明细" :visible.sync="pickerVisible" width="900px" top="8vh" :close-on-click-modal="false" append-to-body v-drag> |
|||
<el-form :inline="true" label-position="top"> |
|||
<el-form-item label="明细名称"> |
|||
<el-input v-model="pickerQuery.skillName" clearable style="width:180px" @keyup.enter.native="loadCandidates" /> |
|||
</el-form-item> |
|||
<el-form-item label=" "> |
|||
<el-button type="primary" icon="el-icon-search" @click="loadCandidates">查询</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
<el-table ref="pickerTable" :data="candidateList" height="420" size="mini" border v-loading="pickerLoading" @selection-change="onPick"> |
|||
<el-table-column type="selection" width="50" /> |
|||
<el-table-column prop="skillCode" label="明细编码" width="130" header-align="center" align="left" /> |
|||
<el-table-column prop="skillName" label="明细名称" min-width="180" header-align="center" align="left" show-overflow-tooltip /> |
|||
<el-table-column prop="maxScore" label="满分" width="80" header-align="center" align="right" /> |
|||
</el-table> |
|||
<span slot="footer" class="dialog-footer"> |
|||
<el-button type="primary" @click="confirmPick">确定添加</el-button> |
|||
<el-button @click="pickerVisible=false">取消</el-button> |
|||
</span> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
searchTemplateList, |
|||
createTemplate, |
|||
updateTemplate, |
|||
deleteTemplate, |
|||
getTemplateDetails |
|||
} from '@/api/repairSkill/repairSkillTemplate.js' |
|||
import { searchContentList } from '@/api/repairSkill/repairSkillContent.js' |
|||
|
|||
export default { |
|||
name: 'RepairSkillTemplate', |
|||
data () { |
|||
return { |
|||
loading: false, |
|||
submitLoading: false, |
|||
dataList: [], |
|||
pageIndex: 1, |
|||
pageSize: 20, |
|||
totalPage: 0, |
|||
searchData: { |
|||
site: this.$store.state.user.site, |
|||
templateCode: '', |
|||
templateName: '' |
|||
}, |
|||
dialogVisible: false, |
|||
dialogTitle: '新增维修技能模板', |
|||
editMode: false, |
|||
form: this.emptyForm(), |
|||
// 明细选择 |
|||
pickerVisible: false, |
|||
pickerLoading: false, |
|||
candidateList: [], |
|||
picked: [], |
|||
pickerQuery: { skillName: '' } |
|||
} |
|||
}, |
|||
computed: { |
|||
canEdit () { |
|||
// TODO 特殊权限:暂时放开,联调后恢复为 this.isAuth('repairSkill:template:edit') |
|||
return true |
|||
} |
|||
}, |
|||
mounted () { |
|||
this.getList() |
|||
}, |
|||
methods: { |
|||
emptyForm () { |
|||
return { |
|||
site: this.$store.state.user.site, |
|||
templateCode: '', |
|||
templateName: '', |
|||
remarks: '', |
|||
status: 'Y', |
|||
createBy: this.$store.state.user.name, |
|||
updateBy: '', |
|||
detailList: [] |
|||
} |
|||
}, |
|||
getList () { |
|||
this.loading = true |
|||
const params = { ...this.searchData, page: this.pageIndex, limit: this.pageSize } |
|||
searchTemplateList(params).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0') && data.page) { |
|||
this.dataList = data.page.list || [] |
|||
this.totalPage = data.page.totalCount || 0 |
|||
} else { |
|||
this.dataList = [] |
|||
this.totalPage = 0 |
|||
} |
|||
}).catch(() => { this.dataList = []; this.totalPage = 0 }) |
|||
.finally(() => { this.loading = false }) |
|||
}, |
|||
sizeChangeHandle (v) { this.pageSize = v; this.pageIndex = 1; this.getList() }, |
|||
currentChangeHandle (v) { this.pageIndex = v; this.getList() }, |
|||
openDialog (row) { |
|||
if (row) { |
|||
this.dialogTitle = '编辑维修技能模板' |
|||
this.editMode = true |
|||
this.form = { ...row, updateBy: this.$store.state.user.name, detailList: [] } |
|||
this.loadDetails(row.templateCode) |
|||
} else { |
|||
this.dialogTitle = '新增维修技能模板' |
|||
this.editMode = false |
|||
this.form = this.emptyForm() |
|||
} |
|||
this.dialogVisible = true |
|||
}, |
|||
loadDetails (templateCode) { |
|||
getTemplateDetails({ templateCode, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.form.detailList = data.obj || [] |
|||
} else { |
|||
this.form.detailList = [] |
|||
} |
|||
}).catch(() => { this.form.detailList = [] }) |
|||
}, |
|||
removeDetail (i) { this.form.detailList.splice(i, 1) }, |
|||
|
|||
// ===== 明细选择 ===== |
|||
openPicker () { |
|||
this.pickerVisible = true |
|||
this.pickerQuery = { skillName: '' } |
|||
this.picked = [] |
|||
this.loadCandidates() |
|||
}, |
|||
loadCandidates () { |
|||
this.pickerLoading = true |
|||
const params = { site: this.$store.state.user.site, skillName: this.pickerQuery.skillName, status: 'Y', page: 1, limit: 1000 } |
|||
searchContentList(params).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0') && data.page) { |
|||
const existing = new Set(this.form.detailList.map(d => d.skillContentId)) |
|||
this.candidateList = (data.page.list || []).filter(s => !existing.has(s.skillCode)) |
|||
} else { |
|||
this.candidateList = [] |
|||
} |
|||
}).catch(() => { this.candidateList = [] }) |
|||
.finally(() => { this.pickerLoading = false }) |
|||
}, |
|||
onPick (val) { this.picked = val }, |
|||
confirmPick () { |
|||
if (!this.picked.length) { this.$message.warning('请选择明细'); return } |
|||
this.picked.forEach(p => { |
|||
this.form.detailList.push({ |
|||
skillContentId: p.skillCode, |
|||
skillCode: p.skillCode, |
|||
skillName: p.skillName, |
|||
maxScore: p.maxScore |
|||
}) |
|||
}) |
|||
this.pickerVisible = false |
|||
}, |
|||
|
|||
validate () { |
|||
if (!this.form.templateCode) { this.$message.warning('模板编码不能为空'); return false } |
|||
if (!this.form.templateName) { this.$message.warning('模板名称不能为空'); return false } |
|||
if (!this.form.detailList.length) { this.$message.warning('请至少添加一条维修明细'); return false } |
|||
return true |
|||
}, |
|||
submitForm () { |
|||
if (!this.validate()) return |
|||
this.submitLoading = true |
|||
const payload = { ...this.form, site: this.$store.state.user.site } |
|||
payload.detailList = (this.form.detailList || []).map((d, idx) => ({ |
|||
skillContentId: d.skillContentId, |
|||
seq: d.seq != null ? d.seq : idx |
|||
})) |
|||
const api = this.editMode ? updateTemplate : createTemplate |
|||
api(payload).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('保存成功') |
|||
this.dialogVisible = false |
|||
this.getList() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '保存失败') |
|||
} |
|||
}).catch(e => this.$message.error('保存失败: ' + e.message)) |
|||
.finally(() => { this.submitLoading = false }) |
|||
}, |
|||
removeRow (row) { |
|||
this.$confirm('确认删除模板: ' + row.templateName + ' ?', '提示', { type: 'warning' }).then(() => { |
|||
deleteTemplate({ templateCode: row.templateCode, site: this.$store.state.user.site }).then(({ data }) => { |
|||
if (data && (data.code === 0 || data.code === '0')) { |
|||
this.$message.success('删除成功') |
|||
this.getList() |
|||
} else { |
|||
this.$message.error((data && data.msg) || '删除失败') |
|||
} |
|||
}).catch(e => this.$message.error('删除失败: ' + e.message)) |
|||
}).catch(() => {}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.rs-search-form { margin-top: 0; } |
|||
.rs-form { margin-top: -5px; } |
|||
.criteria-header { display: flex; align-items: center; justify-content: space-between; font-weight: 600; padding: 6px; margin-top: 50px; background: #f5f7fa; border: 1px solid #ebeef5; border-bottom: none; } |
|||
.criteria-header button { margin-left: 8px; } |
|||
.detail-table /deep/ .cell { height: auto; line-height: 1.5; overflow: visible; } |
|||
/* 列表状态列:覆盖全局固定高度,避免 el-tag 被裁切 */ |
|||
.el-table /deep/ .status-col .cell { height: auto; line-height: 1.5; overflow: visible; } |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue