From 61d5d749daa7b0fbf5fdecf676927ab77f4264e0 Mon Sep 17 00:00:00 2001
From: maven <2163098428@qq.com>
Date: 星期三, 17 九月 2025 11:26:41 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev' into dev

---
 src/store/modules/user.js                                      |    1 
 src/views/reportAnalysis/dataDashboard/index.vue               |   19 
 src/views/inspectionManagement/components/formDia.vue          |  174 ++++++
 src/views/inspectionManagement/components/viewFiles.vue        |  246 +++++++++
 src/assets/BI/biaoti.png                                       |    0 
 src/views/inspectionManagement/index.vue                       |  391 +++++++++++++++
 src/views/inspectionManagement/components/qrCodeDia.vue        |  132 +++++
 src/views/inspectionManagement/components/viewQrCodeFiles.vue  |  169 ++++++
 src/views/collaborativeApproval/attendanceManagement/index.vue |  321 ++++++++++++
 9 files changed, 1,451 insertions(+), 2 deletions(-)

diff --git a/src/assets/BI/biaoti.png b/src/assets/BI/biaoti.png
index f905f1c..3c5ccb9 100644
--- a/src/assets/BI/biaoti.png
+++ b/src/assets/BI/biaoti.png
Binary files differ
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index d52efa6..057af50 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -2,6 +2,7 @@
 import { getToken, setToken, removeToken } from '@/utils/auth'
 import { isHttp, isEmpty } from "@/utils/validate"
 import defAva from '@/assets/images/profile.jpg'
