| | |
| | | CompQuickSet: typeof import('./src/components/search-option/CompQuickSet.vue')['default'] |
| | | DeviceStatus: typeof import('./src/components/monitor/DeviceStatus.vue')['default'] |
| | | DistrictRanking: typeof import('./src/components/monitor/DistrictRanking.vue')['default'] |
| | | ElAffix: typeof import('element-plus/es')['ElAffix'] |
| | | ElAside: typeof import('element-plus/es')['ElAside'] |
| | | ElBadge: typeof import('element-plus/es')['ElBadge'] |
| | | ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] |
| | | ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] |
| | | ElButton: typeof import('element-plus/es')['ElButton'] |
| | | ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] |
| | | ElCalendar: typeof import('element-plus/es')['ElCalendar'] |
| | | ElCard: typeof import('element-plus/es')['ElCard'] |
| | | ElCascader: typeof import('element-plus/es')['ElCascader'] |
| | | ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] |
| | | ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton'] |
| | | ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] |
| | | ElCol: typeof import('element-plus/es')['ElCol'] |
| | | ElCollapse: typeof import('element-plus/es')['ElCollapse'] |
| | |
| | | ElDialog: typeof import('element-plus/es')['ElDialog'] |
| | | ElDivider: typeof import('element-plus/es')['ElDivider'] |
| | | ElDrawer: typeof import('element-plus/es')['ElDrawer'] |
| | | ElDropdown: typeof import('element-plus/es')['ElDropdown'] |
| | | ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] |
| | | ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] |
| | | ElEmpty: typeof import('element-plus/es')['ElEmpty'] |
| | | ElForm: typeof import('element-plus/es')['ElForm'] |
| | | ElFormItem: typeof import('element-plus/es')['ElFormItem'] |
| | |
| | | ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] |
| | | ElInput: typeof import('element-plus/es')['ElInput'] |
| | | ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] |
| | | ElLink: typeof import('element-plus/es')['ElLink'] |
| | | ElMain: typeof import('element-plus/es')['ElMain'] |
| | | ElMenu: typeof import('element-plus/es')['ElMenu'] |
| | | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] |
| | | ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup'] |
| | | ElOption: typeof import('element-plus/es')['ElOption'] |
| | | ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] |
| | | ElPagination: typeof import('element-plus/es')['ElPagination'] |
| | | ElPopover: typeof import('element-plus/es')['ElPopover'] |
| | | ElRadio: typeof import('element-plus/es')['ElRadio'] |
| | | ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] |
| | | ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] |
| | | ElRow: typeof import('element-plus/es')['ElRow'] |
| | | ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] |
| | | ElSelect: typeof import('element-plus/es')['ElSelect'] |
| | | ElSpace: typeof import('element-plus/es')['ElSpace'] |
| | | ElStatistic: typeof import('element-plus/es')['ElStatistic'] |
| | | ElStep: typeof import('element-plus/es')['ElStep'] |
| | | ElSteps: typeof import('element-plus/es')['ElSteps'] |
| | | ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] |
| | |
| | | ElTree: typeof import('element-plus/es')['ElTree'] |
| | | ElUpload: typeof import('element-plus/es')['ElUpload'] |
| | | FormCol: typeof import('./src/components/layout/FormCol.vue')['default'] |
| | | FYBgTaskCard: typeof import('./src/components/bg-task/FYBgTaskCard.vue')['default'] |
| | | FYBgTaskDialog: typeof import('./src/components/bg-task/FYBgTaskDialog.vue')['default'] |
| | | FYBgTaskItem: typeof import('./src/components/bg-task/FYBgTaskItem.vue')['default'] |
| | | FYDownloadTableButton: typeof import('./src/components/button/FYDownloadTableButton.vue')['default'] |
| | | FYForm: typeof import('./src/components/form/FYForm.vue')['default'] |
| | | FYImageSelectDialog: typeof import('./src/components/FYImageSelectDialog.vue')['default'] |
| | |
| | | FYReconfrimButton: typeof import('./src/components/button/FYReconfrimButton.vue')['default'] |
| | | FYSearchBar: typeof import('./src/components/search-option/FYSearchBar.vue')['default'] |
| | | FYTable: typeof import('./src/components/table/FYTable.vue')['default'] |
| | | IEpDataLine: typeof import('~icons/ep/data-line')['default'] |
| | | IEpDownload: typeof import('~icons/ep/download')['default'] |
| | | IEpGrid: typeof import('~icons/ep/grid')['default'] |
| | | IEpInfoFilled: typeof import('~icons/ep/info-filled')['default'] |
| | | IEpWarning: typeof import('~icons/ep/warning')['default'] |
| | | ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default'] |
| | | ItemMonitorObj: typeof import('./src/components/list-item/ItemMonitorObj.vue')['default'] |
| | | ItemScene: typeof import('./src/components/list-item/ItemScene.vue')['default'] |
| | |
| | | const CompQuickSet: typeof import('./src/components/search-option/CompQuickSet.vue')['default'] |
| | | const DeviceStatus: typeof import('./src/components/monitor/DeviceStatus.vue')['default'] |
| | | const DistrictRanking: typeof import('./src/components/monitor/DistrictRanking.vue')['default'] |
| | | const ElAffix: typeof import('element-plus/es')['ElAffix'] |
| | | const ElAside: typeof import('element-plus/es')['ElAside'] |
| | | const ElBadge: typeof import('element-plus/es')['ElBadge'] |
| | | const ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] |
| | | const ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] |
| | | const ElButton: typeof import('element-plus/es')['ElButton'] |
| | | const ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] |
| | | const ElCalendar: typeof import('element-plus/es')['ElCalendar'] |
| | | const ElCard: typeof import('element-plus/es')['ElCard'] |
| | | const ElCascader: typeof import('element-plus/es')['ElCascader'] |
| | | const ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] |
| | | const ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton'] |
| | | const ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] |
| | | const ElCol: typeof import('element-plus/es')['ElCol'] |
| | | const ElCollapse: typeof import('element-plus/es')['ElCollapse'] |
| | |
| | | const ElDialog: typeof import('element-plus/es')['ElDialog'] |
| | | const ElDivider: typeof import('element-plus/es')['ElDivider'] |
| | | const ElDrawer: typeof import('element-plus/es')['ElDrawer'] |
| | | const ElDropdown: typeof import('element-plus/es')['ElDropdown'] |
| | | const ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] |
| | | const ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] |
| | | const ElEmpty: typeof import('element-plus/es')['ElEmpty'] |
| | | const ElForm: typeof import('element-plus/es')['ElForm'] |
| | | const ElFormItem: typeof import('element-plus/es')['ElFormItem'] |
| | |
| | | const ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] |
| | | const ElInput: typeof import('element-plus/es')['ElInput'] |
| | | const ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] |
| | | const ElLink: typeof import('element-plus/es')['ElLink'] |
| | | const ElMain: typeof import('element-plus/es')['ElMain'] |
| | | const ElMenu: typeof import('element-plus/es')['ElMenu'] |
| | | const ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] |
| | | const ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup'] |
| | | const ElOption: typeof import('element-plus/es')['ElOption'] |
| | | const ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] |
| | | const ElPagination: typeof import('element-plus/es')['ElPagination'] |
| | | const ElPopover: typeof import('element-plus/es')['ElPopover'] |
| | | const ElRadio: typeof import('element-plus/es')['ElRadio'] |
| | | const ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] |
| | | const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] |
| | | const ElRow: typeof import('element-plus/es')['ElRow'] |
| | | const ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] |
| | | const ElSelect: typeof import('element-plus/es')['ElSelect'] |
| | | const ElSpace: typeof import('element-plus/es')['ElSpace'] |
| | | const ElStatistic: typeof import('element-plus/es')['ElStatistic'] |
| | | const ElStep: typeof import('element-plus/es')['ElStep'] |
| | | const ElSteps: typeof import('element-plus/es')['ElSteps'] |
| | | const ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] |
| | |
| | | const ElTree: typeof import('element-plus/es')['ElTree'] |
| | | const ElUpload: typeof import('element-plus/es')['ElUpload'] |
| | | const FormCol: typeof import('./src/components/layout/FormCol.vue')['default'] |
| | | const FYBgTaskCard: typeof import('./src/components/bg-task/FYBgTaskCard.vue')['default'] |
| | | const FYBgTaskDialog: typeof import('./src/components/bg-task/FYBgTaskDialog.vue')['default'] |
| | | const FYBgTaskItem: typeof import('./src/components/bg-task/FYBgTaskItem.vue')['default'] |
| | | const FYDownloadTableButton: typeof import('./src/components/button/FYDownloadTableButton.vue')['default'] |
| | | const FYForm: typeof import('./src/components/form/FYForm.vue')['default'] |
| | | const FYImageSelectDialog: typeof import('./src/components/FYImageSelectDialog.vue')['default'] |
| | |
| | | const FYReconfrimButton: typeof import('./src/components/button/FYReconfrimButton.vue')['default'] |
| | | const FYSearchBar: typeof import('./src/components/search-option/FYSearchBar.vue')['default'] |
| | | const FYTable: typeof import('./src/components/table/FYTable.vue')['default'] |
| | | const IEpDataLine: typeof import('~icons/ep/data-line')['default'] |
| | | const IEpDownload: typeof import('~icons/ep/download')['default'] |
| | | const IEpGrid: typeof import('~icons/ep/grid')['default'] |
| | | const IEpInfoFilled: typeof import('~icons/ep/info-filled')['default'] |
| | | const IEpWarning: typeof import('~icons/ep/warning')['default'] |
| | | const ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default'] |
| | | const ItemMonitorObj: typeof import('./src/components/list-item/ItemMonitorObj.vue')['default'] |
| | | const ItemScene: typeof import('./src/components/list-item/ItemScene.vue')['default'] |
| | |
| | | * ä¿¡ç¨è¯ä¼°APIæ¥å£ |
| | | */ |
| | | export default { |
| | | /** |
| | | * ä¸è½½ç¨æ·ç¯ä¿¡ç |
| | | * @param {*} userId |
| | | * @param {*} userName |
| | | */ |
| | | downloadCode(userId, userName) { |
| | | fetchCodeUrl(userId, userName) { |
| | | return $fytz |
| | | .get(`credit/ecCode/download?userId=${userId}`, { responseType: 'blob' }) |
| | | .then((res) => { |
| | | const name = res.headers.get('fileName') || userName |
| | | const fileName = Base64.decode(name) |
| | | const url = window.URL.createObjectURL(res.data) |
| | | const link = document.createElement('a') |
| | | link.href = url |
| | | link.setAttribute('download', fileName) |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(url) |
| | | return { fileName, url } |
| | | }) |
| | | }, |
| | | /** |
| | | * ä¸è½½ç¨æ·ç¯ä¿¡ç |
| | | * @param {*} userId |
| | | * @param {*} userName |
| | | */ |
| | | downloadCode(userId, userName) { |
| | | return this.fetchCodeUrl(userId, userName).then((res) => { |
| | | const { fileName, url } = res |
| | | const link = document.createElement('a') |
| | | link.href = url |
| | | link.setAttribute('download', fileName) |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | document.body.removeChild(link) |
| | | window.URL.revokeObjectURL(url) |
| | | }) |
| | | }, |
| | | } |
| | |
| | | <el-form :inline="true" :model="formSearch"> |
| | | <el-form-item label="æ»ä»»å¡"> |
| | | <!-- <el-input v-model="formSearch.topTaskId" placeholder="æ»ä»»å¡" /> --> |
| | | <el-select |
| | | v-model="formSearch.topTaskId" |
| | | placeholder="æ»ä»»å¡" |
| | | style="width: 260px" |
| | | > |
| | | <el-option |
| | | v-for="s in topTasks" |
| | | :key="s.value" |
| | | :label="s.label" |
| | | :value="s.value" |
| | | /> |
| | | <el-select v-model="formSearch.topTaskId" placeholder="æ»ä»»å¡" style="width: 260px"> |
| | | <el-option v-for="s in topTasks" :key="s.value" :label="s.label" :value="s.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <FYOptionScene |
| | | :allOption="false" |
| | | :init-value="false" |
| | | :type="2" |
| | | v-model:value="formSearch.scenetype" |
| | | ></FYOptionScene> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import taskApi from '@/api/fysp/taskApi'; |
| | | import taskApi from '@/api/fysp/taskApi' |
| | | import dayjs from 'dayjs' |
| | | |
| | | export default { |
| | | emits: ['onSubmit'], |
| | | props: { |
| | | btnShow: { |
| | | type: Boolean, |
| | | default: true |
| | | default: true, |
| | | }, |
| | | init: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | default: true, |
| | | }, |
| | | }, |
| | | |
| | | data() { |
| | |
| | | topTasks: [], |
| | | formSearch: { |
| | | topTaskId: '', |
| | | scenetype: '' |
| | | } |
| | | }; |
| | | scenetype: { |
| | | label: 'é¤é¥®', |
| | | value: '5', |
| | | }, |
| | | }, |
| | | } |
| | | }, |
| | | methods: { |
| | | //è·åæ¥è¯¢æ¡ä»¶ |
| | | getOptions() { |
| | | taskApi.getTopTask().then((res) => { |
| | | const list = res.map((r) => { |
| | | return { |
| | | value: r.tguid, |
| | | label: r.name, |
| | | data: r |
| | | }; |
| | | }); |
| | | this.topTasks = list; |
| | | this.formSearch.topTaskId = list[0].value; |
| | | const list = res |
| | | .filter((e) => { |
| | | return e.districtname == '徿±åº' && dayjs(e.starttime).isBefore(dayjs('2023-12-31')) |
| | | }) |
| | | .map((r) => { |
| | | return { |
| | | value: r.tguid, |
| | | label: r.name, |
| | | data: r, |
| | | } |
| | | }) |
| | | this.topTasks = list |
| | | this.formSearch.topTaskId = list[0].value |
| | | if (this.init) { |
| | | this.onSubmit(); |
| | | this.onSubmit() |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | //æ¥è¯¢åä»»å¡ç»è®¡ä¿¡æ¯ |
| | | onSubmit() { |
| | | const task = this.topTasks.find( |
| | | (t) => t.data.tguid == this.formSearch.topTaskId |
| | | ); |
| | | const task = this.topTasks.find((t) => t.data.tguid == this.formSearch.topTaskId) |
| | | const param = { |
| | | topTask: task ? task.data : {}, |
| | | sceneTypeId: this.formSearch.scenetype.value, |
| | | sceneTypeName: this.formSearch.scenetype.label, |
| | | }; |
| | | } |
| | | // console.log(param); |
| | | |
| | | this.$emit('onSubmit', param); |
| | | } |
| | | this.$emit('onSubmit', param) |
| | | }, |
| | | }, |
| | | mounted() { |
| | | this.getOptions(); |
| | | this.getOptions() |
| | | }, |
| | | expose: ['onSubmit'] |
| | | }; |
| | | expose: ['onSubmit'], |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-card shadow="never" :body-style="{ padding: 0 }"> |
| | | <template #header> |
| | | <el-row justify="space-between"> |
| | | <div> |
| | | <div><el-text tag="b" size="large">åå°ä»»å¡</el-text></div> |
| | | <el-text size="small" type="info" |
| | | >æ¾ç¤ºå½åæ£å¨è¿è¡çåå°èæ¶ä»»å¡ç¶æ</el-text |
| | | > |
| | | </div> |
| | | <el-button |
| | | icon="Refresh" |
| | | type="primary" |
| | | size="default" |
| | | :loading="loading" |
| | | @click="fetchTask" |
| | | >å·æ°ä»»å¡</el-button |
| | | > |
| | | </el-row> |
| | | <!-- <el-row> |
| | | <el-button type="default" size="default" @click="newTestTask">æ°å¢æµè¯ä»»å¡</el-button> |
| | | <el-button type="default" size="default" @click="startNewTestTask" |
| | | >æ°å»ºå¹¶è¿è¡ä¸ä¸ªæµè¯ä»»å¡</el-button |
| | | > |
| | | <el-button type="default" size="default" @click="shutDownTask" |
| | | >强å¶å
³éæææµè¯ä»»å¡</el-button |
| | | > |
| | | </el-row> --> |
| | | </template> |
| | | <el-scrollbar height="70vh" class="scrollbar"> |
| | | <template v-for="(v, i) in taskList" :key="i"> |
| | | <FYBgTaskItem |
| | | :model="v" |
| | | :index="i" |
| | | @start="startTask" |
| | | @shutDown="shutDownTask" |
| | | @remove="removeTask" |
| | | @gotoResult="gotoResult" |
| | | ></FYBgTaskItem> |
| | | </template> |
| | | </el-scrollbar> |
| | | </el-card> |
| | | </template> |
| | | <script> |
| | | /** |
| | | * èªå¨è¯ä¼°ä»»å¡ç®¡ç |
| | | */ |
| | | import { useFetchData } from '@/composables/fetchData'; |
| | | import bgtaskApi from '@/api/fysp/bgtaskApi'; |
| | | import { enumBgTask, BG_TASK_TYPE, BG_TASK_STATUS } from '@/enum/bgTask'; |
| | | import { useBgtaskStore } from '@/stores/bgtaskStore'; |
| | | |
| | | export default { |
| | | setup() { |
| | | const { loading, fetchData } = useFetchData(); |
| | | const { registerOnFetchTask } = useBgtaskStore(); |
| | | return { loading, fetchData, registerOnFetchTask }; |
| | | }, |
| | | props: { |
| | | modelValue: Number |
| | | }, |
| | | emits: ['update:modelValue'], |
| | | data() { |
| | | return { |
| | | taskList: [], |
| | | taskIndex: 0 |
| | | }; |
| | | }, |
| | | watch: { |
| | | taskList: { |
| | | handler(nV) { |
| | | let count = 0; |
| | | for (const e of nV) { |
| | | if (e.status == BG_TASK_STATUS.RUNNING.name) { |
| | | count++; |
| | | } |
| | | } |
| | | this.$emit('update:modelValue', count); |
| | | }, |
| | | deep: true |
| | | } |
| | | }, |
| | | methods: { |
| | | addTask() {}, |
| | | newTestTask() { |
| | | this.fetchData((page, pageSize) => { |
| | | return bgtaskApi |
| | | .newTestTask(`Test-Task-${++this.taskIndex}`) |
| | | .then((res) => { |
| | | this.taskList.push(res.data); |
| | | }); |
| | | }); |
| | | }, |
| | | startNewTestTask() { |
| | | this.fetchData((page, pageSize) => { |
| | | return bgtaskApi |
| | | .startNewTestTask(`Test-Task-${++this.taskIndex}`) |
| | | .then((res) => { |
| | | this.taskList.push(res.data); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | _getParam(taskStatus) { |
| | | return { |
| | | type: taskStatus.type, |
| | | id: taskStatus.id |
| | | }; |
| | | }, |
| | | fetchTask() { |
| | | this.fetchData((page, pageSize) => { |
| | | return bgtaskApi |
| | | .fetchTaskStatus({ |
| | | // type: BG_TASK_TYPE.AUTO_SCORE.name |
| | | }) |
| | | .then((res) => { |
| | | this.taskList = res.data; |
| | | }); |
| | | }); |
| | | }, |
| | | startTask(index, callback) { |
| | | this.fetchData((page, pageSize) => { |
| | | const param = this._getParam(this.taskList[index]); |
| | | return bgtaskApi.startTask(param).then((res) => { |
| | | this.taskList[index] = res.data; |
| | | callback(true); |
| | | }); |
| | | }); |
| | | }, |
| | | shutDownTask(index, callback) { |
| | | this.fetchData((page, pageSize) => { |
| | | const param = this._getParam(this.taskList[index]); |
| | | return bgtaskApi.shutDownTask(param).then((res) => { |
| | | if (index && res.data && res.data.length == 1) { |
| | | this.taskList[index] = res.data[0]; |
| | | } else { |
| | | res.data.forEach((e) => { |
| | | let v = this.taskList.find((value) => { |
| | | return value.id == e.id; |
| | | }); |
| | | const i = this.taskList.indexOf(v); |
| | | this.taskList[i] = e; |
| | | }); |
| | | } |
| | | callback(true); |
| | | }); |
| | | }); |
| | | }, |
| | | removeTask(index, callback) { |
| | | this.fetchData((page, pageSize) => { |
| | | const param = this._getParam(this.taskList[index]); |
| | | return bgtaskApi.removeTask(param).then((res) => { |
| | | if (res.data) { |
| | | this.taskList.splice(index, 1); |
| | | callback(true); |
| | | } |
| | | }); |
| | | }); |
| | | }, |
| | | gotoResult(index) {} |
| | | }, |
| | | mounted() { |
| | | this.fetchTask(); |
| | | this.registerOnFetchTask(this.fetchTask); |
| | | // setInterval(() => { |
| | | // this.fetchTask(); |
| | | // }, 10000); |
| | | } |
| | | }; |
| | | </script> |
| | | <style scoped> |
| | | .scrollbar { |
| | | padding: 8px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-popover |
| | | placement="bottom" |
| | | :width="600" |
| | | trigger="click" |
| | | v-model:visible="bgtaskStore.dialogShow" |
| | | > |
| | | <template #reference> |
| | | <el-badge :value="runningNum" :hidden="runningNum == 0" class="m-r-16"> |
| | | <el-button circle> |
| | | <el-icon v-if="runningNum > 0" color="red" class="is-loading" |
| | | ><Clock |
| | | /></el-icon> |
| | | <el-icon v-else><Clock /></el-icon> |
| | | </el-button> |
| | | </el-badge> |
| | | </template> |
| | | <!-- <el-button circle icon="Close" type="danger" @click=</el-button> --> |
| | | <FYBgTaskCard v-model="runningNum"></FYBgTaskCard> |
| | | </el-popover> |
| | | </template> |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import { useBgtaskStore } from '@/stores/bgtaskStore'; |
| | | |
| | | const bgtaskStore = useBgtaskStore(); |
| | | |
| | | const runningNum = ref(0); |
| | | |
| | | </script> |
| | | <style scoped></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-card class="m-b-8" shadow="always" :body-style="{ padding: '8px' }"> |
| | | <el-row> |
| | | <el-col :span="4"> |
| | | <div class="status-btn"> |
| | | <el-icon v-if="waiting" color="var(--el-color-info)" :size="50"><VideoPlay /></el-icon> |
| | | <el-icon v-else-if="running" color="var(--el-color-primary)" :size="50" class="is-loading" |
| | | ><Loading |
| | | /></el-icon> |
| | | <el-icon v-else-if="success" color="var(--el-color-success)" :size="50" |
| | | ><CircleCheck |
| | | /></el-icon> |
| | | <el-icon v-else-if="fail" color="var(--el-color-error)" :size="50" |
| | | ><CircleClose |
| | | /></el-icon> |
| | | <el-icon v-else color="var(--el-color-warning)" :size="50"><Warning /></el-icon> |
| | | <el-text type="info" size="small" style="position: absolute; bottom: 0">{{ |
| | | nameToLabel(model.status) |
| | | }}</el-text> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="20" class="p-l-8"> |
| | | <el-row justify="space-between"> |
| | | <el-text class="m-l-4px w-300px" tag="b" size="large" truncated>{{ model.name }}</el-text> |
| | | <el-tag>{{ nameToLabel(model.type) }}</el-tag> |
| | | </el-row> |
| | | <el-row class="p-v-8" align="bottom"> |
| | | <el-col :span="12"> |
| | | <span class="timer">{{ time }}</span> |
| | | <el-text type="info" size="small" tag="div">è¿è¡æ¶é¿</el-text> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-text type="default" size="default" tag="div" |
| | | >å¼å§ï¼{{ $fm.formatYMDHMS(model.startTime) }}</el-text |
| | | > |
| | | <el-text type="default" size="default" tag="div" |
| | | >ç»æï¼{{ $fm.formatYMDHMS(model.endTime) }}</el-text |
| | | > |
| | | </el-col> |
| | | </el-row> |
| | | <el-row justify="end" align="bottom"> |
| | | <!-- <span class="f-s color-i">IDï¼{{ model.id }}</span> --> |
| | | <el-row> |
| | | <FYReconfrimButton v-if="waiting" @confirm="startTask" v-model="startConfirm"> |
| | | <el-button |
| | | plain |
| | | icon="VideoPlay" |
| | | type="primary" |
| | | size="small" |
| | | :loading="false" |
| | | @click="startConfirm = true" |
| | | >å¼å§ä»»å¡</el-button |
| | | > |
| | | </FYReconfrimButton> |
| | | <FYReconfrimButton v-if="running" @confirm="stopTask" v-model="stopConfirm"> |
| | | <el-button |
| | | icon="VideoPause" |
| | | plain |
| | | type="danger" |
| | | size="small" |
| | | :loading="false" |
| | | @click="stopConfirm = true" |
| | | >强å¶ç»æ</el-button |
| | | > |
| | | </FYReconfrimButton> |
| | | <FYReconfrimButton v-if="!running" @confirm="removeTask" v-model="removeConfirm"> |
| | | <el-button |
| | | icon="Delete" |
| | | plain |
| | | type="danger" |
| | | size="small" |
| | | :loading="false" |
| | | @click="removeConfirm = true" |
| | | >ç§»é¤ä»»å¡</el-button |
| | | > |
| | | </FYReconfrimButton> |
| | | <template v-if="success"> |
| | | <el-button |
| | | v-if="btnType" |
| | | plain |
| | | type="success" |
| | | size="small" |
| | | :loading="false" |
| | | @click="download" |
| | | >ä¸è½½æä»¶<el-icon class="m-l-4"><Right /></el-icon |
| | | ></el-button> |
| | | <el-button |
| | | v-else |
| | | plain |
| | | type="success" |
| | | size="small" |
| | | :loading="false" |
| | | @click="gotoResult" |
| | | >æ¥çç»æ<el-icon class="m-l-4"><Right /></el-icon |
| | | ></el-button> |
| | | </template> |
| | | </el-row> |
| | | </el-row> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | </template> |
| | | <script> |
| | | import { nTlBgTask, BG_TASK_STATUS, BG_TASK_TYPE } from '@/enum/bgTask'; |
| | | import { useTimer } from '@/composables/timer'; |
| | | import downloadApi from '@/api/fysp/downloadApi'; |
| | | // import { useTimer } from '@/composables/timer2'; |
| | | |
| | | export default { |
| | | setup() { |
| | | const { time, startTimer, pauseTimer, stopTimer, count } = useTimer(); |
| | | return { time, startTimer, pauseTimer, stopTimer, count }; |
| | | }, |
| | | props: { |
| | | model: Object, |
| | | index: Number |
| | | }, |
| | | emits: ['start', 'shutDown', 'remove', 'gotoResult'], |
| | | data() { |
| | | return { |
| | | startConfirm: false, |
| | | stopConfirm: false, |
| | | removeConfirm: false |
| | | }; |
| | | }, |
| | | watch: { |
| | | 'model.status': { |
| | | handler(nV) { |
| | | switch (nV) { |
| | | case BG_TASK_STATUS.WAITING.name: |
| | | this.stopTimer(); |
| | | break; |
| | | case BG_TASK_STATUS.RUNNING.name: |
| | | this.startTimer(); |
| | | break; |
| | | case BG_TASK_STATUS.SUCCESS.name: |
| | | case BG_TASK_STATUS.FAIL.name: |
| | | case BG_TASK_STATUS.SHUTDOWN.name: |
| | | this.pauseTimer(); |
| | | break; |
| | | default: |
| | | this.stopTimer(); |
| | | break; |
| | | } |
| | | this.count = this.model.runTime; |
| | | }, |
| | | immediate: true |
| | | } |
| | | }, |
| | | computed: { |
| | | btnType() { |
| | | return this.model.type == BG_TASK_TYPE.DOCUMENT.name; |
| | | }, |
| | | waiting() { |
| | | return this.model.status == BG_TASK_STATUS.WAITING.name; |
| | | }, |
| | | running() { |
| | | return this.model.status == BG_TASK_STATUS.RUNNING.name; |
| | | }, |
| | | success() { |
| | | return this.model.status == BG_TASK_STATUS.SUCCESS.name; |
| | | }, |
| | | fail() { |
| | | return this.model.status == BG_TASK_STATUS.FAIL.name; |
| | | }, |
| | | shutdown() { |
| | | return this.model.status == BG_TASK_STATUS.SHUTDOWN.name; |
| | | } |
| | | }, |
| | | methods: { |
| | | nameToLabel(name) { |
| | | const t = nTlBgTask(name); |
| | | return t.label; |
| | | }, |
| | | startTask() { |
| | | this.$emit('start', this.index, (res) => { |
| | | if (res) { |
| | | this.startTimer(); |
| | | } |
| | | }); |
| | | }, |
| | | stopTask() { |
| | | this.$emit('shutDown', this.index, (res) => { |
| | | if (res) { |
| | | this.stopTimer(); |
| | | } |
| | | }); |
| | | }, |
| | | removeTask() { |
| | | this.$emit('remove', this.index, (res) => { |
| | | if (res) { |
| | | // this.stopTimer(); |
| | | } |
| | | }); |
| | | }, |
| | | gotoResult() { |
| | | this.$emit('gotoResult', this.index); |
| | | }, |
| | | download() { |
| | | downloadApi.downloadFile(this.model.extra); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | <style scoped> |
| | | .status-btn { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | border: var(--el-border); |
| | | border-radius: var(--el-border-radius-base); |
| | | } |
| | | .timer { |
| | | font-size: 30px; |
| | | } |
| | | </style> |
| | |
| | | </el-space> |
| | | </el-col> |
| | | <el-col :span="12" class="logout"> |
| | | <FYBgTaskDialog></FYBgTaskDialog> |
| | | <el-button icon="SwitchButton">éåºç»å½</el-button> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | type: Array, |
| | | default: () => { |
| | | return [ |
| | | { |
| | | name: 'éå®å·¥å°', |
| | | locations: { |
| | | pCode: '31', |
| | | pName: '䏿µ·å¸', |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310106', |
| | | dName: 'éå®åº' |
| | | }, |
| | | scenetype: { label: 'å·¥å°', value: '1' }, |
| | | sourceType: 2 |
| | | }, |
| | | // { |
| | | // name: 'éå®å·¥å°', |
| | | // locations: { |
| | | // pCode: '31', |
| | | // pName: '䏿µ·å¸', |
| | | // cCode: '3100', |
| | | // cName: '䏿µ·å¸', |
| | | // dCode: '310106', |
| | | // dName: 'éå®åº' |
| | | // }, |
| | | // scenetype: { label: 'å·¥å°', value: '1' }, |
| | | // sourceType: 2 |
| | | // }, |
| | | { |
| | | name: '徿±é¤é¥®', |
| | | locations: { |
| | |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310104', |
| | | dName: '徿±åº' |
| | | dName: '徿±åº', |
| | | }, |
| | | scenetype: { label: 'é¤é¥®', value: '5' }, |
| | | sourceType: 2 |
| | | sourceType: 2, |
| | | }, |
| | | { |
| | | name: 'é山工å°', |
| | | locations: { |
| | | pCode: '31', |
| | | pName: '䏿µ·å¸', |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310116', |
| | | dName: 'éå±±åº' |
| | | }, |
| | | scenetype: { label: 'å·¥å°', value: '1' }, |
| | | sourceType: 2 |
| | | }, |
| | | { |
| | | name: 'éå±±ç 头', |
| | | locations: { |
| | | pCode: '31', |
| | | pName: '䏿µ·å¸', |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310116', |
| | | dName: 'éå±±åº' |
| | | }, |
| | | scenetype: { label: 'ç 头', value: '2' }, |
| | | sourceType: 2 |
| | | }, |
| | | { |
| | | name: 'éå±±æ
æç«', |
| | | locations: { |
| | | pCode: '31', |
| | | pName: '䏿µ·å¸', |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310116', |
| | | dName: 'éå±±åº' |
| | | }, |
| | | scenetype: { label: 'æ
æç«', value: '3' }, |
| | | sourceType: 2 |
| | | } |
| | | // { |
| | | // name: 'é山工å°', |
| | | // locations: { |
| | | // pCode: '31', |
| | | // pName: '䏿µ·å¸', |
| | | // cCode: '3100', |
| | | // cName: '䏿µ·å¸', |
| | | // dCode: '310116', |
| | | // dName: 'éå±±åº' |
| | | // }, |
| | | // scenetype: { label: 'å·¥å°', value: '1' }, |
| | | // sourceType: 2 |
| | | // }, |
| | | // { |
| | | // name: 'éå±±ç 头', |
| | | // locations: { |
| | | // pCode: '31', |
| | | // pName: '䏿µ·å¸', |
| | | // cCode: '3100', |
| | | // cName: '䏿µ·å¸', |
| | | // dCode: '310116', |
| | | // dName: 'éå±±åº' |
| | | // }, |
| | | // scenetype: { label: 'ç 头', value: '2' }, |
| | | // sourceType: 2 |
| | | // }, |
| | | // { |
| | | // name: 'éå±±æ
æç«', |
| | | // locations: { |
| | | // pCode: '31', |
| | | // pName: '䏿µ·å¸', |
| | | // cCode: '3100', |
| | | // cName: '䏿µ·å¸', |
| | | // dCode: '310116', |
| | | // dName: 'éå±±åº' |
| | | // }, |
| | | // scenetype: { label: 'æ
æç«', value: '3' }, |
| | | // sourceType: 2 |
| | | // } |
| | | // { |
| | | // name: '徿±æ±½ä¿®', |
| | | // locations: { |
| | |
| | | // scenetype: { label: '汽修', value: '7' }, |
| | | // sourceType: 1, |
| | | // } |
| | | ]; |
| | | } |
| | | } |
| | | ] |
| | | }, |
| | | }, |
| | | }, |
| | | emits: ['quickSet'], |
| | | methods: { |
| | |
| | | // this.formSearch.locations = set.locations |
| | | // this.formSearch.scenetype = set.scenetype |
| | | |
| | | this.$emit('quickSet', set); |
| | | } |
| | | } |
| | | }; |
| | | this.$emit('quickSet', set) |
| | | }, |
| | | }, |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .row { |
| | |
| | | { |
| | | path: '/index/analysis/huanxincode-manage', |
| | | icon: 'solar:archive-down-minimlistic-line-duotone', |
| | | name: 'ç¯ä¿¡ç 管ç', |
| | | name: 'ç¯ä¿¡ç ', |
| | | }, |
| | | { |
| | | path: '/index/inspection/report-manage', |
| | |
| | | { |
| | | name: 'auto-evalution', |
| | | path: 'auto-evalution', |
| | | component: () => import('@/views/analysis/evalution/EvalutationRecord.vue'), |
| | | component: () => import('@/views/analysis/evalution/EvalutationTask.vue'), |
| | | }, |
| | | { |
| | | name: 'huanxincode-manage', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { defineStore } from 'pinia'; |
| | | import { ref } from 'vue'; |
| | | |
| | | export const useBgtaskStore = defineStore('bgtask', () => { |
| | | // å¼¹åºæ¡æ¾ç¤º |
| | | const dialogShow = ref(false); |
| | | |
| | | const events = []; |
| | | |
| | | function toggleShow(show) { |
| | | if (typeof show === 'boolean') { |
| | | dialogShow.value = show; |
| | | } else { |
| | | dialogShow.value = !dialogShow.value; |
| | | } |
| | | } |
| | | |
| | | function registerOnFetchTask(func) { |
| | | events.push(func); |
| | | } |
| | | |
| | | function fetchTask() { |
| | | events.forEach((e) => { |
| | | if (typeof e === 'function') { |
| | | e(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | return { |
| | | dialogShow, |
| | | toggleShow, |
| | | registerOnFetchTask, |
| | | fetchTask |
| | | }; |
| | | }); |
| | |
| | | <template> |
| | | LoginPage |
| | | |
| | | <el-button type="primary" class="login-btn" @click="login">ç»å½</el-button> |
| | | <div class="login-container"> |
| | | <div class="login-wrapper"> |
| | | <div class="login-header"> |
| | | <h1 class="login-title">é¤é¥®æ²¹çæºè½çæµä¸ç管ä¸ä½åå¹³å°</h1> |
| | | <p class="login-subtitle">欢è¿ç»å½</p> |
| | | </div> |
| | | <div class="login-form"> |
| | | <el-form :model="loginForm" :rules="rules" ref="loginFormRef" label-position="top"> |
| | | <el-form-item label="è´¦å·" prop="username"> |
| | | <el-input |
| | | v-model="loginForm.username" |
| | | placeholder="请è¾å
¥è´¦å·" |
| | | prefix-icon="User" |
| | | size="large" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å¯ç " prop="password"> |
| | | <el-input |
| | | v-model="loginForm.password" |
| | | type="password" |
| | | placeholder="请è¾å
¥å¯ç " |
| | | prefix-icon="Lock" |
| | | size="large" |
| | | show-password |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | class="login-btn" |
| | | :loading="loading" |
| | | @click="login" |
| | | size="large" |
| | | style="width: 100%" |
| | | > |
| | | ç»å½ |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import type { FormInstance, FormRules } from 'element-plus' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | const router = useRouter() |
| | | function login() { |
| | | router.push('/index/monitor/data-dashboard') |
| | | // router.push('/index') |
| | | const loginFormRef = ref<FormInstance>() |
| | | const loading = ref(false) |
| | | |
| | | const loginForm = reactive({ |
| | | username: 'admin', |
| | | password: '123456', |
| | | }) |
| | | |
| | | const rules = reactive<FormRules>({ |
| | | username: [{ required: true, message: '请è¾å
¥è´¦å·', trigger: 'blur' }], |
| | | password: [{ required: true, message: '请è¾å
¥å¯ç ', trigger: 'blur' }], |
| | | }) |
| | | |
| | | async function login() { |
| | | if (!loginFormRef.value) return |
| | | |
| | | try { |
| | | await loginFormRef.value.validate() |
| | | loading.value = true |
| | | |
| | | // 模æç»å½éªè¯ |
| | | setTimeout(() => { |
| | | if (loginForm.username === 'admin' && loginForm.password === '123456') { |
| | | router.push('/index/monitor/data-dashboard') |
| | | } else { |
| | | ElMessage.error('è´¦å·æå¯ç é误') |
| | | } |
| | | loading.value = false |
| | | }, 500) |
| | | } catch (error) { |
| | | console.log('éªè¯å¤±è´¥:', error) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | /* å
¨å±æ ·å¼éç½® */ |
| | | * { |
| | | margin: 0; |
| | | padding: 0; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | html, |
| | | body { |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| | | |
| | | <style scoped> |
| | | .login-container { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | background-image: url('@/assets/loginPageBg.png'); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding-right: 10%; |
| | | } |
| | | |
| | | .login-wrapper { |
| | | width: 400px; |
| | | background: rgba(255, 255, 255, 0.95); |
| | | border-radius: 8px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | padding: 32px; |
| | | animation: fadeIn 0.5s ease-in-out; |
| | | } |
| | | |
| | | .login-header { |
| | | text-align: center; |
| | | margin-bottom: 32px; |
| | | } |
| | | |
| | | .login-title { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .login-subtitle { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | .login-form { |
| | | width: 100%; |
| | | } |
| | | |
| | | .login-btn { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | @keyframes fadeIn { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateY(-20px); |
| | | } |
| | | to { |
| | | opacity: 1; |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <el-steps :active="stepIndex" finish-status="success" style="" align-center> |
| | | <el-step title="è¯ä¼°èå´" /> |
| | | <el-step title="æ°æ®æºæ£æ¥" /> |
| | | <el-step title="æ¡ç®è±å
" /> |
| | | <!-- <el-step title="æ¡ç®è±å
" /> --> |
| | | <el-step title="èªå¨è¯ä¼°" /> |
| | | </el-steps> |
| | | <CompCheckArea v-show="stepIndex == 0" v-model="stepIndex" @change="onAreaChange"></CompCheckArea> |
| | |
| | | ref="refSource" |
| | | @change="onDataSourceChange" |
| | | ></CompCheckSource> |
| | | <CompCheckExemption |
| | | <!-- <CompCheckExemption |
| | | v-show="stepIndex == 2" |
| | | v-model="stepIndex" |
| | | @change="onExemptionChange" |
| | | ></CompCheckExemption> |
| | | ></CompCheckExemption> --> |
| | | <CompCheckConfirm |
| | | v-show="stepIndex == 3" |
| | | v-show="stepIndex == 2" |
| | | v-model="stepIndex" |
| | | :area-info="area" |
| | | :data-source="dataSource" |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import dayjs from 'dayjs'; |
| | | import CompCheckArea from './components/CompCheckArea.vue'; |
| | | import CompCheckSource from './components/CompCheckSource.vue'; |
| | | import CompCheckExemption from './components/CompCheckExemption.vue'; |
| | | import CompCheckConfirm from './components/CompCheckConfirm.vue'; |
| | | import dayjs from 'dayjs' |
| | | import CompCheckArea from './components/CompCheckArea.vue' |
| | | import CompCheckSource from './components/CompCheckSource.vue' |
| | | import CompCheckExemption from './components/CompCheckExemption.vue' |
| | | import CompCheckConfirm from './components/CompCheckConfirm.vue' |
| | | |
| | | /** |
| | | * èªå¨è¯ä¼°æ¡ä»¶åè§æ§æ£æ¥ |
| | |
| | | stepIndex: 0, |
| | | area: { |
| | | _locations: {}, |
| | | _scenetype: {} |
| | | _scenetype: {}, |
| | | }, |
| | | dataSource: {}, |
| | | // è±å
æ¡ç® |
| | | exemptionItems: {} |
| | | }; |
| | | exemptionItems: {}, |
| | | } |
| | | }, |
| | | methods: { |
| | | /** |
| | | * çå¬è¯ä¼°èå´åæ´ |
| | | */ |
| | | onAreaChange(val) { |
| | | const v = val.value; |
| | | this.area = v; |
| | | const v = val.value |
| | | this.area = v |
| | | const a = { |
| | | provincecode: v._locations.pCode, |
| | | provincename: v._locations.pName, |
| | |
| | | endtime: this.$fm.formatYMDHMS(v.time), |
| | | scensetypeid: v._scenetype.value, |
| | | online: true, |
| | | sourceType: v.sourceType |
| | | }; |
| | | this.$refs.refSource.startCheck(a); |
| | | sourceType: v.sourceType, |
| | | } |
| | | this.$refs.refSource.startCheck(a) |
| | | }, |
| | | onDataSourceChange(val) { |
| | | this.dataSource = val; |
| | | this.dataSource = val |
| | | }, |
| | | onExemptionChange(val) { |
| | | this.exemptionItems = val; |
| | | this.exemptionItems = val |
| | | }, |
| | | /** |
| | | * èªå¨è¯ä¼°åç½®åè§æ§æ£æ¥ |
| | | * æ£æ¥æéèå´å
å项è¯ä¼°æ°æ®æºæ¯å¦å®æ´ |
| | | */ |
| | | onNewTask() { |
| | | this.$emit('startTask'); |
| | | } |
| | | } |
| | | }; |
| | | this.$emit('startTask') |
| | | }, |
| | | }, |
| | | } |
| | | </script> |
| | |
| | | <template> |
| | | <div class="huanxin-code-manage"> |
| | | <FYSearchBar @search="onSearch"> |
| | | <template #options> |
| | | <!-- åºå¿ --> |
| | | <FYOptionLocation |
| | | :initValue="false" |
| | | :allOption="false" |
| | | :level="3" |
| | | :checkStrictly="false" |
| | | v-model:value="formSearch.locations" |
| | | ></FYOptionLocation> |
| | | <!-- åºæ¯ç±»å --> |
| | | <FYOptionScene |
| | | :initValue="false" |
| | | :allOption="false" |
| | | :type="1" |
| | | v-model:value="formSearch.scenetype" |
| | | ></FYOptionScene> |
| | | <!-- æ¶é´ --> |
| | | <FYOptionTime |
| | | :initValue="false" |
| | | type="month" |
| | | v-model:value="formSearch.time" |
| | | ></FYOptionTime> |
| | | </template> |
| | | <template #buttons v-if="$slots.buttons"> </template> |
| | | </FYSearchBar> |
| | | <!-- é¡¶é¨å®è§çæ¿åº --> |
| | | <el-row :gutter="20" class="dashboard"> |
| | | <el-col :span="8"> |
| | |
| | | </el-button> |
| | | </div> --> |
| | | |
| | | <!-- ä¸é¨è§å¾åæ¢åº --> |
| | | <el-tabs v-model="activeView" class="view-tabs"> |
| | | <!-- å表è§å¾ --> |
| | | <el-tab-pane label="å表è§å¾" name="list"> |
| | | <el-table :data="filteredShopList" style="width: 100%"> |
| | | <el-table-column prop="shopName" label="åºéºåç§°" /> |
| | | <el-table-column prop="district" label="æå¨åºå¿" width="120" /> |
| | | <el-table-column prop="town" label="æå¨è¡é" width="150" /> |
| | | <el-table-column label="ç¯ä¿¡ç " width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getCodeType(scope.row.code)">{{ getCodeText(scope.row.code) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="score" label="å½åè¯å" width="120" sortable /> |
| | | <el-table-column label="è¯åååè¶å¿" width="150"> |
| | | <template #default="scope"> |
| | | <div class="trend"> |
| | | <el-icon :class="['trend-icon', scope.row.trend > 0 ? 'up' : 'down']"> |
| | | <ArrowUp v-if="scope.row.trend > 0" /> |
| | | <ArrowDown v-else /> |
| | | </el-icon> |
| | | <span :class="scope.row.trend > 0 ? 'up' : 'down'"> |
| | | {{ scope.row.trend > 0 ? '+' : '' }}{{ scope.row.trend }}å |
| | | </span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="lastUpdate" label="䏿¬¡æ´æ°æ¶é´" width="180" /> |
| | | <el-table-column label="æä½" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="viewDetails(scope.row)">æ¥ç详æ
</el-button> |
| | | <!-- <el-button size="small" type="warning" @click="viewRiskWarnings(scope.row)" |
| | | >é£é©é¢è¦è®°å½</el-button |
| | | > --> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | |
| | | <!-- å°å¾è§å¾ --> |
| | | <el-tab-pane label="å°å¾è§å¾" name="map"> |
| | | <div class="map-container"> |
| | | <div class="map-placeholder"> |
| | | <el-empty description="å°å¾å è½½ä¸..." /> |
| | | <!-- è¿éåºè¯¥éæçå®çå°å¾ç»ä»¶ --> |
| | | </div> |
| | | <div class="map-legend"> |
| | | <div class="legend-item"> |
| | | <div class="legend-dot green"></div> |
| | | <span>绿ç </span> |
| | | <!-- åºéºå表 --> |
| | | <div class="shop-list"> |
| | | <el-table :data="pagedShopList" style="width: 100%"> |
| | | <el-table-column prop="shopName" label="åºéºåç§°" /> |
| | | <el-table-column prop="district" label="æå¨åºå¿" width="120" /> |
| | | <el-table-column prop="town" label="æå¨è¡é" width="150" /> |
| | | <el-table-column label="ç¯ä¿¡ç " width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getCodeType(scope.row.code)">{{ getCodeText(scope.row.code) }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="score" label="å½åè¯å" width="120" sortable /> |
| | | <el-table-column label="è¯åååè¶å¿" width="150"> |
| | | <template #default="scope"> |
| | | <div class="trend"> |
| | | <el-icon :class="['trend-icon', scope.row.trend > 0 ? 'up' : 'down']"> |
| | | <ArrowUp v-if="scope.row.trend > 0" /> |
| | | <ArrowDown v-else /> |
| | | </el-icon> |
| | | <span :class="scope.row.trend > 0 ? 'up' : 'down'"> |
| | | {{ scope.row.trend > 0 ? '+' : '' }}{{ scope.row.trend }}å |
| | | </span> |
| | | </div> |
| | | <div class="legend-item"> |
| | | <div class="legend-dot yellow"></div> |
| | | <span>é»ç </span> |
| | | </div> |
| | | <div class="legend-item"> |
| | | <div class="legend-dot red"></div> |
| | | <span>红ç </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="lastUpdate" label="䏿¬¡æ´æ°æ¶é´" width="180" /> |
| | | <el-table-column label="æä½" width="100" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="viewDetails(scope.row)">æ¥ç详æ
</el-button> |
| | | <!-- <el-button size="small" type="warning" @click="viewRiskWarnings(scope.row)" |
| | | >é£é©é¢è¦è®°å½</el-button |
| | | > --> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- å页ç»ä»¶ --> |
| | | <div class="pagination"> |
| | | <el-pagination |
| | | v-model:current-page="currentPage" |
| | | v-model:page-size="pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="filteredShopList.length" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 详æ
æ½å± --> |
| | | <el-drawer v-model="drawerVisible" title="åºéºè¯¦æ
" direction="rtl" size="70%"> |
| | | <el-drawer |
| | | v-model="drawerVisible" |
| | | :title="selectedShop?.shopName || 'åºéºè¯¦æ
'" |
| | | direction="rtl" |
| | | size="60%" |
| | | > |
| | | <div v-if="selectedShop" class="shop-details"> |
| | | <!-- ç¯ä¿¡ç 大徿 åå½åè¯å --> |
| | | <div class="code-header"> |
| | | <div class="code-icon" :class="selectedShop.code"> |
| | | {{ getCodeText(selectedShop.code) }} |
| | | <el-row justify="space-between" style="flex-wrap: nowrap"> |
| | | <!-- ç¯ä¿¡ç 大徿 åå½åè¯å --> |
| | | <div class="code-header"> |
| | | <div class="score-info"> |
| | | <div class="score-label">å½åè¯å</div> |
| | | <div class="score-value">{{ selectedShop.score }}</div> |
| | | </div> |
| | | <div class="code-icon"> |
| | | <el-image |
| | | class="image" |
| | | :src="codeImageUrl" |
| | | :preview-src-list="[codeImageUrl]" |
| | | :initial-index="0" |
| | | fit="cover" |
| | | lazy |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div class="score-info"> |
| | | <div class="score-label">å½åè¯å</div> |
| | | <div class="score-value">{{ selectedShop.score }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¯å维度é·è¾¾å¾ --> |
| | | <div class="chart-section"> |
| | | <h3>è¯å维度åæ</h3> |
| | | <div class="radar-chart"> |
| | | <!-- è¿éåºè¯¥éæçå®çé·è¾¾å¾ç»ä»¶ --> |
| | | <el-empty description="é·è¾¾å¾å è½½ä¸..." /> |
| | | <!-- è¯å维度é·è¾¾å¾ --> |
| | | <div class="chart-section"> |
| | | <h3>è¯å维度åæ</h3> |
| | | <div class="radar-chart"> |
| | | <canvas ref="radarChart" width="500" height="400"></canvas> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-row> |
| | | |
| | | <!-- è¯ååå²è¶å¿å¾ --> |
| | | <div class="chart-section"> |
| | | <h3>è¯ååå²è¶å¿</h3> |
| | | <div class="trend-chart"> |
| | | <!-- è¿éåºè¯¥éæçå®çè¶å¿å¾ç»ä»¶ --> |
| | | <el-empty description="è¶å¿å¾å è½½ä¸..." /> |
| | | <canvas ref="trendChart" width="800" height="350"></canvas> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import dayjs from 'dayjs' |
| | | import { ref, reactive, computed, onMounted, watch } from 'vue' |
| | | import { Setting, ArrowUp, ArrowDown } from '@element-plus/icons-vue' |
| | | import * as echarts from 'echarts' |
| | | import userApi from '@/api/fytz/userApi' |
| | | import creditApi from '@/api/fytz/creditApi' |
| | | |
| | | // æç´¢è¡¨å |
| | | const formSearch = ref({ |
| | | locations: { |
| | | aCode: null, |
| | | aName: null, |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310104', |
| | | dName: '徿±åº', |
| | | mCode: null, |
| | | mName: null, |
| | | pCode: '31', |
| | | pName: '䏿µ·å¸', |
| | | tCode: null, |
| | | tName: null, |
| | | }, |
| | | scenetype: { |
| | | label: 'é¤é¥®', |
| | | value: '1', |
| | | }, |
| | | time: dayjs('2023-08-01').date(1).toDate(), |
| | | }) |
| | | // ç¶æ |
| | | const activeView = ref('list') |
| | | const drawerVisible = ref(false) |
| | | const modelConfigVisible = ref(false) |
| | | const selectedShop = ref(null) |
| | | const isAdmin = ref(true) // 模æç®¡çåæé |
| | | const filterCode = ref('all') |
| | | // å页ç¸å
³ |
| | | const currentPage = ref(1) |
| | | const pageSize = ref(10) |
| | | // ç¯ä¿¡ç å¾çURL |
| | | const codeImageUrl = ref('') |
| | | |
| | | // ç»è®¡æ°æ® |
| | | const statistics = reactive({ |
| | |
| | | 'åæ³¾é', |
| | | ] |
| | | |
| | | function onSearch() { |
| | | const f = formSearch.value |
| | | const area = {} |
| | | // è¡æ¿åºå |
| | | area.provinceCode = f.locations.pCode |
| | | area.provinceName = f.locations.pName |
| | | if (area.provinceCode == null) { |
| | | area.provinceCode = null |
| | | area.provinceName = null |
| | | } |
| | | area.cityCode = f.locations.cCode |
| | | area.cityName = f.locations.cName |
| | | area.districtCode = f.locations.dCode |
| | | area.districtName = f.locations.dName |
| | | area.townCode = f.locations.tCode |
| | | area.townName = f.locations.tName |
| | | // åºæ¯ç±»å |
| | | area.sceneTypes = [] |
| | | f.scenetype.value == null ? (area.sceneTypes = []) : (area.sceneTypes = [f.scenetype.value]) |
| | | // ä¸ä¸çº¿ç¶æ |
| | | area.online = true |
| | | // å
³é®å |
| | | area.searchText = '' |
| | | |
| | | userApi.fetchUser(currentPage.value, pageSize.value, area).then((res) => { |
| | | if (res) { |
| | | res.data |
| | | res.head.totalCount |
| | | |
| | | shopList.value = res.data.map((item, index) => { |
| | | const { score, code } = generateRandomScore() |
| | | return { |
| | | id: index + 1, |
| | | guid: item.biGuid, |
| | | shopName: item.biName, |
| | | district: item.biDistrictName, |
| | | town: item.biTownName, |
| | | code: code, |
| | | score: score, |
| | | trend: generateRandomTrend(), |
| | | lastUpdate: generateRandomDate(), |
| | | warnings: [ |
| | | { |
| | | time: generateRandomDate(), |
| | | content: 'ååå¨è¿è¡æ¶é¿ä¸è¶³', |
| | | score: 90, |
| | | handled: true, |
| | | }, |
| | | ], |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // çæ2023å¹´8æå
çéæºæ¶é´ |
| | | function generateRandomDate() { |
| | | const year = 2023 |
| | |
| | | return array[Math.floor(Math.random() * array.length)] |
| | | } |
| | | |
| | | // çæéæºè¯åå对åºç¯ä¿¡ç ç级 |
| | | function generateRandomScore() { |
| | | const score = Math.floor(Math.random() * 101) // 0-100 |
| | | let code |
| | | if (score >= 90) { |
| | | code = 'green' |
| | | } else if (score >= 60) { |
| | | code = 'yellow' |
| | | } else { |
| | | code = 'red' |
| | | } |
| | | return { |
| | | score, |
| | | code, |
| | | } |
| | | } |
| | | |
| | | // çæéæºè¯åè¶å¿ |
| | | function generateRandomTrend() { |
| | | return Math.floor(Math.random() * 11) - 5 // -5 å° 5 |
| | |
| | | district: '徿±åº', |
| | | town: getRandomElement(xuhuiTowns), |
| | | code: 'green', |
| | | score: 95, |
| | | score: 90, |
| | | trend: generateRandomTrend(), |
| | | lastUpdate: generateRandomDate(), |
| | | warnings: [ |
| | |
| | | return shopList.value.filter((shop) => shop.code === filterCode.value) |
| | | }) |
| | | |
| | | // å页åçåºéºå表 |
| | | const pagedShopList = computed(() => { |
| | | const start = (currentPage.value - 1) * pageSize.value |
| | | const end = start + pageSize.value |
| | | return filteredShopList.value.slice(start, end) |
| | | }) |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // è¿éå¯ä»¥ä»APIè·åæ°æ® |
| | | console.log('ç¯ä¿¡ç 管ç页é¢å è½½') |
| | | onSearch() |
| | | }) |
| | | |
| | | // æ¹æ³ |
| | |
| | | |
| | | function filterByCode(code) { |
| | | filterCode.value = code === filterCode.value ? 'all' : code |
| | | activeView.value = 'list' // 忢å°å表è§å¾ |
| | | currentPage.value = 1 // éç½®å°ç¬¬ä¸é¡µ |
| | | } |
| | | |
| | | // åé¡µæ¹æ³ |
| | | function handleSizeChange(size) { |
| | | pageSize.value = size |
| | | currentPage.value = 1 |
| | | } |
| | | |
| | | function handleCurrentChange(current) { |
| | | currentPage.value = current |
| | | } |
| | | |
| | | function getCodeType(code) { |
| | |
| | | } |
| | | } |
| | | |
| | | // é·è¾¾å¾åè¶å¿å¾å¼ç¨ |
| | | const radarChart = ref(null) |
| | | const trendChart = ref(null) |
| | | let radarChartInstance = null |
| | | let trendChartInstance = null |
| | | |
| | | function viewDetails(shop) { |
| | | selectedShop.value = shop |
| | | drawerVisible.value = true |
| | | |
| | | // è·åç¯ä¿¡ç å¾ç |
| | | if (shop.guid && shop.shopName) { |
| | | creditApi.fetchCodeUrl(shop.guid, shop.shopName).then((res) => { |
| | | if (res && res.url) { |
| | | codeImageUrl.value = res.url |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // å»¶è¿ç»å¶å¾è¡¨ï¼ç¡®ä¿DOMå·²æ´æ° |
| | | setTimeout(() => { |
| | | drawRadarChart() |
| | | drawTrendChart() |
| | | }, 100) |
| | | } |
| | | |
| | | // ç»å¶é·è¾¾å¾ |
| | | function drawRadarChart() { |
| | | if (!radarChart.value) return |
| | | |
| | | // 鿝æ§å®ä¾ |
| | | if (radarChartInstance) { |
| | | radarChartInstance.dispose() |
| | | } |
| | | |
| | | // åå§åechartså®ä¾ |
| | | radarChartInstance = echarts.init(radarChart.value) |
| | | |
| | | // é·è¾¾å¾æ°æ® |
| | | const labels = [ |
| | | 'å¨çº¿çæµè®¾å¤', |
| | | 'åå设æ½è®¾å¤', |
| | | 'å¨çº¿çæµè®¾å¤ç»´æ¤', |
| | | 'åå设æ½è®¾å¤ç»´æ¤', |
| | | 'å¨çº¿çæµæ°æ®é级', |
| | | '空è°å飿ºåªå£°', |
| | | 'å°ç«ç®¡ç', |
| | | 'ä¿¡ç¨æ¿è¯ºèªè¯', |
| | | ] |
| | | |
| | | // çæéæºè¯åæ°æ®ï¼å®é
项ç®ä¸åºä»APIè·åï¼ |
| | | const data = labels.map(() => Math.floor(Math.random() * 40) + 60) // 60-100å |
| | | |
| | | // é
置项 |
| | | const option = { |
| | | radar: { |
| | | indicator: labels.map((label) => ({ |
| | | name: label, |
| | | max: 100, |
| | | })), |
| | | radius: '70%', |
| | | }, |
| | | series: [ |
| | | { |
| | | type: 'radar', |
| | | data: [ |
| | | { |
| | | value: data, |
| | | name: 'è¯å维度', |
| | | areaStyle: { |
| | | color: 'rgba(103, 194, 58, 0.2)', |
| | | }, |
| | | lineStyle: { |
| | | color: '#67c23a', |
| | | }, |
| | | itemStyle: { |
| | | color: '#67c23a', |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | // 渲æå¾è¡¨ |
| | | radarChartInstance.setOption(option) |
| | | |
| | | // çå¬çªå£å¤§å°åå |
| | | window.addEventListener('resize', () => { |
| | | radarChartInstance.resize() |
| | | }) |
| | | } |
| | | |
| | | // ç»å¶è¶å¿å¾ |
| | | function drawTrendChart() { |
| | | if (!trendChart.value) return |
| | | |
| | | // 鿝æ§å®ä¾ |
| | | if (trendChartInstance) { |
| | | trendChartInstance.dispose() |
| | | } |
| | | |
| | | // åå§åechartså®ä¾ |
| | | trendChartInstance = echarts.init(trendChart.value) |
| | | |
| | | // çæè¿å»12个æçæ ç¾ |
| | | const labels = [] |
| | | const data = [] |
| | | const now = dayjs() |
| | | |
| | | for (let i = 11; i >= 0; i--) { |
| | | const date = now.subtract(i, 'month') |
| | | labels.push(date.format('YYYY-MM')) |
| | | // çæéæºè¯åæ°æ®ï¼å®é
项ç®ä¸åºä»APIè·åï¼ |
| | | data.push(Math.floor(Math.random() * 30) + 70) // 70-100å |
| | | } |
| | | |
| | | // é
置项 |
| | | const option = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | boundaryGap: false, |
| | | data: labels, |
| | | axisLabel: { |
| | | rotate: 45, |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | min: 60, |
| | | max: 100, |
| | | interval: 8, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: 'è¯å', |
| | | type: 'line', |
| | | data: data, |
| | | smooth: true, |
| | | lineStyle: { |
| | | color: '#409eff', |
| | | }, |
| | | itemStyle: { |
| | | color: '#409eff', |
| | | }, |
| | | areaStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgba(64, 158, 255, 0.3)', |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgba(64, 158, 255, 0.1)', |
| | | }, |
| | | ]), |
| | | }, |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | // 渲æå¾è¡¨ |
| | | trendChartInstance.setOption(option) |
| | | |
| | | // çå¬çªå£å¤§å°åå |
| | | window.addEventListener('resize', () => { |
| | | trendChartInstance.resize() |
| | | }) |
| | | } |
| | | |
| | | function viewRiskWarnings(shop) { |
| | |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .view-tabs { |
| | | .shop-list { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .pagination { |
| | | margin-top: 20px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .trend { |
| | |
| | | |
| | | .code-header { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 30px; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .code-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: white; |
| | | margin-right: 30px; |
| | | } |
| | | |
| | | .code-icon.green { |
| | | background-color: #67c23a; |
| | | } |
| | | |
| | | .code-icon.yellow { |
| | | background-color: #e6a23c; |
| | | } |
| | | |
| | | .code-icon.red { |
| | | background-color: #f56c6c; |
| | | } |
| | | |
| | | .score-info { |
| | |
| | | } |
| | | |
| | | .chart-section { |
| | | margin-bottom: 30px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-section h3 { |
| | | margin-bottom: 15px; |
| | | font-size: 18px; |
| | | margin-bottom: 10px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .radar-chart, |
| | | .radar-chart { |
| | | width: 500px; |
| | | height: 500px; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .trend-chart { |
| | | height: 300px; |
| | | height: 350px; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 4px; |
| | | display: flex; |
| | |
| | | } |
| | | |
| | | .warning-section { |
| | | margin-top: 30px; |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .warning-section h3 { |
| | | margin-bottom: 15px; |
| | | font-size: 18px; |
| | | margin-bottom: 10px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .image { |
| | | width: 300px; |
| | | /* height: 250px; */ |
| | | border-radius: 4px; |
| | | margin-bottom: 6px; |
| | | } |
| | | </style> |
| | |
| | | <template>s</template> |
| | | <template> |
| | | <div class="monitor-control"> |
| | | <!-- æ»è§ç°åºå·¡æ¥å¡ç --> |
| | | <el-card class="mb-4"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>ç°åºå·¡æ¥æ»è§</span> |
| | | <div class="filter-group"> |
| | | <FYOptionTime |
| | | :initValue="false" |
| | | type="daterange" |
| | | v-model:value="params.timeRange" |
| | | style="width: 300px; margin-bottom: 0px" |
| | | :shortcuts="shortcuts" |
| | | ></FYOptionTime> |
| | | <!-- åºå¿ --> |
| | | <FYOptionLocation |
| | | class="m-l-8" |
| | | :allOption="false" |
| | | :level="3" |
| | | :checkStrictly="false" |
| | | :initValue="false" |
| | | v-model:value="params.locations" |
| | | style="width: 300px; margin-bottom: 0px" |
| | | ></FYOptionLocation> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- ç»è®¡æ°æ®åºå --> |
| | | <div class="stats-sections"> |
| | | <!-- 左侧ï¼å·²å·¡æ¥åºéºçãå·¡æ¥ç¹æ¬¡ã夿¥ç¹æ¬¡ --> |
| | | <div class="stats-section left-section"> |
| | | <h3>å·¡æ¥æ¦åµ</h3> |
| | | <div class="chart-item"> |
| | | <div class="progress-container"> |
| | | <el-progress |
| | | type="dashboard" |
| | | :percentage="parseFloat(inspectionStats.inspectedRate)" |
| | | :color="['#409EFF', '#67C23A']" |
| | | :width="120" |
| | | /> |
| | | <div class="progress-label">已巡æ¥åºéºç</div> |
| | | <div class="progress-value"> |
| | | {{ `${inspectionStats.inspectedShops}/${inspectionStats.totalShops}` }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stats-grid m-t-16"> |
| | | <el-statistic |
| | | class="stat-item" |
| | | :value="inspectionStats.inspectionPoints" |
| | | title="å·¡æ¥ç¹æ¬¡" |
| | | /> |
| | | <el-statistic |
| | | class="stat-item" |
| | | :value="inspectionStats.reviewPoints" |
| | | title="夿¥ç¹æ¬¡" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å³ä¾§ï¼é®é¢æ°ãé®é¢æ´æ¹æ°ãé®é¢æ´æ¹çç»è®¡å¾ --> |
| | | <div class="stats-section right-section"> |
| | | <h3>é®é¢æ´æ¹æ¦åµ</h3> |
| | | <div class="stats-grid"> |
| | | <el-statistic class="stat-item" :value="inspectionStats.problemCount" title="é®é¢æ°" /> |
| | | <el-statistic |
| | | class="stat-item" |
| | | :value="inspectionStats.rectifiedProblems" |
| | | title="é®é¢æ´æ¹æ°" |
| | | /> |
| | | </div> |
| | | <!-- <div class="chart-item"> --> |
| | | <div ref="rectificationRateChart" class="chart"></div> |
| | | <!-- </div> --> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å
¶ä»å¾è¡¨å±ç¤º --> |
| | | <div class="chart-container"> |
| | | <div class="chart-item"> |
| | | <h3>å·¡æ¥å®ææ
åµè¶å¿</h3> |
| | | <div ref="inspectionTrendChart" class="chart"></div> |
| | | </div> |
| | | <div class="chart-item"> |
| | | <h3>é®é¢ç±»ååå¸</h3> |
| | | <div ref="problemTypeChart" class="chart"></div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- é¤é¥®åºéºè¡æ¿å¤ç½å¡ç --> |
| | | <el-card class="mb-4"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¤é¥®åºéºè¡æ¿å¤ç½</span> |
| | | <div class="filter-group"> |
| | | <FYOptionTime |
| | | class="m-r-8" |
| | | :initValue="false" |
| | | type="daterange" |
| | | v-model:value="punishmentDateRange" |
| | | style="width: 300px; margin-bottom: 0px" |
| | | :shortcuts="shortcuts" |
| | | ></FYOptionTime> |
| | | <el-button type="success" icon="Plus" @click="addPunishment">æ°å¢å¤ç½</el-button> |
| | | <el-button type="info" icon="Upload" @click="importPunishment">æ¹é导å
¥</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- å¾è¡¨å±ç¤º --> |
| | | <div class="chart-container"> |
| | | <div class="chart-item"> |
| | | <div class="chart-header"> |
| | | <h3>å¤ç½æ°è¶å¿</h3> |
| | | <div class="chart-summary">å¤ç½æ»æ°: {{ punishmentStats.totalCount }}</div> |
| | | </div> |
| | | <div ref="dailyPunishmentChart" class="chart"></div> |
| | | </div> |
| | | <div class="chart-item"> |
| | | <h3>åºéºç±»åå¤ç½åå¸</h3> |
| | | <div ref="shopTypePunishmentChart" class="chart"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å¤ç½è®°å½è¡¨æ ¼ --> |
| | | <el-table |
| | | :data="filteredPunishmentData" |
| | | table-layout="fixed" |
| | | :show-overflow-tooltip="true" |
| | | height="400px" |
| | | border |
| | | > |
| | | <el-table-column prop="shopName" label="å¤ç½åºéº" /> |
| | | <el-table-column prop="punishmentItem" label="å¤ç½äºé¡¹" width="180" /> |
| | | <el-table-column prop="punishmentTime" label="å¤ç½æ¶é´" width="180" /> |
| | | <el-table-column prop="punishmentReason" label="å¤ç½çç±" width="200" /> |
| | | <el-table-column prop="punishmentResult" label="å¤ç½ç»æ" width="150" /> |
| | | <el-table-column prop="punishmentDepartment" label="å¤ç½é¨é¨" width="150" /> |
| | | <el-table-column width="250"> |
| | | <template #header> |
| | | <el-input v-model="punishmentKeyword" placeholder="å
³é®åæç´¢" style="width: 120px" /> |
| | | </template> |
| | | <template #default="scope"> |
| | | <el-button size="small" type="primary" @click="editPunishment(scope.row)" |
| | | >ç¼è¾</el-button |
| | | > |
| | | <el-button size="small" type="danger" @click="deletePunishment(scope.row.id)" |
| | | >å é¤</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | v-model:current-page="punishmentPagination.currentPage" |
| | | v-model:page-size="punishmentPagination.pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="punishmentPagination.total" |
| | | @size-change="handlePunishmentSizeChange" |
| | | @current-change="handlePunishmentCurrentChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- é¤é¥®åºéºä¿¡è®¿æè¯å¡ç --> |
| | | <el-card class="mb-4"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span>é¤é¥®åºéºä¿¡è®¿æè¯</span> |
| | | <div class="filter-group"> |
| | | <FYOptionTime |
| | | class="m-r-8" |
| | | :initValue="false" |
| | | type="daterange" |
| | | v-model:value="complaintDateRange" |
| | | style="width: 300px; margin-bottom: 0px" |
| | | :shortcuts="shortcuts" |
| | | ></FYOptionTime> |
| | | <el-button type="success" icon="Plus" @click="addComplaint">æ°å¢æè¯</el-button> |
| | | <el-button type="info" icon="Upload" @click="importComplaint">æ¹é导å
¥</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- å¾è¡¨å±ç¤º --> |
| | | <div class="chart-container"> |
| | | <div class="chart-item"> |
| | | <div class="chart-header"> |
| | | <h3>æè¯æ°è¶å¿</h3> |
| | | <div class="chart-summary">æè¯æ»æ°: {{ complaintStats.totalCount }}</div> |
| | | </div> |
| | | <div ref="dailyComplaintChart" class="chart"></div> |
| | | </div> |
| | | <div class="chart-item"> |
| | | <h3>æè¯æ¥æºåå¸</h3> |
| | | <div ref="sourceComplaintChart" class="chart"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æè¯è®°å½è¡¨æ ¼ --> |
| | | <el-table |
| | | :data="filteredComplaintData" |
| | | table-layout="fixed" |
| | | :show-overflow-tooltip="true" |
| | | height="400px" |
| | | border |
| | | > |
| | | <el-table-column prop="shopName" label="æè¯åºéº" /> |
| | | <el-table-column prop="complaintReason" label="æè¯åå " width="180" /> |
| | | <el-table-column prop="complaintRequest" label="æè¯è¯æ±" width="180" /> |
| | | <el-table-column prop="complaintTime" label="æè¯æ¶é´" width="180" /> |
| | | <el-table-column prop="complaintSource" label="æè¯æ¥æº" width="150" /> |
| | | <el-table-column prop="handlingDepartment" label="å¤çé¨é¨" width="150" /> |
| | | <el-table-column prop="complaintResult" label="æè¯ç»æ" width="150" /> |
| | | <el-table-column width="250"> |
| | | <template #header> |
| | | <el-input v-model="complaintKeyword" placeholder="å
³é®åæç´¢" style="width: 120px" /> |
| | | </template> |
| | | <template #default="scope"> |
| | | <el-button size="small" type="primary" @click="editComplaint(scope.row)" |
| | | >ç¼è¾</el-button |
| | | > |
| | | <el-button size="small" type="danger" @click="deleteComplaint(scope.row.id)" |
| | | >å é¤</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <div class="pagination-container"> |
| | | <el-pagination |
| | | v-model:current-page="complaintPagination.currentPage" |
| | | v-model:page-size="complaintPagination.pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="complaintPagination.total" |
| | | @size-change="handleComplaintSizeChange" |
| | | @current-change="handleComplaintCurrentChange" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¤ç½å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="punishmentDialogVisible" title="å¤ç½ä¿¡æ¯" width="600px"> |
| | | <el-form :model="punishmentForm" label-width="100px"> |
| | | <el-form-item label="å¤ç½åºéº"> |
| | | <el-input v-model="punishmentForm.shopName" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ç½äºé¡¹"> |
| | | <el-input v-model="punishmentForm.punishmentItem" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ç½æ¶é´"> |
| | | <el-date-picker v-model="punishmentForm.punishmentTime" type="datetime" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ç½çç±"> |
| | | <el-input v-model="punishmentForm.punishmentReason" type="textarea" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ç½ç»æ"> |
| | | <el-input v-model="punishmentForm.punishmentResult" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤ç½é¨é¨"> |
| | | <el-input v-model="punishmentForm.punishmentDepartment" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="punishmentDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="savePunishment">ä¿å</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æ°å¢/ç¼è¾æè¯å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="complaintDialogVisible" title="æè¯ä¿¡æ¯" width="600px"> |
| | | <el-form :model="complaintForm" label-width="100px"> |
| | | <el-form-item label="æè¯åºéº"> |
| | | <el-input v-model="complaintForm.shopName" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¯åå "> |
| | | <el-input v-model="complaintForm.complaintReason" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¯è¯æ±"> |
| | | <el-input v-model="complaintForm.complaintRequest" type="textarea" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¯æ¶é´"> |
| | | <el-date-picker v-model="complaintForm.complaintTime" type="datetime" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¯æ¥æº"> |
| | | <el-input v-model="complaintForm.complaintSource" /> |
| | | </el-form-item> |
| | | <el-form-item label="å¤çé¨é¨"> |
| | | <el-input v-model="complaintForm.handlingDepartment" /> |
| | | </el-form-item> |
| | | <el-form-item label="æè¯ç»æ"> |
| | | <el-input v-model="complaintForm.complaintResult" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="complaintDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="saveComplaint">ä¿å</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- å¤ç½æ¹é导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="punishmentImportDialogVisible" title="å¤ç½æ¹é导å
¥" width="600px"> |
| | | <div class="import-container"> |
| | | <p class="import-tip">è¯·éæ©è¦å¯¼å
¥çExcelæä»¶</p> |
| | | <el-upload |
| | | class="upload-demo" |
| | | action="#" |
| | | :auto-upload="false" |
| | | :on-change="handlePunishmentFileChange" |
| | | :file-list="punishmentImportFileList" |
| | | accept=".xlsx,.xls" |
| | | :limit="1" |
| | | :on-exceed="handleExceed" |
| | | > |
| | | <el-button type="primary">éæ©æä»¶</el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip">åªè½ä¸ä¼ Excelæä»¶ï¼ä¸ä¸è¶
è¿5MB</div> |
| | | </template> |
| | | </el-upload> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="punishmentImportDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="confirmPunishmentImport">导å
¥</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- æè¯æ¹é导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog v-model="complaintImportDialogVisible" title="æè¯æ¹é导å
¥" width="600px"> |
| | | <div class="import-container"> |
| | | <p class="import-tip">è¯·éæ©è¦å¯¼å
¥çExcelæä»¶</p> |
| | | <el-upload |
| | | class="upload-demo" |
| | | action="#" |
| | | :auto-upload="false" |
| | | :on-change="handleComplaintFileChange" |
| | | :file-list="complaintImportFileList" |
| | | accept=".xlsx,.xls" |
| | | :limit="1" |
| | | :on-exceed="handleExceed" |
| | | > |
| | | <el-button type="primary">éæ©æä»¶</el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip">åªè½ä¸ä¼ Excelæä»¶ï¼ä¸ä¸è¶
è¿5MB</div> |
| | | </template> |
| | | </el-upload> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="complaintImportDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="confirmComplaintImport">导å
¥</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, watch, computed } from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import { Icon } from '@iconify/vue' |
| | | import dayjs from 'dayjs' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | // æ»è§ç°åºå·¡æ¥æ°æ® |
| | | const dayStart = dayjs('2023-08-01').startOf('date') |
| | | const dayEnd = dayStart.endOf('date') |
| | | const shortcuts = [ |
| | | { |
| | | text: 'ä»å¤©', |
| | | value: [dayStart.toDate(), dayEnd.toDate()], |
| | | }, |
| | | { |
| | | text: 'æ¬å¨', |
| | | value: [dayStart.startOf('week').toDate(), dayEnd.endOf('week').toDate()], |
| | | }, |
| | | { |
| | | text: 'ä¸å¨', |
| | | value: [dayStart.day(-7).toDate(), dayEnd.day(-1).toDate()], |
| | | }, |
| | | { |
| | | text: 'æ¬æ', |
| | | value: [dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()], |
| | | }, |
| | | { |
| | | text: '䏿', |
| | | value: [ |
| | | dayStart.subtract(1, 'month').startOf('month').toDate(), |
| | | dayEnd.subtract(1, 'month').endOf('month').toDate(), |
| | | ], |
| | | }, |
| | | { |
| | | text: 'æ¬å£åº¦', |
| | | value: [dayStart.startOf('quarter').toDate(), dayEnd.endOf('quarter').toDate()], |
| | | }, |
| | | { |
| | | text: 'ä¸å£åº¦', |
| | | value: [ |
| | | dayStart.subtract(1, 'quarter').startOf('quarter').toDate(), |
| | | dayEnd.subtract(1, 'quarter').endOf('quarter').toDate(), |
| | | ], |
| | | }, |
| | | { |
| | | text: 'å»å¹´', |
| | | value: [ |
| | | dayStart.subtract(1, 'year').startOf('year').toDate(), |
| | | dayEnd.subtract(1, 'year').endOf('year').toDate(), |
| | | ], |
| | | }, |
| | | { |
| | | text: 'ä»å¹´', |
| | | value: [dayStart.startOf('year').toDate(), dayEnd.endOf('year').toDate()], |
| | | }, |
| | | ] |
| | | const params = ref({ |
| | | prodBaseTypes: [], |
| | | prodCheck: '', |
| | | scenetype: '', |
| | | topTask: '', |
| | | locations: { |
| | | aCode: null, |
| | | aName: null, |
| | | cCode: '3100', |
| | | cName: '䏿µ·å¸', |
| | | dCode: '310104', |
| | | dName: '徿±åº', |
| | | mCode: null, |
| | | mName: null, |
| | | pCode: '31', |
| | | pName: '䏿µ·å¸', |
| | | tCode: null, |
| | | tName: null, |
| | | }, |
| | | timeRange: [dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()], |
| | | }) |
| | | const inspectionStats = ref({ |
| | | // totalShops: 1250, |
| | | // inspectedShops: 980, |
| | | // inspectedRate: '78.4%', |
| | | // inspectionPoints: 2350, |
| | | // reviewPoints: 450, |
| | | // problemCount: 320, |
| | | // rectifiedProblems: 280, |
| | | // sameDayRectificationRate: '65.2%', |
| | | // effectiveRectificationRate: '78.5%', |
| | | // comprehensiveRectificationRate: '82.3%', |
| | | // auditPassRate: '87.5%', |
| | | }) |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const inspectionTrendChart = ref(null) |
| | | const problemTypeChart = ref(null) |
| | | const rectificationRateChart = ref(null) |
| | | const dailyPunishmentChart = ref(null) |
| | | const shopTypePunishmentChart = ref(null) |
| | | const dailyComplaintChart = ref(null) |
| | | const sourceComplaintChart = ref(null) |
| | | |
| | | // è¡æ¿å¤ç½æ°æ® |
| | | const punishmentDateRange = ref([ |
| | | dayStart.startOf('month').toDate(), |
| | | dayEnd.endOf('month').toDate(), |
| | | ]) |
| | | const punishmentKeyword = ref('') |
| | | const punishmentStats = ref({ |
| | | totalCount: 120, |
| | | }) |
| | | const punishmentTableData = ref([ |
| | | // { |
| | | // id: 1, |
| | | // shopName: 'å³ç¾é¤å
', |
| | | // punishmentItem: 'æ²¹çè¶
æ ææ¾', |
| | | // punishmentTime: '2026-03-10 14:30', |
| | | // punishmentReason: 'æªå®è£
æ²¹çåå设å¤', |
| | | // punishmentResult: 'ç½æ¬¾5000å
', |
| | | // punishmentDepartment: '徿±åºç¯ä¿å±', |
| | | // }, |
| | | // { |
| | | // id: 2, |
| | | // shopName: 'é¦è¾£å°é¾è¾', |
| | | // punishmentItem: 'æ²¹çè¶
æ ææ¾', |
| | | // punishmentTime: '2026-03-08 10:15', |
| | | // punishmentReason: 'æ²¹çååè®¾å¤æªæ£å¸¸è¿è¡', |
| | | // punishmentResult: 'ç½æ¬¾3000å
', |
| | | // punishmentDepartment: '徿±åºç¯ä¿å±', |
| | | // }, |
| | | ]) |
| | | const punishmentPagination = ref({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 2, |
| | | }) |
| | | |
| | | // è¿æ»¤åçå¤ç½æ°æ® |
| | | const filteredPunishmentData = computed(() => { |
| | | if (!punishmentKeyword.value) { |
| | | return punishmentTableData.value |
| | | } |
| | | const keyword = punishmentKeyword.value.toLowerCase() |
| | | return punishmentTableData.value.filter((item) => { |
| | | return Object.values(item).some((value) => { |
| | | return String(value).toLowerCase().includes(keyword) |
| | | }) |
| | | }) |
| | | }) |
| | | |
| | | // 信访æè¯æ°æ® |
| | | const complaintDateRange = ref([dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()]) |
| | | const complaintKeyword = ref('') |
| | | const complaintStats = ref({ |
| | | totalCount: 85, |
| | | }) |
| | | const complaintTableData = ref([ |
| | | // { |
| | | // id: 1, |
| | | // shopName: 'é²å³é¦', |
| | | // complaintReason: 'æ²¹çæ°æ°', |
| | | // complaintRequest: 'è¦æ±å®è£
æ²¹çåå设å¤', |
| | | // complaintTime: '2026-03-12 09:20', |
| | | // complaintSource: '12345ç线', |
| | | // handlingDepartment: '徿±åºç¯ä¿å±', |
| | | // complaintResult: 'å·²å¤ç', |
| | | // }, |
| | | // { |
| | | // id: 2, |
| | | // shopName: 'ç§ç¤è¾¾äºº', |
| | | // complaintReason: 'å¤é´æ²¹ç污æ', |
| | | // complaintRequest: 'è¦æ±æ´æ¹', |
| | | // complaintTime: '2026-03-10 22:30', |
| | | // complaintSource: 'å±
æ°æè¯', |
| | | // handlingDepartment: '徿±åºç¯ä¿å±', |
| | | // complaintResult: 'å¤çä¸', |
| | | // }, |
| | | ]) |
| | | const complaintPagination = ref({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 2, |
| | | }) |
| | | |
| | | // è¿æ»¤åçæè¯æ°æ® |
| | | const filteredComplaintData = computed(() => { |
| | | if (!complaintKeyword.value) { |
| | | return complaintTableData.value |
| | | } |
| | | const keyword = complaintKeyword.value.toLowerCase() |
| | | return complaintTableData.value.filter((item) => { |
| | | return Object.values(item).some((value) => { |
| | | return String(value).toLowerCase().includes(keyword) |
| | | }) |
| | | }) |
| | | }) |
| | | |
| | | // å¯¹è¯æ¡ç¶æ |
| | | const punishmentDialogVisible = ref(false) |
| | | const complaintDialogVisible = ref(false) |
| | | const punishmentImportDialogVisible = ref(false) |
| | | const complaintImportDialogVisible = ref(false) |
| | | |
| | | // 导å
¥æä»¶å表 |
| | | const punishmentImportFileList = ref([]) |
| | | const complaintImportFileList = ref([]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const punishmentForm = ref({ |
| | | id: '', |
| | | shopName: '', |
| | | punishmentItem: '', |
| | | punishmentTime: '', |
| | | punishmentReason: '', |
| | | punishmentResult: '', |
| | | punishmentDepartment: '', |
| | | }) |
| | | |
| | | const complaintForm = ref({ |
| | | id: '', |
| | | shopName: '', |
| | | complaintReason: '', |
| | | complaintRequest: '', |
| | | complaintTime: '', |
| | | complaintSource: '', |
| | | handlingDepartment: '', |
| | | complaintResult: '', |
| | | }) |
| | | |
| | | const refreshInspectionData = () => { |
| | | // 模æå·æ°æ°æ® |
| | | console.log('å·æ°å·¡æ¥æ°æ®', params.value) |
| | | // çææ°çæ¨¡ææ°æ® |
| | | const totalShops = 90 + Math.floor(Math.random() * 10) |
| | | const inspectedShops = 70 + Math.floor(Math.random() * 10) |
| | | const inspectedRate = ((inspectedShops / totalShops) * 100).toFixed(1) + '%' |
| | | |
| | | // è®¡ç®æ¶é´èå´ |
| | | const startTime = params.value.timeRange[0] |
| | | const endTime = params.value.timeRange[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | const months = daysDiff / 30 |
| | | |
| | | // æ ¹æ®æ¶é´èå´è°æ´æ°æ®é级 |
| | | const inspectionPoints = Math.floor(100 * months) + Math.floor(Math.random() * 20) |
| | | const problemCount = Math.floor(200 * months) + Math.floor(Math.random() * 30) |
| | | const reviewPoints = Math.floor(inspectionPoints * 0.2) + Math.floor(Math.random() * 10) |
| | | const rectifiedProblems = Math.floor(problemCount * 0.8) + Math.floor(Math.random() * 20) |
| | | |
| | | inspectionStats.value = { |
| | | totalShops, |
| | | inspectedShops, |
| | | inspectedRate, |
| | | inspectionPoints, |
| | | reviewPoints, |
| | | problemCount, |
| | | rectifiedProblems, |
| | | sameDayRectificationRate: (60 + Math.random() * 20).toFixed(1) + '%', |
| | | effectiveRectificationRate: (70 + Math.random() * 20).toFixed(1) + '%', |
| | | comprehensiveRectificationRate: (75 + Math.random() * 15).toFixed(1) + '%', |
| | | auditPassRate: (80 + Math.random() * 15).toFixed(1) + '%', |
| | | } |
| | | // éæ°åå§åå¾è¡¨ä»¥æ´æ°æ°æ® |
| | | initCharts() |
| | | } |
| | | |
| | | const searchPunishment = () => { |
| | | // 模ææç´¢å¤ç½æ°æ® |
| | | console.log('æç´¢å¤ç½æ°æ®', { |
| | | dateRange: punishmentDateRange.value, |
| | | keyword: punishmentKeyword.value, |
| | | }) |
| | | |
| | | // è®¡ç®æ¶é´èå´ |
| | | const startTime = punishmentDateRange.value[0] |
| | | const endTime = punishmentDateRange.value[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | const months = daysDiff / 30 |
| | | |
| | | // çææ°çæ¨¡ææ°æ® |
| | | const totalCount = Math.floor(10 * months) + Math.floor(Math.random() * 3) |
| | | punishmentStats.value = { |
| | | totalCount, |
| | | } |
| | | |
| | | // çææ°çå¤ç½è®°å½ |
| | | const newData = [] |
| | | const shopNames = [ |
| | | 'å³ç¾é¤å
', |
| | | 'é¦è¾£å°é¾è¾', |
| | | 'é²å³é¦', |
| | | 'ç§ç¤è¾¾äºº', |
| | | 'å·èé¦', |
| | | '西é¤å
', |
| | | 'æ¥æåº', |
| | | 'ç«é
åº', |
| | | ] |
| | | const punishmentItems = ['æ²¹çè¶
æ ææ¾', 'æªå®è£
æ²¹çåå设å¤', 'è®¾å¤æªæ£å¸¸è¿è¡', 'åªå£°æ±¡æ'] |
| | | const departments = ['徿±åºç¯ä¿å±', 'é¿å®åºç¯ä¿å±', 'éå®åºç¯ä¿å±', 'æ®éåºç¯ä¿å±'] |
| | | |
| | | for (let i = 0; i < totalCount; i++) { |
| | | // çæå¨æ¶é´èå´å
çéæºæ¶é´ |
| | | const randomDays = Math.floor(Math.random() * (daysDiff + 1)) |
| | | const randomTime = startDate |
| | | .add(randomDays, 'day') |
| | | .add(Math.floor(Math.random() * 24), 'hour') |
| | | .add(Math.floor(Math.random() * 60), 'minute') |
| | | |
| | | newData.push({ |
| | | id: i + 1, |
| | | shopName: shopNames[Math.floor(Math.random() * shopNames.length)], |
| | | punishmentItem: punishmentItems[Math.floor(Math.random() * punishmentItems.length)], |
| | | punishmentTime: randomTime.format('YYYY-MM-DD HH:mm'), |
| | | punishmentReason: |
| | | punishmentItems[Math.floor(Math.random() * punishmentItems.length)] + 'çè¿è§è¡ä¸º', |
| | | punishmentResult: 'ç½æ¬¾' + (Math.floor(Math.random() * 5) + 1) * 1000 + 'å
', |
| | | punishmentDepartment: departments[Math.floor(Math.random() * departments.length)], |
| | | }) |
| | | } |
| | | |
| | | punishmentTableData.value = newData |
| | | punishmentPagination.value.total = newData.length |
| | | // éæ°åå§åå¾è¡¨ä»¥æ´æ°æ°æ® |
| | | initCharts() |
| | | } |
| | | |
| | | const searchComplaint = () => { |
| | | // 模ææç´¢æè¯æ°æ® |
| | | console.log('æç´¢æè¯æ°æ®', { |
| | | dateRange: complaintDateRange.value, |
| | | keyword: complaintKeyword.value, |
| | | }) |
| | | |
| | | // è®¡ç®æ¶é´èå´ |
| | | const startTime = complaintDateRange.value[0] |
| | | const endTime = complaintDateRange.value[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | const months = daysDiff / 30 |
| | | |
| | | // çææ°çæ¨¡ææ°æ® |
| | | const totalCount = Math.floor(20 * months) + Math.floor(Math.random() * 5) |
| | | complaintStats.value = { |
| | | totalCount, |
| | | } |
| | | |
| | | // çææ°çæè¯è®°å½ |
| | | const newData = [] |
| | | const shopNames = [ |
| | | 'é²å³é¦', |
| | | 'ç§ç¤è¾¾äºº', |
| | | 'å³ç¾é¤å
', |
| | | 'é¦è¾£å°é¾è¾', |
| | | 'å·èé¦', |
| | | '西é¤å
', |
| | | 'æ¥æåº', |
| | | 'ç«é
åº', |
| | | ] |
| | | const complaintReasons = ['æ²¹çæ°æ°', 'å¤é´åªå£°', 'å¼å³æ±¡æ', 'å«çé®é¢'] |
| | | const sources = ['12345ç线', 'å±
æ°æè¯', 'ç½ç»å¹³å°', 'å
¶ä»'] |
| | | const departments = ['徿±åºç¯ä¿å±', 'é¿å®åºç¯ä¿å±', 'éå®åºç¯ä¿å±', 'æ®éåºç¯ä¿å±'] |
| | | const results = ['å·²å¤ç', 'å¤çä¸', 'æªå¤ç'] |
| | | |
| | | for (let i = 0; i < totalCount; i++) { |
| | | // çæå¨æ¶é´èå´å
çéæºæ¶é´ |
| | | const randomDays = Math.floor(Math.random() * (daysDiff + 1)) |
| | | const randomTime = startDate |
| | | .add(randomDays, 'day') |
| | | .add(Math.floor(Math.random() * 24), 'hour') |
| | | .add(Math.floor(Math.random() * 60), 'minute') |
| | | |
| | | newData.push({ |
| | | id: i + 1, |
| | | shopName: shopNames[Math.floor(Math.random() * shopNames.length)], |
| | | complaintReason: complaintReasons[Math.floor(Math.random() * complaintReasons.length)], |
| | | complaintRequest: |
| | | 'è¦æ±æ´æ¹' + complaintReasons[Math.floor(Math.random() * complaintReasons.length)], |
| | | complaintTime: randomTime.format('YYYY-MM-DD HH:mm'), |
| | | complaintSource: sources[Math.floor(Math.random() * sources.length)], |
| | | handlingDepartment: departments[Math.floor(Math.random() * departments.length)], |
| | | complaintResult: results[Math.floor(Math.random() * results.length)], |
| | | }) |
| | | } |
| | | |
| | | complaintTableData.value = newData |
| | | complaintPagination.value.total = newData.length |
| | | // éæ°åå§åå¾è¡¨ä»¥æ´æ°æ°æ® |
| | | initCharts() |
| | | } |
| | | |
| | | // çå¬å¤ç½æ¥æèå´åå |
| | | watch( |
| | | () => punishmentDateRange.value, |
| | | () => { |
| | | searchPunishment() |
| | | }, |
| | | { deep: true }, |
| | | ) |
| | | |
| | | // çå¬æè¯æ¥æèå´åå |
| | | watch( |
| | | () => complaintDateRange.value, |
| | | () => { |
| | | searchComplaint() |
| | | }, |
| | | { deep: true }, |
| | | ) |
| | | |
| | | const addPunishment = () => { |
| | | // é置表å |
| | | punishmentForm.value = { |
| | | id: '', |
| | | shopName: '', |
| | | punishmentItem: '', |
| | | punishmentTime: '', |
| | | punishmentReason: '', |
| | | punishmentResult: '', |
| | | punishmentDepartment: '', |
| | | } |
| | | punishmentDialogVisible.value = true |
| | | } |
| | | |
| | | const editPunishment = (row) => { |
| | | // å¡«å
表å |
| | | punishmentForm.value = { ...row } |
| | | punishmentDialogVisible.value = true |
| | | } |
| | | |
| | | const savePunishment = () => { |
| | | // 模æä¿åæ°æ® |
| | | console.log('ä¿åå¤ç½æ°æ®', punishmentForm.value) |
| | | punishmentDialogVisible.value = false |
| | | // è¿éå¯ä»¥æ·»å å®é
çæ°æ®ä¿åé»è¾ |
| | | } |
| | | |
| | | const deletePunishment = (id) => { |
| | | // 模æå 餿°æ® |
| | | console.log('å é¤å¤ç½æ°æ®', id) |
| | | // è¿éå¯ä»¥æ·»å å®é
çæ°æ®å é¤é»è¾ |
| | | } |
| | | |
| | | const importPunishment = () => { |
| | | // æå¼å¯¼å
¥å¯¹è¯æ¡ |
| | | punishmentImportFileList.value = [] |
| | | punishmentImportDialogVisible.value = true |
| | | } |
| | | |
| | | const handlePunishmentFileChange = (file, fileList) => { |
| | | punishmentImportFileList.value = fileList |
| | | console.log('éæ©çå¤ç½æä»¶:', file) |
| | | } |
| | | |
| | | const confirmPunishmentImport = () => { |
| | | if (punishmentImportFileList.value.length === 0) { |
| | | ElMessage.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶') |
| | | return |
| | | } |
| | | |
| | | const file = punishmentImportFileList.value[0] |
| | | console.log('å¼å§å¯¼å
¥å¤ç½æ°æ®:', file.name) |
| | | |
| | | // é¢ç导å
¥é»è¾ |
| | | // è¿éå°å®ç°å®é
çæä»¶è§£æåæ°æ®å¯¼å
¥ |
| | | |
| | | punishmentImportDialogVisible.value = false |
| | | ElMessage.success('导å
¥æä½å·²è§¦åï¼é¢ç导å
¥é»è¾') |
| | | } |
| | | |
| | | const addComplaint = () => { |
| | | // é置表å |
| | | complaintForm.value = { |
| | | id: '', |
| | | shopName: '', |
| | | complaintReason: '', |
| | | complaintRequest: '', |
| | | complaintTime: '', |
| | | complaintSource: '', |
| | | handlingDepartment: '', |
| | | complaintResult: '', |
| | | } |
| | | complaintDialogVisible.value = true |
| | | } |
| | | |
| | | const editComplaint = (row) => { |
| | | // å¡«å
表å |
| | | complaintForm.value = { ...row } |
| | | complaintDialogVisible.value = true |
| | | } |
| | | |
| | | const saveComplaint = () => { |
| | | // 模æä¿åæ°æ® |
| | | console.log('ä¿åæè¯æ°æ®', complaintForm.value) |
| | | complaintDialogVisible.value = false |
| | | // è¿éå¯ä»¥æ·»å å®é
çæ°æ®ä¿åé»è¾ |
| | | } |
| | | |
| | | const deleteComplaint = (id) => { |
| | | // 模æå 餿°æ® |
| | | console.log('å é¤æè¯æ°æ®', id) |
| | | // è¿éå¯ä»¥æ·»å å®é
çæ°æ®å é¤é»è¾ |
| | | } |
| | | |
| | | const importComplaint = () => { |
| | | // æå¼å¯¼å
¥å¯¹è¯æ¡ |
| | | complaintImportFileList.value = [] |
| | | complaintImportDialogVisible.value = true |
| | | } |
| | | |
| | | const handleComplaintFileChange = (file, fileList) => { |
| | | complaintImportFileList.value = fileList |
| | | console.log('éæ©çæè¯æä»¶:', file) |
| | | } |
| | | |
| | | const confirmComplaintImport = () => { |
| | | if (complaintImportFileList.value.length === 0) { |
| | | ElMessage.warning('è¯·éæ©è¦å¯¼å
¥çæä»¶') |
| | | return |
| | | } |
| | | |
| | | const file = complaintImportFileList.value[0] |
| | | console.log('å¼å§å¯¼å
¥æè¯æ°æ®:', file.name) |
| | | |
| | | // é¢ç导å
¥é»è¾ |
| | | // è¿éå°å®ç°å®é
çæä»¶è§£æåæ°æ®å¯¼å
¥ |
| | | |
| | | complaintImportDialogVisible.value = false |
| | | ElMessage.success('导å
¥æä½å·²è§¦åï¼é¢ç导å
¥é»è¾') |
| | | } |
| | | |
| | | const handleExceed = (files, fileList) => { |
| | | ElMessage.warning('åªè½ä¸ä¼ ä¸ä¸ªæä»¶') |
| | | } |
| | | |
| | | const handlePunishmentSizeChange = (size) => { |
| | | punishmentPagination.value.pageSize = size |
| | | // è¿éå¯ä»¥æ·»å å®é
çå页é»è¾ |
| | | } |
| | | |
| | | const handlePunishmentCurrentChange = (current) => { |
| | | punishmentPagination.value.currentPage = current |
| | | // è¿éå¯ä»¥æ·»å å®é
çå页é»è¾ |
| | | } |
| | | |
| | | const handleComplaintSizeChange = (size) => { |
| | | complaintPagination.value.pageSize = size |
| | | // è¿éå¯ä»¥æ·»å å®é
çå页é»è¾ |
| | | } |
| | | |
| | | const handleComplaintCurrentChange = (current) => { |
| | | complaintPagination.value.currentPage = current |
| | | // è¿éå¯ä»¥æ·»å å®é
çå页é»è¾ |
| | | } |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | // å·¡æ¥å®ææ
åµè¶å¿å¾ |
| | | if (inspectionTrendChart.value) { |
| | | const chart = echarts.init(inspectionTrendChart.value) |
| | | |
| | | // è®¡ç®æ¶é´èå´å¹¶çæxè½´æ°æ® |
| | | const startTime = params.value.timeRange[0] |
| | | const endTime = params.value.timeRange[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | |
| | | let xAxisData = [] |
| | | let seriesData = [] |
| | | |
| | | if (daysDiff <= 30) { |
| | | // åä¸ä¸ªæå
ï¼ææ¥æ¾ç¤º |
| | | for (let i = 0; i <= daysDiff; i++) { |
| | | const date = startDate.add(i, 'day') |
| | | xAxisData.push(date.format('MM/DD')) |
| | | seriesData.push(60 + Math.floor(Math.random() * 30)) // çæéæºæ°æ® |
| | | } |
| | | } else { |
| | | // è¶
è¿ä¸ä¸ªæï¼æææ¾ç¤º |
| | | const startMonth = startDate.startOf('month') |
| | | const endMonth = endDate.endOf('month') |
| | | const monthsDiff = endMonth.diff(startMonth, 'month') |
| | | |
| | | for (let i = 0; i <= monthsDiff; i++) { |
| | | const date = startMonth.add(i, 'month') |
| | | xAxisData.push(date.format('YYYY/MM')) |
| | | seriesData.push(60 + Math.floor(Math.random() * 30)) // çæéæºæ°æ® |
| | | } |
| | | } |
| | | |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: xAxisData, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { |
| | | formatter: '{value}%', |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: seriesData, |
| | | type: 'bar', |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // é®é¢ç±»ååå¸å¾ |
| | | if (problemTypeChart.value) { |
| | | const chart = echarts.init(problemTypeChart.value) |
| | | chart.setOption({ |
| | | series: [ |
| | | { |
| | | type: 'pie', |
| | | data: [ |
| | | { value: 30, name: 'æ²¹çå¨çº¿çæµè®¾å¤' }, |
| | | { value: 25, name: 'æ²¹çåå设æ½è®¾å¤' }, |
| | | { value: 20, name: 'æ²¹çå¨çº¿çæµè®¾å¤ç»´æ¤' }, |
| | | { value: 15, name: 'æ²¹çåå设æ½è®¾å¤ç»´æ¤' }, |
| | | { value: 10, name: 'æ²¹çå¨çº¿çæµæ°æ®é级' }, |
| | | { value: 20, name: '空è°å飿ºåªå£°' }, |
| | | { value: 18, name: 'å°è´¦ç®¡ç' }, |
| | | { value: 22, name: 'ä¿¡ç¨æ¿è¯ºèªè¯' }, |
| | | ], |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}: {d}%', |
| | | }, |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // æ¯æ¥å¤ç½æ°éå¾ |
| | | if (dailyPunishmentChart.value && punishmentDateRange.value.length > 0) { |
| | | const chart = echarts.init(dailyPunishmentChart.value) |
| | | |
| | | // è®¡ç®æ¶é´èå´å¹¶çæxè½´æ°æ® |
| | | const startTime = punishmentDateRange.value[0] |
| | | const endTime = punishmentDateRange.value[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | |
| | | let xAxisData = [] |
| | | let seriesData = [] |
| | | |
| | | // å¤çå¤ç½æ°æ® |
| | | const punishmentData = punishmentTableData.value |
| | | const dateFormat = daysDiff <= 30 ? 'MM/DD' : 'YYYY/MM' |
| | | |
| | | // çææ¥æèå´ |
| | | if (daysDiff <= 30) { |
| | | // åä¸ä¸ªæå
ï¼ææ¥æ¾ç¤º |
| | | for (let i = 0; i <= daysDiff; i++) { |
| | | const date = startDate.add(i, 'day') |
| | | xAxisData.push(date.format(dateFormat)) |
| | | // 计ç®è¯¥æ¥æçå¤ç½æ°é |
| | | const count = punishmentData.filter((item) => { |
| | | const itemDate = dayjs(item.punishmentTime) |
| | | return itemDate.format(dateFormat) === date.format(dateFormat) |
| | | }).length |
| | | seriesData.push(count) |
| | | } |
| | | } else { |
| | | // è¶
è¿ä¸ä¸ªæï¼æææ¾ç¤º |
| | | const startMonth = startDate.startOf('month') |
| | | const endMonth = endDate.endOf('month') |
| | | const monthsDiff = endMonth.diff(startMonth, 'month') |
| | | |
| | | for (let i = 0; i <= monthsDiff; i++) { |
| | | const date = startMonth.add(i, 'month') |
| | | xAxisData.push(date.format(dateFormat)) |
| | | // 计ç®è¯¥æä»½çå¤ç½æ°é |
| | | const count = punishmentData.filter((item) => { |
| | | const itemDate = dayjs(item.punishmentTime) |
| | | return itemDate.format(dateFormat) === date.format(dateFormat) |
| | | }).length |
| | | seriesData.push(count) |
| | | } |
| | | } |
| | | |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: xAxisData, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | }, |
| | | series: [ |
| | | { |
| | | data: seriesData, |
| | | type: 'bar', |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // åºéºç±»åå¤ç½åå¸å¾ |
| | | if (shopTypePunishmentChart.value) { |
| | | const chart = echarts.init(shopTypePunishmentChart.value) |
| | | chart.setOption({ |
| | | series: [ |
| | | { |
| | | type: 'pie', |
| | | data: [ |
| | | { value: 50, name: 'ä¸é¤' }, |
| | | { value: 30, name: 'ç§ç¤' }, |
| | | { value: 20, name: '西é¤' }, |
| | | { value: 15, name: 'å
¶ä»' }, |
| | | ], |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}: {d}%', |
| | | }, |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // æ¯æ¥æè¯æ°éå¾ |
| | | if (dailyComplaintChart.value && complaintDateRange.value.length > 0) { |
| | | const chart = echarts.init(dailyComplaintChart.value) |
| | | |
| | | // è®¡ç®æ¶é´èå´å¹¶çæxè½´æ°æ® |
| | | const startTime = complaintDateRange.value[0] |
| | | const endTime = complaintDateRange.value[1] |
| | | const startDate = dayjs(startTime) |
| | | const endDate = dayjs(endTime) |
| | | const daysDiff = endDate.diff(startDate, 'day') |
| | | |
| | | let xAxisData = [] |
| | | let seriesData = [] |
| | | |
| | | // å¤çæè¯æ°æ® |
| | | const complaintData = complaintTableData.value |
| | | const dateFormat = daysDiff <= 30 ? 'MM/DD' : 'YYYY/MM' |
| | | |
| | | // çææ¥æèå´ |
| | | if (daysDiff <= 30) { |
| | | // åä¸ä¸ªæå
ï¼ææ¥æ¾ç¤º |
| | | for (let i = 0; i <= daysDiff; i++) { |
| | | const date = startDate.add(i, 'day') |
| | | xAxisData.push(date.format(dateFormat)) |
| | | // 计ç®è¯¥æ¥æçæè¯æ°é |
| | | const count = complaintData.filter((item) => { |
| | | const itemDate = dayjs(item.complaintTime) |
| | | return itemDate.format(dateFormat) === date.format(dateFormat) |
| | | }).length |
| | | seriesData.push(count) |
| | | } |
| | | } else { |
| | | // è¶
è¿ä¸ä¸ªæï¼æææ¾ç¤º |
| | | const startMonth = startDate.startOf('month') |
| | | const endMonth = endDate.endOf('month') |
| | | const monthsDiff = endMonth.diff(startMonth, 'month') |
| | | |
| | | for (let i = 0; i <= monthsDiff; i++) { |
| | | const date = startMonth.add(i, 'month') |
| | | xAxisData.push(date.format(dateFormat)) |
| | | // 计ç®è¯¥æä»½çæè¯æ°é |
| | | const count = complaintData.filter((item) => { |
| | | const itemDate = dayjs(item.complaintTime) |
| | | return itemDate.format(dateFormat) === date.format(dateFormat) |
| | | }).length |
| | | seriesData.push(count) |
| | | } |
| | | } |
| | | |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: xAxisData, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | }, |
| | | series: [ |
| | | { |
| | | data: seriesData, |
| | | type: 'bar', |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // æè¯æ¥æºåå¸å¾ |
| | | if (sourceComplaintChart.value) { |
| | | const chart = echarts.init(sourceComplaintChart.value) |
| | | chart.setOption({ |
| | | series: [ |
| | | { |
| | | type: 'pie', |
| | | data: [ |
| | | { value: 40, name: '12345ç线' }, |
| | | { value: 25, name: 'å±
æ°æè¯' }, |
| | | { value: 15, name: 'ç½ç»å¹³å°' }, |
| | | { value: 5, name: 'å
¶ä»' }, |
| | | ], |
| | | label: { |
| | | show: true, |
| | | formatter: '{b}: {d}%', |
| | | }, |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | |
| | | // é®é¢æ´æ¹çç»è®¡å¾ |
| | | if (rectificationRateChart.value) { |
| | | const chart = echarts.init(rectificationRateChart.value) |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['彿¥æ´æ¹ç', '48å°æ¶å
æ´æ¹ç', 'ç»¼åæ´æ¹ç', 'å®¡æ ¸éè¿ç'], |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { |
| | | formatter: '{value}%', |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: [ |
| | | parseFloat(inspectionStats.value.sameDayRectificationRate), |
| | | parseFloat(inspectionStats.value.effectiveRectificationRate), |
| | | parseFloat(inspectionStats.value.comprehensiveRectificationRate), |
| | | parseFloat(inspectionStats.value.auditPassRate), |
| | | ], |
| | | type: 'bar', |
| | | itemStyle: { |
| | | color: function (params) { |
| | | const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'] |
| | | return colors[params.dataIndex] |
| | | }, |
| | | }, |
| | | }, |
| | | ], |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // çå¬çªå£å¤§å°åå |
| | | const handleResize = () => { |
| | | // éæ°è°æ´å¾è¡¨å¤§å° |
| | | if (inspectionTrendChart.value) { |
| | | echarts.init(inspectionTrendChart.value).resize() |
| | | } |
| | | if (problemTypeChart.value) { |
| | | echarts.init(problemTypeChart.value).resize() |
| | | } |
| | | if (rectificationRateChart.value) { |
| | | echarts.init(rectificationRateChart.value).resize() |
| | | } |
| | | if (dailyPunishmentChart.value) { |
| | | echarts.init(dailyPunishmentChart.value).resize() |
| | | } |
| | | if (shopTypePunishmentChart.value) { |
| | | echarts.init(shopTypePunishmentChart.value).resize() |
| | | } |
| | | if (dailyComplaintChart.value) { |
| | | echarts.init(dailyComplaintChart.value).resize() |
| | | } |
| | | if (sourceComplaintChart.value) { |
| | | echarts.init(sourceComplaintChart.value).resize() |
| | | } |
| | | } |
| | | |
| | | // çå¬paramsåå |
| | | watch( |
| | | () => params.value, |
| | | () => { |
| | | refreshInspectionData() |
| | | }, |
| | | { deep: true }, |
| | | ) |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | refreshInspectionData() |
| | | searchPunishment() |
| | | searchComplaint() |
| | | initCharts() |
| | | window.addEventListener('resize', handleResize) |
| | | }) |
| | | |
| | | // ç»ä»¶å¸è½½æ¶æ¸
çäºä»¶çå¬ |
| | | onUnmounted(() => { |
| | | cleanup() |
| | | }) |
| | | |
| | | // æ¸
ç |
| | | const cleanup = () => { |
| | | window.removeEventListener('resize', handleResize) |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .monitor-control { |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .filter-group { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .mr-2 { |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .stats-sections { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .stats-section { |
| | | flex: 1; |
| | | background-color: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .stats-section h3 { |
| | | margin-bottom: 15px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .stats-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .stat-item { |
| | | background-color: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .progress-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 100%; |
| | | } |
| | | |
| | | .progress-label { |
| | | margin-top: 10px; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #606266; |
| | | } |
| | | |
| | | .progress-value { |
| | | margin-top: 5px; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .chart-container { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); |
| | | gap: 20px; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | .chart-item { |
| | | background-color: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .chart-item h3 { |
| | | margin-bottom: 15px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .chart-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .chart-summary { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 300px; |
| | | } |
| | | |
| | | .pagination-container { |
| | | margin-top: 20px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .import-container { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .import-tip { |
| | | margin-bottom: 20px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .upload-demo { |
| | | margin-bottom: 20px; |
| | | } |
| | | </style> |
| | |
| | | <template>å·¡æ¥è¯ä¼°æ¥å</template> |
| | | <template> |
| | | <div class="report-manage-container"> |
| | | <h2>å·¡æ¥è¯ä¼°æ¥å</h2> |
| | | |
| | | <!-- æ¥è¯¢æ¡ä»¶åºå --> |
| | | <div class="search-section"> |
| | | <el-form :inline="true" :model="searchForm" class="search-form"> |
| | | <el-form-item label="åºå¿"> |
| | | <el-select v-model="searchForm.district" placeholder="è¯·éæ©åºå¿" style="width: 120px"> |
| | | <el-option |
| | | v-for="district in districts" |
| | | :key="district.value" |
| | | :label="district.label" |
| | | :value="district.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="searchReports">æ¥è¯¢</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <!-- æ¥åè¡¨æ ¼åºå --> |
| | | <div class="table-section"> |
| | | <el-table :data="reportList" style="width: 100%"> |
| | | <el-table-column prop="index" label="ç´¢å¼ç¼å·" width="80" /> |
| | | <el-table-column prop="name" label="æ¥ååç§°" min-width="200" /> |
| | | <el-table-column prop="district" label="åºå¿" width="120" /> |
| | | <el-table-column prop="reportMonth" label="æ¥åæ¶é´" width="120" /> |
| | | <el-table-column prop="generateTime" label="çææ¶é´" width="180" /> |
| | | <el-table-column prop="auditStatus" label="å®¡æ ¸ç¶æ" width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.auditStatus === 'å·²å®¡æ ¸' ? 'success' : 'warning'"> |
| | | {{ scope.row.auditStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="120" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button size="small" type="primary" @click="viewReport(scope.row)"> |
| | | æ¥çæ¥å |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ¥åé¢è§æ½å± --> |
| | | <el-drawer v-model="drawerVisible" title="æ¥åé¢è§" direction="rtl" size="80%"> |
| | | <!-- <div class="report-preview"> |
| | | <iframe :src="reportUrl" width="100%" height="800px" frameborder="0"></iframe> |
| | | </div> --> |
| | | <div ref="refWord" class="report-preview"> |
| | | <div :id="`word-preview`"></div> |
| | | </div> |
| | | </el-drawer> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { |
| | | exportDocx, |
| | | prepareDocxBlob, |
| | | preparePdf, |
| | | previewDocx, |
| | | downloadDocx, |
| | | print, |
| | | } from '@/utils/doc' |
| | | |
| | | const refWord = ref(null) |
| | | |
| | | // 䏿µ·å¸åºå¿å表 |
| | | const districts = [ |
| | | { value: 'xuhui', label: '徿±åº' }, |
| | | { value: 'changning', label: 'é¿å®åº' }, |
| | | { value: 'jingan', label: 'éå®åº' }, |
| | | { value: 'putuo', label: 'æ®éåº' }, |
| | | { value: 'hongkou', label: 'è¹å£åº' }, |
| | | { value: 'yangpu', label: 'æ¨æµ¦åº' }, |
| | | { value: 'minhang', label: 'éµè¡åº' }, |
| | | { value: 'baoshan', label: 'å®å±±åº' }, |
| | | { value: 'pudong', label: 'æµ¦ä¸æ°åº' }, |
| | | { value: 'jiading', label: 'åå®åº' }, |
| | | { value: 'jinshan', label: 'éå±±åº' }, |
| | | { value: 'songjiang', label: 'æ¾æ±åº' }, |
| | | { value: 'qingpu', label: 'éæµ¦åº' }, |
| | | { value: 'fengxian', label: 'å¥è´¤åº' }, |
| | | { value: 'chongming', label: 'å´æåº' }, |
| | | ] |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = ref({ |
| | | district: 'xuhui', // é»è®¤éä¸å¾æ±åº |
| | | }) |
| | | |
| | | // æ¥ååè¡¨æ°æ® |
| | | const reportList = ref([]) |
| | | |
| | | // æ½å±ç¶æ |
| | | const drawerVisible = ref(false) |
| | | // æ¥åé¢è§URL |
| | | const reportUrl = ref('') |
| | | |
| | | // æ¨¡ææ°æ®çæ |
| | | const generateMockData = () => { |
| | | const data = [] |
| | | const baseName = 'é¤é¥®çç®¡ç®æ¥' |
| | | const areas = ['天饿¡¥', 'å¾å®¶æ±', '衡山路', 'é¾å'] |
| | | |
| | | for (let i = 1; i <= 10; i++) { |
| | | const area = areas[Math.floor(Math.random() * areas.length)] |
| | | const month = Math.floor(Math.random() * 12) + 1 |
| | | const date = new Date() |
| | | date.setMonth(month - 1) |
| | | |
| | | data.push({ |
| | | index: i, |
| | | name: `${searchForm.value.district === 'xuhui' ? '徿±åº' : 'å
¶ä»åº'}${baseName}ï¼${area}ï¼-2023å¹´${month}æ`, |
| | | district: searchForm.value.district === 'xuhui' ? '徿±åº' : 'å
¶ä»åº', |
| | | reportMonth: `2023å¹´${month}æ`, |
| | | generateTime: new Date().toLocaleString('zh-CN'), |
| | | auditStatus: Math.random() > 0.5 ? 'å·²å®¡æ ¸' : 'æªå®¡æ ¸', |
| | | }) |
| | | } |
| | | |
| | | return data |
| | | } |
| | | |
| | | // æ¥è¯¢æ¥å |
| | | const searchReports = () => { |
| | | reportList.value = generateMockData() |
| | | } |
| | | |
| | | // æ¥çæ¥å |
| | | const viewReport = (row) => { |
| | | // ä½¿ç¨æ¨¡æçæ¥åæä»¶ |
| | | reportUrl.value = '/徿±åºé¤é¥®çç®¡ç®æ¥ï¼å¤©é¥æ¡¥ï¼-2023å¹´8æ(1).docx' |
| | | drawerVisible.value = true |
| | | |
| | | prepareDocxBlob(reportUrl.value).then((blob) => { |
| | | previewDocx(blob, document.getElementById(`word-preview`)) |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶çæåå§æ°æ® |
| | | onMounted(() => { |
| | | searchReports() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .report-manage-container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search-section { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .search-form { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .table-section { |
| | | background-color: #fff; |
| | | border-radius: 4px; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .report-preview { |
| | | padding: 20px; |
| | | } |
| | | </style> |
| | |
| | | /> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- é¤é¥®åºéºç®¡ç --> |
| | | <el-tab-pane label="é¤é¥®åºéºç®¡ç" name="restaurants"> |
| | | <div class="tab-content"> |
| | | <!-- æç´¢åæ·»å æé® --> |
| | | <div class="search-add-bar"> |
| | | <el-input |
| | | v-model="restaurantSearchQuery" |
| | | placeholder="æç´¢åºéº" |
| | | style="width: 200px" |
| | | prefix-icon="el-icon-search" |
| | | /> |
| | | <el-button type="primary" @click="openRestaurantDialog"> |
| | | <el-icon><Plus /></el-icon> æ·»å åºéº |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- åºéºè¡¨æ ¼ --> |
| | | <el-table :data="filteredRestaurants" style="width: 100%"> |
| | | <el-table-column prop="id" label="ID" width="80" /> |
| | | <el-table-column prop="name" label="åºéºåç§°" /> |
| | | <el-table-column prop="address" label="å°å" /> |
| | | <el-table-column prop="contact" label="è系人" /> |
| | | <el-table-column prop="phone" label="èç³»çµè¯" /> |
| | | <el-table-column label="æä½" width="250"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="editRestaurant(scope.row)"> ç¼è¾ </el-button> |
| | | <el-button size="small" type="danger" @click="deleteRestaurant(scope.row.id)"> |
| | | å é¤ |
| | | </el-button> |
| | | <el-button size="small" @click="manageDevices(scope.row)"> 设å¤ç®¡ç </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <el-pagination |
| | | v-model:current-page="restaurantCurrentPage" |
| | | v-model:page-size="restaurantPageSize" |
| | | :page-sizes="[10, 20, 50]" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="restaurants.length" |
| | | style="margin-top: 20px" |
| | | /> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 设å¤ç®¡ç --> |
| | | <el-tab-pane label="设å¤ç®¡ç" name="devices"> |
| | | <div class="tab-content" v-if="selectedRestaurant"> |
| | | <h3>{{ selectedRestaurant.name }} - 设å¤å表</h3> |
| | | |
| | | <!-- æç´¢åæ·»å æé® --> |
| | | <div class="search-add-bar"> |
| | | <el-input |
| | | v-model="deviceSearchQuery" |
| | | placeholder="æç´¢è®¾å¤" |
| | | style="width: 200px" |
| | | prefix-icon="el-icon-search" |
| | | /> |
| | | <el-button type="primary" @click="openDeviceDialog"> |
| | | <el-icon><Plus /></el-icon> æ·»å è®¾å¤ |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- 设å¤è¡¨æ ¼ --> |
| | | <el-table :data="filteredDevices" style="width: 100%"> |
| | | <el-table-column prop="id" label="ID" width="80" /> |
| | | <el-table-column prop="deviceId" label="设å¤ç¼å·" /> |
| | | <el-table-column prop="type" label="设å¤ç±»å" /> |
| | | <el-table-column prop="status" label="ç¶æ"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.status === 'online' ? 'success' : 'danger'"> |
| | | {{ scope.row.status === 'online' ? 'å¨çº¿' : '离线' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="installDate" label="å®è£
æ¥æ" /> |
| | | <el-table-column label="æä½" width="200"> |
| | | <template #default="scope"> |
| | | <el-button size="small" @click="editDevice(scope.row)"> ç¼è¾ </el-button> |
| | | <el-button size="small" type="danger" @click="deleteDevice(scope.row.id)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <el-pagination |
| | | v-model:current-page="deviceCurrentPage" |
| | | v-model:page-size="devicePageSize" |
| | | :page-sizes="[10, 20, 50]" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="devices.length" |
| | | style="margin-top: 20px" |
| | | /> |
| | | </div> |
| | | <div class="tab-content" v-else> |
| | | <el-empty description="请å
éæ©ä¸ä¸ªé¤é¥®åºéº" /> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </el-card> |
| | | |