From 2cfc4ce5ec5095aa7be712444c32de12ffa891e3 Mon Sep 17 00:00:00 2001
From: kongdeqiang <123456>
Date: 星期一, 23 三月 2026 11:40:55 +0800
Subject: [PATCH] fix: 更新系统

---
 src/api/statistics.js                   |   10 +
 src/api/unitTask.js                     |   39 +++
 src/views/data/UnitTask.vue             |  307 ++++++++++++++++++++++++++++++
 src/views/statistics/UnitStatistics.vue |  194 +++++++++++++++++++
 4 files changed, 550 insertions(+), 0 deletions(-)

diff --git a/src/api/statistics.js b/src/api/statistics.js
new file mode 100644
index 0000000..f7f483e
--- /dev/null
+++ b/src/api/statistics.js
@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+
+export const getUnitStatisticsPage = (params) => {
+  return request({
+    url: '/data-task/pageNew',
+    method: 'get',
+    params
+  })
+}
diff --git a/src/api/unitTask.js b/src/api/unitTask.js
new file mode 100644
index 0000000..55990fe
--- /dev/null
+++ b/src/api/unitTask.js
@@ -0,0 +1,39 @@
+import request from '@/utils/request'
+
+export const getUnitTaskPage = (params) => {
+  return request({
+    url: '/data-task/page',
+    method: 'get',
+    params
+  })
+}
+
+export const getUnitTaskById = (id) => {
+  return request({
+    url: `/data-task/${id}`,
+    method: 'get'
+  })
+}
+
+export const createUnitTask = (data) => {
+  return request({
+    url: '/data-task',
+    method: 'post',
+    data
+  })
+}
+
+export const updateUnitTask = (data) => {
+  return request({
+    url: '/data-task',
+    method: 'put',
+    data
+  })
+}
+
+export const deleteUnitTask = (id) => {
+  return request({
+    url: `/data-task/${id}`,
+    method: 'delete'
+  })
+}
diff --git a/src/views/data/UnitTask.vue b/src/views/data/UnitTask.vue
new file mode 100644
index 0000000..3db51c6
--- /dev/null
+++ b/src/views/data/UnitTask.vue
@@ -0,0 +1,307 @@
+<template>
+  <div class="unit-task-management">
+    <el-card>
+      <template #header>
+        <div class="card-header">
+          <span>鍗曚綅涓婁紶浠诲姟</span>
+          <el-button type="primary" :icon="Plus" @click="handleAdd">鏂板浠诲姟</el-button>
+        </div>
+      </template>
+
+      <el-form :inline="true" :model="searchForm" class="search-form">
+        <el-form-item label="鍗曚綅">
+          <el-select
+              v-model="searchForm.unitCode"
+              placeholder="璇烽�夋嫨鍗曚綅"
+              clearable
+              filterable
+              style="width: 250px"
+          >
+            <el-option
+                v-for="dept in flatDepartmentList"
+                :key="dept.deptCode"
+                :label="`${dept.deptCode} - ${dept.deptName}`"
+                :value="dept.deptCode"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">鏌ヨ</el-button>
+          <el-button :icon="Refresh" @click="handleReset">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="tableData" v-loading="loading" border stripe>
+        <el-table-column prop="unitCode" label="鍗曚綅缂栫爜" width="150" />
+        <el-table-column prop="unitName" label="鍗曚綅鍚嶇О" width="300" />
+        <el-table-column prop="taskCount" label="浠诲姟鏉℃暟" width="120" />
+        <el-table-column prop="createTime" label="鍒涘缓鏃堕棿" width="300" />
+        <el-table-column prop="updateTime" label="鏇存柊鏃堕棿" width="300" />
+        <el-table-column label="鎿嶄綔" fixed="right" >
+          <template #default="{ row }">
+            <el-button type="primary" link :icon="Edit" @click="handleEdit(row)">缂栬緫</el-button>
+            <el-button type="danger" link :icon="Delete" @click="handleDelete(row)">鍒犻櫎</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        v-model:current-page="pagination.currentPage"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="pagination.total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        style="margin-top: 20px; justify-content: flex-end"
+      />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" @close="handleDialogClose">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="鍗曚綅" prop="unitCode">
+          <el-select
+            v-model="form.unitCode"
+            placeholder="璇烽�夋嫨鍗曚綅"
+            style="width: 100%"
+            filterable
+            @change="handleUnitChange"
+            :disabled="!!form.id"
+          >
+            <el-option
+              v-for="dept in flatDepartmentList"
+              :key="dept.deptCode"
+              :label="`${dept.deptCode} - ${dept.deptName}`"
+              :value="dept.deptCode"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鍗曚綅鍚嶇О" prop="unitName">
+          <el-input v-model="form.unitName" placeholder="閫夋嫨鍗曚綅鍚庤嚜鍔ㄥ甫鍑�" disabled />
+        </el-form-item>
+        <el-form-item label="浠诲姟鏉℃暟" prop="taskCount">
+          <el-input-number v-model="form.taskCount" :min="0" style="width: 100%" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" @click="handleSubmit">纭畾</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus, Edit, Delete,Refresh,Search } from '@element-plus/icons-vue'
+import { getDepartmentTree } from '@/api/department'
+import { getUnitTaskPage, createUnitTask, updateUnitTask, deleteUnitTask } from '@/api/unitTask'
+
+const loading = ref(false)
+const tableData = ref([])
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formRef = ref(null)
+const departmentList = ref([])
+
+const pagination = reactive({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0
+})
+
+const form = reactive({
+  id: null,
+  unitCode: '',
+  unitName: '',
+  taskCount: 0
+})
+
+const searchForm = reactive({
+  unitName: '',
+  unitCode: ''
+})
+
+const rules = {
+  unitCode: [{ required: true, message: '璇烽�夋嫨鍗曚綅', trigger: 'change' }],
+  unitName: [{ required: true, message: '鍗曚綅鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' }],
+  taskCount: [{ required: true, message: '璇疯緭鍏ヤ换鍔℃潯鏁�', trigger: 'blur' }]
+}
+
+const flatDepartmentList = computed(() => {
+  const flatten = (list) => {
+    let result = []
+    list.forEach(item => {
+      result.push(item)
+      if (item.children && item.children.length > 0) {
+        result = result.concat(flatten(item.children))
+      }
+    })
+    return result
+  }
+  return flatten(departmentList.value)
+})
+
+const buildTree = (list) => {
+  const map = {}
+  const roots = []
+
+  list.forEach(item => {
+    map[item.deptCode] = { ...item, children: [] }
+  })
+
+  list.forEach(item => {
+    const parent = map[item.parentCode]
+    if (parent) {
+      parent.children.push(map[item.deptCode])
+    } else {
+      roots.push(map[item.deptCode])
+    }
+  })
+
+  const cleanEmptyChildren = (nodes) => {
+    nodes.forEach(node => {
+      if (node.children && node.children.length === 0) {
+        delete node.children
+      } else {
+        cleanEmptyChildren(node.children)
+      }
+    })
+  }
+
+  cleanEmptyChildren(roots)
+  return roots
+}
+
+const handleSearch = () => {
+  pagination.current = 1
+  fetchData()
+}
+
+const handleReset = () => {
+  searchForm.unitName = ''
+  handleSearch()
+}
+
+const fetchDepartmentList = async () => {
+  try {
+    const res = await getDepartmentTree()
+    departmentList.value = buildTree(res.data)
+  } catch (error) {
+    console.error('鑾峰彇閮ㄩ棬鍒楄〃澶辫触:', error)
+  }
+}
+
+const fetchData = async () => {
+  loading.value = true
+  try {
+    const res = await getUnitTaskPage({
+      page: pagination.currentPage,
+      size: pagination.pageSize,
+      ...searchForm
+    })
+    tableData.value = res.data.records || res.data
+    pagination.total = res.data.total || res.data.length
+  } catch (error) {
+    console.error('鑾峰彇鍗曚綅涓婁紶浠诲姟鍒楄〃澶辫触:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleUnitChange = (value) => {
+  const dept = flatDepartmentList.value.find(item => item.deptCode === value)
+  if (dept) {
+    form.unitName = dept.deptName
+  }
+}
+
+const handleAdd = () => {
+  dialogTitle.value = '鏂板浠诲姟'
+  resetForm()
+  dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+  dialogTitle.value = '缂栬緫浠诲姟'
+  resetForm()
+  Object.assign(form, row)
+  dialogVisible.value = true
+}
+
+const handleDelete = async (row) => {
+  try {
+    await ElMessageBox.confirm('纭畾瑕佸垹闄よ浠诲姟鍚楋紵', '鎻愮ず', {
+      type: 'warning'
+    })
+    await deleteUnitTask(row.id)
+    ElMessage.success('鍒犻櫎鎴愬姛')
+    fetchData()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('鍒犻櫎浠诲姟澶辫触:', error)
+    }
+  }
+}
+
+const handleSubmit = async () => {
+  if (!formRef.value) return
+
+  await formRef.value.validate(async (valid) => {
+    if (valid) {
+      try {
+        if (form.id) {
+          await updateUnitTask(form)
+          ElMessage.success('淇敼鎴愬姛')
+        } else {
+          await createUnitTask(form)
+          ElMessage.success('鏂板鎴愬姛')
+        }
+        dialogVisible.value = false
+        fetchData()
+      } catch (error) {
+        console.error('淇濆瓨浠诲姟澶辫触:', error)
+      }
+    }
+  })
+}
+
+const handleDialogClose = () => {
+  formRef.value?.resetFields()
+}
+
+const resetForm = () => {
+  form.id = null
+  form.unitCode = ''
+  form.unitName = ''
+  form.taskCount = 0
+}
+
+const handleSizeChange = (val) => {
+  pagination.pageSize = val
+  fetchData()
+}
+
+const handleCurrentChange = (val) => {
+  pagination.currentPage = val
+  fetchData()
+}
+
+onMounted(() => {
+  fetchDepartmentList()
+  fetchData()
+})
+</script>
+
+<style scoped>
+.unit-task-management {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+</style>
diff --git a/src/views/statistics/UnitStatistics.vue b/src/views/statistics/UnitStatistics.vue
new file mode 100644
index 0000000..343541b
--- /dev/null
+++ b/src/views/statistics/UnitStatistics.vue
@@ -0,0 +1,194 @@
+<template>
+  <div class="unit-statistics">
+    <el-card>
+      <template #header>
+        <div class="card-header">
+          <span>鍚勫崟浣嶆暟鎹粺璁�</span>
+        </div>
+      </template>
+
+      <el-form :inline="true" :model="searchForm" class="search-form">
+        <el-form-item label="鍗曚綅">
+          <el-select
+            v-model="searchForm.unitCode"
+            placeholder="璇烽�夋嫨鍗曚綅"
+            clearable
+            filterable
+            style="width: 250px"
+          >
+            <el-option
+              v-for="dept in flatDepartmentList"
+              :key="dept.deptCode"
+              :label="`${dept.deptCode} - ${dept.deptName}`"
+              :value="dept.deptCode"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">鎼滅储</el-button>
+          <el-button :icon="Refresh" @click="handleReset">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="tableData" v-loading="loading" border stripe>
+        <el-table-column prop="unitCode" label="鍗曚綅缂栫爜" width="150" />
+        <el-table-column prop="unitName" label="鍗曚綅鍚嶇О" width="300" />
+        <el-table-column prop="taskCount" label="浠诲姟鏉℃暟" width="120" />
+        <el-table-column prop="successCount" label="瀹屾垚鏉℃暟" width="120" />
+        <el-table-column prop="status" label="瀹屾垚鐘舵��" width="100">
+          <template #default="{ row }">
+            <el-tag :type="row.status === 1 ? 'success' : 'warning'">
+              {{ row.status === 1 ? '宸插畬鎴�' : '鏈畬鎴�' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="lastTime" label="鏈�鍚庝笂浼犳椂闂�" />
+      </el-table>
+
+      <el-pagination
+        v-model:current-page="pagination.currentPage"
+        v-model:page-size="pagination.pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="pagination.total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        style="margin-top: 20px; justify-content: flex-end"
+      />
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { Search, Refresh } from '@element-plus/icons-vue'
+import { getDepartmentTree } from '@/api/department'
+import { getUnitStatisticsPage } from '@/api/statistics'
+
+const loading = ref(false)
+const tableData = ref([])
+const departmentList = ref([])
+
+const searchForm = reactive({
+  unitCode: ''
+})
+
+const pagination = reactive({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0
+})
+
+const flatDepartmentList = computed(() => {
+  const flatten = (list) => {
+    let result = []
+    list.forEach(item => {
+      result.push(item)
+      if (item.children && item.children.length > 0) {
+        result = result.concat(flatten(item.children))
+      }
+    })
+    return result
+  }
+  return flatten(departmentList.value)
+})
+
+const buildTree = (list) => {
+  const map = {}
+  const roots = []
+
+  list.forEach(item => {
+    map[item.deptCode] = { ...item, children: [] }
+  })
+
+  list.forEach(item => {
+    const parent = map[item.parentCode]
+    if (parent) {
+      parent.children.push(map[item.deptCode])
+    } else {
+      roots.push(map[item.deptCode])
+    }
+  })
+
+  const cleanEmptyChildren = (nodes) => {
+    nodes.forEach(node => {
+      if (node.children && node.children.length === 0) {
+        delete node.children
+      } else {
+        cleanEmptyChildren(node.children)
+      }
+    })
+  }
+
+  cleanEmptyChildren(roots)
+  return roots
+}
+
+const fetchDepartmentList = async () => {
+  try {
+    const res = await getDepartmentTree()
+    departmentList.value = buildTree(res.data)
+  } catch (error) {
+    console.error('鑾峰彇閮ㄩ棬鍒楄〃澶辫触:', error)
+  }
+}
+
+const fetchData = async () => {
+  loading.value = true
+  try {
+    const res = await getUnitStatisticsPage({
+      page: pagination.currentPage,
+      size: pagination.pageSize,
+      unitCode: searchForm.unitCode
+    })
+    tableData.value = res.data.records || res.data
+    pagination.total = res.data.total || res.data.length
+  } catch (error) {
+    console.error('鑾峰彇缁熻鏁版嵁澶辫触:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleSearch = () => {
+  pagination.currentPage = 1
+  fetchData()
+}
+
+const handleReset = () => {
+  searchForm.unitCode = ''
+  pagination.currentPage = 1
+  fetchData()
+}
+
+const handleSizeChange = (val) => {
+  pagination.pageSize = val
+  fetchData()
+}
+
+const handleCurrentChange = (val) => {
+  pagination.currentPage = val
+  fetchData()
+}
+
+onMounted(() => {
+  fetchDepartmentList()
+  fetchData()
+})
+</script>
+
+<style scoped>
+.unit-statistics {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.search-form {
+  margin-bottom: 20px;
+}
+</style>

--
Gitblit v1.9.1