+import { defineStore } from 'pinia'
 
 const useUserStore = defineStore(
   'user',
diff --git a/src/views/collaborativeApproval/attendanceManagement/index.vue b/src/views/collaborativeApproval/attendanceManagement/index.vue
index ce6709d..f6a3e3c 100644
--- a/src/views/collaborativeApproval/attendanceManagement/index.vue
+++ b/src/views/collaborativeApproval/attendanceManagement/index.vue
@@ -129,6 +129,52 @@
           </el-table>
         </div>
       </el-tab-pane>
+
+      <!-- 鎵撳崱璁板綍 -->
+      <el-tab-pane label="鎵撳崱璁板綍" name="attendance">
+        <div class="tab-content">
+          <div style="margin-bottom: 20px;">
+            <el-date-picker
+              v-model="attendanceDate"
+              type="date"
+              placeholder="閫夋嫨鏃ユ湡"
+              format="YYYY-MM-DD"
+              value-format="YYYY-MM-DD"
+              style="margin-right: 10px;"
+              @change="filterAttendanceData"
+            />
+            <el-select
+              v-model="attendanceStatus"
+              placeholder="閫夋嫨鐘舵��"
+              style="width: 120px; margin-right: 10px;"
+              @change="filterAttendanceData"
+            >
+              <el-option label="鍏ㄩ儴" value="" />
+              <el-option label="姝e父" value="normal" />
+              <el-option label="杩熷埌" value="late" />
+              <el-option label="鏃╅��" value="early" />
+              <el-option label="缂哄嫟" value="absent" />
+            </el-select>
+            <el-button type="primary" @click="exportAttendance">瀵煎嚭璁板綍</el-button>
+          </div>
+
+          <el-table :data="filteredAttendanceData" border style="width: 100%;">
+            <el-table-column prop="employeeName" label="鍛樺伐濮撳悕" width="120" />
+            <el-table-column prop="department" label="閮ㄩ棬" width="120" />
+            <el-table-column prop="date" label="鏃ユ湡" width="120" />
+            <el-table-column prop="clockInTime" label="涓婄彮鎵撳崱" width="120" />
+            <el-table-column prop="clockOutTime" label="涓嬬彮鎵撳崱" width="120" />
+            <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" width="100" align="center" />
+            <el-table-column prop="status" label="鐘舵��" width="100" align="center">
+              <template #default="scope">
+                <el-tag :type="getAttendanceTagType(scope.row.status)">{{ getAttendanceStatusLabel(scope.row.status) }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="location" label="鎵撳崱鍦扮偣" width="150" />
+            <el-table-column prop="remark" label="澶囨敞" min-width="150" />
+          </el-table>
+        </div>
+      </el-tab-pane>
     </el-tabs>
 
     <!-- 閫氱敤寮圭獥 -->
@@ -290,6 +336,12 @@
 const overtimeData = ref([])
 const worktimeData = ref([])
 
+// 鎵撳崱璁板綍鐩稿叧鏁版嵁
+const attendanceData = ref([])
+const filteredAttendanceData = ref([])
+const attendanceDate = ref('')
+const attendanceStatus = ref('')
+
 // 琛ㄥ崟鏁版嵁
 const form = reactive({
   name: '',
@@ -389,6 +441,27 @@
     regular: '姝e紡鍛樺伐', probation: '璇曠敤鏈熷憳宸�', intern: '瀹炰範鐢�'
   }
   return labelMap[type] || type
+}
+
+// 鎵撳崱璁板綍鐩稿叧宸ュ叿鍑芥暟
+const getAttendanceTagType = (status) => {
+  const tagMap = {
+    normal: 'success',
+    late: 'warning', 
+    early: 'warning',
+    absent: 'danger'
+  }
+  return tagMap[status] || 'info'
+}
+
+const getAttendanceStatusLabel = (status) => {
+  const labelMap = {
+    normal: '姝e父',
+    late: '杩熷埌',
+    early: '鏃╅��', 
+    absent: '缂哄嫟'
+  }
+  return labelMap[status] || status
 }
 
 const getTypeOptions = () => {
@@ -773,6 +846,253 @@
   }
 }
 
+// 鎵撳崱璁板綍杩囨护鍔熻兘
+const filterAttendanceData = () => {
+  let filtered = attendanceData.value
+  
+  // 鎸夋棩鏈熻繃婊�
+  if (attendanceDate.value) {
+    filtered = filtered.filter(item => item.date === attendanceDate.value)
+  }
+  
+  // 鎸夌姸鎬佽繃婊�
+  if (attendanceStatus.value) {
+    filtered = filtered.filter(item => item.status === attendanceStatus.value)
+  }
+  
+  filteredAttendanceData.value = filtered
+}
+
+// 瀵煎嚭鎵撳崱璁板綍
+const exportAttendance = () => {
+  ElMessage.success('瀵煎嚭鍔熻兘寮�鍙戜腑...')
+}
+
+// 鍒濆鍖栨墦鍗¤褰曞亣鏁版嵁
+const initAttendanceData = () => {
+  const mockData = [
+    {
+      id: 1,
+      employeeName: '闄堝織寮�',
+      department: '鎶�鏈儴',
+      date: '2025-08-15',
+      clockInTime: '09:00:00',
+      clockOutTime: '18:00:00',
+      workHours: '8.0h',
+      status: 'normal',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    },
+    {
+      id: 2,
+      employeeName: '鏉庨洩姊�',
+      department: '甯傚満閮�',
+      date: '2025-08-16',
+       clockInTime: '08:58:00',
+       clockOutTime: '18:05:00',
+       workHours: '8.12h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 3,
+       employeeName: '鐜嬪缓鍗�',
+       department: '浜轰簨閮�',
+       date: '2025-08-16',
+       clockInTime: '09:02:00',
+       clockOutTime: '18:00:00',
+       workHours: '7.97h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 4,
+       employeeName: '璧垫檽涓�',
+       department: '璐㈠姟閮�',
+       date: '2025-09-02',
+       clockInTime: '08:55:00',
+       clockOutTime: '18:10:00',
+       workHours: '8.25h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 5,
+       employeeName: '寮犲浗搴�',
+       department: '鎶�鏈儴',
+       date: '2025-09-02',
+       clockInTime: '09:00:00',
+       clockOutTime: '18:30:00',
+       workHours: '8.5h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: '鍔犵彮'
+     },
+     {
+       id: 6,
+       employeeName: '鍒樻槑杈�',
+       department: '杩愯惀閮�',
+       date: '2025-09-03',
+       clockInTime: '09:05:00',
+       clockOutTime: '18:00:00',
+       workHours: '7.92h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 7,
+       employeeName: '瀛欎附鍗�',
+       department: '璁捐閮�',
+       date: '2025-09-03',
+       clockInTime: '08:59:00',
+       clockOutTime: '18:02:00',
+       workHours: '8.05h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 8,
+       employeeName: '鍛ㄥ缓鍐�',
+       department: '閿�鍞儴',
+       date: '2025-09-04',
+       clockInTime: '09:15:00',
+       clockOutTime: '18:00:00',
+       workHours: '7.75h',
+       status: 'late',
+       location: '鍏徃鎬婚儴',
+       remark: '浜ら�氬牭濉�'
+     },
+     {
+       id: 9,
+       employeeName: '鍚村皬鑺�',
+       department: '瀹㈡湇閮�',
+       date: '2025-09-04',
+       clockInTime: '09:01:00',
+       clockOutTime: '18:00:00',
+       workHours: '7.98h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 10,
+       employeeName: '椹枃鏉�',
+       department: '鎶�鏈儴',
+       date: '2025-09-05',
+       clockInTime: '08:57:00',
+       clockOutTime: '17:30:00',
+       workHours: '7.55h',
+       status: 'early',
+       location: '鍏徃鎬婚儴',
+       remark: '鏈夋�ヤ簨鎻愬墠绂诲紑'
+     },
+     {
+       id: 11,
+       employeeName: '鏋楁檽涓�',
+       department: '琛屾斂閮�',
+       date: '2025-09-05',
+       clockInTime: '09:03:00',
+       clockOutTime: '18:08:00',
+       workHours: '8.08h',
+       status: 'normal',
+       location: '鍏徃鎬婚儴',
+       remark: ''
+     },
+     {
+       id: 12,
+       employeeName: '榛勭編鐜�',
+       department: '璐㈠姟閮�',
+       date: '2025-09-06',
+       clockInTime: '',
+       clockOutTime: '',
+       workHours: '0h',
+       status: 'absent',
+       location: '',
+       remark: '璇风梾鍋�'
+     },
+    {
+      id: 13,
+      employeeName: '閮戞捣娑�',
+      department: '甯傚満閮�',
+      date: '2025-08-14',
+      clockInTime: '09:00:00',
+      clockOutTime: '18:00:00',
+      workHours: '8.0h',
+      status: 'normal',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    },
+    {
+      id: 14,
+      employeeName: '璋附濞�',
+      department: '浜轰簨閮�',
+      date: '2025-08-20',
+      clockInTime: '08:58:00',
+      clockOutTime: '18:03:00',
+      workHours: '8.08h',
+      status: 'normal',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    },
+    {
+      id: 15,
+      employeeName: '浣曞織浼�',
+      department: '鎶�鏈儴',
+      date: '2025-08-21',
+      clockInTime: '09:10:00',
+      clockOutTime: '18:00:00',
+      workHours: '7.83h',
+      status: 'late',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    },
+    {
+      id: 16,
+      employeeName: '璁搁泤鑺�',
+      department: '璁捐閮�',
+      date: '2025-08-22',
+      clockInTime: '09:01:00',
+      clockOutTime: '18:00:00',
+      workHours: '7.98h',
+      status: 'normal',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    },
+    {
+      id: 17,
+      employeeName: '閭撳缓骞�',
+      department: '杩愯惀閮�',
+      date: '2025-09-10',
+      clockInTime: '08:59:00',
+      clockOutTime: '18:05:00',
+      workHours: '8.1h',
+      status: 'normal',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    },
+    {
+      id: 18,
+      employeeName: '鏇惧皬绾�',
+      department: '瀹㈡湇閮�',
+      date: '2025-09-11',
+      clockInTime: '09:02:00',
+      clockOutTime: '18:00:00',
+      workHours: '7.97h',
+      status: 'normal',
+      location: '鍏徃鎬婚儴',
+      remark: ''
+    }
+  ]
+  
+  attendanceData.value = mockData
+  filteredAttendanceData.value = mockData
+}
+
 // 鍒犻櫎椤圭洰
 const deleteItem = (type, row) => {
   ElMessageBox.confirm('纭畾瑕佸垹闄よ繖涓」鐩悧锛�', '鎻愮ず', {
@@ -888,6 +1208,7 @@
   getAnnualLeaveSettingList()
   getOvertimeSettingList()
   getWorkingHoursSettingList()
+  initAttendanceData()
   console.log('鑰冨嫟绠$悊椤甸潰鍔犺浇瀹屾垚')
 })
 
diff --git a/src/views/inspectionManagement/components/formDia.vue b/src/views/inspectionManagement/components/formDia.vue
new file mode 100644
index 0000000..9d9a9fd
--- /dev/null
+++ b/src/views/inspectionManagement/components/formDia.vue
@@ -0,0 +1,174 @@
+<template>
+  <div>
+    <el-dialog :title="operationType === 'add' ? '鏂板宸℃浠诲姟' : '缂栬緫宸℃浠诲姟'"
+               v-model="dialogVisitable" width="800px" @close="cancel">
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="浠诲姟鍚嶇О" prop="taskName">
+              <el-input v-model="form.taskName" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" maxlength="30" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍦扮偣" prop="inspectionLocation">
+              <el-input v-model="form.inspectionLocation" placeholder="璇疯緭鍏ュ湴鐐�" maxlength="30" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="宸℃浜�" prop="inspector">
+              <el-select v-model="form.inspector" placeholder="璇烽�夋嫨" multiple clearable>
+                <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="澶囨敞" prop="remarks">
+              <el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
+              <el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
+                <el-option label="姣忔棩" value="DAILY"/>
+                <el-option label="姣忓懆" value="WEEKLY"/>
+                <el-option label="姣忔湀" value="MONTHLY"/>
+                <el-option label="瀛e害" value="QUARTERLY"/>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
+            <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+              <el-time-picker v-model="form.frequencyDetail" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+                              value-format="HH:mm" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
+            <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+              <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
+                <el-option label="鍛ㄤ竴" value="MON"/>
+                <el-option label="鍛ㄤ簩" value="TUE"/>
+                <el-option label="鍛ㄤ笁" value="WED"/>
+                <el-option label="鍛ㄥ洓" value="THU"/>
+                <el-option label="鍛ㄤ簲" value="FRI"/>
+                <el-option label="鍛ㄥ叚" value="SAT"/>
+                <el-option label="鍛ㄦ棩" value="SUN"/>
+              </el-select>
+              <el-time-picker v-model="form.time" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
+                              value-format="HH:mm"  style="width: 50%"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
+            <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+              <el-date-picker
+                  v-model="form.frequencyDetail"
+                  type="datetime"
+                  clearable
+                  placeholder="閫夋嫨寮�濮嬫棩鏈�"
+                  format="DD,HH:mm"
+                  value-format="DD,HH:mm"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
+            <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+              <el-date-picker
+                  v-model="form.frequencyDetail"
+                  type="datetime"
+                  clearable
+                  placeholder="閫夋嫨寮�濮嬫棩鏈�"
+                  format="MM,DD,HH:mm"
+                  value-format="MM,DD,HH:mm"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">鍙栨秷</el-button>
+          <el-button type="primary" @click="submitForm">淇濆瓨</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {reactive, ref} from "vue";
+import useUserStore from '@/store/modules/user'
+import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
+import {userListAll} from "@/api/publicApi/index.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const userStore = useUserStore()
+const dialogVisitable = ref(false);
+const operationType = ref('add');
+const data = reactive({
+  form: {
+    taskName: '',
+    inspectionLocation: '',
+    inspector: '',
+    inspectorIds: '',
+    remarks: '',
+    frequencyType: '',
+    frequencyDetail: '',
+  },
+  rules: {
+    taskName: [{ required: true, message: "璇疯緭鍏ヤ换鍔″悕绉�", trigger: "blur" },],
+    inspectionLocation: [{ required: true, message: "璇疯緭鍏ュ湴鐐�", trigger: "blur" },],
+    inspector: [{ required: true, message: "璇疯緭鍏ュ贰妫�浜�", trigger: "blur" },],
+  }
+})
+const { form, rules } = toRefs(data)
+const userList = ref([])
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+  dialogVisitable.value = true
+  userListAll().then(res => {
+    userList.value = res.data
+  })
+  if (type === 'edit') {
+    form.value = {...row}
+    form.value.inspector = form.value.inspectorIds.split(',').map(Number)
+  }
+}
+
+// 鍏抽棴鍚堝苟琛ㄥ崟
+const cancel = () => {
+  proxy.resetForm("formRef")
+  dialogVisitable.value = false
+  emit('closeDia')
+}
+
+// 鎻愪氦鍚堝苟琛ㄥ崟
+const submitForm = () => {
+  proxy.$refs["formRef"].validate(async valid => {
+    if (valid) {
+      form.value.inspectorIds = form.value.inspector.join(',')
+			delete form.value.inspector
+      if (form.value.frequencyType === 'WEEKLY') {
+        let frequencyDetail = ''
+        frequencyDetail = form.value.week + ',' + form.value.time
+        form.value.frequencyDetail = frequencyDetail
+      }
+      let res = await userStore.getInfo()
+      form.value.registrantId = res.user.userId
+      addOrEditTimingTask(form.value).then(() => {
+        cancel()
+        proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
+      })
+    }
+  })
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/components/qrCodeDia.vue b/src/views/inspectionManagement/components/qrCodeDia.vue
new file mode 100644
index 0000000..136c18c
--- /dev/null
+++ b/src/views/inspectionManagement/components/qrCodeDia.vue
@@ -0,0 +1,132 @@
+<template>
+  <div>
+    <el-dialog :title="operationType === 'add' ? '鏂板浜岀淮鐮�' : '缂栬緫浜岀淮鐮�'"
+               v-model="dialogVisitable" width="500px" @close="cancel">
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="璁惧鍚嶇О" prop="deviceName">
+              <el-input v-model="form.deviceName" placeholder="璇疯緭鍏ヨ澶囧悕绉�" maxlength="30" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="鎵�鍦ㄤ綅缃弿杩�" prop="location">
+              <el-input v-model="form.location" placeholder="璇疯緭鍏ユ墍鍦ㄤ綅缃弿杩�" maxlength="30"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div>
+        <el-button type="primary" @click="submitForm">鐢熸垚骞舵墦鍗颁簩缁寸爜</el-button>
+      </div>
+      <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer">
+        <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import useUserStore from "@/store/modules/user.js";
+import {reactive, ref} from "vue";
+import printJS from 'print-js';
+import {addOrEditQrCode} from "@/api/inspectionUpload/index.js";
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits()
+const userStore = useUserStore()
+const dialogVisitable = ref(false);
+const isShowQrCode = ref(false);
+const operationType = ref('add');
+
+const qrCodeValue = ref('');
+const qrCodeSize = ref(100);
+const data = reactive({
+  form: {
+    deviceName: '',
+    location: '',
+    qrCodeId: '',
+    id: ''
+  },
+  rules: {
+    deviceName: [{ required: true, message: '璇疯緭鍏ヨ澶囧悕绉�', trigger: 'blur' }],
+    location: [{ required: true, message: '璇疯緭鍏ュ湴鐐�', trigger: 'blur' }]
+  }
+})
+const { form, rules } = toRefs(data)
+
+
+// 鎵撳紑寮规
+const openDialog = async (type, row) => {
+  dialogVisitable.value = true
+  qrCodeValue.value = ''
+  isShowQrCode.value = false;
+  if (type === 'edit') {
+    form.value.id = row.id
+    form.value.qrCodeId = row.id
+    form.value.deviceName = row.deviceName
+    form.value.location = row.location
+    // 灏嗚〃鍗曟暟鎹浆涓� JSON 瀛楃涓蹭綔涓轰簩缁寸爜鍐呭
+    qrCodeValue.value = JSON.stringify(form.value);
+    isShowQrCode.value = true;
+  }
+}
+// 鎻愪氦鍚堝苟琛ㄥ崟
+const submitForm = () => {
+  proxy.$refs["formRef"].validate(valid => {
+    if (valid) {
+      addOrEditQrCode(form.value).then((res) => {
+        form.value.qrCodeId = res.data
+      })
+      // 灏嗚〃鍗曟暟鎹浆涓� JSON 瀛楃涓蹭綔涓轰簩缁寸爜鍐呭
+      qrCodeValue.value = JSON.stringify(form.value);
+      isShowQrCode.value = true;
+      showQrCode()
+    }
+  })
+}
+const showQrCode = () => {
+  // 寤惰繜鎵ц鎵撳嵃锛岄伩鍏� DOM 鏇存柊鍓嶅氨璋冪敤鎵撳嵃
+  setTimeout(() => {
+    printJS({
+      printable: 'qrCodeContainer',//椤甸潰
+      type: "html",//鏂囨。绫诲瀷
+      maxWidth: 360,
+      style: `@page {
+                margin:0;
+                size: 400px 75px collapse;
+                margin-top:3px;
+                &:first-of-type{
+                  margin-top:0 !important;
+                }
+              }
+              html{
+                zoom:100%;
+              }
+              @media print{
+                width: 400px;
+                height: 75px;
+                margin:0;
+              }`,
+      targetStyles: ["*"], // 浣跨敤dom鐨勬墍鏈夋牱寮忥紝寰堥噸瑕�
+      font_size: '0.20cm',
+    });
+  }, 300);
+}
+// 鍏抽棴鍚堝苟琛ㄥ崟
+const cancel = () => {
+  proxy.resetForm("formRef")
+  dialogVisitable.value = false
+  emit('closeDia')
+}
+defineExpose({ openDialog })
+</script>
+
+<style scoped>
+.print-section {
+  text-align: center;
+  margin-top: 30px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/components/viewFiles.vue b/src/views/inspectionManagement/components/viewFiles.vue
new file mode 100644
index 0000000..d1c9d8d
--- /dev/null
+++ b/src/views/inspectionManagement/components/viewFiles.vue
@@ -0,0 +1,246 @@
+<template>
+  <div>
+    <el-dialog title="鏌ョ湅闄勪欢"
+               v-model="dialogVisitable" width="800px" @close="cancel">
+      <div class="upload-container">
+        <!-- 鐢熶骇鍓� -->
+        <div class="form-container">
+          <div class="title">鐢熶骇鍓�</div>
+          
+          <!-- 鍥剧墖鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <img v-for="(item, index) in beforeProductionImgs" :key="index"
+                 @click="showMedia(beforeProductionImgs, index, 'image')"
+                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+          </div>
+          
+          <!-- 瑙嗛鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <div
+                v-for="(videoUrl, index) in beforeProductionVideos"
+                :key="index"
+                @click="showMedia(beforeProductionVideos, index, 'video')"
+                style="position: relative; margin: 10px; cursor: pointer;"
+            >
+              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+              </div>
+              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 鐢熶骇鍚� -->
+        <div class="form-container">
+          <div class="title">鐢熶骇鍚�</div>
+          
+          <!-- 鍥剧墖鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <img v-for="(item, index) in afterProductionImgs" :key="index"
+                 @click="showMedia(afterProductionImgs, index, 'image')"
+                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+          </div>
+          
+          <!-- 瑙嗛鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <div
+                v-for="(videoUrl, index) in afterProductionVideos"
+                :key="index"
+                @click="showMedia(afterProductionVideos, index, 'video')"
+                style="position: relative; margin: 10px; cursor: pointer;"
+            >
+              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+              </div>
+              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 鐢熶骇闂 -->
+        <div class="form-container">
+          <div class="title">鐢熶骇闂</div>
+          
+          <!-- 鍥剧墖鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <img v-for="(item, index) in productionIssuesImgs" :key="index"
+                 @click="showMedia(productionIssuesImgs, index, 'image')"
+                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+          </div>
+          
+          <!-- 瑙嗛鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <div
+                v-for="(videoUrl, index) in productionIssuesVideos"
+                :key="index"
+                @click="showMedia(productionIssuesVideos, index, 'video')"
+                style="position: relative; margin: 10px; cursor: pointer;"
+            >
+              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+              </div>
+              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+    
+    <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
+      <div class="media-viewer-content" @click.stop>
+        <!-- 鍥剧墖 -->
+        <vue-easy-lightbox
+            v-if="mediaType === 'image'"
+            :visible="isMediaViewerVisible"
+            :imgs="mediaList"
+            :index="currentMediaIndex"
+            @hide="closeMediaViewer"
+        ></vue-easy-lightbox>
+        
+        <!-- 瑙嗛 -->
+        <div v-else-if="mediaType === 'video'" style="position: relative;">
+          <Video
+              :src="mediaList[currentMediaIndex]"
+              autoplay
+              controls
+              style="max-width: 90vw; max-height: 80vh;"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import { ref } from 'vue';
+import VueEasyLightbox from 'vue-easy-lightbox';
+
+// 鎺у埗寮圭獥鏄剧ず
+const dialogVisitable = ref(false);
+
+// 鍥剧墖鏁扮粍
+const beforeProductionImgs = ref([]);
+const afterProductionImgs = ref([]);
+const productionIssuesImgs = ref([]);
+
+// 瑙嗛鏁扮粍
+const beforeProductionVideos = ref([]);
+const afterProductionVideos = ref([]);
+const productionIssuesVideos = ref([]);
+
+// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
+const isMediaViewerVisible = ref(false);
+const currentMediaIndex = ref(0);
+const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
+const mediaType = ref('image'); // image | video
+
+// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
+function processItems(items) {
+  const images = [];
+  const videos = [];
+  items.forEach(item => {
+    if (item.contentType?.startsWith('image/')) {
+      images.push(item.url);
+    } else if (item.contentType?.startsWith('video/')) {
+      videos.push(item.url);
+    }
+  });
+  return { images, videos };
+}
+
+// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
+const openDialog = async (row) => {
+  const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
+  const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
+  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues);
+  
+  beforeProductionImgs.value = beforeImgs;
+  beforeProductionVideos.value = beforeVids;
+  
+  afterProductionImgs.value = afterImgs;
+  afterProductionVideos.value = afterVids;
+  
+  productionIssuesImgs.value = issueImgs;
+  productionIssuesVideos.value = issueVids;
+  
+  dialogVisitable.value = true;
+};
+
+// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
+function showMedia(mediaArray, index, type) {
+  mediaList.value = mediaArray;
+  currentMediaIndex.value = index;
+  mediaType.value = type;
+  isMediaViewerVisible.value = true;
+}
+
+// 鍏抽棴濯掍綋鏌ョ湅鍣�
+function closeMediaViewer() {
+  isMediaViewerVisible.value = false;
+  mediaList.value = [];
+  mediaType.value = 'image';
+}
+
+// 琛ㄥ崟鍏抽棴鏂规硶
+const cancel = () => {
+  dialogVisitable.value = false;
+};
+
+defineExpose({ openDialog });
+</script>
+<style scoped lang="scss">
+.upload-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20px;
+  border: 1px solid #dcdfe6;
+  box-sizing: border-box;
+  
+  .form-container {
+    flex: 1;
+    width: 100%;
+    margin-bottom: 20px;
+  }
+}
+
+.title {
+  font-size: 14px;
+  color: #165dff;
+  line-height: 20px;
+  font-weight: 600;
+  padding-left: 10px;
+  position: relative;
+  margin: 6px 0;
+  
+  &::before {
+    content: "";
+    position: absolute;
+    left: 0;
+    top: 3px;
+    width: 4px;
+    height: 14px;
+    background-color: #165dff;
+  }
+}
+
+.media-viewer-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.8);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-viewer-content {
+  position: relative;
+  max-width: 90vw;
+  max-height: 90vh;
+  overflow: hidden;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/components/viewQrCodeFiles.vue b/src/views/inspectionManagement/components/viewQrCodeFiles.vue
new file mode 100644
index 0000000..f8e923a
--- /dev/null
+++ b/src/views/inspectionManagement/components/viewQrCodeFiles.vue
@@ -0,0 +1,169 @@
+<template>
+  <div>
+    <el-dialog title="鏌ョ湅闄勪欢"
+               v-model="dialogVisitable" width="800px" @close="cancel">
+      <div class="upload-container">
+        <div class="form-container">
+          <div class="title">宸℃闄勪欢</div>
+          <!-- 鍥剧墖鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <img v-for="(item, index) in beforeProductionImgs" :key="index"
+                 @click="showMedia(beforeProductionImgs, index, 'image')"
+                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
+          </div>
+          
+          <!-- 瑙嗛鍒楄〃 -->
+          <div style="display: flex; flex-wrap: wrap;">
+            <div
+                v-for="(videoUrl, index) in beforeProductionVideos"
+                :key="index"
+                @click="showMedia(beforeProductionVideos, index, 'video')"
+                style="position: relative; margin: 10px; cursor: pointer;"
+            >
+              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
+                <img src="@/assets/images/video.png" alt="鎾斁" style="width: 30px; height: 30px; opacity: 0.8;" />
+              </div>
+              <div style="text-align: center; font-size: 12px; color: #666;">鐐瑰嚮鎾斁</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+    <!-- 缁熶竴濯掍綋鏌ョ湅鍣� -->
+    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
+      <div class="media-viewer-content" @click.stop>
+        <!-- 鍥剧墖 -->
+        <vue-easy-lightbox
+            v-if="mediaType === 'image'"
+            :visible="isMediaViewerVisible"
+            :imgs="mediaList"
+            :index="currentMediaIndex"
+            @hide="closeMediaViewer"
+        ></vue-easy-lightbox>
+        
+        <!-- 瑙嗛 -->
+        <div v-else-if="mediaType === 'video'" style="position: relative;">
+          <Video
+              :src="mediaList[currentMediaIndex]"
+              autoplay
+              controls
+              style="max-width: 90vw; max-height: 80vh;"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 鎺у埗寮圭獥鏄剧ず
+import VueEasyLightbox from "vue-easy-lightbox";
+
+const dialogVisitable = ref(false);
+// 鍥剧墖鏁扮粍
+const beforeProductionImgs = ref([]);
+// 瑙嗛鏁扮粍
+const beforeProductionVideos = ref([]);
+// 濯掍綋鏌ョ湅鍣ㄧ姸鎬�
+const isMediaViewerVisible = ref(false);
+const currentMediaIndex = ref(0);
+const mediaList = ref([]); // 瀛樺偍褰撳墠瑕佹煡鐪嬬殑濯掍綋鍒楄〃锛堝惈鍥剧墖鍜岃棰戝璞★級
+const mediaType = ref('image'); // image | video
+
+// 鎵撳紑寮圭獥骞跺姞杞芥暟鎹�
+const openDialog = async (row) => {
+  const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO);
+  
+  beforeProductionImgs.value = beforeImgs;
+  beforeProductionVideos.value = beforeVids;
+  dialogVisitable.value = true;
+};
+// 鏄剧ず濯掍綋锛堝浘鐗� or 瑙嗛锛�
+function showMedia(mediaArray, index, type) {
+  mediaList.value = mediaArray;
+  currentMediaIndex.value = index;
+  mediaType.value = type;
+  isMediaViewerVisible.value = true;
+}
+// 鍏抽棴濯掍綋鏌ョ湅鍣�
+function closeMediaViewer() {
+  isMediaViewerVisible.value = false;
+  mediaList.value = [];
+  mediaType.value = 'image';
+}
+// 琛ㄥ崟鍏抽棴鏂规硶
+const cancel = () => {
+  dialogVisitable.value = false;
+};
+// 澶勭悊姣忎竴绫绘暟鎹細鍒嗙鍥剧墖鍜岃棰�
+function processItems(items) {
+  const images = [];
+  const videos = [];
+  items.forEach(item => {
+    if (item.contentType?.startsWith('image/')) {
+      images.push(item.url);
+    } else if (item.contentType?.startsWith('video/')) {
+      videos.push(item.url);
+    }
+  });
+  return { images, videos };
+}
+defineExpose({ openDialog });
+</script>
+
+<style scoped lang="scss">
+.upload-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20px;
+  border: 1px solid #dcdfe6;
+  box-sizing: border-box;
+  
+  .form-container {
+    flex: 1;
+    width: 100%;
+    margin-bottom: 20px;
+  }
+}
+
+.title {
+  font-size: 14px;
+  color: #165dff;
+  line-height: 20px;
+  font-weight: 600;
+  padding-left: 10px;
+  position: relative;
+  margin: 6px 0;
+  
+  &::before {
+    content: "";
+    position: absolute;
+    left: 0;
+    top: 3px;
+    width: 4px;
+    height: 14px;
+    background-color: #165dff;
+  }
+}
+
+.media-viewer-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.8);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-viewer-content {
+  position: relative;
+  max-width: 90vw;
+  max-height: 90vh;
+  overflow: hidden;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/inspectionManagement/index.vue b/src/views/inspectionManagement/index.vue
new file mode 100644
index 0000000..7e5edc0
--- /dev/null
+++ b/src/views/inspectionManagement/index.vue
@@ -0,0 +1,391 @@
+<template>
+  <div class="app-container">
+    <el-form :inline="true" :model="queryParams" class="search-form">
+      <el-form-item label="鎼滅储">
+        <el-input
+            v-model="queryParams.searchAll"
+            placeholder="璇疯緭鍏ュ叧閿瓧"
+            clearable
+            :style="{ width: '100%' }"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+        <el-button @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+    <el-card>
+      <!-- 鏍囩椤� -->
+      <el-tabs
+          v-model="activeTab"
+          class="info-tabs"
+          @tab-click="handleTabClick"
+      >
+        <el-tab-pane
+            v-for="tab in tabs"
+            :key="tab.name"
+            :label="tab.label"
+            :name="tab.name"
+        />
+      </el-tabs>
+      <div style="display: flex;flex-direction: row;justify-content: space-between;" v-if="tabName === 'task'">
+        <el-radio-group v-model="activeRadio" @change="radioChange">
+          <el-radio-button v-for="tab in radios"
+                           :key="tab.name"
+                           :label="tab.label"
+                           :value="tab.name"/>
+        </el-radio-group>
+        <!-- 鎿嶄綔鎸夐挳鍖� -->
+        <el-space v-if="activeRadio !== 'task'">
+          <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">鏂板缓</el-button>
+          <el-button type="danger" :icon="Delete" @click="handleDelete">鍒犻櫎</el-button>
+          <!-- <el-button type="info" plain :icon="Download">瀵煎嚭</el-button> -->
+        </el-space>
+      </div>
+      <div>
+        <div>
+          <ETable :loading="tableLoading"
+                  :table-data="tableData"
+                  :columns="tableColumns"
+                  @selection-change="handleSelectionChange"
+                  :show-selection="true"
+                  :border="true"
+                  style="width: 100%;height: calc(100vh - 30em)"
+                  operationsWidth="130"
+                  :operations="operationsArr"
+                  @edit="handleAdd"
+                  @viewFile="viewFile"
+                  v-if="tabName === 'task'"
+          >
+          <template #inspector="{ row }">
+            <div class="person-tags">
+              <!-- 璋冭瘯淇℃伅锛屼笂绾挎椂鍒犻櫎 -->
+              <!-- {{ console.log('inspector data:', row.inspector) }} -->
+              <template v-if="row.inspector && row.inspector.length > 0">
+                <el-tag
+                  v-for="(person, index) in row.inspector"
+                  :key="index"
+                  size="small"
+                  type="primary"
+                  class="person-tag"
+                >
+                  {{ person }}
+                </el-tag>
+              </template>
+              <span v-else class="no-data">--</span>
+            </div>
+          </template>
+          </ETable>
+          <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" border v-else style="width: 100%;height: calc(100vh - 25em)">
+            <el-table-column label="搴忓彿" type="index" width="60" align="center" />
+            <el-table-column prop="deviceName" label="璁惧鍚嶇О" :show-overflow-tooltip="true">
+              <template #default="scope">
+                {{scope.row.qrCode.deviceName}}
+              </template>
+            </el-table-column>
+            <el-table-column prop="location" label="鎵�鍦ㄤ綅缃弿杩�" :show-overflow-tooltip="true">
+              <template #default="scope">
+                {{scope.row.qrCode.location}}
+              </template>
+            </el-table-column>
+            <el-table-column prop="scanner" label="宸℃浜�"></el-table-column>
+            <el-table-column prop="scanTime" label="宸℃鏃堕棿"></el-table-column>
+            <el-table-column fixed="right" label="鎿嶄綔">
+              <template #default="scope">
+                <el-button link type="primary" @click="handleAdd(scope.row)">鏌ョ湅闄勪欢</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <pagination
+            v-if="total>0"
+            :page="pageNum"
+            :limit="pageSize"
+            :total="total"
+            @pagination="handlePagination"
+            :layout="'total, prev, pager, next, jumper'"
+        />
+      </div>
+    </el-card>
+    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
+    <qr-code-dia ref="qrCodeDia" @closeDia="handleQuery"></qr-code-dia>
+    <view-files ref="viewFiles"></view-files>
+    <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files>
+  </div>
+</template>
+
+<script setup>
+import { Delete, Plus } from "@element-plus/icons-vue";
+import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
+
+// 缁勪欢寮曞叆
+import Pagination from "@/components/Pagination/index.vue";
+import ETable from "@/components/Table/ETable.vue";
+import FormDia from "@/views/inspectionManagement/components/formDia.vue";
+import QrCodeDia from "@/views/inspectionManagement/components/qrCodeDia.vue";
+import ViewFiles from "@/views/inspectionManagement/components/viewFiles.vue";
+import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue";
+
+// 鎺ュ彛寮曞叆
+import {
+  delTimingTask,
+  inspectionTaskList,
+  timingTaskList
+} from "@/api/inspectionManagement/index.js";
+import {
+  delQrCode,
+  qrCodeList,
+  qrCodeScanRecordList
+} from "@/api/inspectionUpload/index.js";
+
+// 鍏ㄥ眬鍙橀噺
+const { proxy } = getCurrentInstance();
+const formDia = ref();
+const qrCodeDia = ref();
+const viewFiles = ref();
+const viewQrCodeFiles = ref();
+
+// 鏌ヨ鍙傛暟
+const queryParams = reactive({
+  searchAll: "",
+});
+
+// 鏍囩椤甸厤缃�
+const activeTab = ref("task");
+const tabName = ref("task");
+const tabs = reactive([
+  { name: "task", label: "鐢熶骇宸℃" },
+  { name: "qrCodeScanRecord", label: "鐜板満宸℃璁板綍" },
+]);
+
+// 鍗曢�夋閰嶇疆
+const activeRadio = ref("taskManage");
+const radios = reactive([
+  { name: "taskManage", label: "瀹氭椂浠诲姟绠$悊" },
+  { name: "task", label: "瀹氭椂浠诲姟璁板綍" },
+  { name: "qrCode", label: "浜岀淮鐮佺鐞�" },
+]);
+
+// 琛ㄦ牸鏁版嵁
+const selectedRows = ref([]);
+const tableData = ref([]);
+const operationsArr = ref([]);
+const tableColumns = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
+
+// 鍒楅厤缃�
+const columns = ref([
+  { prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 160 },
+  { prop: "inspectionLocation", label: "鍦扮偣", minWidth: 120 },
+  { prop: "remarks", label: "澶囨敞", minWidth: 150 },
+  { prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 150, slot: "inspector" },
+  {
+    prop: "frequencyType",
+    label: "棰戞",
+    minWidth: 150,
+    formatter: (_, __, val) => ({
+      DAILY: "姣忔棩",
+      WEEKLY: "姣忓懆",
+      MONTHLY: "姣忔湀",
+      QUARTERLY: "瀛e害"
+    }[val] || "")
+  },
+  {
+    prop: "frequencyDetail",
+    label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+    minWidth: 150,
+    formatter: (row, column, cellValue) => {
+      // 鍏堝垽鏂槸鍚︽槸瀛楃涓�
+      if (typeof cellValue !== 'string') return '';
+      let val = cellValue;
+      const replacements = {
+        MON: '鍛ㄤ竴',
+        TUE: '鍛ㄤ簩',
+        WED: '鍛ㄤ笁',
+        THU: '鍛ㄥ洓',
+        FRI: '鍛ㄤ簲',
+        SAT: '鍛ㄥ叚',
+        SUN: '鍛ㄦ棩'
+      };
+      // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
+      return val.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]);
+    }
+  },
+  { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+  { prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 100 },
+]);
+
+const columns1 = ref([
+  { prop: "deviceName", label: "璁惧鍚嶇О", minWidth: 160 },
+  { prop: "location", label: "鎵�鍦ㄤ綅缃弿杩�", minWidth: 120 },
+  { prop: "createBy", label: "鍒涘缓鑰�", minWidth: 100 },
+  { prop: "createTime", label: "鍒涘缓鏃堕棿", minWidth: 100 },
+]);
+
+onMounted(() => {
+  radioChange('taskManage');
+});
+
+// 鏍囩椤电偣鍑讳簨浠�
+const handleTabClick = (tab) => {
+  tabName.value = tab.props.name;
+  tableData.value = [];
+  getList();
+};
+
+// 鍗曢�夊彉鍖�
+const radioChange = (value) => {
+  if (value === "taskManage") {
+    tableColumns.value = columns.value;
+    operationsArr.value = ['edit'];
+  } else if (value === "task") {
+    tableColumns.value = columns.value;
+    operationsArr.value = ['viewFile'];
+  } else {
+    tableColumns.value = columns1.value;
+    operationsArr.value = ['edit'];
+  }
+  pageNum.value = 1;
+  pageSize.value = 10;
+  getList();
+};
+
+// 鏌ヨ鎿嶄綔
+const handleQuery = () => {
+  pageNum.value = 1;
+  pageSize.value = 10;
+  getList();
+};
+// 鍒嗛〉澶勭悊
+const handlePagination = (val) => {
+	pageNum.value = val.page;
+	pageSize.value = val.limit;
+	getList();
+};
+// 鑾峰彇鍒楄〃鏁版嵁
+const getList = () => {
+  tableLoading.value = true;
+  
+  const params = { ...queryParams, size: pageSize.value, current: pageNum.value };
+  
+  let apiCall;
+  if (tabName.value === 'task') {
+    switch (activeRadio.value) {
+      case "task":
+        apiCall = inspectionTaskList(params);
+        break;
+      case "qrCode":
+        apiCall = qrCodeList(params);
+        break;
+      default:
+        apiCall = timingTaskList(params);
+    }
+  } else {
+    apiCall = qrCodeScanRecordList(params);
+  }
+  
+  apiCall.then(res => {
+    const rawData = res.data.records || [];
+    // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
+    tableData.value = rawData.map(item => {
+      const processedItem = { ...item };
+      
+      // 澶勭悊 inspector 瀛楁
+      if (processedItem.inspector) {
+        if (typeof processedItem.inspector === 'string') {
+          // 瀛楃涓叉寜閫楀彿鍒嗗壊
+          processedItem.inspector = processedItem.inspector.split(',').map(s => s.trim()).filter(s => s);
+        } else if (!Array.isArray(processedItem.inspector)) {
+          // 闈炴暟缁勮浆涓烘暟缁�
+          processedItem.inspector = [processedItem.inspector];
+        }
+      } else {
+        // 绌哄�艰涓虹┖鏁扮粍
+        processedItem.inspector = [];
+      }
+      
+      return processedItem;
+    });
+    total.value = res.data.total || 0;
+  }).finally(() => {
+    tableLoading.value = false;
+  });
+};
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+  for (const key in queryParams) {
+    if (!["pageNum", "pageSize"].includes(key)) {
+      queryParams[key] = "";
+    }
+  }
+  handleQuery();
+};
+
+// 鏂板 / 缂栬緫
+const handleAdd = (row) => {
+  const type = row ? 'edit' : 'add';
+  nextTick(() => {
+    if (tabName.value === 'task') {
+      if (activeRadio.value === "taskManage") {
+        formDia.value?.openDialog(type, row);
+      } else if (activeRadio.value === "qrCode") {
+        qrCodeDia.value?.openDialog(type, row);
+      }
+    } else {
+      viewQrCodeFiles.value?.openDialog(row);
+    }
+  });
+};
+
+// 鏌ョ湅闄勪欢
+const viewFile = (row) => {
+  nextTick(() => {
+    viewFiles.value?.openDialog(row);
+  });
+};
+
+// 鍒犻櫎鎿嶄綔
+const handleDelete = () => {
+  if (!selectedRows.value.length) {
+    proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+    return;
+  }
+  
+  const deleteIds = selectedRows.value.map(item => item.id);
+  const api = activeRadio.value === "taskManage" ? delTimingTask : delQrCode;
+  
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�').then(() => {
+    return api(deleteIds);
+  }).then(() => {
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    handleQuery();
+  }).catch(() => {});
+};
+
+// 澶氶�夊彉鏇�
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+</script>
+
+<style scoped>
+.person-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+
+.person-tag {
+  margin-right: 4px;
+  margin-bottom: 2px;
+}
+
+.no-data {
+  color: #909399;
+  font-size: 14px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/reportAnalysis/dataDashboard/index.vue b/src/views/reportAnalysis/dataDashboard/index.vue
index 6fe840b..9848898 100644
--- a/src/views/reportAnalysis/dataDashboard/index.vue
+++ b/src/views/reportAnalysis/dataDashboard/index.vue
@@ -12,6 +12,7 @@
 
       <!-- 椤堕儴鏍囬鏍� -->
       <div class="dashboard-header">
+        <div class="factory-name">{{ userStore.currentFactoryName }}</div>
       </div>
 
       <!-- 涓昏鍐呭鍖哄煙 -->
@@ -241,6 +242,7 @@
 import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
 import autofit from 'autofit.js'
 import Echarts from "@/components/Echarts/echarts.vue";
+import useUserStore from '@/store/modules/user'
 import {
 	analysisCustomerContractAmounts, getAmountHalfYear,
 	homeTodos,
@@ -258,6 +260,9 @@
 
 // 鍏ㄥ睆鐩稿叧鐘舵��
 const isFullscreen = ref(false);
+
+// 鐢ㄦ埛store
+const userStore = useUserStore()
 
 // 鍝嶅簲寮忔暟鎹�
 const currentTime = ref('')
@@ -915,7 +920,7 @@
   // 浣跨敤nextTick纭繚DOM瀹屽叏娓叉煋鍚庡啀鍒濆鍖栧浘琛�
   nextTick(() => {
     // 鍒濆鍖朼utofit鑷�傚簲
-    autofit.init({ dh: 1440, dw: 2560, el: '.data-dashboard', resize: true }, false)
+    autofit.init({ dh: 1080, dw: 1920, el: '.data-dashboard', resize: true }, false)
     
     // 娣诲姞鑷姩婊氬姩鍔ㄧ敾鏁堟灉 - 瀹㈡埛淇℃伅鍒楄〃
     const contractList = refContractList.value
@@ -1044,7 +1049,6 @@
   position: relative;
   width: 100%;
 	height: 100%;
-  overflow: hidden;
 	background-image: url("@/assets/BI/backImage@2x.png");
 	background-size: cover;
 	background-position: center;
@@ -1090,6 +1094,17 @@
 	background-size: cover;
 	background-position: center;
 	background-repeat: no-repeat;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.factory-name {
+  font-weight: 600;
+font-size: 52px;
+color: #FFFFFF;
+top: 32px;
+position: absolute;
 }
 
 .fullscreen-btn {

--
Gitblit v1.9.3