From 64d3eae2852d17c3bbade662d3f7a7c47d681ad6 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期四, 19 三月 2026 14:17:04 +0800
Subject: [PATCH] 2026.3.19
---
src/views/inspection/MonitorControl.vue | 971 -----------
src/views/inspection/PunishmentManage.vue | 590 +++++++
src/views/analysis/data-product/DataProduct.vue | 116 +
src/components/search-option/FYOptionTopTask.vue | 80
src/components/search-option/FYOptionTime.vue | 60
src/views/analysis/data-product/components/ProdQueryOptCompare.vue | 114 +
components.d.ts | 30
src/constants/menu.js | 30
src/router/index.js | 12
src/sfc/TimeSelect.vue | 55
src/views/analysis/data-product/components/ProdQueryOpt.vue | 71
src/views/analysis/huanxincode/HuanxinCodeManage.vue | 12
src/views/monitor/DataDashboard.vue | 718 ++++---
/dev/null | 2
src/views/analysis/data-product/components/ProdQueryOptWithMode.vue | 156 +
src/views/inspection/task/components/CompMonitorPlan.vue | 194 -
src/views/analysis/data-product/components/ProdDownload.vue | 110 +
src/views/monitor/DataException.vue | 575 ++++---
src/views/analysis/data-product/components/BaseProdProcess.vue | 316 +++
src/sfc/ExceptionText.vue | 4
src/views/analysis/data-product/prod-step-change.js | 15
src/views/inspection/ComplaintManage.vue | 597 +++++++
22 files changed, 3,133 insertions(+), 1,695 deletions(-)
diff --git a/components.d.ts b/components.d.ts
index f866cd1..5d57b87 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -21,11 +21,13 @@
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']
+ 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']
@@ -39,14 +41,18 @@
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
+ ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
+ ElFrom: typeof import('element-plus/es')['ElFrom']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
+ 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']
@@ -55,12 +61,17 @@
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
+ ElProgress: typeof import('element-plus/es')['ElProgress']
+ 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']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
@@ -70,6 +81,8 @@
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
+ 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']
@@ -96,6 +109,8 @@
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']
+ IEpArrowLeft: typeof import('~icons/ep/arrow-left')['default']
+ IEpArrowRight: typeof import('~icons/ep/arrow-right')['default']
IEpDownload: typeof import('~icons/ep/download')['default']
IEpInfoFilled: typeof import('~icons/ep/info-filled')['default']
ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default']
@@ -129,11 +144,13 @@
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 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']
@@ -147,14 +164,18 @@
const ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
const ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
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 ElEmpty: typeof import('element-plus/es')['ElEmpty']
const ElForm: typeof import('element-plus/es')['ElForm']
const ElFormItem: typeof import('element-plus/es')['ElFormItem']
+ const ElFrom: typeof import('element-plus/es')['ElFrom']
const ElHeader: typeof import('element-plus/es')['ElHeader']
const ElIcon: typeof import('element-plus/es')['ElIcon']
const ElImage: typeof import('element-plus/es')['ElImage']
+ 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']
@@ -163,12 +184,17 @@
const ElOption: typeof import('element-plus/es')['ElOption']
const ElPagination: typeof import('element-plus/es')['ElPagination']
const ElPopover: typeof import('element-plus/es')['ElPopover']
+ const ElProgress: typeof import('element-plus/es')['ElProgress']
+ 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 ElSwitch: typeof import('element-plus/es')['ElSwitch']
const ElTable: typeof import('element-plus/es')['ElTable']
@@ -178,6 +204,8 @@
const ElTag: typeof import('element-plus/es')['ElTag']
const ElText: typeof import('element-plus/es')['ElText']
const ElTooltip: typeof import('element-plus/es')['ElTooltip']
+ 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']
@@ -204,6 +232,8 @@
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 IEpArrowLeft: typeof import('~icons/ep/arrow-left')['default']
+ const IEpArrowRight: typeof import('~icons/ep/arrow-right')['default']
const IEpDownload: typeof import('~icons/ep/download')['default']
const IEpInfoFilled: typeof import('~icons/ep/info-filled')['default']
const ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default']
diff --git a/src/components/search-option/FYOptionTime.vue b/src/components/search-option/FYOptionTime.vue
index e1fa691..52180c5 100644
--- a/src/components/search-option/FYOptionTime.vue
+++ b/src/components/search-option/FYOptionTime.vue
@@ -14,66 +14,76 @@
</template>
<script>
-import dayjs from 'dayjs';
+import dayjs from 'dayjs'
-const MONTH = 'month';
-const DATE = 'date';
-const RANGE = 'datetimerange';
-const RANGE2 = 'daterange';
+const MONTH = 'month'
+const DATE = 'date'
+const RANGE = 'datetimerange'
+const RANGE2 = 'daterange'
export default {
props: {
type: {
type: String,
- default: MONTH
+ default: MONTH,
},
// 杩斿洖缁撴灉
value: Date || Array,
// 鏄惁榛樿杩斿洖鍒濆閫夐」
initValue: {
type: Boolean,
- default: true
+ default: true,
},
label: {
type: String,
- default: '鏃堕棿'
+ default: '鏃堕棿',
},
prop: {
type: String,
- default: 'time'
- }
+ default: 'time',
+ },
},
emits: ['update:value', 'change'],
data() {
return {
- date: this.value
- };
+ date: this.value,
+ }
+ },
+ watch: {
+ value: {
+ handler(newVal, oldVal) {
+ if (newVal != oldVal) {
+ this.date = newVal
+ }
+ },
+ immediate: true,
+ },
},
computed: {},
methods: {
handleChange(value) {
- this.$emit('update:value', value);
- this.$emit('change', value);
- }
+ this.$emit('update:value', value)
+ this.$emit('change', value)
+ },
},
mounted() {
if (this.initValue) {
switch (this.type) {
case RANGE:
case RANGE2:
- this.date = [dayjs().startOf('month').toDate(), dayjs().toDate()];
- break;
+ this.date = [dayjs().startOf('month').toDate(), dayjs().toDate()]
+ break
case MONTH:
- this.date = dayjs().startOf('month').toDate();
- break;
+ this.date = dayjs().startOf('month').toDate()
+ break
case DATE:
- this.date = dayjs().toDate();
- break;
+ this.date = dayjs().toDate()
+ break
default:
- break;
+ break
}
- this.handleChange(this.date);
+ this.handleChange(this.date)
}
- }
-};
+ },
+}
</script>
diff --git a/src/components/search-option/FYOptionTopTask.vue b/src/components/search-option/FYOptionTopTask.vue
index 1005c96..9ee17ca 100644
--- a/src/components/search-option/FYOptionTopTask.vue
+++ b/src/components/search-option/FYOptionTopTask.vue
@@ -6,76 +6,68 @@
:placeholder="label"
style="width: 260px"
>
- <el-option
- v-for="s in filtedBeforeTask"
- :key="s.value"
- :label="s.label"
- :value="s.value"
- />
+ <el-option v-for="s in filtedBeforeTask" :key="s.value" :label="s.label" :value="s.value" />
</el-select>
</el-form-item>
</template>
<script>
-import taskApi from '@/api/fysp/taskApi';
+import taskApi from '@/api/fysp/taskApi'
+import dayjs from 'dayjs'
export default {
props: {
label: {
type: String,
- default: '鎬讳换鍔�'
+ default: '鎬讳换鍔�',
},
// 杩斿洖缁撴灉
value: Object,
// 鏄惁榛樿杩斿洖鍒濆閫夐」
initValue: {
type: Boolean,
- default: true
+ default: true,
},
// form琛ㄥ崟缁戝畾灞炴�у悕
prop: {
type: String,
- default: 'topTaskId'
+ default: 'topTaskId',
},
// 閫夐」绛涢�夋潯浠讹紝绛涢�夋煇浠诲姟涔嬪墠鐨勭浉鍚岃鏀垮尯鍒掑唴鐨勪换鍔�
beforeTask: {
type: Object,
default: () => {
- return {};
- }
- }
+ return {}
+ },
+ },
},
emits: ['update:value'],
data() {
return {
selected: {},
- topTasks: []
- };
+ topTasks: [],
+ }
},
computed: {
// 閫夋嫨妗嗕腑浣跨敤椤跺眰浠诲姟id浣滀负閫夐」鍊�
formatedValue() {
- return this.value?.tguid;
+ return this.value?.tguid
},
// 鏌愪换鍔′箣鍓嶇殑鐩稿悓琛屾斂鍖哄垝鍐呯殑浠诲姟
filtedBeforeTask() {
const filteredTasks = this.topTasks.filter((t) => {
return (
- (!this.beforeTask.provincecode ||
- this.beforeTask.provincecode == t.data.provincecode) &&
- (!this.beforeTask.citycode ||
- this.beforeTask.citycode == t.data.citycode) &&
- (!this.beforeTask.districtcode ||
- this.beforeTask.districtcode == t.data.districtcode) &&
- (!this.beforeTask.starttime ||
- t.data.starttime < this.beforeTask.starttime)
- );
- });
+ (!this.beforeTask.provincecode || this.beforeTask.provincecode == t.data.provincecode) &&
+ (!this.beforeTask.citycode || this.beforeTask.citycode == t.data.citycode) &&
+ (!this.beforeTask.districtcode || this.beforeTask.districtcode == t.data.districtcode) &&
+ (!this.beforeTask.starttime || t.data.starttime < this.beforeTask.starttime)
+ )
+ })
if (filteredTasks.length > 0) {
- this.handleChange(filteredTasks[0]?.value);
+ this.handleChange(filteredTasks[0]?.value)
}
- return filteredTasks;
- }
+ return filteredTasks
+ },
},
methods: {
//鑾峰彇鏌ヨ鏉′欢
@@ -85,25 +77,29 @@
return {
value: r.tguid,
label: r.name,
- data: r
- };
- });
- this.topTasks = list;
+ data: r,
+ }
+ })
+ this.topTasks = list.filter((e) => {
+ return (
+ e.data.districtname == '寰愭眹鍖�' && dayjs(e.data.starttime).isBefore(dayjs('2023-12-31'))
+ )
+ })
if (this.initValue) {
- this.handleChange(list[0].value);
+ this.handleChange(list[0].value)
}
- });
+ })
},
//鏌ヨ瀛愪换鍔$粺璁′俊鎭�
handleChange(value) {
- const task = this.topTasks.find((t) => t.data.tguid == value);
- const param = task ? task.data : {};
+ const task = this.topTasks.find((t) => t.data.tguid == value)
+ const param = task ? task.data : {}
- this.$emit('update:value', param);
- }
+ this.$emit('update:value', param)
+ },
},
mounted() {
- this.getOptions();
- }
-};
+ this.getOptions()
+ },
+}
</script>
diff --git a/src/constants/menu.js b/src/constants/menu.js
index d13ac9d..8790f24 100644
--- a/src/constants/menu.js
+++ b/src/constants/menu.js
@@ -73,21 +73,35 @@
icon: 'solar:people-nearby-line-duotone',
name: '鐩戠宸℃煡',
children: [
- {
- path: '/index/inspection/monitor-control',
- icon: 'solar:eye-scan-line-duotone',
- name: '鐩戠鐩戞帶',
- },
+ // {
+ // path: '/index/inspection/monitor-control',
+ // icon: 'solar:eye-scan-line-duotone',
+ // name: '鐩戠鐩戞帶',
+ // },
{
path: '/index/inspection/task-manage',
icon: 'solar:file-text-line-duotone',
- name: '浠诲姟绠$悊',
+ name: '宸℃煡浠诲姟',
},
-
{
path: '/index/inspection/pro-check',
+ icon: 'solar:check-square-line-duotone',
+ name: '闂瀹℃牳',
+ },
+ {
+ path: '/index/analysis/data-product',
icon: 'solar:checklist-minimalistic-line-duotone',
- name: '闂鏁存敼',
+ name: '鏁存敼娓呭崟',
+ },
+ {
+ path: '/index/inspection/punishment-manage',
+ icon: 'solar:hand-shake-line-duotone',
+ name: '琛屾斂澶勭綒',
+ },
+ {
+ path: '/index/inspection/complaint-manage',
+ icon: 'solar:call-chat-line-duotone',
+ name: '淇¤鎶曡瘔',
},
],
},
diff --git a/src/router/index.js b/src/router/index.js
index 9a913c5..9cd0139 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -110,6 +110,16 @@
path: 'monitor-control',
component: () => import('@/views/inspection/MonitorControl.vue'),
},
+ {
+ name: 'punishment-manage',
+ path: 'punishment-manage',
+ component: () => import('@/views/inspection/PunishmentManage.vue'),
+ },
+ {
+ name: 'complaint-manage',
+ path: 'complaint-manage',
+ component: () => import('@/views/inspection/ComplaintManage.vue'),
+ },
],
},
{
@@ -129,7 +139,7 @@
{
name: 'data-product',
path: 'data-product',
- component: () => import('@/views/analysis/DataProduct.vue'),
+ component: () => import('@/views/analysis/data-product/DataProduct.vue'),
},
],
},
diff --git a/src/sfc/ExceptionText.vue b/src/sfc/ExceptionText.vue
index 25d8bdf..8e919bf 100644
--- a/src/sfc/ExceptionText.vue
+++ b/src/sfc/ExceptionText.vue
@@ -35,7 +35,9 @@
</script>
<template>
- <el-link @click="requestExceptionData" class="text"><slot /></el-link>
+ <el-tag type="info">
+ <el-link @click="requestExceptionData" class="text"><slot /></el-link>
+ </el-tag>
</template>
<style lang="scss" scoped>
diff --git a/src/sfc/TimeSelect.vue b/src/sfc/TimeSelect.vue
index 0cd5e1a..231d5fa 100644
--- a/src/sfc/TimeSelect.vue
+++ b/src/sfc/TimeSelect.vue
@@ -14,6 +14,57 @@
-->
<script>
import dayjs from 'dayjs'
+// 鏃堕棿鑼冨洿蹇嵎閫夐」
+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()],
+ },
+]
+
export default {
emits: ['submitTime'],
props: {
@@ -42,6 +93,8 @@
],
// 閫変腑鐨勬椂闂磋寖鍥�
selectedRange: '',
+ // 鏃堕棿鑼冨洿蹇嵎閫夐」
+ shortcuts,
}
},
@@ -138,6 +191,7 @@
<div v-show="showTimePicker" class="time-picker-container">
<el-date-picker
v-model="time"
+ :shortcuts="shortcuts"
type="daterange"
range-separator="~"
start-placeholder="寮�濮嬫椂闂�"
@@ -153,6 +207,7 @@
<span class="demonstration">鏃堕棿锛�</span>
<el-date-picker
v-model="time"
+ :shortcuts="shortcuts"
type="daterange"
range-separator="~"
start-placeholder="Start date"
diff --git a/src/views/analysis/DataProduct.vue b/src/views/analysis/DataProduct.vue
deleted file mode 100644
index 78bd720..0000000
--- a/src/views/analysis/DataProduct.vue
+++ /dev/null
@@ -1,2 +0,0 @@
-<template>鏁版嵁浜у搧</template>
-<script setup lang="ts"></script>
diff --git a/src/views/analysis/data-product/DataProduct.vue b/src/views/analysis/data-product/DataProduct.vue
new file mode 100644
index 0000000..921a3fb
--- /dev/null
+++ b/src/views/analysis/data-product/DataProduct.vue
@@ -0,0 +1,116 @@
+<template>
+ <BaseProdProcess
+ v-model:active="active"
+ @onStep1="onStep1"
+ @onStep2="onStep2"
+ @onStep3="onStep3"
+ :loading="loading"
+ >
+ <template #step1="{ onSearch }">
+ <ProdQueryOptWithMode :loading="loading" @submit="onSearch"></ProdQueryOptWithMode>
+ </template>
+ <template #step2="{ contentHeight }">
+ <el-table
+ id="prod-inspection-table"
+ :data="tableData"
+ v-loading="loading"
+ :height="contentHeight + 'px'"
+ table-layout="fixed"
+ :show-overflow-tooltip="true"
+ size="small"
+ border
+ >
+ <el-table-column fixed="left" prop="index" label="缂栧彿" width="50"> </el-table-column>
+ <el-table-column
+ fixed="left"
+ prop="subTask.scensename"
+ label="鍚嶇О"
+ :show-overflow-tooltip="true"
+ min-width="200"
+ >
+ </el-table-column>
+ <el-table-column
+ prop="subTask.planstarttime"
+ label="宸℃煡鏃堕棿"
+ :formatter="timeFormat"
+ width="90"
+ />
+ <!-- <el-table-column prop="provincename" label="鐪�" width="90" />
+ <el-table-column prop="cityname" label="甯�" width="90" />
+ <el-table-column prop="districtname" label="鍖哄幙" width="90" /> -->
+ <el-table-column prop="subTask.townname" label="琛楅亾" width="80" />
+ <el-table-column prop="problems.length" label="闂鏁�" width="60" />
+ <el-table-column label="闂鎽樿" width="300">
+ <template #default="{ row }">
+ <template v-for="(value, index) in row.problems" :key="value.guid">
+ <br v-if="index > 0" />{{ index + 1 + '銆�' + value.problemname }}
+ </template>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unChangeProblems.length" label="鏈暣鏀规暟" width="60" />
+ <el-table-column label="鏈暣鏀归棶棰�" width="300">
+ <template #default="{ row }">
+ <template v-for="(value, index) in row.unChangeProblems" :key="value.guid">
+ <br v-if="index > 0" />{{ index + 1 + '銆�' + value.problemname }}
+ </template>
+ </template>
+ </el-table-column>
+ <!-- <el-table-column
+ prop="evaluate.updatedate"
+ label="鏇存柊鏃堕棿"
+ width="140"
+ :formatter="timeFormat"
+ /> -->
+ </el-table>
+ </template>
+ </BaseProdProcess>
+</template>
+<script setup>
+import { ref, inject } from 'vue'
+import dayjs from 'dayjs'
+import BaseProdProcess from './components/BaseProdProcess.vue'
+import dataprodbaseApi from '@/api/fysp/dataprodbaseApi.js'
+import { conversionFromTable } from '@/utils/excel'
+import { useProdStepChange } from './prod-step-change.js'
+import ProdQueryOptWithMode from './components/ProdQueryOptWithMode.vue'
+
+const { active, changeActive } = useProdStepChange()
+const loading = ref(false)
+const tableData = ref([])
+
+function onStep1(opt) {
+ loading.value = true
+ dataprodbaseApi
+ .fetchProdInspectionInfo(opt)
+ .then((res) => {
+ if (res.success) {
+ tableData.value = res.data.map((item) => {
+ return {
+ ...item,
+ unChangeProblems: item.problems.filter((p) => !p.ischanged),
+ }
+ })
+ }
+ changeActive()
+ })
+ .finally(() => {
+ loading.value = false
+ })
+}
+
+function onStep2() {
+ changeActive()
+}
+
+function onStep3(val) {
+ if (val.downloadType == '1') {
+ loading.value = true
+ conversionFromTable('prod-inspection-table', '鏁存敼娓呭崟')
+ loading.value = false
+ }
+}
+
+function timeFormat(row, column, cellValue, index) {
+ return dayjs(cellValue).format('YYYY-MM-DD')
+}
+</script>
diff --git a/src/views/analysis/data-product/components/BaseProdProcess.vue b/src/views/analysis/data-product/components/BaseProdProcess.vue
new file mode 100644
index 0000000..a4c7870
--- /dev/null
+++ b/src/views/analysis/data-product/components/BaseProdProcess.vue
@@ -0,0 +1,316 @@
+<template>
+ <el-row>
+ <!-- 姝ラ1 鏁版嵁浜у搧鐢熸垚閫夐」 -->
+ <div :class="active == 1 ? 'prod-active' : 'prod-inactive'" ref="step1Ref">
+ <transition name="el-fade-in" @after-leave="handleTransitionContentEnd(1)">
+ <div v-show="showStep1Content">
+ <template v-if="$slots.step1">
+ <slot name="step1" :onSearch="onSearch"></slot>
+ </template>
+ <template v-else>
+ <ProdQueryOpt :loading="loading" @submit="onSearch"> </ProdQueryOpt>
+ </template>
+ </div>
+ </transition>
+ <transition name="el-fade-in" @after-leave="handleTransitionThumbnailEnd(1)">
+ <div
+ v-show="showStep1Thumbnail"
+ class="prod-thumbnail-wrapper"
+ :style="{ height: viewHeight + 'px' }"
+ @click="changeActive(1)"
+ >
+ <div class="prod-thumbnail">鈶犱慨鏀归�夐」</div>
+ </div>
+ </transition>
+ </div>
+ <!-- 姝ラ2 鏁版嵁浜у搧缁撴灉棰勮 -->
+ <div :class="active == 2 ? 'prod-active' : 'prod-inactive'" ref="step2Ref">
+ <transition name="el-fade-in" @after-leave="handleTransitionContentEnd(2)">
+ <div v-show="showStep2Content">
+ <div ref="titleRef" class="prod-title">
+ <el-text tag="b" size="large">鏁版嵁浜у搧棰勮</el-text>
+ <el-button type="primary" @click="$emit('onStep2')"> 涓嬭浇鏁版嵁浜у搧 </el-button>
+ </div>
+ <slot name="step2" :contentHeight="contentHeight"></slot>
+ </div>
+ </transition>
+ <transition name="el-fade-in" @after-leave="handleTransitionThumbnailEnd(2)">
+ <div
+ v-show="showStep2Thumbnail"
+ class="prod-thumbnail-wrapper"
+ :style="{ height: viewHeight + 'px' }"
+ @click="changeActive(2)"
+ >
+ <div
+ :class="
+ 'prod-thumbnail prod-thumbnail_middle ' +
+ (active < 2 ? 'prod-thumbnail-disabled' : '')
+ "
+ >
+ 鈶℃暟鎹骇鍝侀瑙�
+ </div>
+ </div>
+ </transition>
+ </div>
+ <!-- 姝ラ3 鏁版嵁浜у搧琛ㄥ崟涓嬭浇 -->
+ <div :class="active == 3 ? 'prod-active' : 'prod-inactive'" ref="step3Ref">
+ <transition name="el-fade-in" @after-leave="handleTransitionContentEnd(3)">
+ <div v-show="showStep3Content">
+ <template v-if="$slots.step3">
+ <slot name="step3" :onDownload="onDownload" :queryOpt="queryOpt"></slot>
+ </template>
+ <template v-else>
+ <ProdDownload
+ :loading="loading"
+ :queryOpt="queryOpt"
+ @submit="onDownload"
+ ></ProdDownload>
+ </template>
+ </div>
+ </transition>
+ <transition name="el-fade-in" @after-leave="handleTransitionThumbnailEnd(3)">
+ <div
+ v-show="showStep3Thumbnail"
+ class="prod-thumbnail-wrapper"
+ :style="{ height: viewHeight + 'px' }"
+ @click="changeActive(3)"
+ >
+ <div
+ :class="
+ 'prod-thumbnail prod-thumbnail_end ' + (active < 3 ? 'prod-thumbnail-disabled' : '')
+ "
+ >
+ 鈶㈡暟鎹骇鍝佷笅杞�
+ </div>
+ </div>
+ </transition>
+ </div>
+ </el-row>
+</template>
+<script setup>
+import { computed, inject, ref, watch, onMounted, onUnmounted } from 'vue'
+import ProdQueryOpt from './ProdQueryOpt.vue'
+import ProdDownload from './ProdDownload.vue'
+
+const props = defineProps({
+ active: {
+ type: Number,
+ default: 1,
+ },
+ loading: {
+ type: Boolean,
+ default: false,
+ },
+})
+
+const emit = defineEmits(['update:active', 'onStep1', 'onStep2', 'onStep3'])
+
+const contentMaxHeight = inject('contentMaxHeight')
+const viewHeight = inject('viewHeight', contentMaxHeight.value)
+
+const btnDisabled = ref(false)
+
+const titleRef = ref(null)
+const contentHeight = ref('50vh')
+
+function calContentHeight() {
+ // console.log(titleRef.value.offsetHeight);
+ contentHeight.value = viewHeight - (titleRef.value?.offsetHeight || 0)
+ // console.log(contentHeight.value);
+}
+
+// 鏁版嵁浜у搧鐢熸垚閫夐」
+const queryOpt = ref({})
+
+// 姝ラ寮曠敤
+const step1Ref = ref(null)
+const step2Ref = ref(null)
+const step3Ref = ref(null)
+
+// 鎺у埗鏄剧ず/闅愯棌鐨勭姸鎬�
+const showStep1Content = ref(props.active === 1)
+const showStep1Thumbnail = ref(props.active != 1)
+const showStep2Content = ref(props.active === 2)
+const showStep2Thumbnail = ref(props.active != 2)
+const showStep3Content = ref(props.active === 3)
+const showStep3Thumbnail = ref(props.active != 3)
+
+// 璁板綍鍔ㄧ敾鏄惁姝e湪杩涜涓�
+const isAnimating = ref({})
+
+// 鐩戝惉active鍙樺寲
+watch(
+ () => props.active,
+ (newActive, oldActive) => {
+ // 鏍囪鍔ㄧ敾寮�濮�
+ isAnimating.value[oldActive] = true
+ isAnimating.value[newActive] = true
+
+ // 鍏堥殣钘忔墍鏈夊唴瀹癸紝绛夊緟鍔ㄧ敾缁撴潫鍚庡啀鏄剧ず姝g‘鐨勫唴瀹�
+ if (oldActive === 1) {
+ showStep1Content.value = false
+ } else if (oldActive === 2) {
+ showStep2Content.value = false
+ } else if (oldActive === 3) {
+ showStep3Content.value = false
+ }
+
+ if (newActive === 1) {
+ showStep1Thumbnail.value = false
+ } else if (newActive === 2) {
+ showStep2Thumbnail.value = false
+ } else if (newActive === 3) {
+ showStep3Thumbnail.value = false
+ }
+ },
+)
+
+// 澶勭悊鍔ㄧ敾缁撴潫浜嬩欢
+function handleTransitionThumbnailEnd(stepIndex) {
+ // 妫�鏌ュ姩鐢绘槸鍚︾‘瀹炵粨鏉燂紙閬垮厤閲嶅瑙﹀彂锛�
+ if (isAnimating.value[stepIndex]) {
+ isAnimating.value[stepIndex] = false
+
+ // setTimeout(() => {
+ // 鍔ㄧ敾缁撴潫鍚庯紝鏇存柊鏄剧ず鐘舵��
+ if (stepIndex === 1) {
+ showStep1Content.value = props.active === 1
+ } else if (stepIndex === 2) {
+ showStep2Content.value = props.active === 2
+ } else if (stepIndex === 3) {
+ showStep3Content.value = props.active === 3
+ }
+ // }, 50);
+ }
+}
+
+function handleTransitionContentEnd(stepIndex) {
+ // 妫�鏌ュ姩鐢绘槸鍚︾‘瀹炵粨鏉燂紙閬垮厤閲嶅瑙﹀彂锛�
+ if (isAnimating.value[stepIndex]) {
+ isAnimating.value[stepIndex] = false
+
+ // setTimeout(() => {
+ // 鍔ㄧ敾缁撴潫鍚庯紝鏇存柊鏄剧ず鐘舵��
+ if (stepIndex === 1) {
+ showStep1Thumbnail.value = props.active != 1
+ } else if (stepIndex === 2) {
+ showStep2Thumbnail.value = props.active != 2
+ } else if (stepIndex === 3) {
+ showStep3Thumbnail.value = props.active != 3
+ }
+ // }, 50);
+ }
+}
+
+function onSearch(opt) {
+ queryOpt.value = opt
+ emit('onStep1', opt)
+}
+function onDownload(val) {
+ emit('onStep3', val)
+}
+function changeActive(index) {
+ let isAnimate = false
+ Object.values(isAnimating.value).forEach((item) => {
+ isAnimate = isAnimate || item
+ })
+ if (!isAnimate && !btnDisabled.value && props.active >= index) {
+ emit('update:active', index)
+ btnDisabled.value = true
+ setTimeout(() => {
+ btnDisabled.value = false
+ }, 500)
+ }
+ // emit('update:active', index);
+}
+
+let resizeObserver = null
+
+onMounted(() => {
+ if (titleRef.value) {
+ resizeObserver = new ResizeObserver(() => {
+ calContentHeight()
+ })
+ resizeObserver.observe(titleRef.value)
+ }
+})
+// 鍦ㄧ粍浠跺嵏杞芥椂娓呯悊
+onUnmounted(() => {
+ if (resizeObserver && titleRef.value) {
+ resizeObserver.unobserve(titleRef)
+ }
+})
+</script>
+<style scoped>
+.prod-active {
+ /* width: 66.667%; */
+ width: 90%;
+ transition:
+ width 0.5s ease,
+ box-shadow 0.3s ease;
+ /* background-color: #409eff; */
+ margin: 5px 0;
+ border-radius: 4px;
+ box-shadow:
+ -3px 0 6px rgba(0, 0, 0, 0.1),
+ 3px 0 6px rgba(0, 0, 0, 0.1);
+}
+
+.prod-inactive {
+ /* width: 16.667%; */
+ width: 5%;
+ transition: width 0.5s ease;
+ /* background-color: #e4e7ed; */
+ margin: 5px 0;
+ border-radius: 4px;
+}
+
+.prod-title {
+ padding: 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.prod-thumbnail-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0 2px;
+}
+
+.prod-thumbnail {
+ height: 90%;
+ width: 100%;
+ background-color: #409eff;
+ color: white;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ writing-mode: vertical-rl;
+ text-orientation: upright;
+ letter-spacing: 8px;
+ font-size: 18px;
+ font-weight: 600;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ cursor: pointer;
+}
+
+.prod-thumbnail_middle {
+ border-radius: 0px;
+}
+
+.prod-thumbnail_end {
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+
+.prod-thumbnail-disabled {
+ background-color: #e4e7ed;
+ color: #c0c4cc;
+ cursor: not-allowed;
+}
+</style>
diff --git a/src/views/analysis/data-product/components/ProdDownload.vue b/src/views/analysis/data-product/components/ProdDownload.vue
new file mode 100644
index 0000000..c7552a1
--- /dev/null
+++ b/src/views/analysis/data-product/components/ProdDownload.vue
@@ -0,0 +1,110 @@
+<template>
+ <el-card shadow="never">
+ <template #header>
+ <div><el-text tag="b" size="large">鏁版嵁浜у搧涓嬭浇</el-text></div>
+ </template>
+ <el-form :inline="false" label-position="left" label-width="150px">
+ <el-form-item label="鍖哄幙">
+ <el-text>{{ opts.districtName }}</el-text>
+ </el-form-item>
+ <el-form-item label="鏃堕棿鑼冨洿">
+ <el-text>{{ opts.startTime }} 鑷� {{ opts.endTime }}</el-text>
+ </el-form-item>
+ <el-form-item label="鍦烘櫙绫诲瀷">
+ <el-text>{{ opts.sceneTypeName }}</el-text>
+ </el-form-item>
+ <el-form-item label="浜у搧褰㈠紡">
+ <el-radio-group v-model="downloadType">
+ <el-radio
+ v-for="item in _downloadTypeOptions"
+ :key="item.value"
+ :value="item.value"
+ :disabled="item.disabled"
+ >
+ {{ item.label }}
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-row justify="end">
+ <el-button
+ type="primary"
+ size="default"
+ :loading="loading"
+ @click="submit"
+ icon="Download"
+ >涓嬭浇</el-button
+ >
+ </el-row>
+ </template>
+ </el-card>
+</template>
+<script setup>
+import { ref, computed } from 'vue';
+import dayjs from 'dayjs';
+import scene_1 from '@/assets/image/scene_1.png';
+
+const props = defineProps({
+ // 鏁版嵁浜у搧鐢熸垚閫夐」
+ queryOpt: {
+ type: Object,
+ default: () => {}
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ downloadTypeOptions: {
+ type: Array,
+ default: () => [
+ {
+ value: '1',
+ label: 'Excel琛ㄥ崟'
+ },
+ {
+ value: '2',
+ label: 'Word鏂囨。'
+ }
+ ]
+ },
+ // 涓嬭浇绫诲瀷鏄惁鏈夋晥
+ downloadTypeValid: {
+ type: Array,
+ default: () => ['1']
+ },
+ defaultDownloadType: {
+ type: String,
+ default: '1'
+ }
+});
+const emit = defineEmits(['submit']);
+
+const downloadType = ref(props.defaultDownloadType);
+const opts = computed(() => {
+ if (props.queryOpt instanceof Array && props.queryOpt.length > 0) {
+ return props.queryOpt[0];
+ } else {
+ return props.queryOpt;
+ }
+});
+
+const _downloadTypeOptions = computed(() => {
+ return props.downloadTypeOptions.map((item) => ({
+ ...item,
+ disabled: !props.downloadTypeValid.includes(item.value)
+ }));
+});
+
+const submit = () => {
+ emit('submit', {
+ downloadType: downloadType.value
+ });
+};
+</script>
+<style scoped>
+/* .image {
+ width: 200px;
+ height: 200px;
+} */
+</style>
diff --git a/src/views/analysis/data-product/components/ProdQueryOpt.vue b/src/views/analysis/data-product/components/ProdQueryOpt.vue
new file mode 100644
index 0000000..3c4cbc4
--- /dev/null
+++ b/src/views/analysis/data-product/components/ProdQueryOpt.vue
@@ -0,0 +1,71 @@
+<template>
+ <el-card shadow="never">
+ <template #header>
+ <div><el-text tag="b" size="large">浜у搧鐢熸垚閫夐」</el-text></div>
+ </template>
+ <SearchBar
+ v-show="active"
+ ref="refSearchBar"
+ :btn-show="false"
+ :init="false"
+ @on-submit="search"
+ >
+ </SearchBar>
+ <template #footer>
+ <el-row v-show="active" justify="end">
+ <el-button
+ type="primary"
+ size="default"
+ :loading="loading"
+ @click="submit"
+ >鐢熸垚</el-button
+ >
+ </el-row>
+ </template>
+ </el-card>
+</template>
+<script setup>
+import { ref, computed } from 'vue';
+import dayjs from 'dayjs';
+
+const props = defineProps({
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ active: {
+ type: Boolean,
+ default: true
+ }
+});
+const emit = defineEmits(['submit']);
+
+const refSearchBar = ref(null);
+
+const submit = () => {
+ refSearchBar.value.onSubmit();
+};
+
+const search = (options) => {
+ const opt = {
+ topTaskId: options.topTask.tguid,
+ topTaskName: options.topTask.name,
+ provinceCode: options.topTask.provincecode,
+ provinceName: options.topTask.provincename,
+ cityCode: options.topTask.citycode,
+ cityName: options.topTask.cityname,
+ districtCode: options.topTask.districtcode,
+ districtName: options.topTask.districtname,
+ townCode: options.topTask.towncode,
+ townName: options.topTask.townname,
+ startTime: dayjs(options.topTask.starttime).format('YYYY-MM-DD HH:mm:ss'),
+ endTime: dayjs(options.topTask.endtime)
+ .endOf('day')
+ .format('YYYY-MM-DD HH:mm:ss'),
+ sceneTypeId: options.sceneTypeId,
+ sceneTypeName: options.sceneTypeName,
+ needCache: true
+ };
+ emit('submit', opt);
+};
+</script>
diff --git a/src/views/analysis/data-product/components/ProdQueryOptCompare.vue b/src/views/analysis/data-product/components/ProdQueryOptCompare.vue
new file mode 100644
index 0000000..285fa87
--- /dev/null
+++ b/src/views/analysis/data-product/components/ProdQueryOptCompare.vue
@@ -0,0 +1,114 @@
+<template>
+ <el-card shadow="never">
+ <template #header>
+ <div><el-text tag="b" size="large">浜у搧鐢熸垚閫夐」</el-text></div>
+ </template>
+ <el-space fill>
+ <el-alert type="info" show-icon :closable="false">
+ <p>閫夋嫨鏈湡闇�瑕佺粺璁$殑鎬讳换鍔″拰鍦烘櫙绫诲瀷</p>
+ </el-alert>
+ <el-form :inline="true" :model="formSearch">
+ <FYOptionTopTask v-model:value="formSearch.topTask"></FYOptionTopTask>
+ <FYOptionScene
+ :allOption="false"
+ :type="2"
+ v-model:value="formSearch.scenetype"
+ ></FYOptionScene>
+ </el-form>
+ </el-space>
+
+ <el-space fill>
+ <el-alert type="info" show-icon :closable="false">
+ <p>閫夋嫨闇�瑕佽繘琛屽姣旂殑鍘嗗彶鐗堟湰</p>
+ </el-alert>
+ <el-form :inline="true" :model="formSearch2">
+ <FYOptionTopTask
+ :beforeTask="formSearch.topTask"
+ v-model:value="formSearch2.topTask"
+ ></FYOptionTopTask>
+ </el-form>
+ </el-space>
+ <template #footer>
+ <el-row v-show="active" justify="end">
+ <el-button
+ type="primary"
+ size="default"
+ :loading="loading"
+ @click="submit"
+ >鐢熸垚</el-button
+ >
+ </el-row>
+ </template>
+ </el-card>
+</template>
+<script setup>
+import { ref, computed } from 'vue';
+import dayjs from 'dayjs';
+
+const props = defineProps({
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ active: {
+ type: Boolean,
+ default: true
+ }
+});
+const emit = defineEmits(['submit']);
+
+const formSearch = ref({
+ topTask: {},
+ scenetype: {}
+});
+
+const formSearch2 = ref({
+ topTask: {}
+});
+
+const submit = () => {
+ const opt1 = {
+ topTaskId: formSearch.value.topTask.tguid,
+ topTaskName: formSearch.value.topTask.name,
+ provinceCode: formSearch.value.topTask.provincecode,
+ provinceName: formSearch.value.topTask.provincename,
+ cityCode: formSearch.value.topTask.citycode,
+ cityName: formSearch.value.topTask.cityname,
+ districtCode: formSearch.value.topTask.districtcode,
+ districtName: formSearch.value.topTask.districtname,
+ townCode: formSearch.value.topTask.towncode,
+ townName: formSearch.value.topTask.townname,
+ startTime: dayjs(formSearch.value.topTask.starttime).format(
+ 'YYYY-MM-DD HH:mm:ss'
+ ),
+ endTime: dayjs(formSearch.value.topTask.endtime)
+ .endOf('day')
+ .format('YYYY-MM-DD HH:mm:ss'),
+ sceneTypeId: formSearch.value.scenetype.value,
+ sceneTypeName: formSearch.value.scenetype.label,
+ needCache: true
+ };
+ const opt2 = {
+ topTaskId: formSearch2.value.topTask.tguid,
+ topTaskName: formSearch2.value.topTask.name,
+ provinceCode: formSearch2.value.topTask.provincecode,
+ provinceName: formSearch2.value.topTask.provincename,
+ cityCode: formSearch2.value.topTask.citycode,
+ cityName: formSearch2.value.topTask.cityname,
+ districtCode: formSearch2.value.topTask.districtcode,
+ districtName: formSearch2.value.topTask.districtname,
+ townCode: formSearch2.value.topTask.towncode,
+ townName: formSearch2.value.topTask.townname,
+ startTime: dayjs(formSearch2.value.topTask.starttime).format(
+ 'YYYY-MM-DD HH:mm:ss'
+ ),
+ endTime: dayjs(formSearch2.value.topTask.endtime)
+ .endOf('day')
+ .format('YYYY-MM-DD HH:mm:ss'),
+ sceneTypeId: formSearch.value.scenetype.value,
+ sceneTypeName: formSearch.value.scenetype.label,
+ needCache: true
+ };
+ emit('submit', [opt1, opt2]);
+};
+</script>
diff --git a/src/views/analysis/data-product/components/ProdQueryOptWithMode.vue b/src/views/analysis/data-product/components/ProdQueryOptWithMode.vue
new file mode 100644
index 0000000..893bd1c
--- /dev/null
+++ b/src/views/analysis/data-product/components/ProdQueryOptWithMode.vue
@@ -0,0 +1,156 @@
+<template>
+ <el-card shadow="never">
+ <template #header>
+ <div><el-text tag="b" size="large">浜у搧鐢熸垚閫夐」</el-text></div>
+ </template>
+ <el-switch
+ v-model="mode"
+ size="large"
+ active-text="鎸夋墍閫夋椂娈电粺璁�"
+ inactive-text="鎸夋�讳换鍔$粺璁�"
+ />
+ <!-- <SearchBar
+ v-show="active && !mode"
+ ref="refSearchBar"
+ :btn-show="false"
+ :init="false"
+ @on-submit="search"
+ >
+ </SearchBar> -->
+ <el-form :inline="true">
+ <FYOptionScene
+ :allOption="false"
+ :type="2"
+ :initValue="false"
+ v-model:value="scenetype"
+ ></FYOptionScene>
+ <FYOptionTopTask v-show="!mode" v-model:value="topTask"></FYOptionTopTask>
+ <!-- 鍖哄幙 -->
+ <FYOptionLocation
+ v-show="mode"
+ :allOption="false"
+ :level="3"
+ :checkStrictly="false"
+ v-model:value="locations"
+ ></FYOptionLocation>
+ <FYOptionTime
+ v-show="mode"
+ :initValue="false"
+ type="daterange"
+ v-model:value="timeRange"
+ style="width: 300px"
+ ></FYOptionTime>
+ </el-form>
+ <template #footer>
+ <el-row v-show="active" justify="end">
+ <el-button type="primary" size="default" :loading="loading" @click="search">鐢熸垚</el-button>
+ </el-row>
+ </template>
+ </el-card>
+</template>
+<script setup>
+import { ref, computed, watch } from 'vue'
+import dayjs from 'dayjs'
+
+const props = defineProps({
+ loading: {
+ type: Boolean,
+ default: false,
+ },
+ active: {
+ type: Boolean,
+ default: true,
+ },
+})
+const emit = defineEmits(['submit'])
+
+const mode = ref(true)
+const scenetype = ref({
+ label: '椁愰ギ',
+ value: '5',
+})
+const topTask = ref({})
+
+const locations = ref({})
+const timeRange = ref([])
+
+watch(
+ () => topTask.value,
+ (newVal, oldVal) => {
+ if (newVal?.provincecode) {
+ locations.value.pCode = newVal.provincecode
+ locations.value.pName = newVal.provincename
+ locations.value.cCode = newVal.citycode
+ locations.value.cName = newVal.cityname
+ locations.value.dCode = newVal.districtcode
+ locations.value.dName = newVal.districtname
+ // locations.value.tCode = newVal.towncode
+ // locations.value.tName = newVal.townname
+ timeRange.value = [new Date(newVal.starttime), new Date(newVal.endtime)]
+ console.log(timeRange.value)
+ }
+ },
+ { deep: true },
+)
+
+const search = (options) => {
+ const [st, et] = timeRange.value
+ const startTime = dayjs(st).startOf('day').format('YYYY-MM-DD HH:mm:ss')
+ const endTime = dayjs(et).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+
+ let opt = {
+ sceneTypeId: scenetype.value.sceneTypeId,
+ sceneTypeName: scenetype.value.sceneTypeName,
+ needCache: true,
+ }
+ // 鎸夋墍閫夋椂娈电粺璁�
+ if (mode.value) {
+ opt = {
+ provinceCode: locations.value.pCode,
+ provinceName: locations.value.pName,
+ cityCode: locations.value.cCode,
+ cityName: locations.value.cName,
+ districtCode: locations.value.dCode,
+ districtName: locations.value.dName,
+ townCode: locations.value.tCode,
+ townName: locations.value.tName,
+ startTime,
+ endTime,
+ ...opt,
+ }
+ }
+ // 鎸夋�讳换鍔$粺璁�
+ else {
+ opt = {
+ provinceCode: topTask.value.provincecode,
+ provinceName: topTask.value.provincename,
+ cityCode: topTask.value.citycode,
+ cityName: topTask.value.cityname,
+ districtCode: topTask.value.districtcode,
+ districtName: topTask.value.districtname,
+ townCode: topTask.value.towncode,
+ townName: topTask.value.townname,
+ ...opt,
+ }
+ }
+
+ // const opt = {
+ // topTaskId: options.topTask.tguid,
+ // topTaskName: options.topTask.name,
+ // provinceCode: options.topTask.provincecode,
+ // provinceName: options.topTask.provincename,
+ // cityCode: options.topTask.citycode,
+ // cityName: options.topTask.cityname,
+ // districtCode: options.topTask.districtcode,
+ // districtName: options.topTask.districtname,
+ // townCode: options.topTask.towncode,
+ // townName: options.topTask.townname,
+ // startTime,
+ // endTime,
+ // sceneTypeId: options.sceneTypeId,
+ // sceneTypeName: options.sceneTypeName,
+ // needCache: true
+ // };
+ emit('submit', opt)
+}
+</script>
diff --git a/src/views/analysis/data-product/prod-step-change.js b/src/views/analysis/data-product/prod-step-change.js
new file mode 100644
index 0000000..a6630ab
--- /dev/null
+++ b/src/views/analysis/data-product/prod-step-change.js
@@ -0,0 +1,15 @@
+import { ref } from 'vue';
+/**
+ * 鏁版嵁浜у搧姝ラ鍒囨崲
+ */
+export function useProdStepChange() {
+ const active = ref(1);
+ function changeActive() {
+ active.value++;
+ active.value = active.value > 3 ? 1 : active.value;
+ }
+ return {
+ active,
+ changeActive
+ };
+}
diff --git a/src/views/analysis/huanxincode/HuanxinCodeManage.vue b/src/views/analysis/huanxincode/HuanxinCodeManage.vue
index 9140d7c..5c9f815 100644
--- a/src/views/analysis/huanxincode/HuanxinCodeManage.vue
+++ b/src/views/analysis/huanxincode/HuanxinCodeManage.vue
@@ -31,8 +31,8 @@
<el-col :span="8">
<el-card shadow="hover" class="dashboard-card green-card" @click="filterByCode('green')">
<div class="card-content">
- <div class="card-title">缁跨爜搴楅摵鏁�</div>
- <div class="card-value">{{ statistics.greenCount }}</div>
+ <div class="card-title">缁跨爜搴楅摵</div>
+ <div class="card-value">{{ statistics.greenCount }}<el-text>涓�</el-text></div>
<div class="card-percentage">{{ statistics.greenPercentage }}%</div>
</div>
</el-card>
@@ -40,8 +40,8 @@
<el-col :span="8">
<el-card shadow="hover" class="dashboard-card yellow-card" @click="filterByCode('yellow')">
<div class="card-content">
- <div class="card-title">榛勭爜搴楅摵鏁�</div>
- <div class="card-value">{{ statistics.yellowCount }}</div>
+ <div class="card-title">榛勭爜搴楅摵</div>
+ <div class="card-value">{{ statistics.yellowCount }}<el-text>涓�</el-text></div>
<div class="card-percentage">{{ statistics.yellowPercentage }}%</div>
</div>
</el-card>
@@ -49,8 +49,8 @@
<el-col :span="8">
<el-card shadow="hover" class="dashboard-card red-card" @click="filterByCode('red')">
<div class="card-content">
- <div class="card-title">绾㈢爜搴楅摵鏁�</div>
- <div class="card-value">{{ statistics.redCount }}</div>
+ <div class="card-title">绾㈢爜搴楅摵</div>
+ <div class="card-value">{{ statistics.redCount }}<el-text>涓�</el-text></div>
<div class="card-percentage">{{ statistics.redPercentage }}%</div>
</div>
</el-card>
diff --git a/src/views/inspection/ComplaintManage.vue b/src/views/inspection/ComplaintManage.vue
new file mode 100644
index 0000000..e2817c1
--- /dev/null
+++ b/src/views/inspection/ComplaintManage.vue
@@ -0,0 +1,597 @@
+<template>
+ <div class="complaint-manage">
+ <!-- 椁愰ギ搴楅摵淇¤鎶曡瘔鍗$墖 -->
+ <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="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="complaintImportDialogVisible" title="鎶曡瘔鎵归噺瀵煎叆" width="600px">
+ <div class="import-container">
+ <p class="import-tip">璇烽�夋嫨瑕佸鍏ョ殑Excel鏂囦欢</p>
+ <el-upload
+ class="upload-demo"
+ action="#"
+ drag
+ :auto-upload="false"
+ :on-change="handleComplaintFileChange"
+ :file-list="complaintImportFileList"
+ accept=".xlsx,.xls"
+ :limit="1"
+ :on-exceed="handleExceed"
+ >
+ <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+ <div class="el-upload__text">鎷栧姩鏂囦欢鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <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 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 dailyComplaintChart = ref(null)
+const sourceComplaintChart = ref(null)
+
+// 淇¤鎶曡瘔鏁版嵁
+const complaintDateRange = ref([dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()])
+const complaintKeyword = ref('')
+const complaintStats = ref({
+ totalCount: 85,
+})
+const complaintTableData = ref([])
+const complaintPagination = ref({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+})
+
+// 杩囨护鍚庣殑鎶曡瘔鏁版嵁
+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 complaintDialogVisible = ref(false)
+const complaintImportDialogVisible = ref(false)
+
+// 瀵煎叆鏂囦欢鍒楄〃
+const complaintImportFileList = ref([])
+
+// 琛ㄥ崟鏁版嵁
+const complaintForm = ref({
+ id: '',
+ shopName: '',
+ complaintReason: '',
+ complaintRequest: '',
+ complaintTime: '',
+ complaintSource: '',
+ handlingDepartment: '',
+ complaintResult: '',
+})
+
+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 = ['娌圭儫鎵版皯', '澶滈棿鍣0', '寮傚懗姹℃煋', '鍗敓闂']
+ const sources = ['12345鐑嚎', '灞呮皯鎶曡瘔', '缃戠粶骞冲彴', '鍏朵粬']
+ const departments = ['寰愭眹鍖虹幆淇濆眬', '闀垮畞鍖虹幆淇濆眬', '闈欏畨鍖虹幆淇濆眬', '鏅檧鍖虹幆淇濆眬']
+ // const results = ['宸插鐞�', '澶勭悊涓�', '鏈鐞�']
+ 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(
+ () => complaintDateRange.value,
+ () => {
+ searchComplaint()
+ },
+ { deep: true },
+)
+
+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)
+
+ // 棰勭暀瀵煎叆閫昏緫
+ // 杩欓噷灏嗗疄鐜板疄闄呯殑鏂囦欢瑙f瀽鍜屾暟鎹鍏�
+
+ complaintImportDialogVisible.value = false
+ ElMessage.success('瀵煎叆鎿嶄綔宸茶Е鍙戯紝棰勭暀瀵煎叆閫昏緫')
+}
+
+const handleExceed = (files, fileList) => {
+ ElMessage.warning('鍙兘涓婁紶涓�涓枃浠�')
+}
+
+const handleComplaintSizeChange = (size) => {
+ complaintPagination.value.pageSize = size
+ // 杩欓噷鍙互娣诲姞瀹為檯鐨勫垎椤甸�昏緫
+}
+
+const handleComplaintCurrentChange = (current) => {
+ complaintPagination.value.currentPage = current
+ // 杩欓噷鍙互娣诲姞瀹為檯鐨勫垎椤甸�昏緫
+}
+
+// 鍒濆鍖栧浘琛�
+const initCharts = () => {
+ // 姣忔棩鎶曡瘔鏁伴噺鍥�
+ if (dailyComplaintChart.value && complaintDateRange.value.length > 0) {
+ const chart = echarts.init(dailyComplaintChart.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')
+
+ 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}%',
+ },
+ },
+ ],
+ })
+ }
+}
+
+// 鐩戝惉绐楀彛澶у皬鍙樺寲
+const handleResize = () => {
+ // 閲嶆柊璋冩暣鍥捐〃澶у皬
+ if (dailyComplaintChart.value) {
+ echarts.init(dailyComplaintChart.value).resize()
+ }
+ if (sourceComplaintChart.value) {
+ echarts.init(sourceComplaintChart.value).resize()
+ }
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ searchComplaint()
+ initCharts()
+ window.addEventListener('resize', handleResize)
+})
+
+// 缁勪欢鍗歌浇鏃舵竻鐞嗕簨浠剁洃鍚�
+onUnmounted(() => {
+ cleanup()
+})
+
+// 娓呯悊
+const cleanup = () => {
+ window.removeEventListener('resize', handleResize)
+}
+</script>
+
+<style scoped>
+.complaint-manage {
+ 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;
+}
+
+.chart-container {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.chart-item {
+ flex: 1;
+ 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;
+ color: #303133;
+}
+
+.chart-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.chart-summary {
+ font-size: 14px;
+ color: #606266;
+}
+
+.chart {
+ height: 300px;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.import-container {
+ padding: 20px;
+}
+
+.import-tip {
+ margin-bottom: 20px;
+ color: #606266;
+}
+
+.dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/src/views/inspection/MonitorControl.vue b/src/views/inspection/MonitorControl.vue
index e1a2f1a..f54e046 100644
--- a/src/views/inspection/MonitorControl.vue
+++ b/src/views/inspection/MonitorControl.vue
@@ -1,32 +1,30 @@
<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>
+ <!-- 鎬昏鐜板満宸℃煡鍗$墖 -->
+ <el-card class="mb-4">
+ <!-- <template #header>
+ <div class="card-header">
+ <span>鐜板満宸℃煡鎬昏</span>
+ <el-form :model="params" label-position="left" label-width="70px">
+ <FYOptionTime
+ :initValue="false"
+ type="daterange"
+ v-model:value="params.timeRange"
+ style="width: 300px"
+ :shortcuts="shortcuts"
+ ></FYOptionTime>
+ <FYOptionLocation
+ :allOption="false"
+ :level="3"
+ :checkStrictly="false"
+ :initValue="false"
+ v-model:value="params.locations"
+ style="width: 300px; margin-bottom: 0px"
+ ></FYOptionLocation>
+ </el-form>
+ </div>
+ </template> -->
+ <el-scrollbar>
<!-- 缁熻鏁版嵁鍖哄煙 -->
<div class="stats-sections">
<!-- 宸︿晶锛氬凡宸℃煡搴楅摵鐜囥�佸贰鏌ョ偣娆°�佸鏌ョ偣娆� -->
@@ -88,284 +86,8 @@
<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>
+ </el-scrollbar>
+ </el-card>
</template>
<script setup>
@@ -464,137 +186,6 @@
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: '娌圭儫鍑�鍖栬澶囨湭姝e父杩愯',
- // 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 = () => {
// 妯℃嫙鍒锋柊鏁版嵁
@@ -635,294 +226,8 @@
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 = ['娌圭儫瓒呮爣鎺掓斁', '鏈畨瑁呮补鐑熷噣鍖栬澶�', '璁惧鏈甯歌繍琛�', '鍣0姹℃煋']
- 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 = ['娌圭儫鎵版皯', '澶滈棿鍣0', '寮傚懗姹℃煋', '鍗敓闂']
- 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)
-
- // 棰勭暀瀵煎叆閫昏緫
- // 杩欓噷灏嗗疄鐜板疄闄呯殑鏂囦欢瑙f瀽鍜屾暟鎹鍏�
-
- 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)
-
- // 棰勭暀瀵煎叆閫昏緫
- // 杩欓噷灏嗗疄鐜板疄闄呯殑鏂囦欢瑙f瀽鍜屾暟鎹鍏�
-
- 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
- // 杩欓噷鍙互娣诲姞瀹為檯鐨勫垎椤甸�昏緫
}
// 鍒濆鍖栧浘琛�
@@ -988,6 +293,8 @@
series: [
{
type: 'pie',
+ radius: ['20%', '45%'],
+ center: ['50%', '45%'],
data: [
{ value: 30, name: '娌圭儫鍦ㄧ嚎鐩戞祴璁惧' },
{ value: 25, name: '娌圭儫鍑�鍖栬鏂借澶�' },
@@ -997,182 +304,6 @@
{ 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)
-
- // 璁$畻鏃堕棿鑼冨洿骞剁敓鎴恱杞存暟鎹�
- 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)
-
- // 璁$畻鏃堕棿鑼冨洿骞剁敓鎴恱杞存暟鎹�
- 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,
@@ -1230,18 +361,6 @@
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鍙樺寲
@@ -1256,8 +375,6 @@
// 鐢熷懡鍛ㄦ湡
onMounted(() => {
refreshInspectionData()
- searchPunishment()
- searchComplaint()
initCharts()
window.addEventListener('resize', handleResize)
})
@@ -1274,21 +391,23 @@
</script>
<style scoped>
-.monitor-control {
- padding: 20px;
- background-color: #f5f7fa;
- min-height: 100vh;
+.mb-4 {
+ /* width: 600px; */
}
.card-header {
display: flex;
- justify-content: space-between;
- align-items: center;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
}
.filter-group {
display: flex;
- align-items: center;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 100%;
+ gap: 10px;
}
.mr-2 {
@@ -1297,6 +416,7 @@
.stats-sections {
display: flex;
+ flex-direction: column;
gap: 20px;
margin-bottom: 30px;
}
@@ -1304,9 +424,8 @@
.stats-section {
flex: 1;
background-color: #fff;
- padding: 20px;
border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ /* border: 1px solid #e4e7ed; */
}
.stats-section h3 {
@@ -1318,8 +437,8 @@
.stats-grid {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 20px;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
margin-bottom: 20px;
}
@@ -1353,8 +472,8 @@
}
.chart-container {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ display: flex;
+ flex-direction: column;
gap: 20px;
margin-bottom: 30px;
}
@@ -1387,7 +506,7 @@
.chart {
width: 100%;
- height: 300px;
+ height: 250px;
}
.pagination-container {
diff --git a/src/views/inspection/PunishmentManage.vue b/src/views/inspection/PunishmentManage.vue
new file mode 100644
index 0000000..7cc3eed
--- /dev/null
+++ b/src/views/inspection/PunishmentManage.vue
@@ -0,0 +1,590 @@
+<template>
+ <div class="punishment-manage">
+ <!-- 椁愰ギ搴楅摵琛屾斂澶勭綒鍗$墖 -->
+ <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-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="punishmentImportDialogVisible" title="澶勭綒鎵归噺瀵煎叆" width="600px">
+ <div class="import-container">
+ <p class="import-tip">璇烽�夋嫨瑕佸鍏ョ殑Excel鏂囦欢</p>
+ <el-upload
+ class="upload-demo"
+ action="#"
+ drag
+ :auto-upload="false"
+ :on-change="handlePunishmentFileChange"
+ :file-list="punishmentImportFileList"
+ accept=".xlsx,.xls"
+ :limit="1"
+ :on-exceed="handleExceed"
+ >
+ <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+ <div class="el-upload__text">鎷栧姩鏂囦欢鎴�<em>鐐瑰嚮涓婁紶</em></div>
+ <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>
+ </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
+import * as echarts from 'echarts'
+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 dailyPunishmentChart = ref(null)
+const shopTypePunishmentChart = ref(null)
+
+// 琛屾斂澶勭綒鏁版嵁
+const punishmentDateRange = ref([
+ dayStart.startOf('month').toDate(),
+ dayEnd.endOf('month').toDate(),
+])
+const punishmentKeyword = ref('')
+const punishmentStats = ref({
+ totalCount: 120,
+})
+const punishmentTableData = ref([])
+const punishmentPagination = ref({
+ currentPage: 1,
+ pageSize: 10,
+ total: 0,
+})
+
+// 杩囨护鍚庣殑澶勭綒鏁版嵁
+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 punishmentDialogVisible = ref(false)
+const punishmentImportDialogVisible = ref(false)
+
+// 瀵煎叆鏂囦欢鍒楄〃
+const punishmentImportFileList = ref([])
+
+// 琛ㄥ崟鏁版嵁
+const punishmentForm = ref({
+ id: '',
+ shopName: '',
+ punishmentItem: '',
+ punishmentTime: '',
+ punishmentReason: '',
+ punishmentResult: '',
+ punishmentDepartment: '',
+})
+
+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 = ['娌圭儫瓒呮爣鎺掓斁', '鏈畨瑁呮补鐑熷噣鍖栬澶�', '璁惧鏈甯歌繍琛�', '鍣0姹℃煋']
+ 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()
+}
+
+// 鐩戝惉澶勭綒鏃ユ湡鑼冨洿鍙樺寲
+watch(
+ () => punishmentDateRange.value,
+ () => {
+ searchPunishment()
+ },
+ { 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)
+
+ // 棰勭暀瀵煎叆閫昏緫
+ // 杩欓噷灏嗗疄鐜板疄闄呯殑鏂囦欢瑙f瀽鍜屾暟鎹鍏�
+
+ punishmentImportDialogVisible.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 initCharts = () => {
+ // 姣忔棩澶勭綒鏁伴噺鍥�
+ if (dailyPunishmentChart.value && punishmentDateRange.value.length > 0) {
+ const chart = echarts.init(dailyPunishmentChart.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')
+
+ 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}%',
+ },
+ },
+ ],
+ })
+ }
+}
+
+// 鐩戝惉绐楀彛澶у皬鍙樺寲
+const handleResize = () => {
+ // 閲嶆柊璋冩暣鍥捐〃澶у皬
+ if (dailyPunishmentChart.value) {
+ echarts.init(dailyPunishmentChart.value).resize()
+ }
+ if (shopTypePunishmentChart.value) {
+ echarts.init(shopTypePunishmentChart.value).resize()
+ }
+}
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(() => {
+ searchPunishment()
+ initCharts()
+ window.addEventListener('resize', handleResize)
+})
+
+// 缁勪欢鍗歌浇鏃舵竻鐞嗕簨浠剁洃鍚�
+onUnmounted(() => {
+ cleanup()
+})
+
+// 娓呯悊
+const cleanup = () => {
+ window.removeEventListener('resize', handleResize)
+}
+</script>
+
+<style scoped>
+.punishment-manage {
+ 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;
+}
+
+.chart-container {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.chart-item {
+ flex: 1;
+ 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;
+ color: #303133;
+}
+
+.chart-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.chart-summary {
+ font-size: 14px;
+ color: #606266;
+}
+
+.chart {
+ height: 300px;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.import-container {
+ padding: 20px;
+}
+
+.import-tip {
+ margin-bottom: 20px;
+ color: #606266;
+}
+
+.dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/src/views/inspection/task/components/CompMonitorPlan.vue b/src/views/inspection/task/components/CompMonitorPlan.vue
index c2f5ea2..515d16c 100644
--- a/src/views/inspection/task/components/CompMonitorPlan.vue
+++ b/src/views/inspection/task/components/CompMonitorPlan.vue
@@ -1,19 +1,13 @@
<template>
- <el-calendar
- v-model="dateValue"
- :range="dateRange"
- @update:model-value="onDateChange"
- >
+ <el-calendar v-model="dateValue" :range="dateRange" @update:model-value="onDateChange">
<template #header="{ date }">
<div style="width: 100%">
<el-row justify="space-between">
<el-space>
<el-tag type="default"
- >宸℃煡閲忥細{{
- `${taskStatistic.complete}/${taskStatistic.total}`
- }}</el-tag
+ >宸℃煡閲忥細{{ `${taskStatistic.complete}/${taskStatistic.total}` }}</el-tag
>
- <el-tag type="default"
+ <!-- <el-tag type="default"
>褰撴棩鏁存敼鐜囷細{{
formatPercent(
taskStatistic.changedProblemNumOnTime /
@@ -28,18 +22,15 @@
taskStatistic.totalProblemNum
)
}}</el-tag
- >
+ > -->
<el-tag type="default"
- >缁煎悎鏁存敼鐜囷細{{
- formatPercent(
- taskStatistic.changedProblemNum /
- taskStatistic.totalProblemNum
- )
+ >鏁存敼鐜囷細{{
+ formatPercent(taskStatistic.changedProblemNum / taskStatistic.totalProblemNum)
}}</el-tag
>
<!-- <el-tag type="default">鏁存敼锛歿{ taskStatistic.changed }}</el-tag> -->
</el-space>
- <el-space>
+ <!-- <el-space>
<el-text>鑱氱劍鐢ㄦ埛锛�</el-text>
<el-select
v-model="selectedUsers"
@@ -56,24 +47,15 @@
:value="user.userName"
/>
</el-select>
- </el-space>
+ </el-space> -->
</el-row>
- <el-row class="m-t-4">
+ <!-- <el-row class="m-t-4">
<el-space wrap>
<el-tag
type="default"
v-for="user in taskStatistic.progressPerUser"
:key="user.userId"
>
- <!-- {{
- `${user.userName}锛氬贰鏌ラ噺 ${
- user.completeTaskNum
- }锛屽嵆鏃舵暣鏀圭巼 ${formatPercent(
- user.changedProblemNumOnTime / user.totalProblemNum
- )}锛屽钩鍧囪�楁椂 ${
- user.avgInspectionTime ? user.avgInspectionTime : '--'
- }`
- }} -->
{{
`${user.userName}锛�${
user.completeTaskNum
@@ -85,17 +67,14 @@
}}
</el-tag>
</el-space>
- </el-row>
+ </el-row> -->
</div>
</template>
<template #date-cell="{ data }">
<div :class="calendarDayClz(data.day)">
<div style="background-color: #f8f4f4">{{ getDay(data.day) }}</div>
<template v-if="computeDayTask(data.day)">
- <el-row
- v-if="computeDayTask(data.day).totalTaskNum > 0"
- justify="space-between"
- >
+ <el-row v-if="computeDayTask(data.day).totalTaskNum > 0" justify="space-between">
<el-space direction="vertical">
<el-text size="small" tag="b">宸℃煡閲�</el-text>
<el-text size="small"
@@ -108,7 +87,7 @@
<el-text size="small">{{
formatPercent(
computeDayTask(data.day).changedProblemNum /
- computeDayTask(data.day).totalProblemNum
+ computeDayTask(data.day).totalProblemNum,
)
}}</el-text>
</el-space>
@@ -123,9 +102,7 @@
<el-text
title="宸℃煡浜哄憳"
size="small"
- :type="
- selectedUsers.includes(item.userName) ? 'primary' : 'info'
- "
+ :type="selectedUsers.includes(item.userName) ? 'primary' : 'info'"
:tag="selectedUsers.includes(item.userName) ? 'b' : 'span'"
>{{ item.userName }}</el-text
>
@@ -136,9 +113,7 @@
title="宸℃煡閲�"
size="small"
style="text-align: center; flex: 1"
- :type="
- selectedUsers.includes(item.userName) ? 'primary' : 'info'
- "
+ :type="selectedUsers.includes(item.userName) ? 'primary' : 'info'"
:tag="selectedUsers.includes(item.userName) ? 'b' : 'span'"
>{{ item.completeTaskNum }}</el-text
>
@@ -146,27 +121,17 @@
title="褰撴棩鏁存敼鐜�"
size="small"
style="text-align: center; flex: 1"
- :type="
- selectedUsers.includes(item.userName) ? 'primary' : 'info'
- "
+ :type="selectedUsers.includes(item.userName) ? 'primary' : 'info'"
:tag="selectedUsers.includes(item.userName) ? 'b' : 'span'"
- >{{
- formatPercent(
- item.changedProblemNumOnTime / item.totalProblemNum
- )
- }}</el-text
+ >{{ formatPercent(item.changedProblemNumOnTime / item.totalProblemNum) }}</el-text
>
<el-text
title="骞冲潎鑰楁椂"
size="small"
style="text-align: center; flex: 1"
- :type="
- selectedUsers.includes(item.userName) ? 'primary' : 'info'
- "
+ :type="selectedUsers.includes(item.userName) ? 'primary' : 'info'"
:tag="selectedUsers.includes(item.userName) ? 'b' : 'span'"
- >{{
- timeUtil.formatSecondsToChinese(item.avgInspectionTime)
- }}</el-text
+ >{{ timeUtil.formatSecondsToChinese(item.avgInspectionTime) }}</el-text
>
<!-- </el-space> -->
</el-row>
@@ -177,52 +142,43 @@
</el-calendar>
</template>
<script setup>
-import { ref, computed, onMounted, watch } from 'vue';
-import taskApi from '@/api/fysp/taskApi';
-import dayjs from 'dayjs';
-import timeUtil from '@/utils/time-util';
+import { ref, computed, onMounted, watch } from 'vue'
+import taskApi from '@/api/fysp/taskApi'
+import dayjs from 'dayjs'
+import timeUtil from '@/utils/time-util'
const props = defineProps({
task: {
type: Object,
- default: () => {}
+ default: () => {},
},
dayTaskList: {
type: Array,
- default: () => []
- }
-});
-const emit = defineEmits(['dateChange']);
+ default: () => [],
+ },
+})
+const emit = defineEmits(['dateChange'])
// 閫変腑鏃ユ湡
-const selectedUsers = ref([]);
+const selectedUsers = ref([])
// const dateValue = ref(new Date());
-const dateValue = ref();
+const dateValue = ref()
// 鏃ュ巻鑼冨洿
-const startDay = computed(() => dayjs(props.task.starttime));
-const endDay = computed(() => dayjs(props.task.endtime));
-const dateRange = computed(() => [
- startDay.value.toDate(),
- endDay.value.toDate()
-]);
+const startDay = computed(() => dayjs(props.task.starttime))
+const endDay = computed(() => dayjs(props.task.endtime))
+const dateRange = computed(() => [startDay.value.toDate(), endDay.value.toDate()])
// 鏃ユ湡鏄惁鍦ㄤ换鍔¤寖鍥村唴
function isDayEnable(day) {
- const _day = dayjs(day);
- return (
- _day.isSameOrAfter(startDay.value, 'day') &&
- _day.isSameOrBefore(endDay.value, 'day')
- );
+ const _day = dayjs(day)
+ return _day.isSameOrAfter(startDay.value, 'day') && _day.isSameOrBefore(endDay.value, 'day')
}
/********************** 鏃ユ湡鏍峰紡 *********************************/
function calendarDayClz(day) {
- return (
- 'calendar-day ' +
- (isDayEnable(day) ? 'calendar-day-enable' : 'calendar-day-disable')
- );
+ return 'calendar-day ' + (isDayEnable(day) ? 'calendar-day-enable' : 'calendar-day-disable')
}
function getDay(day) {
- return day.split('-').splice(1, 2).join('-');
+ return day.split('-').splice(1, 2).join('-')
}
/********************** 浠诲姟鏁版嵁 *********************************/
@@ -231,11 +187,11 @@
() => props.dayTaskList,
(nV, oV) => {
if (nV && dateValue.value) {
- onDateChange(dateValue.value);
+ onDateChange(dateValue.value)
}
},
- { immediate: false }
-);
+ { immediate: false },
+)
// // 鑾峰彇鏃ヤ换鍔$粺璁′俊鎭�
// const dayTaskLoading = ref(false);
@@ -255,26 +211,26 @@
// }
// 鏃ヤ换鍔℃暟鎹睍绀�
-const compMap = new Map();
+const compMap = new Map()
function computeDayTask(day) {
- const key = props.task.tguid + day;
+ const key = props.task.tguid + day
if (compMap.has(key)) {
- return compMap.get(key).value;
+ return compMap.get(key).value
}
const result = computed(() => {
return props.dayTaskList.find((v) => {
- return dayjs(v.date).isSame(dayjs(day));
- });
- });
- compMap.set(key, result);
- return result.value;
+ return dayjs(v.date).isSame(dayjs(day))
+ })
+ })
+ compMap.set(key, result)
+ return result.value
}
function onDateChange(e) {
if (isDayEnable(e)) {
- const day = dayjs(e).format('YYYY-MM-DD');
- const t = computeDayTask(day);
- emit('dateChange', t, day);
+ const day = dayjs(e).format('YYYY-MM-DD')
+ const t = computeDayTask(day)
+ emit('dateChange', t, day)
}
}
@@ -288,15 +244,15 @@
changedProblemNum: 0,
totalProblemNum: 0,
changedProblemNumOnTime: 0,
- efficientChangedProNum: 0
- };
- const userMap = new Map();
+ efficientChangedProNum: 0,
+ }
+ const userMap = new Map()
props.dayTaskList.forEach((e) => {
- res.total += e.totalTaskNum;
- res.complete += e.completeTaskNum;
- res.changed += e.changedTaskNum;
- res.changedProblemNum += e.changedProblemNum;
- res.totalProblemNum += e.totalProblemNum;
+ res.total += e.totalTaskNum
+ res.complete += e.completeTaskNum
+ res.changed += e.changedTaskNum
+ res.changedProblemNum += e.changedProblemNum
+ res.totalProblemNum += e.totalProblemNum
e.progressPerUser.forEach((user) => {
if (!userMap.has(user.userId)) {
userMap.set(user.userId, {
@@ -305,32 +261,30 @@
changedProblemNumOnTime: 0,
totalProblemNum: 0,
totalInspectionTime: 0,
- dayTaskNum: 0
- });
+ dayTaskNum: 0,
+ })
}
res.changedProblemNumOnTime += user.changedProblemNumOnTime
res.efficientChangedProNum += user.efficientChangedProNum
- const userItem = userMap.get(user.userId);
- userItem.completeTaskNum += user.completeTaskNum;
- userItem.changedProblemNumOnTime += user.changedProblemNumOnTime;
- userItem.totalProblemNum += user.totalProblemNum;
- userItem.totalInspectionTime += user.avgInspectionTime ?? 0;
- userItem.dayTaskNum++;
- });
- });
+ const userItem = userMap.get(user.userId)
+ userItem.completeTaskNum += user.completeTaskNum
+ userItem.changedProblemNumOnTime += user.changedProblemNumOnTime
+ userItem.totalProblemNum += user.totalProblemNum
+ userItem.totalInspectionTime += user.avgInspectionTime ?? 0
+ userItem.dayTaskNum++
+ })
+ })
res.progressPerUser = Array.from(userMap.values()).map((user) => ({
...user,
completeTaskNum: Math.round(user.completeTaskNum * 100) / 100,
- avgInspectionTime: timeUtil.formatSecondsToChinese(
- user.totalInspectionTime / user.dayTaskNum
- )
- }));
- return res;
-});
+ avgInspectionTime: timeUtil.formatSecondsToChinese(user.totalInspectionTime / user.dayTaskNum),
+ }))
+ return res
+})
const formatPercent = (num) => {
- return isNaN(num) ? '0%' : parseInt(num * 100) + '%';
-};
+ return isNaN(num) ? '0%' : parseInt(num * 100) + '%'
+}
/********************** 鍒濆鍖� *********************************/
// watch(
diff --git a/src/views/monitor/DataDashboard.vue b/src/views/monitor/DataDashboard.vue
index 45fa087..d039dc4 100644
--- a/src/views/monitor/DataDashboard.vue
+++ b/src/views/monitor/DataDashboard.vue
@@ -4,7 +4,7 @@
<div class="top-cards">
<!-- 鏃堕棿鍛ㄦ湡閫夐」鍗$墖 -->
<div class="time-period-card">
- <div class="card-title">鏃堕棿閫夋嫨</div>
+ <!-- <div class="card-title">鏃堕棿閫夋嫨</div> -->
<div class="time-controls">
<div class="time-tab-container">
<div
@@ -57,10 +57,10 @@
</div>
</div>
<div class="cards-container">
- <!-- 瓒呮爣鏁� -->
+ <!-- 娴撳害棰勮 -->
<div class="metric-card">
<div class="card-header">
- <div class="card-title">{{ getPeriodLabel() }}瓒呮爣鏁�</div>
+ <div class="card-title">{{ getPeriodLabel() }}娴撳害棰勮</div>
<div class="card-icon warning-icon">
<svg
width="20"
@@ -86,7 +86,7 @@
</svg>
</div>
</div>
- <div class="card-value">{{ metrics.overStandardCount }}</div>
+ <div class="card-value">{{ metrics.overStandardCount }}<el-text>娆�</el-text></div>
<div class="card-trend">
<span
class="trend-arrow"
@@ -99,52 +99,97 @@
</div>
</div>
- <!-- 鍦ㄧ嚎鐜� -->
- <div class="metric-card">
- <div class="card-header">
- <div class="card-title">鍦ㄧ嚎鐜�</div>
- <div class="card-icon online-icon">
- <svg
- width="20"
- height="20"
- viewBox="0 0 24 24"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M9 12L11 14L15 10"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- <path
- d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
+ <!-- 璁惧鍦ㄧ嚎鐜� -->
+ <el-popover placement="right-start" title="璁惧鐩戞帶" width="400" trigger="click">
+ <div class="popover-content">
+ <div class="overview-items-container">
+ <div class="overview-item">
+ <div class="overview-label">椁愰ギ搴楅摵鎬绘暟</div>
+ <div class="overview-value">{{ overview.totalShops }}</div>
+ </div>
+
+ <div class="overview-item">
+ <div class="overview-label">鍦ㄧ嚎璁惧鏁�</div>
+ <div class="overview-value">{{ overview.onlineDevices }}</div>
+ </div>
+
+ <div class="overview-item">
+ <div class="overview-label">绂荤嚎璁惧鏁�</div>
+ <div class="overview-value">{{ overview.offlineDevices }}</div>
+ </div>
+ </div>
+
+ <!-- 璁惧鐘舵�侀ゼ鍥� -->
+ <div class="device-status-chart">
+ <canvas id="deviceStatusChart"></canvas>
</div>
</div>
- <div class="card-value">{{ metrics.onlineRate }}%</div>
- <div class="card-trend">
- <span
- class="trend-arrow"
- :class="{ up: metrics.onlineRateTrend > 0, down: metrics.onlineRateTrend < 0 }"
- >
- {{ metrics.onlineRateTrend > 0 ? '鈫�' : '鈫�' }}
- </span>
- <span class="trend-text">{{ Math.abs(metrics.onlineRateTrend) }}%</span>
- <span class="trend-label">{{ getCompareLabel() }}</span>
- </div>
- </div>
+ <template #reference>
+ <div class="metric-card">
+ <div class="card-header">
+ <div class="card-title">璁惧鍦ㄧ嚎鐜�</div>
+ <div class="card-icon online-icon">
+ <svg
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M9 12L11 14L15 10"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ <path
+ d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg>
+ </div>
+ </div>
+ <div class="card-value">{{ metrics.onlineRate }}%</div>
+ <div class="card-trend">
+ <span
+ class="trend-arrow"
+ :class="{ up: metrics.onlineRateTrend > 0, down: metrics.onlineRateTrend < 0 }"
+ >
+ {{ metrics.onlineRateTrend > 0 ? '鈫�' : '鈫�' }}
+ </span>
+ <span class="trend-text">{{ Math.abs(metrics.onlineRateTrend) }}%</span>
+ <span class="trend-label">{{ getCompareLabel() }}</span>
+ </div>
+ <div class="view-details">
+ <span>璇︽儏</span>
+ <svg
+ width="12"
+ height="12"
+ viewBox="0 0 24 24"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M9 18L15 12L9 6"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg>
+ </div>
+ </div>
+ </template>
+ </el-popover>
- <!-- 鍑�鍖栧櫒杩愯鏁堢巼 -->
+ <!-- 鐜俊鐮佺豢鐮佺巼 -->
<div class="metric-card">
<div class="card-header">
- <div class="card-title">鍑�鍖栧櫒杩愯鏁堢巼</div>
+ <div class="card-title">鐜俊鐮佺豢鐮佺巼</div>
<div class="card-icon efficiency-icon">
<svg
width="20"
@@ -187,49 +232,153 @@
</div>
<!-- 宸℃煡鐐规 -->
- <div class="metric-card">
- <div class="card-header">
- <div class="card-title">宸℃煡鐐规</div>
- <div class="card-icon task-icon">
- <svg
- width="20"
- height="20"
- viewBox="0 0 24 24"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M22 11.08V12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C15.7376 2 19.0503 4.16113 20.7748 7.33007"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- <path
- d="M22 4L12 14.01L9 11.01"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
+ <el-popover placement="right-start" title="鐜板満宸℃煡缁熻" width="350" trigger="click">
+ <div class="inspection-popover-content">
+ <!-- 宸℃煡閲忕粺璁� -->
+ <div class="inspection-metrics">
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">搴楅摵鎬昏</div>
+ <div class="inspection-metric-value">{{ inspectionStats.totalShops }}</div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">宸℃煡搴楅摵</div>
+ <div class="inspection-metric-value">{{ inspectionStats.inspectedShops }}</div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">宸℃煡鐐规</div>
+ <div class="inspection-metric-value">{{ inspectionStats.inspectionPoints }}</div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">澶嶆煡鐐规</div>
+ <div class="inspection-metric-value">{{ inspectionStats.reviewPoints }}</div>
+ </div>
+ </div>
+
+ <!-- 闂鏁存敼鎯呭喌 -->
+ <div class="inspection-chart-container">
+ <div class="section-header"><h3>闂鏁存敼</h3></div>
+ <canvas id="rectificationChart"></canvas>
+ </div>
+
+ <!-- 闂瀹℃牳鎯呭喌 -->
+ <div class="inspection-table-container" style="display: none">
+ <div class="section-header"><h3>瀹℃牳姹囨��</h3></div>
+ <div class="inspection-metric-label">闂瀹℃牳</div>
+ <div class="inspection-table">
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">鏃犻棶棰�</div>
+ <div class="inspection-metric-value">{{ inspectionStats.noProblemShops }}</div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">鏈鏍�</div>
+ <div class="inspection-metric-value">
+ {{ inspectionStats.unreviewedProblemShops }}
+ </div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">閮ㄥ垎瀹℃牳</div>
+ <div class="inspection-metric-value">
+ {{ inspectionStats.partiallyReviewedProblemShops }}
+ </div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">鍏ㄩ儴瀹℃牳</div>
+ <div class="inspection-metric-value">
+ {{ inspectionStats.fullyReviewedProblemShops }}
+ </div>
+ </div>
+ </div>
+ <div class="inspection-metric-label">鏁存敼瀹℃牳</div>
+ <div class="inspection-table">
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">鏈暣鏀�</div>
+ <div class="inspection-metric-value">{{ inspectionStats.unrectifiedShops }}</div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">鏈鏍�</div>
+ <div class="inspection-metric-value">
+ {{ inspectionStats.unreviewedRectifiedShops }}
+ </div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">閮ㄥ垎瀹℃牳</div>
+ <div class="inspection-metric-value">
+ {{ inspectionStats.partiallyReviewedRectifiedShops }}
+ </div>
+ </div>
+ <div class="inspection-metric-item">
+ <div class="inspection-metric-label">鍏ㄩ儴瀹℃牳</div>
+ <div class="inspection-metric-value">
+ {{ inspectionStats.fullyReviewedRectifiedShops }}
+ </div>
+ </div>
+ </div>
</div>
</div>
- <div class="card-value">{{ metrics.inspectionPoints }}</div>
- <div class="card-trend">
- <span
- class="trend-arrow"
- :class="{
- up: metrics.inspectionPointsTrend > 0,
- down: metrics.inspectionPointsTrend < 0,
- }"
- >
- {{ metrics.inspectionPointsTrend > 0 ? '鈫�' : '鈫�' }}
- </span>
- <span class="trend-text">{{ Math.abs(metrics.inspectionPointsTrend) }}</span>
- <span class="trend-label">{{ getCompareLabel() }}</span>
- </div>
- </div>
+ <template #reference>
+ <div class="metric-card">
+ <div class="card-header">
+ <div class="card-title">宸℃煡鐐规</div>
+ <div class="card-icon task-icon">
+ <svg
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M22 11.08V12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C15.7376 2 19.0503 4.16113 20.7748 7.33007"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ <path
+ d="M22 4L12 14.01L9 11.01"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg>
+ </div>
+ </div>
+ <div class="card-value">{{ metrics.inspectionPoints }}</div>
+ <div class="card-trend">
+ <span
+ class="trend-arrow"
+ :class="{
+ up: metrics.inspectionPointsTrend > 0,
+ down: metrics.inspectionPointsTrend < 0,
+ }"
+ >
+ {{ metrics.inspectionPointsTrend > 0 ? '鈫�' : '鈫�' }}
+ </span>
+ <span class="trend-text">{{ Math.abs(metrics.inspectionPointsTrend) }}</span>
+ <span class="trend-label">{{ getCompareLabel() }}</span>
+ </div>
+ <div class="view-details">
+ <span>璇︽儏</span>
+ <svg
+ width="12"
+ height="12"
+ viewBox="0 0 24 24"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M9 18L15 12L9 6"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg>
+ </div>
+ </div>
+ </template>
+ </el-popover>
</div>
</div>
@@ -240,73 +389,26 @@
<div id="map" class="map-container">
<BaseMap :showSatellite="true"></BaseMap>
</div>
-
- <!-- 鍦板浘鐐逛綅寮圭獥 -->
- <el-dialog v-model="dialogVisible" title="浼佷笟瀹炴椂鏁版嵁" width="400px">
- <div class="dialog-content">
- <el-descriptions :column="1" border>
- <el-descriptions-item label="浼佷笟鍚嶇О">{{
- selectedPoint.enterpriseName
- }}</el-descriptions-item>
- <el-descriptions-item label="璁惧缂栧彿">{{
- selectedPoint.deviceId
- }}</el-descriptions-item>
- <el-descriptions-item label="娌圭儫娴撳害"
- >{{ selectedPoint.oilSmokeConcentration }} mg/m鲁</el-descriptions-item
- >
- <el-descriptions-item label="棰楃矑鐗�"
- >{{ selectedPoint.particulateMatter }} mg/m鲁</el-descriptions-item
- >
- <el-descriptions-item label="闈炵敳鐑锋�荤儍"
- >{{ selectedPoint.nonMethaneHydrocarbon }} mg/m鲁</el-descriptions-item
- >
- <el-descriptions-item label="鐩戞祴鏃堕棿">{{
- selectedPoint.monitoringTime
- }}</el-descriptions-item>
- <el-descriptions-item label="瓒呮爣鎯呭喌">
- <el-tag :type="selectedPoint.isOverStandard ? 'danger' : 'success'">
- {{ selectedPoint.isOverStandard ? '瓒呮爣' : '姝e父' }}
- </el-tag>
- </el-descriptions-item>
- </el-descriptions>
- </div>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">鍏抽棴</el-button>
- <el-button type="primary" @click="viewDetails">鏌ョ湅璇︽儏</el-button>
- </span>
- </template>
- </el-dialog>
</div>
</div>
- <!-- 鍙充晶瀹炴椂鐩戞祴鎬昏鍖� -->
- <div class="overview-section">
- <div class="section-header">
- <h3>璁惧鐩戞帶</h3>
- <!-- <span class="view-more">鏌ョ湅鏇村</span> -->
- </div>
-
- <div class="overview-items-container">
- <div class="overview-item">
- <div class="overview-label">椁愰ギ搴楅摵鎬绘暟</div>
- <div class="overview-value">{{ overview.totalShops }}</div>
+ <div class="monitor-control-container">
+ <el-button size="large" @click="toggleMonitorControl" class="push-btn">
+ <div style="display: flex; flex-direction: column">
+ <el-icon>
+ <ArrowRight v-if="isMonitorControlExpanded" />
+ <ArrowLeft v-else />
+ </el-icon>
+ <div>鐜�</div>
+ <div>鍦�</div>
+ <div>宸�</div>
+ <div>鏌�</div>
</div>
-
- <div class="overview-item">
- <div class="overview-label">鍦ㄧ嚎璁惧鏁�</div>
- <div class="overview-value">{{ overview.onlineDevices }}</div>
- </div>
-
- <div class="overview-item">
- <div class="overview-label">绂荤嚎璁惧鏁�</div>
- <div class="overview-value">{{ overview.offlineDevices }}</div>
- </div>
- </div>
-
- <!-- 璁惧鐘舵�侀ゼ鍥� -->
- <div class="device-status-chart">
- <canvas id="deviceStatusChart"></canvas>
- </div>
+ </el-button>
+ <MonitorControl
+ v-if="isMonitorControlExpanded"
+ :class="{ 'monitor-control': true, collapsed: !isMonitorControlExpanded }"
+ style="height: calc(90vh - 40px)"
+ />
</div>
<!-- 鍦板浘鍥句緥 -->
@@ -345,129 +447,6 @@
</div>
</div>
</div>
- <!-- 宸℃煡鎯呭喌缁熻鍗$墖 -->
- <el-scrollbar class="inspection-section">
- <div class="section-header">
- <h3>宸℃煡姹囨��</h3>
- </div>
-
- <!-- 宸℃煡閲忕粺璁� -->
- <div class="inspection-metrics">
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">搴楅摵鎬昏</div>
- <div class="inspection-metric-value">{{ inspectionStats.totalShops }}</div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">宸℃煡搴楅摵</div>
- <div class="inspection-metric-value">{{ inspectionStats.inspectedShops }}</div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">宸℃煡鐐规</div>
- <div class="inspection-metric-value">{{ inspectionStats.inspectionPoints }}</div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">澶嶆煡鐐规</div>
- <div class="inspection-metric-value">{{ inspectionStats.reviewPoints }}</div>
- </div>
- </div>
-
- <!-- 闂鏁存敼鎯呭喌 -->
- <div class="inspection-chart-container">
- <div class="section-header"><h3>鏁存敼姹囨��</h3></div>
- <canvas id="rectificationChart"></canvas>
- </div>
-
- <!-- 闂瀹℃牳鎯呭喌 -->
- <div class="inspection-table-container">
- <div class="section-header"><h3>瀹℃牳姹囨��</h3></div>
- <div class="inspection-metric-label">闂瀹℃牳</div>
- <div class="inspection-table">
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">鏃犻棶棰�</div>
- <div class="inspection-metric-value">{{ inspectionStats.noProblemShops }}</div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">鏈鏍�</div>
- <div class="inspection-metric-value">
- {{ inspectionStats.unreviewedProblemShops }}
- </div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">閮ㄥ垎瀹℃牳</div>
- <div class="inspection-metric-value">
- {{ inspectionStats.partiallyReviewedProblemShops }}
- </div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">鍏ㄩ儴瀹℃牳</div>
- <div class="inspection-metric-value">
- {{ inspectionStats.fullyReviewedProblemShops }}
- </div>
- </div>
- </div>
- <div class="inspection-metric-label">鏁存敼瀹℃牳</div>
- <div class="inspection-table">
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">鏈暣鏀�</div>
- <div class="inspection-metric-value">{{ inspectionStats.unrectifiedShops }}</div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">鏈鏍�</div>
- <div class="inspection-metric-value">
- {{ inspectionStats.unreviewedRectifiedShops }}
- </div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">閮ㄥ垎瀹℃牳</div>
- <div class="inspection-metric-value">
- {{ inspectionStats.partiallyReviewedRectifiedShops }}
- </div>
- </div>
- <div class="inspection-metric-item">
- <div class="inspection-metric-label">鍏ㄩ儴瀹℃牳</div>
- <div class="inspection-metric-value">
- {{ inspectionStats.fullyReviewedRectifiedShops }}
- </div>
- </div>
- <!-- <div class="table-row">
- <div class="table-cell">鏃犻棶棰樺簵閾烘暟閲�</div>
- <div class="table-cell value">{{ inspectionStats.noProblemShops }}</div>
- </div>
- <div class="table-row">
- <div class="table-cell">闂鏈鏍稿簵閾烘暟閲�</div>
- <div class="table-cell value">{{ inspectionStats.unreviewedProblemShops }}</div>
- </div>
- <div class="table-row">
- <div class="table-cell">闂閮ㄥ垎瀹℃牳搴楅摵鏁伴噺</div>
- <div class="table-cell value">
- {{ inspectionStats.partiallyReviewedProblemShops }}
- </div>
- </div>
- <div class="table-row">
- <div class="table-cell">闂鍏ㄩ儴瀹℃牳搴楅摵鏁伴噺</div>
- <div class="table-cell value">{{ inspectionStats.fullyReviewedProblemShops }}</div>
- </div>
- <div class="table-row">
- <div class="table-cell">鏈暣鏀瑰簵閾烘暟</div>
- <div class="table-cell value">{{ inspectionStats.unrectifiedShops }}</div>
- </div>
- <div class="table-row">
- <div class="table-cell">鏁存敼鏈鏍稿簵閾烘暟</div>
- <div class="table-cell value">{{ inspectionStats.unreviewedRectifiedShops }}</div>
- </div>
- <div class="table-row">
- <div class="table-cell">鏁存敼閮ㄥ垎瀹℃牳搴楅摵鏁�</div>
- <div class="table-cell value">
- {{ inspectionStats.partiallyReviewedRectifiedShops }}
- </div>
- </div>
- <div class="table-row">
- <div class="table-cell">鏁存敼鍏ㄩ儴瀹℃牳搴楅摵鏁�</div>
- <div class="table-cell value">{{ inspectionStats.fullyReviewedRectifiedShops }}</div>
- </div> -->
- </div>
- </div>
- </el-scrollbar>
</div>
</template>
@@ -477,19 +456,22 @@
import districtSearch from '@/utils/map/districtsearch.js'
import marks from '@/utils/map/marks.js'
import { generateTestShops } from '@/debug/debugdata'
+import MonitorControl from '@/views/inspection/MonitorControl.vue'
export default {
name: 'DataDashboard',
+ components: {
+ MonitorControl,
+ },
data() {
return {
activeTime: 'day',
- currentDate: new Date(),
+ currentDate: new Date('2023-08-01'),
timeTabs: [
{ label: '鏃�', value: 'day' },
{ label: '鍛�', value: 'week' },
{ label: '鏈�', value: 'month' },
],
- dialogVisible: false,
selectedPoint: {
enterpriseName: '',
deviceId: '',
@@ -504,8 +486,8 @@
overStandardTrend: 5,
onlineRate: 92,
onlineRateTrend: 2,
- purifierEfficiency: 85,
- purifierEfficiencyTrend: -3,
+ purifierEfficiency: 95,
+ purifierEfficiencyTrend: 2,
inspectionPoints: 350,
inspectionPointsTrend: 50,
},
@@ -536,19 +518,20 @@
},
map: null,
refreshTimer: null,
+ isMonitorControlExpanded: true,
}
},
computed: {
currentTimeDisplay() {
const date = this.currentDate
+ let weekStart = new Date(date)
+ let weekEnd = new Date(date)
switch (this.activeTime) {
case 'day':
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
case 'week':
// 绠�鍗曡绠楀懆鏄剧ず锛屽疄闄呴」鐩腑鍙兘闇�瑕佹洿澶嶆潅鐨勫懆璁$畻閫昏緫
- let weekStart = new Date(date)
weekStart.setDate(date.getDate() - date.getDay() + 1)
- let weekEnd = new Date(date)
weekEnd.setDate(date.getDate() + (7 - date.getDay()))
return `${weekStart.getFullYear()}-${String(weekStart.getMonth() + 1).padStart(2, '0')}-${String(weekStart.getDate()).padStart(2, '0')} ~ ${weekEnd.getFullYear()}-${String(weekEnd.getMonth() + 1).padStart(2, '0')}-${String(weekEnd.getDate()).padStart(2, '0')}`
case 'month':
@@ -562,7 +545,7 @@
this.initMap()
this.initDeviceStatusChart()
this.initRectificationChart()
- this.startAutoRefresh()
+ // this.startAutoRefresh()
},
beforeUnmount() {
if (this.refreshTimer) {
@@ -570,6 +553,9 @@
}
},
methods: {
+ toggleMonitorControl() {
+ this.isMonitorControlExpanded = !this.isMonitorControlExpanded
+ },
handleTimeChange(tab) {
this.activeTime = tab.value
// 妯℃嫙鍒囨崲鏃堕棿鍛ㄦ湡鍚庣殑鏁版嵁鏇存柊
@@ -631,12 +617,18 @@
// 杩欓噷搴旇鏍规嵁閫夋嫨鐨勬椂闂村懆鏈熶粠鎺ュ彛鑾峰彇鏁版嵁
// 妯℃嫙鏁版嵁鏇存柊
setTimeout(() => {
+ const m = Math.floor(Math.random() * 50) + 150
+ this.overview = {
+ totalShops: 245,
+ onlineDevices: m,
+ offlineDevices: 245 - m,
+ }
this.metrics = {
overStandardCount: Math.floor(Math.random() * 30),
overStandardTrend: Math.floor(Math.random() * 20) - 10,
- onlineRate: Math.floor(Math.random() * 20) + 80,
+ onlineRate: ((this.overview.onlineDevices / this.overview.totalShops) * 100).toFixed(0),
onlineRateTrend: Math.floor(Math.random() * 10) - 5,
- purifierEfficiency: Math.floor(Math.random() * 30) + 70,
+ purifierEfficiency: Math.floor(Math.random() * 20) + 80,
purifierEfficiencyTrend: Math.floor(Math.random() * 10) - 5,
inspectionPoints: Math.floor(Math.random() * 100) + 300,
inspectionPointsTrend: Math.floor(Math.random() * 100) - 50,
@@ -662,6 +654,7 @@
}
// 鏇存柊鍥捐〃
+ this.initDeviceStatusChart()
this.initRectificationChart()
}, 300)
},
@@ -779,6 +772,7 @@
left: '3%',
right: '4%',
bottom: '3%',
+ top: '5%',
containLabel: true,
},
xAxis: {
@@ -878,6 +872,34 @@
/* padding: 16px; */
border-radius: 8px;
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
+}
+
+/* 鐩戞帶鎺у埗鍗$墖 */
+.monitor-control {
+ /* position: absolute; */
+ width: 500px;
+ transition: all 0.3s ease;
+ /* top: 0px; */
+ /* right: 0px; */
+ /* z-index: 10; */
+}
+
+.push-btn {
+ z-index: 1;
+ width: 2.5rem;
+ height: initial;
+ margin: initial;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ /* background-color: white; */
+ /* border-color: white; */
+ /* border-top: 1px solid;
+ border-left: 1px solid;
+ border-bottom: 1px solid; */
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+ /* box-shadow: var(--el-box-shadow-light); */
}
/* 鏃堕棿鍛ㄦ湡鍗$墖 */
@@ -993,6 +1015,7 @@
.metric-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ cursor: pointer;
}
.card-header {
@@ -1024,7 +1047,7 @@
}
.efficiency-icon {
- color: #722ed1;
+ color: #52c41a;
}
.task-icon {
@@ -1133,19 +1156,63 @@
}
/* 鍙充晶瀹炴椂鐩戞祴鎬昏鍖� */
-.overview-section {
- position: absolute;
- bottom: 4px;
- left: 4px;
- width: 320px;
- background-color: rgba(255, 255, 255, 0.9);
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- padding: 20px;
+.popover-content {
+ padding: 10px;
+}
+
+.overview-items-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 16px;
+ border-bottom: 1px solid #f0f0f0;
+ margin-bottom: 20px;
+}
+
+.overview-item {
display: flex;
flex-direction: column;
- z-index: 10;
- max-height: calc(100vh - 220px);
+ align-items: center;
+ flex: 1;
+ text-align: center;
+}
+
+.overview-label {
+ font-size: 12px;
+ color: #86909c;
+ font-weight: 500;
+ margin-bottom: 8px;
+}
+
+.overview-value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #262626;
+}
+
+.device-status-chart {
+ flex: 1;
+ min-height: 200px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 16px;
+}
+
+.view-details {
+ position: absolute;
+ bottom: 12px;
+ right: 16px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
+ color: #1890ff;
+ cursor: pointer;
+}
+
+.view-details:hover {
+ text-decoration: underline;
}
.overview-items-container {
@@ -1177,30 +1244,51 @@
color: #262626;
}
-.device-status-chart {
- flex: 1;
- min-height: 100px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 16px;
+/* 宸℃煡鎯呭喌缁熻 */
+.inspection-popover-content {
+ padding: 10px;
+ max-height: 400px;
+ overflow-y: auto;
}
-/* 宸℃煡鎯呭喌缁熻 */
-.inspection-section {
+.monitor-control-container {
position: absolute;
top: 4px;
right: 4px;
- width: 320px;
- background-color: rgba(255, 255, 255, 0.9);
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- padding: 20px;
- display: flex;
- flex-direction: column;
z-index: 10;
- max-height: calc(70vh);
- border-top: 1px solid #f0f0f0;
+ transition: all 0.3s ease;
+ /* background-color: rgba(255, 255, 255, 0.9); */
+ display: flex;
+ border-radius: 8px;
+ /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
+ overflow: hidden;
+}
+
+.monitor-control-container.collapsed {
+ width: 60px;
+}
+
+.monitor-control-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 15px;
+ border-bottom: 1px solid #e8e8e8;
+ height: 40px;
+ position: relative;
+}
+
+.monitor-control-header span {
+ font-weight: 600;
+ color: #333;
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ letter-spacing: 2px;
+ white-space: nowrap;
+}
+
+.collapse-btn {
+ /* transform: translateY(-50%); */
}
.inspection-metrics {
@@ -1304,12 +1392,12 @@
.map-legend {
position: absolute;
bottom: 4px;
- right: 4px;
- width: 200px;
+ left: 4px;
+ /* width: 200px; */
background-color: rgba(255, 255, 255, 0.9);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- padding: 16px;
+ padding: 4px;
z-index: 10;
}
diff --git a/src/views/monitor/DataException.vue b/src/views/monitor/DataException.vue
index 271d46b..ea215e1 100644
--- a/src/views/monitor/DataException.vue
+++ b/src/views/monitor/DataException.vue
@@ -2,264 +2,281 @@
<div class="data-exception-container">
<!-- 鎼滅储鍖哄煙 -->
<div ref="h1" class="search-container">
- <!-- <div class="search-header">
- <h3>鏌ヨ琛ㄦ牸</h3>
- </div> -->
- <el-row>
- <div class="search-form">
- <div class="form-row">
- <div class="form-item">
- <span class="form-label">搴楅摵璁惧锛�</span>
- <ShopNameAndID @submit-id="(n) => (deviceId[1] = n)"></ShopNameAndID>
- </div>
- <div class="form-item">
- <ExceptionType @submitExceptionType="(val) => (exceptionValue = val)">
- </ExceptionType>
- </div>
- </div>
- <div class="form-row">
- <div class="form-item full-width">
- <TimeSelect @submit-time="giveTime" :useNewStyle="false"></TimeSelect>
- </div>
- </div>
- </div>
- <div class="form-actions">
- <el-button type="primary" :loading="button.queryButton" @click="showTable"
- >鏌ヨ</el-button
- >
- <el-tooltip
- class="box-item"
- effect="dark"
- content="鐐瑰嚮鍙鍑篍xcel鏂囦欢"
- placement="top-start"
- >
- <el-icon class="iconExcel clickable" title="瀵煎嚭Excel鏂囦欢" @click="exportDom">
- <i-ep-Download />
- </el-icon>
- </el-tooltip>
- </div>
- </el-row>
- <div class="summary-info">
- <span>{{ beginTime }} 鈥斺�� {{ endTime }} 娌圭儫鐩戞祴寮傚父淇℃伅姹囨��</span>
- </div>
- </div>
-
- <!-- 寮傚父鍒嗘瀽 -->
- <div class="analysis-container">
- <el-collapse v-model="activeNames">
+ <el-collapse v-model="activeSearchNames" @change="handleSearchCollapseChange">
<el-collapse-item name="1">
<template #title>
- <div class="collapse-title">
- <h4 class="collapse-header">寮傚父鍒嗘瀽</h4>
- <el-icon class="header-icon">
- <i-ep-info-filled />
- </el-icon>
+ <div class="search-header">
+ <h3>鏌ヨ鏉′欢</h3>
+ <span v-if="!isSearchExpanded" class="search-summary">
+ {{ getSearchSummary() }}
+ </span>
</div>
</template>
- <el-card class="analysis-card">
- <el-row :gutter="24">
- <el-col :span="8">
- <div class="analysis-item">
- <div class="item-header">
- <img src="@/assets/exceed.jpg" class="item-icon" />
- <span class="item-title">娌圭儫娴撳害瓒呮爣</span>
- </div>
- <div class="item-content">
- <div class="item-stats">
- <span class="stats-label">寮傚父搴楅摵鍗犳瘮锛�</span>
- <span class="stats-value">{{ exception0.length }} /{{ shopsTotal }}</span>
- <span class="stats-percent"
- >({{ ((exception0.length / shopsTotal) * 100).toFixed(1) }}%)</span
- >
- </div>
- <div class="item-percent">
- 寮傚父鏁板崰姣旓細{{ ((exception0Num / exceptionAllNum) * 100).toFixed(1) }}%
- </div>
- </div>
- <hr class="item-divider" />
- <div class="item-shops">
- <el-scrollbar max-height="80px">
- <ExceptionText
- v-for="(item, index) in exception0"
- :key="item"
- :devId="item.devId"
- exception-value="0"
- :begin-time="beginTime"
- :end-time="endTime"
- @submit-exception-data="getAbnormalDataByClick"
- >
- {{ item.diName }}
- <span v-if="index < exception0.length - 1" class="text-blank">,</span>
- </ExceptionText>
- </el-scrollbar>
- </div>
+ <el-row>
+ <div class="search-form">
+ <div class="form-row">
+ <div class="form-item">
+ <span class="form-label">搴楅摵璁惧锛�</span>
+ <ShopNameAndID @submit-id="(n) => (deviceId[1] = n)"></ShopNameAndID>
</div>
- </el-col>
- <el-col :span="8">
- <div class="analysis-item">
- <div class="item-header">
- <img src="@/assets/exception.jpg" class="item-icon" />
- <span class="item-title">渚涚數寮傚父</span>
- </div>
- <div class="item-content">
- <div class="item-stats">
- <span class="stats-label">寮傚父搴楅摵鍗犳瘮锛�</span>
- <span class="stats-value">{{ exception1.length }} /{{ shopsTotal }}</span>
- <span class="stats-percent"
- >({{ ((exception1.length / shopsTotal) * 100).toFixed(1) }}%)</span
- >
- </div>
- <div class="item-percent">
- 寮傚父鏁板崰姣旓細{{ ((exception1Num / exceptionAllNum) * 100).toFixed(1) }}%
- </div>
- </div>
- <hr class="item-divider" />
- <div class="item-shops">
- <el-scrollbar max-height="80px">
- <ExceptionText
- v-for="(item, index) in exception1"
- :key="item"
- :devId="item.devId"
- exception-value="1"
- :begin-time="beginTime"
- :end-time="endTime"
- @submit-exception-data="getAbnormalDataByClick"
- >
- {{ item.diName }}
- <span v-if="index < exception1.length - 1" class="text-blank">,</span>
- </ExceptionText>
- </el-scrollbar>
- </div>
+ <div class="form-item">
+ <ExceptionType @submitExceptionType="(val) => (exceptionValue = val)">
+ </ExceptionType>
</div>
- </el-col>
- <el-col :span="8">
- <div class="analysis-item">
- <div class="item-header">
- <img src="@/assets/offline.jpg" class="item-icon" />
- <span class="item-title">璁惧鎴栫綉缁滃紓甯�</span>
- </div>
- <div class="item-content">
- <div class="item-stats">
- <span class="stats-label">寮傚父搴楅摵鍗犳瘮锛�</span>
- <span class="stats-value">{{ exception2.length }} /{{ shopsTotal }}</span>
- <span class="stats-percent"
- >({{ ((exception2.length / shopsTotal) * 100).toFixed(1) }}%)</span
- >
- </div>
- <div class="item-percent">寮傚父鏁板崰姣旓細{{ connectException }}%</div>
- </div>
- <hr class="item-divider" />
- <div class="item-shops">
- <el-scrollbar max-height="80px">
- <ExceptionText
- v-for="(item, index) in exception2"
- :key="item"
- :devId="item.devId"
- exception-value="2"
- :begin-time="beginTime"
- :end-time="endTime"
- @submit-exception-data="getAbnormalDataByClick"
- >
- {{ item.diName }}
- <span v-if="index < exception2.length - 1" class="text-blank">,</span>
- </ExceptionText>
- </el-scrollbar>
- </div>
+ </div>
+ <div class="form-row">
+ <div class="form-item full-width">
+ <TimeSelect @submit-time="giveTime" :useNewStyle="false"></TimeSelect>
</div>
- </el-col>
- </el-row>
- </el-card>
+ </div>
+ </div>
+ <div class="form-actions">
+ <el-button type="primary" :loading="button.queryButton" @click="showTable"
+ >鏌ヨ</el-button
+ >
+ <el-tooltip
+ class="box-item"
+ effect="dark"
+ content="鐐瑰嚮鍙鍑篍xcel鏂囦欢"
+ placement="top-start"
+ >
+ <el-icon class="iconExcel clickable" title="瀵煎嚭Excel鏂囦欢" @click="exportDom">
+ <i-ep-Download />
+ </el-icon>
+ </el-tooltip>
+ </div>
+ </el-row>
+ <div class="summary-info">
+ <span>{{ beginTime }} 鈥斺�� {{ endTime }} 娌圭儫鐩戞祴寮傚父淇℃伅姹囨��</span>
+ </div>
</el-collapse-item>
</el-collapse>
</div>
+ <!-- 寮傚父鍒嗘瀽 -->
+ <div class="analysis-container">
+ <el-space>
+ <h4 class="collapse-header">鐩戞祴棰勮</h4>
+ <el-icon class="header-icon">
+ <i-ep-info-filled />
+ </el-icon>
+ </el-space>
+ <el-card class="analysis-card">
+ <el-row :gutter="24">
+ <el-col :span="8">
+ <div class="analysis-item">
+ <div class="item-header">
+ <img src="@/assets/exceed.jpg" class="item-icon" />
+ <span class="item-title">娌圭儫娴撳害瓒呮爣</span>
+ </div>
+ <div class="item-content">
+ <div class="item-stats">
+ <span class="stats-label">寮傚父搴楅摵鍗犳瘮锛�</span>
+ <span class="stats-value">{{ exception0.length }} /{{ shopsTotal }}</span>
+ <span class="stats-percent"
+ >({{ ((exception0.length / shopsTotal) * 100).toFixed(1) }}%)</span
+ >
+ </div>
+ <div class="item-percent">
+ 寮傚父鏁板崰姣旓細{{ ((exception0Num / exceptionAllNum) * 100).toFixed(1) }}%
+ </div>
+ </div>
+ <hr class="item-divider" />
+ <div class="item-shops">
+ <el-scrollbar :height="scrollbarHeight">
+ <el-space wrap>
+ <ExceptionText
+ v-for="(item, index) in exception0"
+ :key="item"
+ :devId="item.devId"
+ exception-value="0"
+ :begin-time="beginTime"
+ :end-time="endTime"
+ @submit-exception-data="getAbnormalDataByClick"
+ >
+ {{ item.diName }}
+ <span v-if="index < exception0.length - 1" class="text-blank"></span>
+ </ExceptionText>
+ </el-space>
+ </el-scrollbar>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="analysis-item">
+ <div class="item-header">
+ <img src="@/assets/exception.jpg" class="item-icon" />
+ <span class="item-title">渚涚數寮傚父</span>
+ </div>
+ <div class="item-content">
+ <div class="item-stats">
+ <span class="stats-label">寮傚父搴楅摵鍗犳瘮锛�</span>
+ <span class="stats-value">{{ exception1.length }} /{{ shopsTotal }}</span>
+ <span class="stats-percent"
+ >({{ ((exception1.length / shopsTotal) * 100).toFixed(1) }}%)</span
+ >
+ </div>
+ <div class="item-percent">
+ 寮傚父鏁板崰姣旓細{{ ((exception1Num / exceptionAllNum) * 100).toFixed(1) }}%
+ </div>
+ </div>
+ <hr class="item-divider" />
+ <div class="item-shops">
+ <el-scrollbar :height="scrollbarHeight">
+ <el-space wrap>
+ <ExceptionText
+ v-for="(item, index) in exception1"
+ :key="item"
+ :devId="item.devId"
+ exception-value="1"
+ :begin-time="beginTime"
+ :end-time="endTime"
+ @submit-exception-data="getAbnormalDataByClick"
+ >
+ {{ item.diName }}
+ <span v-if="index < exception1.length - 1" class="text-blank"></span>
+ </ExceptionText>
+ </el-space>
+ </el-scrollbar>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="8">
+ <div class="analysis-item">
+ <div class="item-header">
+ <img src="@/assets/offline.jpg" class="item-icon" />
+ <span class="item-title">璁惧鎴栫綉缁滃紓甯�</span>
+ </div>
+ <div class="item-content">
+ <div class="item-stats">
+ <span class="stats-label">寮傚父搴楅摵鍗犳瘮锛�</span>
+ <span class="stats-value">{{ exception2.length }} /{{ shopsTotal }}</span>
+ <span class="stats-percent"
+ >({{ ((exception2.length / shopsTotal) * 100).toFixed(1) }}%)</span
+ >
+ </div>
+ <div class="item-percent">寮傚父鏁板崰姣旓細{{ connectException }}%</div>
+ </div>
+ <hr class="item-divider" />
+ <div class="item-shops">
+ <el-scrollbar :height="scrollbarHeight">
+ <el-space wrap>
+ <ExceptionText
+ v-for="(item, index) in exception2"
+ :key="item"
+ :devId="item.devId"
+ exception-value="2"
+ :begin-time="beginTime"
+ :end-time="endTime"
+ @submit-exception-data="getAbnormalDataByClick"
+ >
+ {{ item.diName }}
+ <span v-if="index < exception2.length - 1" class="text-blank"></span>
+ </ExceptionText>
+ </el-space>
+ </el-scrollbar>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+ </div>
+
<!-- 寮傚父鏁版嵁琛ㄦ牸 -->
<div class="table-container">
- <h4 class="table-title">寮傚父鏁版嵁</h4>
- <el-card v-show="!isNoData">
- <el-table
- ref="tableH"
- v-loading="loading"
- :data="displayData"
- style="width: 100%"
- border
- :height="tableHeight"
- :cell-class-name="tableCellClassName"
- >
- <el-table-column prop="diName" label="搴楅摵鍚嶇О" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.diName">
- <div class="cell ellipsis">{{ row.diName }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column prop="devId" label="璁惧缂栧彿" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.devId">
- <div class="cell ellipsis">{{ row.devId }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column prop="diSupplier" label="渚涘簲鍟�" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.diSupplier">
- <div class="cell ellipsis">{{ row.diSupplier }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column prop="exception" label="寮傚父鍒嗙被" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.exception">
- <div class="cell ellipsis">{{ row.exception }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column label="寮傚父绫诲瀷" align="center">
- <template #default="{ row }">
- <span v-if="row.exceptionType == '0'">娌圭儫鏁版嵁瓒呮爣</span>
- <span v-else-if="row.exceptionType == '1'">鐤戜技渚涚數寮傚父</span>
- <span v-else-if="row.exceptionType == '2'">鎺夌嚎</span>
- </template>
- </el-table-column>
- <el-table-column prop="region" label="鍦板尯" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.region">
- <div class="cell ellipsis">{{ row.region }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column prop="beginTime" label="寮�濮嬫椂闂�" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.beginTime">
- <div class="cell ellipsis">{{ row.beginTime }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column prop="endTime" label="缁撴潫鏃堕棿" align="center">
- <template #default="{ row }">
- <el-tooltip effect="dark" :content="row.endTime">
- <div class="cell ellipsis">{{ row.endTime }}</div>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" align="center" width="120">
- <template #default="{ row }">
- <el-button type="primary" size="small" @click="showDrawer(row)">鏌ョ湅</el-button>
- </template>
- </el-table-column>
- </el-table>
- <div class="pagination-container">
- <el-pagination
- ref="h4"
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :total="total"
- :page-size="pageSize"
- layout="total, prev, pager, next, jumper"
- />
- </div>
- </el-card>
- <el-empty v-show="isNoData" :image-size="200" />
+ <el-collapse v-model="activeNames">
+ <el-collapse-item name="1">
+ <template #title>
+ <div class="collapse-title">
+ <h4 class="table-title">寮傚父鏁版嵁</h4>
+ </div>
+ </template>
+ <el-card v-show="!isNoData">
+ <el-table
+ ref="tableH"
+ v-loading="loading"
+ :data="displayData"
+ style="width: 100%"
+ border
+ :cell-class-name="tableCellClassName"
+ :show-overflow-tooltip="true"
+ >
+ <el-table-column prop="diName" label="搴楅摵鍚嶇О" align="center">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.diName">
+ <div class="cell ellipsis">{{ row.diName }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column prop="devId" label="璁惧缂栧彿" align="center">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.devId">
+ <div class="cell ellipsis">{{ row.devId }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column prop="diSupplier" label="渚涘簲鍟�" align="center">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.diSupplier">
+ <div class="cell ellipsis">{{ row.diSupplier }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column prop="exception" label="寮傚父鍒嗙被" align="center" width="90">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.exception">
+ <div class="cell ellipsis">{{ row.exception }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column label="寮傚父绫诲瀷" align="center" width="120">
+ <template #default="{ row }">
+ <span v-if="row.exceptionType == '0'">娌圭儫鏁版嵁瓒呮爣</span>
+ <span v-else-if="row.exceptionType == '1'">鐤戜技渚涚數寮傚父</span>
+ <span v-else-if="row.exceptionType == '2'">鎺夌嚎</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="region" label="鍦板尯" align="center" width="80">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.region">
+ <div class="cell ellipsis">{{ row.region }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column prop="beginTime" label="寮�濮嬫椂闂�" align="center">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.beginTime">
+ <div class="cell ellipsis">{{ row.beginTime }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column prop="endTime" label="缁撴潫鏃堕棿" align="center">
+ <!-- <template #default="{ row }">
+ <el-tooltip effect="dark" :content="row.endTime">
+ <div class="cell ellipsis">{{ row.endTime }}</div>
+ </el-tooltip>
+ </template> -->
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" width="120">
+ <template #default="{ row }">
+ <el-button type="primary" size="small" @click="showDrawer(row)">鏌ョ湅</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div class="pagination-container">
+ <el-pagination
+ ref="h4"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ :total="total"
+ :page-size="pageSize"
+ layout="total, prev, pager, next, jumper"
+ />
+ </div>
+ </el-card>
+ <el-empty v-show="isNoData" :image-size="200" />
+ </el-collapse-item>
+ </el-collapse>
</div>
<!-- 瀵硅瘽妗� -->
@@ -448,7 +465,11 @@
selectedRowIndex: -1,
// 榛樿閫夋嫨鐨勬姌鍙犻潰鏉跨紪鍙�
- activeNames: ['1'],
+ activeNames: [],
+ // 鎼滅储鍖哄煙鎶樺彔鐘舵��
+ activeSearchNames: [],
+ // 鎼滅储鍖哄煙鏄惁灞曞紑
+ isSearchExpanded: false,
// 寮傚父鏃剁殑琛ㄦ牸
abnormalTb: [],
// 寮傚父鐨勮捣姝㈡椂闂�
@@ -462,6 +483,8 @@
exception1: [],
// 淇濆瓨鐫�寮傚父绫诲瀷2瀵瑰簲鐨勫簵閾哄悕绉板拰璁惧缂栧彿
exception2: [],
+ // 寮傚父搴楅摵婊氬姩鍖哄煙楂樺害
+ scrollbarHeight: 250,
// 鍔犺浇鍔ㄧ敾
loading: false,
// 鎶藉眽鍔犺浇鍔ㄧ敾
@@ -534,7 +557,6 @@
this.isNextCantouch = false
}
},
-
// 褰撻�夋嫨鐨勬椂闂村彂鐢熷彉鍖栨椂锛屽紓甯稿垎鏋愰儴鍒嗙殑寮傚父搴楅摵鏁伴噺鍚屾鍙樺寲
beginTime() {
this.getShopNames()
@@ -573,6 +595,37 @@
window.addEventListener('resize', this.updateChart)
},
methods: {
+ // 澶勭悊鎼滅储鍖哄煙鎶樺彔鍙樺寲
+ handleSearchCollapseChange(val) {
+ this.isSearchExpanded = val.length > 0
+ },
+ // 鑾峰彇鎼滅储鏉′欢鎽樿
+ getSearchSummary() {
+ let summary =
+ '鏃堕棿: ' +
+ (this.beginTime ? this.beginTime.substring(0, 10) : '鍏ㄩ儴') +
+ ' 鑷� ' +
+ (this.endTime ? this.endTime.substring(0, 10) : '鍏ㄩ儴')
+ summary +=
+ ' | 搴楅摵: ' +
+ (this.deviceId[1]
+ ? this.deviceInfo.find((item) => item.diCode === this.deviceId[1])?.diName || '宸查�夋嫨'
+ : '鍏ㄩ儴')
+ if (this.exceptionValue && this.exceptionValue.length > 0) {
+ const exceptionTypes = {
+ 0: '娌圭儫娴撳害瓒呮爣',
+ 1: '渚涚數寮傚父',
+ 2: '璁惧鎴栫綉缁滃紓甯�',
+ }
+ const selectedTypes = this.exceptionValue
+ .map((val) => exceptionTypes[val] || val)
+ .join(', ')
+ summary += ' | 寮傚父绫诲瀷: ' + selectedTypes
+ } else {
+ summary += ' | 寮傚父绫诲瀷: 鍏ㄩ儴'
+ }
+ return summary
+ },
// 鍔熻兘锛氬璇濇琛ㄦ牸搴忓彿閫掑
// 鏃堕棿锛�2023-8-17
indexMethod(index) {
@@ -886,6 +939,9 @@
// 绉婚櫎绌烘暟鎹姸鎬�
this.isNoData = false
this.handleCurrentChange(1)
+ // 鐐瑰嚮鏌ヨ鍚庢姌鍙犳悳绱㈠尯鍩�
+ this.activeSearchNames = []
+ this.isSearchExpanded = false
})
},
handleSizeChange(val) {
@@ -1391,6 +1447,15 @@
this.total = this.abnormalData.length
// 榛樿鏄剧ず绗竴椤�
this.handleCurrentChange(1)
+ this.activeNames = ['1']
+ // 婊氬姩鍒板紓甯告暟鎹〃鏍间綅缃�
+ this.$nextTick(() => {
+ if (this.$refs.tableH) {
+ setTimeout(() => {
+ this.$refs.tableH.$el.scrollIntoView({ behavior: 'smooth' })
+ }, 200)
+ }
+ })
},
// 鏍规嵁寮傚父绫诲瀷杩斿洖搴楅摵鍚嶇О鍜岃澶囩紪鍙�
@@ -1509,7 +1574,10 @@
}
.search-header {
- margin-bottom: 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
}
.search-header h3 {
@@ -1517,6 +1585,16 @@
font-size: 16px;
font-weight: 600;
color: #333;
+}
+
+.search-summary {
+ font-size: 14px;
+ color: #666;
+ flex: 1;
+ margin-left: 20px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.search-form {
@@ -1596,7 +1674,6 @@
}
.analysis-item {
- height: 180px;
display: flex;
flex-direction: column;
}
--
Gitblit v1.9.3