Browse Source

feat(repairSkill): 新增维修技能卡管理和技能内容维护功能

- 创建维修技能卡CRUD接口及人员选择、打分管理功能
- 实现技能卡列表展示、统计信息和筛选查询功能
- 开发技能卡明细查看、打分标准展示和批量打分功能
- 新增选择维修模板功能,支持批量添加技能明细
- 实现技能内容库管理,包含技能编码、名称和分值设置
- 添加技能内容的分数标准配置和状态管理功能
- 更新依赖包版本,调整sass相关配置以解决构建问题
master
qiankanghui 2 days ago
parent
commit
6822249a10
  1. 4
      package.json
  2. 17
      src/api/repairSkill/maintenanceSkillCard.js
  3. 11
      src/api/repairSkill/repairSkillContent.js
  4. 10
      src/api/repairSkill/repairSkillTemplate.js
  5. 587
      src/views/modules/repairSkill/maintenanceSkillCard.vue
  6. 261
      src/views/modules/repairSkill/repairSkillContent.vue
  7. 304
      src/views/modules/repairSkill/repairSkillTemplate.vue

4
package.json

@ -17,7 +17,7 @@
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/runtime": "^7.26.0",
"@vue-office/docx": "^1.6.2",
"@vue-office/excel": "^1.7.11",
"@vue-office/excel": "^1.7.14",
"@vue/composition-api": "^1.7.2",
"axios": "0.17.1",
"babel-plugin-component": "0.10.1",
@ -35,7 +35,6 @@
"lodash": "4.17.5",
"mammoth": "^1.9.0",
"moment": "^2.30.1",
"node-sass": "4.13.1",
"npm": "^6.14.6",
"pubsub-js": "^1.9.3",
"qrcode": "^1.5.3",
@ -90,6 +89,7 @@
"less-loader": "^7.3.0",
"nightwatch": "0.9.12",
"node-notifier": "5.1.2",
"node-sass": "^4.14.1",
"optimize-css-assets-webpack-plugin": "3.2.0",
"ora": "1.2.0",
"portfinder": "1.0.13",

17
src/api/repairSkill/maintenanceSkillCard.js

@ -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)

11
src/api/repairSkill/repairSkillContent.js

@ -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)

10
src/api/repairSkill/repairSkillTemplate.js

@ -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)

587
src/views/modules/repairSkill/maintenanceSkillCard.vue

@ -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>

261
src/views/modules/repairSkill/repairSkillContent.vue

@ -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>

304
src/views/modules/repairSkill/repairSkillTemplate.vue

@ -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>
Loading…
Cancel
Save