得分计算单位展示
생성일: 2024년 11월 2일
생성일: 2024년 11월 2일
</div> </template> <script> import { openDB } from 'idb'; import { Message } from 'element-ui'; // 确保 Element UI 已安装并在项目中引入 import * as echarts from 'echarts'; export default { name: 'ScoreManager', data() { return { activeTab: 'scoreCalculation', activePanels: ['1', '2'], // 默认展开的折叠面板 units: [], scoringCriteria: [], weights: [], unitDialogVisible: false, currentUnit: { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', // 添加反馈字段 }, dialogTitle: '添加单位', editCriteriaDialog: false, editCriteriaData: {}, editWeightDialog: false, editWeightData: {}, formLabelWidth: '120px', db: null, // 查看详情相关 detailDialogVisible: false, detailDialogTitle: '单位详情', selectedUnitDetails: null, deductions: { avgProcessingTime: 0, timeDeduction: 0, resolutionRateDeduction: 0, workQualityDeduction: 0, }, interpretation: '', feedbackInput: '', }; }, methods: { // 初始化 IndexedDB async initDB() { this.db = await openDB('ScoreDB', 1, { upgrade(db) { if (!db.objectStoreNames.contains('units')) { const store = db.createObjectStore('units', { keyPath: 'id', autoIncrement: true }); store.createIndex('unitName', 'unitName', { unique: false }); } if (!db.objectStoreNames.contains('scoringCriteria')) { const store = db.createObjectStore('scoringCriteria', { keyPath: 'id', autoIncrement: true }); store.createIndex('criteriaName', 'criteriaName', { unique: true }); } if (!db.objectStoreNames.contains('weights')) { const store = db.createObjectStore('weights', { keyPath: 'id', autoIncrement: true }); store.createIndex('key', 'key', { unique: true }); } if (!db.objectStoreNames.contains('rankings')) { const store = db.createObjectStore('rankings', { keyPath: 'id', autoIncrement: true }); store.createIndex('month', 'month', { unique: true }); } }, }); await this.initializeScoringCriteria(); await this.initializeWeights(); await this.initializeUnits(); await this.initializeRankings(); }, // 初始化评分标准 async initializeScoringCriteria() { const tx = this.db.transaction('scoringCriteria', 'readonly'); const store = tx.objectStore('scoringCriteria'); const count = await store.count(); if (count === 0) { const initialCriteria = [ { criteriaName: '优秀', label: '高效', time: 5, score: 0 }, { criteriaName: '良好', label: '稳定', time: 10, score: 0 }, { criteriaName: '一般', label: '可接受', time: 24, score: 1 }, { criteriaName: '较差', label: '需要改进', time: 72, score: 5 }, ]; const writeTx = this.db.transaction('scoringCriteria', 'readwrite'); const writeStore = writeTx.objectStore('scoringCriteria'); for (const criteria of initialCriteria) { await writeStore.add(criteria); } await writeTx.done; } this.fetchScoringCriteria(); }, // 初始化权重 async initializeWeights() { const tx = this.db.transaction('weights', 'readonly'); const store = tx.objectStore('weights'); const count = await store.count(); if (count === 0) { const initialWeights = [ { key: 'averageProcessingTime', label: '处理时效', weight: 0.33 }, { key: 'problemResolutionRate', label: '问题解决率', weight: 0.33, unit: '%' }, { key: 'workQuality', label: '工作质量', weight: 0.34, unit: '%' }, ]; const writeTx = this.db.transaction('weights', 'readwrite'); const writeStore = writeTx.objectStore('weights'); for (const weight of initialWeights) { await writeStore.add(weight); } await writeTx.done; } this.fetchWeights(); }, // 初始化单位数据 async initializeUnits() { const tx = this.db.transaction('units', 'readonly'); const store = tx.objectStore('units'); const count = await store.count(); if (count === 0) { const initialData = [ // 分局 { unitName: '历下分局', unitType: '分局', unitScore: 100, complaintCount: 5, supervisionReportCount: 2, problemResolutionRate: 85, // 85% workQuality: 90, // 90% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '网络问题', problemCategory: '举报投诉', problemDescription: '网络连接不稳定', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-01', problemResolutionTime: '2024-10-03', problemResolutionPerson: '张三', satisfactionLevel: null, }, { problemType: '系统错误', problemCategory: '举报投诉', problemDescription: '系统频繁崩溃', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-05', problemResolutionTime: '2024-10-06', problemResolutionPerson: '李四', satisfactionLevel: 90, }, ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '办公区域缺少监控摄像头', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-10', problemResolutionTime: '2024-10-12', problemResolutionPerson: '王五', satisfactionLevel: null, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '打印机无法正常工作', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-15', problemResolutionTime: '2024-10-16', problemResolutionPerson: '赵六', satisfactionLevel: 90, }, ], }, { unitName: '长清分局', unitType: '分局', unitScore: 100, complaintCount: 8, supervisionReportCount: 3, problemResolutionRate: 70, // 70% workQuality: 75, // 75% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '交通问题', problemCategory: '举报投诉', problemDescription: '交通信号灯故障', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-20', problemResolutionTime: '2024-10-21', problemResolutionPerson: '孙七', satisfactionLevel: 85, }, { problemType: '环境问题', problemCategory: '举报投诉', problemDescription: '公共区域垃圾未及时清理', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-22', problemResolutionTime: '2024-10-25', problemResolutionPerson: '周八', satisfactionLevel: null, }, ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '消防设备老化', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-18', problemResolutionTime: '2024-10-19', problemResolutionPerson: '吴九', satisfactionLevel: 80, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '监控系统故障', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-23', problemResolutionTime: '2024-10-24', problemResolutionPerson: '郑十', satisfactionLevel: 88, }, ], }, // 派出所 { unitName: '历下区分局泉城路派出所', unitType: '派出所', unitScore: 100, complaintCount: 10, supervisionReportCount: 4, problemResolutionRate: 60, // 60% workQuality: 65, // 65% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '治安问题', problemCategory: '举报投诉', problemDescription: '夜间治安不好', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-28', problemResolutionTime: '2024-10-30', problemResolutionPerson: '钱十一', satisfactionLevel: null, }, { problemType: '交通问题', problemCategory: '举报投诉', problemDescription: '车辆违停', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-29', problemResolutionTime: '2024-10-30', problemResolutionPerson: '孙十二', satisfactionLevel: 75, }, ], supervisionList: [ { problemType: '设施问题', problemCategory: '数字督察', problemDescription: '警务室照明不足', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-27', problemResolutionTime: '2024-10-28', problemResolutionPerson: '李十三', satisfactionLevel: 70, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '电脑系统崩溃', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-26', problemResolutionTime: '2024-10-27', problemResolutionPerson: '张十四', satisfactionLevel: 80, }, ], }, { unitName: '历下区分局趵突泉派出所', unitType: '派出所', unitScore: 100, complaintCount: 12, supervisionReportCount: 5, problemResolutionRate: 55, // 55% workQuality: 60, // 60% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '环境问题', problemCategory: '举报投诉', problemDescription: '小区环境脏乱', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-24', problemResolutionTime: '2024-10-26', problemResolutionPerson: '周十五', satisfactionLevel: null, }, { problemType: '治安问题', problemCategory: '举报投诉', problemDescription: '频繁盗窃案件', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-25', problemResolutionTime: '2024-10-27', problemResolutionPerson: '赵十六', satisfactionLevel: 78, }, ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '停车场安全措施不足', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-23', problemResolutionTime: '2024-10-24', problemResolutionPerson: '吴十七', satisfactionLevel: 65, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '摄像头损坏', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-22', problemResolutionTime: '2024-10-23', problemResolutionPerson: '郑十八', satisfactionLevel: 70, }, ], }, { unitName: '历下区分局司里街派出所', unitType: '派出所', unitScore: 100, complaintCount: 7, supervisionReportCount: 3, problemResolutionRate: 90, // 90% workQuality: 95, // 95% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '交通问题', problemCategory: '举报投诉', problemDescription: '交通拥堵', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-19', problemResolutionTime: '2024-10-20', problemResolutionPerson: '钱十九', satisfactionLevel: 85, }, { problemType: '治安问题', problemCategory: '举报投诉', problemDescription: '夜间巡逻不足', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-20', problemResolutionTime: '2024-10-21', problemResolutionPerson: '孙二十', satisfactionLevel: 90, }, ], supervisionList: [ { problemType: '设施问题', problemCategory: '数字督察', problemDescription: '警务室设备齐全', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-18', problemResolutionTime: '2024-10-19', problemResolutionPerson: '李二十一', satisfactionLevel: 95, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '通讯设备正常', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-17', problemResolutionTime: '2024-10-18', problemResolutionPerson: '张二十二', satisfactionLevel: 98, }, ], }, { unitName: '长清区分局新城派出所', unitType: '派出所', unitScore: 100, complaintCount: 9, supervisionReportCount: 4, problemResolutionRate: 85, // 85% workQuality: 80, // 80% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '环境问题', problemCategory: '举报投诉', problemDescription: '公共区域清洁不足', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-16', problemResolutionTime: '2024-10-17', problemResolutionPerson: '周二十三', satisfactionLevel: 80, }, { problemType: '治安问题', problemCategory: '举报投诉', problemDescription: '频繁夜间骚扰', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-17', problemResolutionTime: '2024-10-18', problemResolutionPerson: '赵二十四', satisfactionLevel: 82, }, ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '紧急出口标识清晰', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-15', problemResolutionTime: '2024-10-16', problemResolutionPerson: '吴二十五', satisfactionLevel: 85, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '报警系统正常', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-14', problemResolutionTime: '2024-10-15', problemResolutionPerson: '郑二十六', satisfactionLevel: 88, }, ], }, { unitName: '长清区分局崮山派出所', unitType: '派出所', unitScore: 100, complaintCount: 11, supervisionReportCount: 5, problemResolutionRate: 75, // 75% workQuality: 70, // 70% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '交通问题', problemCategory: '举报投诉', problemDescription: '非法改装车辆', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-13', problemResolutionTime: '2024-10-15', problemResolutionPerson: '钱二十七', satisfactionLevel: null, }, { problemType: '治安问题', problemCategory: '举报投诉', problemDescription: '街头斗殴', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-14', problemResolutionTime: '2024-10-16', problemResolutionPerson: '孙二十八', satisfactionLevel: 75, }, ], supervisionList: [ { problemType: '设施问题', problemCategory: '数字督察', problemDescription: '监控摄像头覆盖全面', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-12', problemResolutionTime: '2024-10-13', problemResolutionPerson: '李二十九', satisfactionLevel: 78, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '通讯设备部分故障', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-11', problemResolutionTime: '2024-10-12', problemResolutionPerson: '张三十', satisfactionLevel: 80, }, ], }, ]; const writeTx = this.db.transaction('units', 'readwrite'); const writeStore = writeTx.objectStore('units'); for (const unit of initialData) { await writeStore.add(unit); } await writeTx.done; } this.fetchUnits(); }, // 初始化排名数据 async initializeRankings() { const tx = this.db.transaction('rankings', 'readonly'); const store = tx.objectStore('rankings'); const count = await store.count(); if (count === 0) { const initialRanking = { month: this.getCurrentMonth(), rankings: [], }; const writeTx = this.db.transaction('rankings', 'readwrite'); const writeStore = writeTx.objectStore('rankings'); await writeStore.add(initialRanking); await writeTx.done; } }, // 获取当前月份,格式为 YYYY-MM getCurrentMonth() { const now = new Date(); const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, '0'); return `${year}-${month}`; }, // 获取所有单位数据 async fetchUnits() { const tx = this.db.transaction('units', 'readonly'); const store = tx.objectStore('units'); this.units = await store.getAll(); }, // 获取所有评分标准 async fetchScoringCriteria() { const tx = this.db.transaction('scoringCriteria', 'readonly'); const store = tx.objectStore('scoringCriteria'); this.scoringCriteria = await store.getAll(); }, // 获取所有权重 async fetchWeights() { const tx = this.db.transaction('weights', 'readonly'); const store = tx.objectStore('weights'); this.weights = await store.getAll(); }, // 计算最终得分 calculateFinalScore(unit) { let totalDeduction = 0; // 处理时效扣分 const avgProcessingTime = this.calculateAverageProcessingTime(unit); const timeDeduction = this.getDeduction(avgProcessingTime); const timeWeight = this.getWeight('averageProcessingTime'); totalDeduction += timeDeduction * timeWeight; // 问题解决率扣分 const resolutionRateDeduction = this.getDeductionByResolutionRate(unit.problemResolutionRate); const resolutionWeight = this.getWeight('problemResolutionRate'); totalDeduction += resolutionRateDeduction * resolutionWeight; // 工作质量扣分 const workQualityDeduction = this.getDeductionByWorkQuality(unit.workQuality); const qualityWeight = this.getWeight('workQuality'); totalDeduction += workQualityDeduction * qualityWeight; // 最终得分 return unit.unitScore - totalDeduction; }, // 计算平均处理时间(小时) calculateAverageProcessingTime(unit) { const resolutions = [...unit.problemList, ...unit.supervisionList].filter( (p) => p.problemResolutionTime && p.problemOccurrenceTime ); if (resolutions.length === 0) return 0; const totalHours = resolutions.reduce((sum, item) => { const occurrence = new Date(item.problemOccurrenceTime); const resolution = new Date(item.problemResolutionTime); const diff = (resolution - occurrence) / (1000 * 60 * 60); // 转换为小时 return sum + diff; }, 0); return totalHours / resolutions.length; }, // 根据平均处理时间获取扣分 getDeduction(avgTime) { // 根据 scoringCriteria 进行判断 // 假设 scoringCriteria 已按 time 升序排序 const sortedCriteria = this.scoringCriteria.slice().sort((a, b) => a.time - b.time); for (let criteria of sortedCriteria) { if (avgTime <= criteria.time) { return criteria.score; } } // 如果超过所有标准,使用最高扣分 const maxScore = Math.max(...this.scoringCriteria.map((c) => c.score)); return maxScore; }, // 根据问题解决率获取扣分(固定规则) getDeductionByResolutionRate(rate) { if (rate >= 90) return 0; if (rate >= 75) return 1; if (rate >= 50) return 2; return 3; }, // 根据工作质量获取扣分(固定规则) getDeductionByWorkQuality(quality) { if (quality >= 90) return 0; if (quality >= 75) return 1; if (quality >= 50) return 2; return 3; }, // 获取权重值 getWeight(key) { const weightItem = this.weights.find((w) => w.key === key); return weightItem ? weightItem.weight : 0; }, // 查看单位详情 viewDetails(unit) { this.selectedUnitDetails = unit; // 计算扣分详情 const avgProcessingTime = this.calculateAverageProcessingTime(unit); const timeDeduction = this.getDeduction(avgProcessingTime) * this.getWeight('averageProcessingTime'); const resolutionRateDeduction = this.getDeductionByResolutionRate(unit.problemResolutionRate) * this.getWeight('problemResolutionRate'); const workQualityDeduction = this.getDeductionByWorkQuality(unit.workQuality) * this.getWeight('workQuality'); this.deductions = { avgProcessingTime, timeDeduction, resolutionRateDeduction, workQualityDeduction, }; // 生成解读 const finalScore = this.calculateFinalScore(unit); this.interpretation = this.generateInterpretation(finalScore); // 设置反馈输入的初始值 this.feedbackInput = unit.feedback || ''; this.detailDialogVisible = true; // 绘制图表 this.$nextTick(() => { this.drawScoreChart(); }); }, // 删除单位 async deleteUnit(id) { if (confirm('确定要删除此单位吗?')) { const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.delete(id); await tx.done; this.fetchUnits(); Message.success('单位删除成功'); } }, // 打开添加单位对话框 openAddUnitDialog() { this.dialogTitle = '添加单位'; this.currentUnit = { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', }; this.unitDialogVisible = true; }, // 保存单位(添加或编辑) async saveUnit() { if (!this.currentUnit.unitName || !this.currentUnit.unitType) { Message.error('请填写所有必填字段'); return; } if (this.currentUnit.id) { // 编辑 const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(this.currentUnit); await tx.done; Message.success('单位更新成功'); } else { // 添加 // 默认设置为已挂牌 this.currentUnit.isListed = true; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.add(this.currentUnit); await tx.done; Message.success('单位添加成功'); } this.unitDialogVisible = false; this.currentUnit = { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', }; this.fetchUnits(); }, // 编辑评分标准 editCriteria(criteria) { this.editCriteriaData = { ...criteria }; this.editCriteriaDialog = true; }, // 保存编辑后的评分标准 async updateCriteria() { const { id, criteriaName, label, time, score } = this.editCriteriaData; if (!criteriaName || !label || time === null || score === null) { Message.error('请填写所有必填字段'); return; } // 检查标准名称是否唯一(排除当前记录) const txCheck = this.db.transaction('scoringCriteria', 'readonly'); const storeCheck = txCheck.objectStore('scoringCriteria'); const existing = await storeCheck.index('criteriaName').get(criteriaName); if (existing && existing.id !== id) { Message.error('标准名称已存在'); return; } const tx = this.db.transaction('scoringCriteria', 'readwrite'); const store = tx.objectStore('scoringCriteria'); await store.put({ id, criteriaName, label, time, score }); await tx.done; Message.success('标准更新成功'); this.editCriteriaDialog = false; this.fetchScoringCriteria(); }, // 保存修改后的评分标准 async saveCriteria() { // 由于评分标准是通过编辑对话框修改的,直接提示用户保存已编辑的内容 Message.success('评分标准已更新'); this.fetchScoringCriteria(); }, // 编辑权重 editWeight(weight) { this.editWeightData = { ...weight }; this.editWeightDialog = true; }, // 保存编辑后的权重 async updateWeight() { const { id, key, label, weight, unit } = this.editWeightData; if (!key || !label || weight === null) { Message.error('请填写所有必填字段'); return; } // 检查键是否唯一(排除当前记录) const txCheck = this.db.transaction('weights', 'readonly'); const storeCheck = txCheck.objectStore('weights'); const existing = await storeCheck.index('key').get(key); if (existing && existing.id !== id) { Message.error('键已存在,请使用唯一的键'); return; } const tx = this.db.transaction('weights', 'readwrite'); const store = tx.objectStore('weights'); await store.put({ id, key, label, weight, unit: unit || undefined }); await tx.done; Message.success('权重更新成功'); this.editWeightDialog = false; this.fetchWeights(); }, // 保存修改后的权重配置 async saveWeights() { // 由于权重是通过编辑对话框修改的,直接提示用户保存已编辑的内容 Message.success('权重已更新'); this.fetchWeights(); }, // 处理 Tab 点击事件(可根据需要扩展) handleTabClick(tab, event) { // 可以在此处理 Tab 切换逻辑 }, // 生成解读 generateInterpretation(finalScore) { if (finalScore >= 90) { return '绩效优秀,表现出色。'; } else if (finalScore >= 75) { return '绩效良好,表现稳定。'; } else if (finalScore >= 50) { return '绩效一般,需要改进。'; } else { return '绩效较差,亟需改进。'; } }, // 保存用户反馈 async saveFeedback() { if (this.feedbackInput.trim() === '') { Message.error('反馈内容不能为空'); return; } // 更新 selectedUnitDetails 的 feedback 字段 this.selectedUnitDetails.feedback = this.feedbackInput; // 保存到 IndexedDB const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(this.selectedUnitDetails); await tx.done; Message.success('反馈已保存'); this.feedbackInput = ''; this.detailDialogVisible = false; this.fetchUnits(); }, // 清除反馈输入 clearFeedback() { this.feedbackInput = ''; }, // 绘制图表 drawScoreChart() { this.$nextTick(() => { const chartDom = this.$refs.scoreChart; const myChart = echarts.init(chartDom); const option = { title: { text: '得分扣分分布', left: 'center', }, tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} 分', }, legend: { orient: 'vertical', left: 'left', }, series: [ { name: '扣分项', type: 'pie', radius: '50%', data: [ { value: parseFloat(this.deductions.timeDeduction.toFixed(2)), name: '处理时效扣分' }, { value: parseFloat(this.deductions.resolutionRateDeduction.toFixed(2)), name: '问题解决率扣分' }, { value: parseFloat(this.deductions.workQualityDeduction.toFixed(2)), name: '工作质量扣分' }, ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, }, ], }; myChart.setOption(option); }); }, // 列表挂牌督办 async listUnit(unit) { // 检查当前挂牌数量 const type = unit.unitType; const maxListings = type === '分局' ? 2 : 5; const currentListings = this.units.filter(u => u.unitType === type && u.isListed).length; if (currentListings >= maxListings) { Message.error(`当前${type}已达到最大挂牌数量(${maxListings}个)。请先摘牌其他单位。`); return; } // 更新单位为挂牌 unit.isListed = true; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(unit); await tx.done; Message.success(`${unit.unitName} 已成功挂牌督办。`); this.fetchUnits(); }, // 列表摘牌 async delistUnit(unit) { // 更新单位为未挂牌 unit.isListed = false; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(unit); await tx.done; Message.success(`${unit.unitName} 已成功摘牌。`); this.fetchUnits(); }, // 进行月度考核排名 async rankUnits() { const month = this.getCurrentMonth(); const txCheck = this.db.transaction('rankings', 'readonly'); const storeCheck = txCheck.objectStore('rankings'); const existing = await storeCheck.index('month').get(month); if (existing) { Message.warning('本月的考核排名已存在。'); return; } // 计算排名,假设排名依据 complaintCount + supervisionReportCount const rankings = this.units .map(unit => ({ id: unit.id, unitName: unit.unitName, unitType: unit.unitType, totalIssues: unit.complaintCount + unit.supervisionReportCount, })) .sort((a, b) => b.totalIssues - a.totalIssues); const topRed = rankings .filter(unit => unit.unitType === '分局') .slice(0, 2); const topPublic = rankings .filter(unit => unit.unitType === '派出所') .slice(0, 5); // 标记挂牌单位 for (const unit of this.units) { if (topRed.some(u => u.id === unit.id) || topPublic.some(u => u.id === unit.id)) { unit.isListed = true; } else { unit.isListed = false; } } // 保存更新后的挂牌状态 const writeTx = this.db.transaction('units', 'readwrite'); const writeStore = writeTx.objectStore('units'); for (const unit of this.units) { await writeStore.put(unit); } await writeTx.done; // 保存排名记录 const newRanking = { month: month, rankings: rankings, }; const writeRankingTx = this.db.transaction('rankings', 'readwrite'); const writeRankingStore = writeRankingTx.objectStore('rankings'); await writeRankingStore.add(newRanking); await writeRankingTx.done; Message.success('本月考核排名已完成,并已更新挂牌状态。'); this.fetchUnits(); }, }, mounted() { this.initDB(); }, }; </script> <style scoped> .score-manager { padding: 20px; max-width: 100%; box-sizing: border-box; } .el-card { margin-bottom: 20px; } .dialog-footer { text-align: right; } .el-collapse__item { margin-bottom: 20px; } .table-container { max-height: 500px; /* 根据需要调整 */ overflow-y: auto; } .table-container-detail { max-height: 300px; /* 根据需要调整 */ overflow-y: auto; } .detail-content { max-height: 800px; /* 增加高度以适应图表和反馈 */ overflow-y: auto; } @media (max-width: 768px) { .el-table th, .el-table td { padding: 8px; } .el-dialog { width: 90% !important; } } </style>text<!-- 配置管理 Tab --> <el-tab-pane label="配置管理" name="configManagement"> <el-card> <div slot="header"> <span>配置管理</span> </div> <!-- 问题赋分标准管理 --> <el-collapse v-model="activePanels"> <el-collapse-item title="问题赋分标准" name="1"> <el-form @submit.native.prevent="saveCriteria"> <el-table :data="scoringCriteria" style="width: 100%; margin-top: 20px;" border> <el-table-column prop="criteriaName" label="标准名称" min-width="150"></el-table-column> <el-table-column prop="label" label="标签" min-width="150"></el-table-column> <el-table-column prop="time" label="时间(小时)" min-width="120"></el-table-column> <el-table-column prop="score" label="扣分" min-width="100"></el-table-column> <el-table-column label="操作" min-width="150"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="editCriteria(scope.row)">编辑</el-button> </template> </el-table-column> </el-table> <el-button type="primary" style="margin-top: 20px;" @click="saveCriteria">保存标准</el-button> </el-form> </el-collapse-item> <!-- 权重管理 --> <el-collapse-item title="权重配置" name="2"> <el-form @submit.native.prevent="saveWeights"> <el-table :data="weights" style="width: 100%; margin-top: 20px;" border> <el-table-column prop="key" label="键" min-width="150"></el-table-column> <el-table-column prop="label" label="标签" min-width="150"></el-table-column> <el-table-column prop="weight" label="权重" min-width="100"></el-table-column> <el-table-column prop="unit" label="单位" min-width="100"></el-table-column> <el-table-column label="操作" min-width="150"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="editWeight(scope.row)">编辑</el-button> </template> </el-table-column> </el-table> <el-button type="primary" style="margin-top: 20px;" @click="saveWeights">保存权重</el-button> </el-form> </el-collapse-item> </el-collapse> </el-card> </el-tab-pane> </el-tabs> <!-- 添加/编辑单位对话框 --> <el-dialog :title="dialogTitle" :visible.sync="unitDialogVisible" width="50%"> <el-form :model="currentUnit" label-width="120px"> <el-form-item label="单位名称" required> <el-input v-model="currentUnit.unitName" placeholder="请输入单位名称"></el-input> </el-form-item> <el-form-item label="单位类型" required> <el-select v-model="currentUnit.unitType" placeholder="请选择单位类型"> <el-option label="分局" value="分局"></el-option> <el-option label="派出所" value="派出所"></el-option> </el-select> </el-form-item> <el-form-item label="初始分数" required> <el-input-number v-model="currentUnit.unitScore" :min="0"></el-input-number> </el-form-item> <el-form-item label="问题解决率 (%)" required> <el-input-number v-model="currentUnit.problemResolutionRate" :min="0" :max="100"></el-input-number> </el-form-item> <el-form-item label="工作质量 (%)" required> <el-input-number v-model="currentUnit.workQuality" :min="0" :max="100"></el-input-number> </el-form-item> <!-- 可以根据需要添加更多字段 --> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="unitDialogVisible = false">取消</el-button> <el-button type="primary" @click="saveUnit">保存</el-button> </div> </el-dialog> <!-- 编辑标准对话框 --> <el-dialog title="编辑标准" :visible.sync="editCriteriaDialog" width="40%"> <el-form :model="editCriteriaData" label-width="120px"> <el-form-item label="标准名称" required> <el-input v-model="editCriteriaData.criteriaName" placeholder="例如:优秀"></el-input> </el-form-item> <el-form-item label="标签" required> <el-input v-model="editCriteriaData.label" placeholder="例如:高效"></el-input> </el-form-item> <el-form-item label="时间(小时)" required> <el-input-number v-model="editCriteriaData.time" :min="0"></el-input-number> </el-form-item> <el-form-item label="扣分" required> <el-input-number v-model="editCriteriaData.score" :min="0"></el-input-number> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="editCriteriaDialog = false">取消</el-button> <el-button type="primary" @click="updateCriteria">保存</el-button> </div> </el-dialog> <!-- 编辑权重对话框 --> <el-dialog title="编辑权重" :visible.sync="editWeightDialog" width="40%"> <el-form :model="editWeightData" label-width="120px"> <el-form-item label="键" required> <el-input v-model="editWeightData.key" disabled></el-input> </el-form-item> <el-form-item label="标签" required> <el-input v-model="editWeightData.label" placeholder="例如:处理时效"></el-input> </el-form-item> <el-form-item label="权重" required> <el-input-number v-model="editWeightData.weight" :min="0" :step="0.01"></el-input-number> </el-form-item> <el-form-item label="单位"> <el-input v-model="editWeightData.unit" placeholder="例如:%(可选)"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="editWeightDialog = false">取消</el-button> <el-button type="primary" @click="updateWeight">保存</el-button> </div> </el-dialog> <!-- 查看详情对话框 --> <el-dialog :title="detailDialogTitle" :visible.sync="detailDialogVisible" width="80%"> <div v-if="selectedUnitDetails" class="detail-content"> <el-row :gutter="20"> <el-col :span="12"> <h3>基本信息</h3> <p><strong>单位名称:</strong> {{ selectedUnitDetails.unitName }}</p> <p><strong>单位类型:</strong> {{ selectedUnitDetails.unitType }}</p> <p><strong>初始分数:</strong> {{ selectedUnitDetails.unitScore }}</p> <p><strong>问题解决率:</strong> {{ selectedUnitDetails.problemResolutionRate }}%</p> <p><strong>工作质量:</strong> {{ selectedUnitDetails.workQuality }}%</p> <p><strong>挂牌状态:</strong> <el-tag :type="selectedUnitDetails.isListed ? 'danger' : 'success'"> {{ selectedUnitDetails.isListed ? '已挂牌' : '未挂牌' }} </el-tag> </p> </el-col> <el-col :span="12"> <h3>得分信息</h3> <p><strong>最终得分:</strong> {{ calculateFinalScore(selectedUnitDetails).toFixed(2) }}</p> <p><strong>扣分详情:</strong></p> <ul> <li>处理时效扣分: {{ deductions.timeDeduction.toFixed(2) }}</li> <li>问题解决率扣分: {{ deductions.resolutionRateDeduction.toFixed(2) }}</li> <li>工作质量扣分: {{ deductions.workQualityDeduction.toFixed(2) }}</li> </ul> <p><strong>解读:</strong> {{ interpretation }}</p> </el-col> </el-row> <!-- 图表展示 --> <el-row :gutter="20" style="margin-top: 20px;"> <el-col :span="24"> <h3>得分扣分分布图</h3> <div ref="scoreChart" style="width: 100%; height: 400px;"></div> </el-col> </el-row> <el-divider></el-divider> <!-- 反馈部分 --> <el-row :gutter="20"> <el-col :span="24"> <h3>反馈</h3> <el-form @submit.native.prevent="saveFeedback"> <el-form-item label="反馈" label-width="80px"> <el-input type="textarea" v-model="feedbackInput" placeholder="请输入反馈内容"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="saveFeedback">保存反馈</el-button> <el-button @click="clearFeedback">取消</el-button> </el-form-item> </el-form> <div v-if="selectedUnitDetails.feedback" style="margin-top: 20px;"> <h4>已保存的反馈:</h4> <p>{{ selectedUnitDetails.feedback }}</p> </div> </el-col> </el-row> <el-divider></el-divider> <!-- 扣分规则 --> <h3>扣分规则</h3> <el-table :data="scoringCriteria" style="width: 100%" border> <el-table-column prop="criteriaName" label="标准名称" min-width="150"></el-table-column> <el-table-column prop="label" label="标签" min-width="150"></el-table-column> <el-table-column prop="time" label="时间(小时)" min-width="120"></el-table-column> <el-table-column prop="score" label="扣分" min-width="100"></el-table-column> </el-table> <el-divider></el-divider> <!-- 扣分计算过程 --> <h3>扣分计算过程</h3> <el-row :gutter="20"> <el-col :span="24"> <el-card> <p><strong>1. 处理时效扣分:</strong></p> <p>平均处理时间: {{ deductions.avgProcessingTime.toFixed(2) }} 小时</p> <p>对应扣分: {{ deductions.timeDeduction.toFixed(2) }}</p> </el-card> </el-col> <el-col :span="24"> <el-card> <p><strong>2. 问题解决率扣分:</strong></p> <p>问题解决率: {{ selectedUnitDetails.problemResolutionRate }}%</p> <p>对应扣分: {{ deductions.resolutionRateDeduction.toFixed(2) }}</p> </el-card> </el-col> <el-col :span="24"> <el-card> <p><strong>3. 工作质量扣分:</strong></p> <p>工作质量: {{ selectedUnitDetails.workQuality }}%</p> <p>对应扣分: {{ deductions.workQualityDeduction.toFixed(2) }}</p> </el-card> </el-col> </el-row> <el-divider></el-divider> <!-- 举报投诉列表 (隐藏) --> <!-- <h3>举报投诉列表</h3> <div class="table-container-detail"> <el-table :data="selectedUnitDetails.problemList" style="width: 100%" border> <el-table-column prop="problemType" label="问题类型" min-width="120"></el-table-column> <el-table-column prop="problemCategory" label="问题类别" min-width="120"></el-table-column> <el-table-column prop="problemDescription" label="问题描述"></el-table-column> <el-table-column prop="problemResolutionStatus" label="处理状态" min-width="120"></el-table-column> <el-table-column prop="problemOccurrenceTime" label="发生时间" min-width="150"></el-table-column> <el-table-column prop="problemResolutionTime" label="处理时间" min-width="150"></el-table-column> <el-table-column prop="problemResolutionPerson" label="处理人" min-width="120"></el-table-column> <el-table-column prop="satisfactionLevel" label="满意度" min-width="120"> <template slot-scope="scope"> <span v-if="scope.row.satisfactionLevel !== null">{{ scope.row.satisfactionLevel }}</span> <span v-else>-</span> </template> </el-table-column> </el-table> </div> --> <el-divider></el-divider> <!-- 数字督察列表 (隐藏) --> <!-- <h3>数字督察列表</h3> <div class="table-container-detail"> <el-table :data="selectedUnitDetails.supervisionList" style="width: 100%" border> <el-table-column prop="problemType" label="问题类型" min-width="120"></el-table-column> <el-table-column prop="problemCategory" label="问题类别" min-width="120"></el-table-column> <el-table-column prop="problemDescription" label="问题描述"></el-table-column> <el-table-column prop="problemResolutionStatus" label="处理状态" min-width="120"></el-table-column> <el-table-column prop="problemOccurrenceTime" label="发生时间" min-width="150"></el-table-column> <el-table-column prop="problemResolutionTime" label="处理时间" min-width="150"></el-table-column> <el-table-column prop="problemResolutionPerson" label="处理人" min-width="120"></el-table-column> <el-table-column prop="satisfactionLevel" label="满意度" min-width="120"> <template slot-scope="scope"> <span v-if="scope.row.satisfactionLevel !== null">{{ scope.row.satisfactionLevel }}</span> <span v-else>-</span> </template> </el-table-column> </el-table> </div> --> </div> <div slot="footer" class="dialog-footer"> <el-button @click="detailDialogVisible = false">关闭</el-button> </div> </el-dialog>
按照问题高发、案件投诉总量对济南市各分局、派出所进行排名,针对高风险单位进行挂牌督办,分局为红色2个单位,派出所5个单位进行挂牌公示
列表展示出来 问题量 举报投诉量
并且分局是红色的
并且挂牌你可以在列表加图标
Certainly! I'll help you enhance your Vue.js application to meet the specified requirements:
分局
) and police stations (派出所
) in Jinan city based on the number of issues and total complaints.挂牌督办
), displaying 2 branches in red and 5 police stations with appropriate icons.问题量
) and complaints (举报投诉量
) in the list.Below is the updated code with detailed explanations of the changes made to achieve these functionalities.
Changes Made:
Added Two New Columns:
问题量
(Number of Issues) mapped to supervisionReportCount
.举报投诉量
(Number of Complaints) mapped to complaintCount
.Conditional Styling:
分局
) are displayed in red color in both 单位名称
(Unit Name) and 单位类型
(Unit Type) columns.Added Icons for Listed Units:
el-icon-star-on
) is displayed next to the unit name if the unit is listed (isListed
).Responsive Enhancements:
vue<template> <div class="score-manager"> <el-tabs v-model="activeTab" @tab-click="handleTabClick"> <!-- 得分计算 Tab --> <el-tab-pane label="得分计算" name="scoreCalculation"> <el-card> <div slot="header"> <span>单位得分计算</span> </div> <div class="table-container"> <el-table :data="units" style="width: 100%" border> <!-- 单位名称 Column with Conditional Styling and Icon --> <el-table-column label="单位名称" min-width="150"> <template slot-scope="scope"> <span :style="{ color: scope.row.unitType === '分局' ? 'red' : 'inherit' }"> {{ scope.row.unitName }} <el-icon v-if="scope.row.isListed" name="star-on" style="color: gold; margin-left: 5px;"></el-icon> </span> </template> </el-table-column> <!-- 单位类型 Column with Conditional Styling --> <el-table-column label="单位类型" min-width="120"> <template slot-scope="scope"> <span :style="{ color: scope.row.unitType === '分局' ? 'red' : 'inherit' }"> {{ scope.row.unitType }} </span> </template> </el-table-column> <!-- 初始分数 Column --> <el-table-column prop="unitScore" label="初始分数" min-width="100"></el-table-column> <!-- 最终得分 Column --> <el-table-column label="最终得分" min-width="100"> <template slot-scope="scope"> {{ calculateFinalScore(scope.row).toFixed(2) }} </template> </el-table-column> <!-- 问题量 Column --> <el-table-column prop="supervisionReportCount" label="问题量" min-width="100"></el-table-column> <!-- 举报投诉量 Column --> <el-table-column prop="complaintCount" label="举报投诉量" min-width="120"></el-table-column> <!-- 挂牌状态 Column with Tag and Icon --> <el-table-column label="挂牌状态" min-width="120"> <template slot-scope="scope"> <el-tag :type="scope.row.isListed ? 'danger' : 'success'"> {{ scope.row.isListed ? '已挂牌' : '未挂牌' }} </el-tag> <el-icon v-if="scope.row.isListed" name="star-on" style="color: gold; margin-left: 5px;"></el-icon> </template> </el-table-column> <!-- 操作 Column --> <el-table-column label="操作" min-width="300"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="viewDetails(scope.row)">查看详情</el-button> <el-button type="warning" size="mini" v-if="!scope.row.isListed" @click="listUnit(scope.row)">挂牌督办</el-button> <el-button type="success" size="mini" v-else @click="delistUnit(scope.row)">摘牌</el-button> <el-button type="danger" size="mini" @click="deleteUnit(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> </div> <el-button type="info" style="margin-top: 20px; margin-left: 10px;" @click="rankUnits">进行月度考核排名</el-button> </el-card> </el-tab-pane> <!-- 配置管理 Tab (No changes made) --> <el-tab-pane label="配置管理" name="configManagement"> <!-- Existing configuration management content --> </el-tab-pane> </el-tabs> <!-- Existing Dialogs (No changes made) --> </div> </template>
Changes Made:
Updated Ranking Logic:
rankUnits
method now correctly identifies and marks the top 2 branches (分局
) and top 5 police stations (派出所
) based on the total number of issues and complaints.Icon Integration:
isListed
flag correctly reflects in the UI, enabling the display of icons for listed units.Data Integrity:
supervisionReportCount
and complaintCount
) to support the new columns.javascript<script> import { openDB } from 'idb'; import { Message } from 'element-ui'; // Ensure Element UI is installed and imported import * as echarts from 'echarts'; export default { name: 'ScoreManager', data() { return { activeTab: 'scoreCalculation', activePanels: ['1', '2'], // Default expanded panels units: [], scoringCriteria: [], weights: [], unitDialogVisible: false, currentUnit: { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', // Added feedback field }, dialogTitle: '添加单位', editCriteriaDialog: false, editCriteriaData: {}, editWeightDialog: false, editWeightData: {}, formLabelWidth: '120px', db: null, // Detail Dialog Related detailDialogVisible: false, detailDialogTitle: '单位详情', selectedUnitDetails: null, deductions: { avgProcessingTime: 0, timeDeduction: 0, resolutionRateDeduction: 0, workQualityDeduction: 0, }, interpretation: '', feedbackInput: '', }; }, methods: { // Initialize IndexedDB async initDB() { this.db = await openDB('ScoreDB', 1, { upgrade(db) { if (!db.objectStoreNames.contains('units')) { const store = db.createObjectStore('units', { keyPath: 'id', autoIncrement: true }); store.createIndex('unitName', 'unitName', { unique: false }); } if (!db.objectStoreNames.contains('scoringCriteria')) { const store = db.createObjectStore('scoringCriteria', { keyPath: 'id', autoIncrement: true }); store.createIndex('criteriaName', 'criteriaName', { unique: true }); } if (!db.objectStoreNames.contains('weights')) { const store = db.createObjectStore('weights', { keyPath: 'id', autoIncrement: true }); store.createIndex('key', 'key', { unique: true }); } if (!db.objectStoreNames.contains('rankings')) { const store = db.createObjectStore('rankings', { keyPath: 'id', autoIncrement: true }); store.createIndex('month', 'month', { unique: true }); } }, }); await this.initializeScoringCriteria(); await this.initializeWeights(); await this.initializeUnits(); await this.initializeRankings(); }, // Initialize Scoring Criteria async initializeScoringCriteria() { const tx = this.db.transaction('scoringCriteria', 'readonly'); const store = tx.objectStore('scoringCriteria'); const count = await store.count(); if (count === 0) { const initialCriteria = [ { criteriaName: '优秀', label: '高效', time: 5, score: 0 }, { criteriaName: '良好', label: '稳定', time: 10, score: 0 }, { criteriaName: '一般', label: '可接受', time: 24, score: 1 }, { criteriaName: '较差', label: '需要改进', time: 72, score: 5 }, ]; const writeTx = this.db.transaction('scoringCriteria', 'readwrite'); const writeStore = writeTx.objectStore('scoringCriteria'); for (const criteria of initialCriteria) { await writeStore.add(criteria); } await writeTx.done; } this.fetchScoringCriteria(); }, // Initialize Weights async initializeWeights() { const tx = this.db.transaction('weights', 'readonly'); const store = tx.objectStore('weights'); const count = await store.count(); if (count === 0) { const initialWeights = [ { key: 'averageProcessingTime', label: '处理时效', weight: 0.33 }, { key: 'problemResolutionRate', label: '问题解决率', weight: 0.33, unit: '%' }, { key: 'workQuality', label: '工作质量', weight: 0.34, unit: '%' }, ]; const writeTx = this.db.transaction('weights', 'readwrite'); const writeStore = writeTx.objectStore('weights'); for (const weight of initialWeights) { await writeStore.add(weight); } await writeTx.done; } this.fetchWeights(); }, // Initialize Units Data async initializeUnits() { const tx = this.db.transaction('units', 'readonly'); const store = tx.objectStore('units'); const count = await store.count(); if (count === 0) { const initialData = [ // Sample Units Data (Ensure all units have 'complaintCount' and 'supervisionReportCount') { unitName: '历下分局', unitType: '分局', unitScore: 100, complaintCount: 5, supervisionReportCount: 2, problemResolutionRate: 85, // 85% workQuality: 90, // 90% isListed: true, // Marked as listed feedback: '', problemList: [ { problemType: '网络问题', problemCategory: '举报投诉', problemDescription: '网络连接不稳定', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-01', problemResolutionTime: '2024-10-03', problemResolutionPerson: '张三', satisfactionLevel: null, }, // More problems... ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '办公区域缺少监控摄像头', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-10', problemResolutionTime: '2024-10-12', problemResolutionPerson: '王五', satisfactionLevel: null, }, // More supervisions... ], }, // Add more units as needed ]; const writeTx = this.db.transaction('units', 'readwrite'); const writeStore = writeTx.objectStore('units'); for (const unit of initialData) { await writeStore.add(unit); } await writeTx.done; } this.fetchUnits(); }, // Initialize Rankings Data async initializeRankings() { const tx = this.db.transaction('rankings', 'readonly'); const store = tx.objectStore('rankings'); const count = await store.count(); if (count === 0) { const initialRanking = { month: this.getCurrentMonth(), rankings: [], }; const writeTx = this.db.transaction('rankings', 'readwrite'); const writeStore = writeTx.objectStore('rankings'); await writeStore.add(initialRanking); await writeTx.done; } }, // Get Current Month in YYYY-MM format getCurrentMonth() { const now = new Date(); const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, '0'); return `${year}-${month}`; }, // Fetch All Units Data async fetchUnits() { const tx = this.db.transaction('units', 'readonly'); const store = tx.objectStore('units'); this.units = await store.getAll(); }, // Fetch All Scoring Criteria async fetchScoringCriteria() { const tx = this.db.transaction('scoringCriteria', 'readonly'); const store = tx.objectStore('scoringCriteria'); this.scoringCriteria = await store.getAll(); }, // Fetch All Weights async fetchWeights() { const tx = this.db.transaction('weights', 'readonly'); const store = tx.objectStore('weights'); this.weights = await store.getAll(); }, // Calculate Final Score calculateFinalScore(unit) { let totalDeduction = 0; // Time Deduction const avgProcessingTime = this.calculateAverageProcessingTime(unit); const timeDeduction = this.getDeduction(avgProcessingTime); const timeWeight = this.getWeight('averageProcessingTime'); totalDeduction += timeDeduction * timeWeight; // Resolution Rate Deduction const resolutionRateDeduction = this.getDeductionByResolutionRate(unit.problemResolutionRate); const resolutionWeight = this.getWeight('problemResolutionRate'); totalDeduction += resolutionRateDeduction * resolutionWeight; // Work Quality Deduction const workQualityDeduction = this.getDeductionByWorkQuality(unit.workQuality); const qualityWeight = this.getWeight('workQuality'); totalDeduction += workQualityDeduction * qualityWeight; // Final Score return unit.unitScore - totalDeduction; }, // Calculate Average Processing Time in Hours calculateAverageProcessingTime(unit) { const resolutions = [...unit.problemList, ...unit.supervisionList].filter( (p) => p.problemResolutionTime && p.problemOccurrenceTime ); if (resolutions.length === 0) return 0; const totalHours = resolutions.reduce((sum, item) => { const occurrence = new Date(item.problemOccurrenceTime); const resolution = new Date(item.problemResolutionTime); const diff = (resolution - occurrence) / (1000 * 60 * 60); // Convert to hours return sum + diff; }, 0); return totalHours / resolutions.length; }, // Get Deduction Based on Average Processing Time getDeduction(avgTime) { // Assume scoringCriteria is sorted by time ascending const sortedCriteria = this.scoringCriteria.slice().sort((a, b) => a.time - b.time); for (let criteria of sortedCriteria) { if (avgTime <= criteria.time) { return criteria.score; } } // If exceeds all criteria, use the highest deduction const maxScore = Math.max(...this.scoringCriteria.map((c) => c.score)); return maxScore; }, // Get Deduction Based on Resolution Rate getDeductionByResolutionRate(rate) { if (rate >= 90) return 0; if (rate >= 75) return 1; if (rate >= 50) return 2; return 3; }, // Get Deduction Based on Work Quality getDeductionByWorkQuality(quality) { if (quality >= 90) return 0; if (quality >= 75) return 1; if (quality >= 50) return 2; return 3; }, // Get Weight Value getWeight(key) { const weightItem = this.weights.find((w) => w.key === key); return weightItem ? weightItem.weight : 0; }, // View Unit Details viewDetails(unit) { this.selectedUnitDetails = unit; // Calculate Deductions const avgProcessingTime = this.calculateAverageProcessingTime(unit); const timeDeduction = this.getDeduction(avgProcessingTime) * this.getWeight('averageProcessingTime'); const resolutionRateDeduction = this.getDeductionByResolutionRate(unit.problemResolutionRate) * this.getWeight('problemResolutionRate'); const workQualityDeduction = this.getDeductionByWorkQuality(unit.workQuality) * this.getWeight('workQuality'); this.deductions = { avgProcessingTime, timeDeduction, resolutionRateDeduction, workQualityDeduction, }; // Generate Interpretation const finalScore = this.calculateFinalScore(unit); this.interpretation = this.generateInterpretation(finalScore); // Set Feedback Input Initial Value this.feedbackInput = unit.feedback || ''; this.detailDialogVisible = true; // Draw Chart this.$nextTick(() => { this.drawScoreChart(); }); }, // Delete Unit async deleteUnit(id) { if (confirm('确定要删除此单位吗?')) { const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.delete(id); await tx.done; this.fetchUnits(); Message.success('单位删除成功'); } }, // Open Add Unit Dialog openAddUnitDialog() { this.dialogTitle = '添加单位'; this.currentUnit = { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', }; this.unitDialogVisible = true; }, // Save Unit (Add or Edit) async saveUnit() { if (!this.currentUnit.unitName || !this.currentUnit.unitType) { Message.error('请填写所有必填字段'); return; } if (this.currentUnit.id) { // Edit const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(this.currentUnit); await tx.done; Message.success('单位更新成功'); } else { // Add // Default to listed this.currentUnit.isListed = false; // Initially not listed; listing handled separately const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.add(this.currentUnit); await tx.done; Message.success('单位添加成功'); } this.unitDialogVisible = false; this.currentUnit = { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', }; this.fetchUnits(); }, // Edit Scoring Criteria editCriteria(criteria) { this.editCriteriaData = { ...criteria }; this.editCriteriaDialog = true; }, // Update Scoring Criteria async updateCriteria() { const { id, criteriaName, label, time, score } = this.editCriteriaData; if (!criteriaName || !label || time === null || score === null) { Message.error('请填写所有必填字段'); return; } // Check for unique criteriaName (excluding current record) const txCheck = this.db.transaction('scoringCriteria', 'readonly'); const storeCheck = txCheck.objectStore('scoringCriteria'); const existing = await storeCheck.index('criteriaName').get(criteriaName); if (existing && existing.id !== id) { Message.error('标准名称已存在'); return; } const tx = this.db.transaction('scoringCriteria', 'readwrite'); const store = tx.objectStore('scoringCriteria'); await store.put({ id, criteriaName, label, time, score }); await tx.done; Message.success('标准更新成功'); this.editCriteriaDialog = false; this.fetchScoringCriteria(); }, // Save Scoring Criteria async saveCriteria() { // Since scoring criteria are edited via dialog, just notify and refresh Message.success('评分标准已更新'); this.fetchScoringCriteria(); }, // Edit Weight editWeight(weight) { this.editWeightData = { ...weight }; this.editWeightDialog = true; }, // Update Weight async updateWeight() { const { id, key, label, weight, unit } = this.editWeightData; if (!key || !label || weight === null) { Message.error('请填写所有必填字段'); return; } // Check for unique key (excluding current record) const txCheck = this.db.transaction('weights', 'readonly'); const storeCheck = txCheck.objectStore('weights'); const existing = await storeCheck.index('key').get(key); if (existing && existing.id !== id) { Message.error('键已存在,请使用唯一的键'); return; } const tx = this.db.transaction('weights', 'readwrite'); const store = tx.objectStore('weights'); await store.put({ id, key, label, weight, unit: unit || undefined }); await tx.done; Message.success('权重更新成功'); this.editWeightDialog = false; this.fetchWeights(); }, // Save Weights async saveWeights() { // Since weights are edited via dialog, just notify and refresh Message.success('权重已更新'); this.fetchWeights(); }, // Handle Tab Click Event handleTabClick(tab, event) { // Handle tab switching logic if needed }, // Generate Interpretation Based on Final Score generateInterpretation(finalScore) { if (finalScore >= 90) { return '绩效优秀,表现出色。'; } else if (finalScore >= 75) { return '绩效良好,表现稳定。'; } else if (finalScore >= 50) { return '绩效一般,需要改进。'; } else { return '绩效较差,亟需改进。'; } }, // Save User Feedback async saveFeedback() { if (this.feedbackInput.trim() === '') { Message.error('反馈内容不能为空'); return; } // Update feedback field in selectedUnitDetails this.selectedUnitDetails.feedback = this.feedbackInput; // Save to IndexedDB const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(this.selectedUnitDetails); await tx.done; Message.success('反馈已保存'); this.feedbackInput = ''; this.detailDialogVisible = false; this.fetchUnits(); }, // Clear Feedback Input clearFeedback() { this.feedbackInput = ''; }, // Draw Score Chart drawScoreChart() { this.$nextTick(() => { const chartDom = this.$refs.scoreChart; const myChart = echarts.init(chartDom); const option = { title: { text: '得分扣分分布', left: 'center', }, tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} 分', }, legend: { orient: 'vertical', left: 'left', }, series: [ { name: '扣分项', type: 'pie', radius: '50%', data: [ { value: parseFloat(this.deductions.timeDeduction.toFixed(2)), name: '处理时效扣分' }, { value: parseFloat(this.deductions.resolutionRateDeduction.toFixed(2)), name: '问题解决率扣分' }, { value: parseFloat(this.deductions.workQualityDeduction.toFixed(2)), name: '工作质量扣分' }, ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, }, ], }; myChart.setOption(option); }); }, // List Unit for Supervision async listUnit(unit) { // Check current number of listed units const type = unit.unitType; const maxListings = type === '分局' ? 2 : 5; const currentListings = this.units.filter(u => u.unitType === type && u.isListed).length; if (currentListings >= maxListings) { Message.error(`当前${type}已达到最大挂牌数量(${maxListings}个)。请先摘牌其他单位。`); return; } // Update unit to be listed unit.isListed = true; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(unit); await tx.done; Message.success(`${unit.unitName} 已成功挂牌督办。`); this.fetchUnits(); }, // Delist Unit async delistUnit(unit) { // Update unit to be delisted unit.isListed = false; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(unit); await tx.done; Message.success(`${unit.unitName} 已成功摘牌。`); this.fetchUnits(); }, // Perform Monthly Ranking async rankUnits() { const month = this.getCurrentMonth(); const txCheck = this.db.transaction('rankings', 'readonly'); const storeCheck = txCheck.objectStore('rankings'); const existing = await storeCheck.index('month').get(month); if (existing) { Message.warning('本月的考核排名已存在。'); return; } // Calculate rankings based on complaintCount + supervisionReportCount const rankings = this.units .map(unit => ({ id: unit.id, unitName: unit.unitName, unitType: unit.unitType, totalIssues: unit.complaintCount + unit.supervisionReportCount, })) .sort((a, b) => b.totalIssues - a.totalIssues); const topRed = rankings .filter(unit => unit.unitType === '分局') .slice(0, 2); const topPublic = rankings .filter(unit => unit.unitType === '派出所') .slice(0, 5); // Mark listed units for (const unit of this.units) { if (topRed.some(u => u.id === unit.id) || topPublic.some(u => u.id === unit.id)) { unit.isListed = true; } else { unit.isListed = false; } } // Save updated listing status const writeTx = this.db.transaction('units', 'readwrite'); const writeStore = writeTx.objectStore('units'); for (const unit of this.units) { await writeStore.put(unit); } await writeTx.done; // Save ranking record const newRanking = { month: month, rankings: rankings, }; const writeRankingTx = this.db.transaction('rankings', 'readwrite'); const writeRankingStore = writeRankingTx.objectStore('rankings'); await writeRankingStore.add(newRanking); await writeRankingTx.done; Message.success('本月考核排名已完成,并已更新挂牌状态。'); this.fetchUnits(); }, }, mounted() { this.initDB(); }, }; </script>
Enhancements Made:
Responsive Design:
Visual Clarity:
css<style scoped> .score-manager { padding: 20px; max-width: 100%; box-sizing: border-box; } .el-card { margin-bottom: 20px; } .dialog-footer { text-align: right; } .el-collapse__item { margin-bottom: 20px; } .table-container { max-height: 500px; /* Adjust as needed */ overflow-y: auto; } .table-container-detail { max-height: 300px; /* Adjust as needed */ overflow-y: auto; } .detail-content { max-height: 800px; /* Increased height to accommodate charts and feedback */ overflow-y: auto; } @media (max-width: 768px) { .el-table th, .el-table td { padding: 8px; } .el-dialog { width: 90% !important; } } </style>
Added New Columns (问题量
and 举报投诉量
):
supervisionReportCount
) and the number of complaints (complaintCount
) for each unit.Conditional Styling for Branches (分局
):
分局
are displayed in red within both the 单位名称
and 单位类型
columns.Icons for Listed Units:
el-icon-star-on
) is displayed next to the unit name if the unit is listed (isListed
).Ranking Logic Enhancement:
rankUnits
method now accurately selects the top 2 branches and top 5 police stations based on the combined total of issues and complaints.isListed: true
), ensuring that only the highest-risk units are highlighted.User Feedback and Notifications:
Responsive Design Considerations:
Icon Integration:
el-icon-star-on
) used are supported by your version of Element UI. If you're using Element Plus or a different icon set, you might need to adjust the icon names accordingly.Data Validation:
User Permissions:
Performance Optimization:
Accessibility:
By implementing these changes, your Vue.js application will now effectively rank and display the branches and police stations in Jinan city based on their performance metrics. High-risk units will be clearly marked and easily identifiable, enhancing the overall functionality and user experience of your score management system.
你需要一下返回所有代码给我就行
中文回答
好的,以下是根据您的需求更新后的完整代码。此代码实现了以下功能:
请将以下代码替换您现有的组件代码,以实现上述功能。
vue<template> <div class="score-manager"> <el-tabs v-model="activeTab" @tab-click="handleTabClick"> <!-- 得分计算 Tab --> <el-tab-pane label="得分计算" name="scoreCalculation"> <el-card> <div slot="header"> <span>单位得分计算</span> </div> <div class="table-container"> <el-table :data="units" style="width: 100%" border> <!-- 单位名称 Column with Conditional Styling and Icon --> <el-table-column label="单位名称" min-width="150"> <template slot-scope="scope"> <span :style="{ color: scope.row.unitType === '分局' ? 'red' : 'inherit' }"> {{ scope.row.unitName }} <el-icon v-if="scope.row.isListed" name="star-on" style="color: gold; margin-left: 5px;"></el-icon> </span> </template> </el-table-column> <!-- 单位类型 Column with Conditional Styling --> <el-table-column label="单位类型" min-width="120"> <template slot-scope="scope"> <span :style="{ color: scope.row.unitType === '分局' ? 'red' : 'inherit' }"> {{ scope.row.unitType }} </span> </template> </el-table-column> <!-- 初始分数 Column --> <el-table-column prop="unitScore" label="初始分数" min-width="100"></el-table-column> <!-- 最终得分 Column --> <el-table-column label="最终得分" min-width="100"> <template slot-scope="scope"> {{ calculateFinalScore(scope.row).toFixed(2) }} </template> </el-table-column> <!-- 问题量 Column --> <el-table-column prop="supervisionReportCount" label="问题量" min-width="100"></el-table-column> <!-- 举报投诉量 Column --> <el-table-column prop="complaintCount" label="举报投诉量" min-width="120"></el-table-column> <!-- 挂牌状态 Column with Tag and Icon --> <el-table-column label="挂牌状态" min-width="120"> <template slot-scope="scope"> <el-tag :type="scope.row.isListed ? 'danger' : 'success'"> {{ scope.row.isListed ? '已挂牌' : '未挂牌' }} </el-tag> <el-icon v-if="scope.row.isListed" name="star-on" style="color: gold; margin-left: 5px;"></el-icon> </template> </el-table-column> <!-- 操作 Column --> <el-table-column label="操作" min-width="300"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="viewDetails(scope.row)">查看详情</el-button> <el-button type="warning" size="mini" v-if="!scope.row.isListed" @click="listUnit(scope.row)">挂牌督办</el-button> <el-button type="success" size="mini" v-else @click="delistUnit(scope.row)">摘牌</el-button> <el-button type="danger" size="mini" @click="deleteUnit(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> </div> <el-button type="info" style="margin-top: 20px; margin-left: 10px;" @click="rankUnits">进行月度考核排名</el-button> </el-card> </el-tab-pane> <!-- 配置管理 Tab --> <el-tab-pane label="配置管理" name="configManagement"> <el-card> <div slot="header"> <span>配置管理</span> </div> <!-- 问题赋分标准管理 --> <el-collapse v-model="activePanels"> <el-collapse-item title="问题赋分标准" name="1"> <el-form @submit.native.prevent="saveCriteria"> <el-table :data="scoringCriteria" style="width: 100%; margin-top: 20px;" border> <el-table-column prop="criteriaName" label="标准名称" min-width="150"></el-table-column> <el-table-column prop="label" label="标签" min-width="150"></el-table-column> <el-table-column prop="time" label="时间(小时)" min-width="120"></el-table-column> <el-table-column prop="score" label="扣分" min-width="100"></el-table-column> <el-table-column label="操作" min-width="150"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="editCriteria(scope.row)">编辑</el-button> </template> </el-table-column> </el-table> <el-button type="primary" style="margin-top: 20px;" @click="saveCriteria">保存标准</el-button> </el-form> </el-collapse-item> <!-- 权重管理 --> <el-collapse-item title="权重配置" name="2"> <el-form @submit.native.prevent="saveWeights"> <el-table :data="weights" style="width: 100%; margin-top: 20px;" border> <el-table-column prop="key" label="键" min-width="150"></el-table-column> <el-table-column prop="label" label="标签" min-width="150"></el-table-column> <el-table-column prop="weight" label="权重" min-width="100"></el-table-column> <el-table-column prop="unit" label="单位" min-width="100"></el-table-column> <el-table-column label="操作" min-width="150"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="editWeight(scope.row)">编辑</el-button> </template> </el-table-column> </el-table> <el-button type="primary" style="margin-top: 20px;" @click="saveWeights">保存权重</el-button> </el-form> </el-collapse-item> </el-collapse> </el-card> </el-tab-pane> </el-tabs> <!-- 添加/编辑单位对话框 --> <el-dialog :title="dialogTitle" :visible.sync="unitDialogVisible" width="50%"> <el-form :model="currentUnit" label-width="120px"> <el-form-item label="单位名称" required> <el-input v-model="currentUnit.unitName" placeholder="请输入单位名称"></el-input> </el-form-item> <el-form-item label="单位类型" required> <el-select v-model="currentUnit.unitType" placeholder="请选择单位类型"> <el-option label="分局" value="分局"></el-option> <el-option label="派出所" value="派出所"></el-option> </el-select> </el-form-item> <el-form-item label="初始分数" required> <el-input-number v-model="currentUnit.unitScore" :min="0"></el-input-number> </el-form-item> <el-form-item label="问题解决率 (%)" required> <el-input-number v-model="currentUnit.problemResolutionRate" :min="0" :max="100"></el-input-number> </el-form-item> <el-form-item label="工作质量 (%)" required> <el-input-number v-model="currentUnit.workQuality" :min="0" :max="100"></el-input-number> </el-form-item> <!-- 可以根据需要添加更多字段 --> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="unitDialogVisible = false">取消</el-button> <el-button type="primary" @click="saveUnit">保存</el-button> </div> </el-dialog> <!-- 编辑标准对话框 --> <el-dialog title="编辑标准" :visible.sync="editCriteriaDialog" width="40%"> <el-form :model="editCriteriaData" label-width="120px"> <el-form-item label="标准名称" required> <el-input v-model="editCriteriaData.criteriaName" placeholder="例如:优秀"></el-input> </el-form-item> <el-form-item label="标签" required> <el-input v-model="editCriteriaData.label" placeholder="例如:高效"></el-input> </el-form-item> <el-form-item label="时间(小时)" required> <el-input-number v-model="editCriteriaData.time" :min="0"></el-input-number> </el-form-item> <el-form-item label="扣分" required> <el-input-number v-model="editCriteriaData.score" :min="0"></el-input-number> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="editCriteriaDialog = false">取消</el-button> <el-button type="primary" @click="updateCriteria">保存</el-button> </div> </el-dialog> <!-- 编辑权重对话框 --> <el-dialog title="编辑权重" :visible.sync="editWeightDialog" width="40%"> <el-form :model="editWeightData" label-width="120px"> <el-form-item label="键" required> <el-input v-model="editWeightData.key" disabled></el-input> </el-form-item> <el-form-item label="标签" required> <el-input v-model="editWeightData.label" placeholder="例如:处理时效"></el-input> </el-form-item> <el-form-item label="权重" required> <el-input-number v-model="editWeightData.weight" :min="0" :step="0.01"></el-input-number> </el-form-item> <el-form-item label="单位"> <el-input v-model="editWeightData.unit" placeholder="例如:%(可选)"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="editWeightDialog = false">取消</el-button> <el-button type="primary" @click="updateWeight">保存</el-button> </div> </el-dialog> <!-- 查看详情对话框 --> <el-dialog :title="detailDialogTitle" :visible.sync="detailDialogVisible" width="80%"> <div v-if="selectedUnitDetails" class="detail-content"> <el-row :gutter="20"> <el-col :span="12"> <h3>基本信息</h3> <p><strong>单位名称:</strong> {{ selectedUnitDetails.unitName }}</p> <p><strong>单位类型:</strong> {{ selectedUnitDetails.unitType }}</p> <p><strong>初始分数:</strong> {{ selectedUnitDetails.unitScore }}</p> <p><strong>问题解决率:</strong> {{ selectedUnitDetails.problemResolutionRate }}%</p> <p><strong>工作质量:</strong> {{ selectedUnitDetails.workQuality }}%</p> <p><strong>挂牌状态:</strong> <el-tag :type="selectedUnitDetails.isListed ? 'danger' : 'success'"> {{ selectedUnitDetails.isListed ? '已挂牌' : '未挂牌' }} </el-tag> </p> </el-col> <el-col :span="12"> <h3>得分信息</h3> <p><strong>最终得分:</strong> {{ calculateFinalScore(selectedUnitDetails).toFixed(2) }}</p> <p><strong>扣分详情:</strong></p> <ul> <li>处理时效扣分: {{ deductions.timeDeduction.toFixed(2) }}</li> <li>问题解决率扣分: {{ deductions.resolutionRateDeduction.toFixed(2) }}</li> <li>工作质量扣分: {{ deductions.workQualityDeduction.toFixed(2) }}</li> </ul> <p><strong>解读:</strong> {{ interpretation }}</p> </el-col> </el-row> <!-- 图表展示 --> <el-row :gutter="20" style="margin-top: 20px;"> <el-col :span="24"> <h3>得分扣分分布图</h3> <div ref="scoreChart" style="width: 100%; height: 400px;"></div> </el-col> </el-row> <el-divider></el-divider> <!-- 反馈部分 --> <el-row :gutter="20"> <el-col :span="24"> <h3>反馈</h3> <el-form @submit.native.prevent="saveFeedback"> <el-form-item label="反馈" label-width="80px"> <el-input type="textarea" v-model="feedbackInput" placeholder="请输入反馈内容"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="saveFeedback">保存反馈</el-button> <el-button @click="clearFeedback">取消</el-button> </el-form-item> </el-form> <div v-if="selectedUnitDetails.feedback" style="margin-top: 20px;"> <h4>已保存的反馈:</h4> <p>{{ selectedUnitDetails.feedback }}</p> </div> </el-col> </el-row> <el-divider></el-divider> <!-- 扣分规则 --> <h3>扣分规则</h3> <el-table :data="scoringCriteria" style="width: 100%" border> <el-table-column prop="criteriaName" label="标准名称" min-width="150"></el-table-column> <el-table-column prop="label" label="标签" min-width="150"></el-table-column> <el-table-column prop="time" label="时间(小时)" min-width="120"></el-table-column> <el-table-column prop="score" label="扣分" min-width="100"></el-table-column> </el-table> <el-divider></el-divider> <!-- 扣分计算过程 --> <h3>扣分计算过程</h3> <el-row :gutter="20"> <el-col :span="24"> <el-card> <p><strong>1. 处理时效扣分:</strong></p> <p>平均处理时间: {{ deductions.avgProcessingTime.toFixed(2) }} 小时</p> <p>对应扣分: {{ deductions.timeDeduction.toFixed(2) }}</p> </el-card> </el-col> <el-col :span="24"> <el-card> <p><strong>2. 问题解决率扣分:</strong></p> <p>问题解决率: {{ selectedUnitDetails.problemResolutionRate }}%</p> <p>对应扣分: {{ deductions.resolutionRateDeduction.toFixed(2) }}</p> </el-card> </el-col> <el-col :span="24"> <el-card> <p><strong>3. 工作质量扣分:</strong></p> <p>工作质量: {{ selectedUnitDetails.workQuality }}%</p> <p>对应扣分: {{ deductions.workQualityDeduction.toFixed(2) }}</p> </el-card> </el-col> </el-row> <el-divider></el-divider> </div> <div slot="footer" class="dialog-footer"> <el-button @click="detailDialogVisible = false">关闭</el-button> </div> </el-dialog> </div> </template> <script> import { openDB } from 'idb'; import { Message } from 'element-ui'; // 确保 Element UI 已安装并在项目中引入 import * as echarts from 'echarts'; export default { name: 'ScoreManager', data() { return { activeTab: 'scoreCalculation', activePanels: ['1', '2'], // 默认展开的折叠面板 units: [], scoringCriteria: [], weights: [], unitDialogVisible: false, currentUnit: { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', // 添加反馈字段 }, dialogTitle: '添加单位', editCriteriaDialog: false, editCriteriaData: {}, editWeightDialog: false, editWeightData: {}, formLabelWidth: '120px', db: null, // 查看详情相关 detailDialogVisible: false, detailDialogTitle: '单位详情', selectedUnitDetails: null, deductions: { avgProcessingTime: 0, timeDeduction: 0, resolutionRateDeduction: 0, workQualityDeduction: 0, }, interpretation: '', feedbackInput: '', }; }, methods: { // 初始化 IndexedDB async initDB() { this.db = await openDB('ScoreDB', 1, { upgrade(db) { if (!db.objectStoreNames.contains('units')) { const store = db.createObjectStore('units', { keyPath: 'id', autoIncrement: true }); store.createIndex('unitName', 'unitName', { unique: false }); } if (!db.objectStoreNames.contains('scoringCriteria')) { const store = db.createObjectStore('scoringCriteria', { keyPath: 'id', autoIncrement: true }); store.createIndex('criteriaName', 'criteriaName', { unique: true }); } if (!db.objectStoreNames.contains('weights')) { const store = db.createObjectStore('weights', { keyPath: 'id', autoIncrement: true }); store.createIndex('key', 'key', { unique: true }); } if (!db.objectStoreNames.contains('rankings')) { const store = db.createObjectStore('rankings', { keyPath: 'id', autoIncrement: true }); store.createIndex('month', 'month', { unique: true }); } }, }); await this.initializeScoringCriteria(); await this.initializeWeights(); await this.initializeUnits(); await this.initializeRankings(); }, // 初始化评分标准 async initializeScoringCriteria() { const tx = this.db.transaction('scoringCriteria', 'readonly'); const store = tx.objectStore('scoringCriteria'); const count = await store.count(); if (count === 0) { const initialCriteria = [ { criteriaName: '优秀', label: '高效', time: 5, score: 0 }, { criteriaName: '良好', label: '稳定', time: 10, score: 0 }, { criteriaName: '一般', label: '可接受', time: 24, score: 1 }, { criteriaName: '较差', label: '需要改进', time: 72, score: 5 }, ]; const writeTx = this.db.transaction('scoringCriteria', 'readwrite'); const writeStore = writeTx.objectStore('scoringCriteria'); for (const criteria of initialCriteria) { await writeStore.add(criteria); } await writeTx.done; } this.fetchScoringCriteria(); }, // 初始化权重 async initializeWeights() { const tx = this.db.transaction('weights', 'readonly'); const store = tx.objectStore('weights'); const count = await store.count(); if (count === 0) { const initialWeights = [ { key: 'averageProcessingTime', label: '处理时效', weight: 0.33 }, { key: 'problemResolutionRate', label: '问题解决率', weight: 0.33, unit: '%' }, { key: 'workQuality', label: '工作质量', weight: 0.34, unit: '%' }, ]; const writeTx = this.db.transaction('weights', 'readwrite'); const writeStore = writeTx.objectStore('weights'); for (const weight of initialWeights) { await writeStore.add(weight); } await writeTx.done; } this.fetchWeights(); }, // 初始化单位数据 async initializeUnits() { const tx = this.db.transaction('units', 'readonly'); const store = tx.objectStore('units'); const count = await store.count(); if (count === 0) { const initialData = [ // 分局 { unitName: '历下分局', unitType: '分局', unitScore: 100, complaintCount: 5, supervisionReportCount: 2, problemResolutionRate: 85, // 85% workQuality: 90, // 90% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '网络问题', problemCategory: '举报投诉', problemDescription: '网络连接不稳定', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-01', problemResolutionTime: '2024-10-03', problemResolutionPerson: '张三', satisfactionLevel: null, }, { problemType: '系统错误', problemCategory: '举报投诉', problemDescription: '系统频繁崩溃', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-05', problemResolutionTime: '2024-10-06', problemResolutionPerson: '李四', satisfactionLevel: 90, }, ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '办公区域缺少监控摄像头', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-10', problemResolutionTime: '2024-10-12', problemResolutionPerson: '王五', satisfactionLevel: null, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '打印机无法正常工作', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-15', problemResolutionTime: '2024-10-16', problemResolutionPerson: '赵六', satisfactionLevel: 90, }, ], }, // 添加更多单位数据... { unitName: '长清分局', unitType: '分局', unitScore: 100, complaintCount: 8, supervisionReportCount: 3, problemResolutionRate: 70, // 70% workQuality: 75, // 75% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '交通问题', problemCategory: '举报投诉', problemDescription: '交通信号灯故障', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-20', problemResolutionTime: '2024-10-21', problemResolutionPerson: '孙七', satisfactionLevel: 85, }, { problemType: '环境问题', problemCategory: '举报投诉', problemDescription: '公共区域垃圾未及时清理', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-22', problemResolutionTime: '2024-10-25', problemResolutionPerson: '周八', satisfactionLevel: null, }, ], supervisionList: [ { problemType: '安全隐患', problemCategory: '数字督察', problemDescription: '消防设备老化', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-18', problemResolutionTime: '2024-10-19', problemResolutionPerson: '吴九', satisfactionLevel: 80, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '监控系统故障', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-23', problemResolutionTime: '2024-10-24', problemResolutionPerson: '郑十', satisfactionLevel: 88, }, ], }, // 派出所 { unitName: '历下区分局泉城路派出所', unitType: '派出所', unitScore: 100, complaintCount: 10, supervisionReportCount: 4, problemResolutionRate: 60, // 60% workQuality: 65, // 65% isListed: true, // 设置为已挂牌 feedback: '', problemList: [ { problemType: '治安问题', problemCategory: '举报投诉', problemDescription: '夜间治安不好', problemResolutionStatus: '未解决', problemOccurrenceTime: '2024-10-28', problemResolutionTime: '2024-10-30', problemResolutionPerson: '钱十一', satisfactionLevel: null, }, { problemType: '交通问题', problemCategory: '举报投诉', problemDescription: '车辆违停', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-29', problemResolutionTime: '2024-10-30', problemResolutionPerson: '孙十二', satisfactionLevel: 75, }, ], supervisionList: [ { problemType: '设施问题', problemCategory: '数字督察', problemDescription: '警务室照明不足', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-27', problemResolutionTime: '2024-10-28', problemResolutionPerson: '李十三', satisfactionLevel: 70, }, { problemType: '设备故障', problemCategory: '数字督察', problemDescription: '电脑系统崩溃', problemResolutionStatus: '已解决', problemOccurrenceTime: '2024-10-26', problemResolutionTime: '2024-10-27', problemResolutionPerson: '张十四', satisfactionLevel: 80, }, ], }, // 添加更多派出所数据... ]; const writeTx = this.db.transaction('units', 'readwrite'); const writeStore = writeTx.objectStore('units'); for (const unit of initialData) { await writeStore.add(unit); } await writeTx.done; } this.fetchUnits(); }, // 初始化排名数据 async initializeRankings() { const tx = this.db.transaction('rankings', 'readonly'); const store = tx.objectStore('rankings'); const count = await store.count(); if (count === 0) { const initialRanking = { month: this.getCurrentMonth(), rankings: [], }; const writeTx = this.db.transaction('rankings', 'readwrite'); const writeStore = writeTx.objectStore('rankings'); await writeStore.add(initialRanking); await writeTx.done; } }, // 获取当前月份,格式为 YYYY-MM getCurrentMonth() { const now = new Date(); const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, '0'); return `${year}-${month}`; }, // 获取所有单位数据 async fetchUnits() { const tx = this.db.transaction('units', 'readonly'); const store = tx.objectStore('units'); this.units = await store.getAll(); }, // 获取所有评分标准 async fetchScoringCriteria() { const tx = this.db.transaction('scoringCriteria', 'readonly'); const store = tx.objectStore('scoringCriteria'); this.scoringCriteria = await store.getAll(); }, // 获取所有权重 async fetchWeights() { const tx = this.db.transaction('weights', 'readonly'); const store = tx.objectStore('weights'); this.weights = await store.getAll(); }, // 计算最终得分 calculateFinalScore(unit) { let totalDeduction = 0; // 处理时效扣分 const avgProcessingTime = this.calculateAverageProcessingTime(unit); const timeDeduction = this.getDeduction(avgProcessingTime); const timeWeight = this.getWeight('averageProcessingTime'); totalDeduction += timeDeduction * timeWeight; // 问题解决率扣分 const resolutionRateDeduction = this.getDeductionByResolutionRate(unit.problemResolutionRate); const resolutionWeight = this.getWeight('problemResolutionRate'); totalDeduction += resolutionRateDeduction * resolutionWeight; // 工作质量扣分 const workQualityDeduction = this.getDeductionByWorkQuality(unit.workQuality); const qualityWeight = this.getWeight('workQuality'); totalDeduction += workQualityDeduction * qualityWeight; // 最终得分 return unit.unitScore - totalDeduction; }, // 计算平均处理时间(小时) calculateAverageProcessingTime(unit) { const resolutions = [...unit.problemList, ...unit.supervisionList].filter( (p) => p.problemResolutionTime && p.problemOccurrenceTime ); if (resolutions.length === 0) return 0; const totalHours = resolutions.reduce((sum, item) => { const occurrence = new Date(item.problemOccurrenceTime); const resolution = new Date(item.problemResolutionTime); const diff = (resolution - occurrence) / (1000 * 60 * 60); // 转换为小时 return sum + diff; }, 0); return totalHours / resolutions.length; }, // 根据平均处理时间获取扣分 getDeduction(avgTime) { // 根据 scoringCriteria 进行判断 // 假设 scoringCriteria 已按 time 升序排序 const sortedCriteria = this.scoringCriteria.slice().sort((a, b) => a.time - b.time); for (let criteria of sortedCriteria) { if (avgTime <= criteria.time) { return criteria.score; } } // 如果超过所有标准,使用最高扣分 const maxScore = Math.max(...this.scoringCriteria.map((c) => c.score)); return maxScore; }, // 根据问题解决率获取扣分(固定规则) getDeductionByResolutionRate(rate) { if (rate >= 90) return 0; if (rate >= 75) return 1; if (rate >= 50) return 2; return 3; }, // 根据工作质量获取扣分(固定规则) getDeductionByWorkQuality(quality) { if (quality >= 90) return 0; if (quality >= 75) return 1; if (quality >= 50) return 2; return 3; }, // 获取权重值 getWeight(key) { const weightItem = this.weights.find((w) => w.key === key); return weightItem ? weightItem.weight : 0; }, // 查看单位详情 viewDetails(unit) { this.selectedUnitDetails = unit; // 计算扣分详情 const avgProcessingTime = this.calculateAverageProcessingTime(unit); const timeDeduction = this.getDeduction(avgProcessingTime) * this.getWeight('averageProcessingTime'); const resolutionRateDeduction = this.getDeductionByResolutionRate(unit.problemResolutionRate) * this.getWeight('problemResolutionRate'); const workQualityDeduction = this.getDeductionByWorkQuality(unit.workQuality) * this.getWeight('workQuality'); this.deductions = { avgProcessingTime, timeDeduction, resolutionRateDeduction, workQualityDeduction, }; // 生成解读 const finalScore = this.calculateFinalScore(unit); this.interpretation = this.generateInterpretation(finalScore); // 设置反馈输入的初始值 this.feedbackInput = unit.feedback || ''; this.detailDialogVisible = true; // 绘制图表 this.$nextTick(() => { this.drawScoreChart(); }); }, // 删除单位 async deleteUnit(id) { if (confirm('确定要删除此单位吗?')) { const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.delete(id); await tx.done; this.fetchUnits(); Message.success('单位删除成功'); } }, // 打开添加单位对话框 openAddUnitDialog() { this.dialogTitle = '添加单位'; this.currentUnit = { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', }; this.unitDialogVisible = true; }, // 保存单位(添加或编辑) async saveUnit() { if (!this.currentUnit.unitName || !this.currentUnit.unitType) { Message.error('请填写所有必填字段'); return; } if (this.currentUnit.id) { // 编辑 const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(this.currentUnit); await tx.done; Message.success('单位更新成功'); } else { // 添加 // 默认设置为未挂牌 this.currentUnit.isListed = false; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.add(this.currentUnit); await tx.done; Message.success('单位添加成功'); } this.unitDialogVisible = false; this.currentUnit = { unitName: '', unitType: '', unitScore: 100, problemResolutionRate: 80, workQuality: 85, feedback: '', }; this.fetchUnits(); }, // 编辑评分标准 editCriteria(criteria) { this.editCriteriaData = { ...criteria }; this.editCriteriaDialog = true; }, // 保存编辑后的评分标准 async updateCriteria() { const { id, criteriaName, label, time, score } = this.editCriteriaData; if (!criteriaName || !label || time === null || score === null) { Message.error('请填写所有必填字段'); return; } // 检查标准名称是否唯一(排除当前记录) const txCheck = this.db.transaction('scoringCriteria', 'readonly'); const storeCheck = txCheck.objectStore('scoringCriteria'); const existing = await storeCheck.index('criteriaName').get(criteriaName); if (existing && existing.id !== id) { Message.error('标准名称已存在'); return; } const tx = this.db.transaction('scoringCriteria', 'readwrite'); const store = tx.objectStore('scoringCriteria'); await store.put({ id, criteriaName, label, time, score }); await tx.done; Message.success('标准更新成功'); this.editCriteriaDialog = false; this.fetchScoringCriteria(); }, // 保存修改后的评分标准 async saveCriteria() { // 由于评分标准是通过编辑对话框修改的,直接提示用户保存已编辑的内容 Message.success('评分标准已更新'); this.fetchScoringCriteria(); }, // 编辑权重 editWeight(weight) { this.editWeightData = { ...weight }; this.editWeightDialog = true; }, // 保存编辑后的权重 async updateWeight() { const { id, key, label, weight, unit } = this.editWeightData; if (!key || !label || weight === null) { Message.error('请填写所有必填字段'); return; } // 检查键是否唯一(排除当前记录) const txCheck = this.db.transaction('weights', 'readonly'); const storeCheck = txCheck.objectStore('weights'); const existing = await storeCheck.index('key').get(key); if (existing && existing.id !== id) { Message.error('键已存在,请使用唯一的键'); return; } const tx = this.db.transaction('weights', 'readwrite'); const store = tx.objectStore('weights'); await store.put({ id, key, label, weight, unit: unit || undefined }); await tx.done; Message.success('权重更新成功'); this.editWeightDialog = false; this.fetchWeights(); }, // 保存修改后的权重配置 async saveWeights() { // 由于权重是通过编辑对话框修改的,直接提示用户保存已编辑的内容 Message.success('权重已更新'); this.fetchWeights(); }, // 处理 Tab 点击事件(可根据需要扩展) handleTabClick(tab, event) { // 可以在此处理 Tab 切换逻辑 }, // 生成解读 generateInterpretation(finalScore) { if (finalScore >= 90) { return '绩效优秀,表现出色。'; } else if (finalScore >= 75) { return '绩效良好,表现稳定。'; } else if (finalScore >= 50) { return '绩效一般,需要改进。'; } else { return '绩效较差,亟需改进。'; } }, // 保存用户反馈 async saveFeedback() { if (this.feedbackInput.trim() === '') { Message.error('反馈内容不能为空'); return; } // 更新 selectedUnitDetails 的 feedback 字段 this.selectedUnitDetails.feedback = this.feedbackInput; // 保存到 IndexedDB const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(this.selectedUnitDetails); await tx.done; Message.success('反馈已保存'); this.feedbackInput = ''; this.detailDialogVisible = false; this.fetchUnits(); }, // 清除反馈输入 clearFeedback() { this.feedbackInput = ''; }, // 绘制图表 drawScoreChart() { this.$nextTick(() => { const chartDom = this.$refs.scoreChart; const myChart = echarts.init(chartDom); const option = { title: { text: '得分扣分分布', left: 'center', }, tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} 分', }, legend: { orient: 'vertical', left: 'left', }, series: [ { name: '扣分项', type: 'pie', radius: '50%', data: [ { value: parseFloat(this.deductions.timeDeduction.toFixed(2)), name: '处理时效扣分' }, { value: parseFloat(this.deductions.resolutionRateDeduction.toFixed(2)), name: '问题解决率扣分' }, { value: parseFloat(this.deductions.workQualityDeduction.toFixed(2)), name: '工作质量扣分' }, ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, }, ], }; myChart.setOption(option); }); }, // 列表挂牌督办 async listUnit(unit) { // 检查当前挂牌数量 const type = unit.unitType; const maxListings = type === '分局' ? 2 : 5; const currentListings = this.units.filter(u => u.unitType === type && u.isListed).length; if (currentListings >= maxListings) { Message.error(`当前${type}已达到最大挂牌数量(${maxListings}个)。请先摘牌其他单位。`); return; } // 更新单位为挂牌 unit.isListed = true; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(unit); await tx.done; Message.success(`${unit.unitName} 已成功挂牌督办。`); this.fetchUnits(); }, // 列表摘牌 async delistUnit(unit) { // 更新单位为未挂牌 unit.isListed = false; const tx = this.db.transaction('units', 'readwrite'); const store = tx.objectStore('units'); await store.put(unit); await tx.done; Message.success(`${unit.unitName} 已成功摘牌。`); this.fetchUnits(); }, // 进行月度考核排名 async rankUnits() { const month = this.getCurrentMonth(); const txCheck = this.db.transaction('rankings', 'readonly'); const storeCheck = txCheck.objectStore('rankings'); const existing = await storeCheck.index('month').get(month); if (existing) { Message.warning('本月的考核排名已存在。'); return; } // 计算排名,依据 complaintCount + supervisionReportCount const rankings = this.units .map(unit => ({ id: unit.id, unitName: unit.unitName, unitType: unit.unitType, totalIssues: unit.complaintCount + unit.supervisionReportCount, })) .sort((a, b) => b.totalIssues - a.totalIssues); const topRed = rankings .filter(unit => unit.unitType === '分局') .slice(0, 2); const topPublic = rankings .filter(unit => unit.unitType === '派出所') .slice(0, 5); // 标记挂牌单位 for (const unit of this.units) { if (topRed.some(u => u.id === unit.id) || topPublic.some(u => u.id === unit.id)) { unit.isListed = true; } else { unit.isListed = false; } } // 保存更新后的挂牌状态 const writeTx = this.db.transaction('units', 'readwrite'); const writeStore = writeTx.objectStore('units'); for (const unit of this.units) { await writeStore.put(unit); } await writeTx.done; // 保存排名记录 const newRanking = { month: month, rankings: rankings, }; const writeRankingTx = this.db.transaction('rankings', 'readwrite'); const writeRankingStore = writeRankingTx.objectStore('rankings'); await writeRankingStore.add(newRanking); await writeRankingTx.done; Message.success('本月考核排名已完成,并已更新挂牌状态。'); this.fetchUnits(); }, }, mounted() { this.initDB(); }, }; </script> <style scoped> .score-manager { padding: 20px; max-width: 100%; box-sizing: border-box; } .el-card { margin-bottom: 20px; } .dialog-footer { text-align: right; } .el-collapse__item { margin-bottom: 20px; } .table-container { max-height: 500px; /* 根据需要调整 */ overflow-y: auto; } .table-container-detail { max-height: 300px; /* 根据需要调整 */ overflow-y: auto; } .detail-content { max-height: 800px; /* 增加高度以适应图表和反馈 */ overflow-y: auto; } @media (max-width: 768px) { .el-table th, .el-table td { padding: 8px; } .el-dialog { width: 90% !important; } } </style>
模板部分 (<template>
):
分局
,则文字显示为红色。isListed
为 true
),则在名称旁边显示一个金色星形图标 (el-icon-star-on
) 作为视觉标识。supervisionReportCount
和 complaintCount
的值。脚本部分 (<script>
):
initializeUnits
方法中,确保每个单位都有 complaintCount
和 supervisionReportCount
字段,以支持新添加的列。rankUnits
方法根据 complaintCount
和 supervisionReportCount
计算总问题量,对 分局
和 派出所
进行分别排名,并将排名前2的分局和前5的派出所标记为已挂牌 (isListed = true
)。listUnit
和 delistUnit
方法分别用于挂牌和摘牌操作,并在操作后刷新单位列表。el-icon
组件显示图标,需确保 Element UI 的图标库已正确引入。样式部分 (<style scoped>
):
图标使用:
el-icon-star-on
图标。如果使用的是 Element Plus 或其他版本,可能需要调整图标名称或引入相应的图标库。数据完整性:
complaintCount
和 supervisionReportCount
)都已正确填写,以避免在列表中出现空白或错误的数据。权限管理:
性能优化:
本地化:
通过以上代码更新,您的 Vue.js 应用将能够根据问题高发和案件投诉总量对济南市各分局、派出所进行排名,并对高风险单位进行挂牌督办,同时在列表中直观地展示相关数据和状态。