yeg
2026-09-09 e2c104fdff2df9b35c267652c4849571e5a4b2ca
完善首页统计与待办负责人权限
2个文件已修改
83 ■■■■■ 已修改文件
src/views/Dashboard.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/TodoList.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Dashboard.vue
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
@@ -7,6 +7,9 @@
import { GridComponent, TooltipComponent } from 'echarts/components'
import VChart from 'vue-echarts'
import { useTests, type TestRecord } from '@/stores/tests'
import { useTodos } from '@/stores/todos'
import { getTestList } from '@/api/tests'
import { getBatteryGroupList } from '@/api/batteryGroups'
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent])
@@ -14,6 +17,21 @@
const chartPeriod = ref('week')
const router = useRouter()
const { records } = useTests()
const { records: todoRecords } = useTodos()
onMounted(async () => {
  try {
    const [tests, groups] = await Promise.all([
      getTestList({ pageNum: 1, pageSize: 1000 }),
      getBatteryGroupList({ pageNum: 1, pageSize: 1000 }),
    ])
    records.value = tests.rows
    // 首页的电池组总数来自后端列表,避免用页面种子推断业务事实。
    batteryGroupCount.value = groups.total
  } catch {
    // 页面保留当前已加载数据,错误提示由全局请求层处理。
  }
})
const batteryGroupCount = ref(0)
const testFilterVisible = ref(false)
const testType = ref('全部')
const testTypeDraft = ref('全部')
@@ -49,31 +67,33 @@
const runningTestCount = computed(() => records.value.filter(item => item.status === '检测中').length)
const pendingTestCount = computed(() => records.value.filter(item => item.status === '待测试').length)
const cabinetStatusSummary = [
  { label: '在线', value: 18, color: '#23865e' },
  { label: '离线', value: 18, color: '#8795a3' },
  { label: '待测试', value: 8, color: '#b77a24' },
  { label: '进行中', value: 12, color: '#2188f5' },
  { label: '暂停', value: 0, color: '#b34f4f' },
]
const cabinetStatusSummary = computed(() => [
  { label: '在线', value: cabinets.filter(item => item.status === '在线').length, color: '#23865e' },
  { label: '离线', value: cabinets.filter(item => item.status === '离线').length, color: '#8795a3' },
  { label: '待测试', value: records.value.filter(item => item.status === '待测试').length, color: '#b77a24' },
  { label: '进行中', value: records.value.filter(item => item.status === '检测中').length, color: '#2188f5' },
  { label: '暂停', value: records.value.filter(item => item.status === '暂停').length, color: '#b34f4f' },
])
const todoList = [
  { title: '高温循环测试', time: '09:14 · 待处理', priority: 'P1' },
  { title: '电芯容量测试', time: '10:18 · 待处理', priority: 'P2' },
  { title: '低温充放电测试', time: '11:06 · 待处理', priority: 'P1' },
  { title: 'A-03 设备校准', time: '12:00 · 待处理', priority: 'P2' },
  { title: '数据修订复核', time: '14:05 · 待处理', priority: 'P1' },
  { title: '异常记录确认', time: '14:28 · 待处理', priority: 'P1' },
]
const todoList = computed(() => todoRecords.value.filter(item => item.status === '待处理').slice(0, 6))
const periodData = {
  week: { labels: ['07/04', '07/05', '07/06', '07/07', '07/08', '07/09', '07/10'], values: [312, 348, 320, 401, 376, 428, 392] },
  month: { labels: ['第 1 周', '第 2 周', '第 3 周', '第 4 周'], values: [1460, 1580, 1720, 1640] },
  year: { labels: ['1月', '3月', '5月', '7月', '9月', '11月'], values: [5200, 6480, 7320, 8874, 9120, 9840] },
}
const periodData = computed(() => {
  const now = new Date()
  const dayKey = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
  const count = (key: string) => records.value.filter(item => item.createdAt.slice(0, key.length) === key).length
  const weekLabels: string[] = []; const weekValues: number[] = []
  for (let index = 6; index >= 0; index -= 1) { const date = new Date(now); date.setDate(now.getDate() - index); const key = dayKey(date); weekLabels.push(`${date.getMonth() + 1}/${date.getDate()}`); weekValues.push(count(key)) }
  const monthKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
  const monthDays = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate(); const monthLabels: string[] = []; const monthValues: number[] = []
  for (let day = 1; day <= Math.min(now.getDate(), monthDays); day += 1) { const key = `${monthKey}-${String(day).padStart(2, '0')}`; monthLabels.push(`${day}日`); monthValues.push(count(key)) }
  const yearLabels: string[] = []; const yearValues: number[] = []
  for (let month = 1; month <= now.getMonth() + 1; month += 1) { const key = `${now.getFullYear()}-${String(month).padStart(2, '0')}`; yearLabels.push(`${month}月`); yearValues.push(count(key)) }
  return { week: { labels: weekLabels, values: weekValues }, month: { labels: monthLabels, values: monthValues }, year: { labels: yearLabels, values: yearValues } }
})
const chartOption = computed(() => {
  const data = periodData[chartPeriod.value as keyof typeof periodData]
  const data = periodData.value[chartPeriod.value as keyof typeof periodData.value]
  const values = data.values
  return {
    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
    grid: { left: 8, right: 10, bottom: 4, top: 22, containLabel: true },
@@ -88,7 +108,7 @@
    },
    series: [{
      type: 'bar', barMaxWidth: 28,
      data: data.values.map((value, index) => ({ value, itemStyle: { color: index === data.values.length - 1 ? '#2188f5' : '#b9d8f8' } })),
      data: values.map((value, index) => ({ value, itemStyle: { color: index === values.length - 1 ? '#2188f5' : '#b9d8f8' } })),
      itemStyle: { borderRadius: 0 },
    }],
  }
@@ -154,10 +174,10 @@
        </section>
        <div class="metrics">
          <article><span>总测试数</span><small>+12.4%</small><strong>373</strong></article>
          <article><span>总电池组数</span><small>+4.2%</small><strong>368</strong></article>
          <article><span>日新增测试</span><small>+18.7%</small><strong>8,874</strong></article>
          <article><span>月新增测试</span><small>+9.6%</small><strong>8,874</strong></article>
          <article><span>总测试数</span><strong>{{ records.length }}</strong></article>
          <article><span>总电池组数</span><strong>{{ batteryGroupCount }}</strong></article>
          <article><span>日新增测试</span><strong>{{ records.filter(item => item.createdAt.slice(0, 10) === new Date().toISOString().slice(0, 10)).length }}</strong></article>
          <article><span>月新增测试</span><strong>{{ records.filter(item => item.createdAt.slice(0, 7) === new Date().toISOString().slice(0, 7)).length }}</strong></article>
        </div>
        <section class="panel chart-panel">
@@ -183,8 +203,8 @@
        </section>
        <section class="panel todo-panel">
          <header class="panel-header"><div class="panel-title"><i></i><h3>待办 (18)</h3></div><button class="text-action" type="button" @click="router.push('/todo-list')">全部</button></header>
          <div class="todo-list"><div v-for="item in todoList" :key="item.title"><span><strong>{{ item.title }}</strong><small>{{ item.time }}</small></span><b :class="item.priority.toLowerCase()">{{ item.priority }}</b></div></div>
          <header class="panel-header"><div class="panel-title"><i></i><h3>待办 ({{ todoRecords.filter(item => item.status === '待处理').length }})</h3></div><button class="text-action" type="button" @click="router.push('/todo-list')">全部</button></header>
          <div class="todo-list"><div v-for="item in todoList" :key="item.id"><span><strong>{{ item.content }}</strong><small>{{ item.operatedAt }} · 待处理</small></span><b class="pending">待处理</b></div></div>
        </section>
      </aside>
    </div>
src/views/TodoList.vue
@@ -13,6 +13,7 @@
const owners = ['张工', '李工', '王工', '周工', '赵工', '陈工', 'Admin']
const statuses: TodoStatus[] = ['待处理', '已处理']
const { records, createTodo, handleTodo, removeTodo } = useTodos()
const currentOwner = computed(() => getCurrentUser()?.displayName || getCurrentUser()?.username || 'Admin')
const filters = reactive<FilterState>({ owner: '', status: '', dateRange: null })
const appliedFilters = ref<FilterState>({ owner: '', status: '', dateRange: null })
const currentPage = ref(1)
@@ -23,6 +24,7 @@
const filteredRecords = computed(() => {
  const query = appliedFilters.value
  return records.value
    .filter(item => item.owner === currentOwner.value)
    .filter(item => !query.owner || query.owner === '全部' || item.owner === query.owner)
    .filter(item => !query.status || query.status === '全部' || item.status === query.status)
    .filter(item => !query.dateRange || (item.operatedAt.slice(0, 10) >= query.dateRange[0] && item.operatedAt.slice(0, 10) <= query.dateRange[1]))
@@ -62,7 +64,7 @@
}
async function processTodo(item: TodoRecord) {
  if (item.status === '已处理') return
  if (item.status === '已处理' || item.owner !== currentOwner.value) return
  try {
    const result = await ElMessageBox.prompt(`请填写“${item.content}”的处理说明。`, '处理待办', {
      confirmButtonText: '完成处理', cancelButtonText: '取消', inputType: 'textarea', inputPlaceholder: '请输入处理说明',
@@ -80,6 +82,7 @@
    await ElMessageBox.confirm(`删除待办“${item.content}”后无法恢复,是否继续?`, '确认删除待办', {
      confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning', confirmButtonClass: 'danger-confirm',
    })
    if (item.owner !== currentOwner.value) return
    removeTodo(item.id)
    currentPage.value = Math.min(currentPage.value, Math.max(1, Math.ceil(filteredRecords.value.length / pageSize.value)))
    ElMessage.success('待办已删除')