From 56cccf63a0748c0cf48237bf935fb04e500a4eea Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期六, 28 二月 2026 21:00:22 +0800
Subject: [PATCH] feat: 排版计划接口对接、任务工单详情接口对接

---
 src/service/api/attendance.ts                                              |   24 
 src/views/operations/attendance/plan/components/EditMonthModal.vue         |   24 
 src/service/api/transaction.ts                                             |    5 
 src/views/operations/taskOrder/taskOrderCenter/index.scss                  |   41 +
 src/views/operations/taskOrder/taskOrderCenter/index.vue                   |   63 +
 src/views/operations/attendance/plan/index.vue                             |  362 ++++++-----
 service.config.ts                                                          |    2 
 src/views/operations/attendance/plan/copy.vue                              |  246 +++++--
 src/views/operations/attendance/plan/edit.vue                              |  394 +++++++-----
 src/views/operations/taskOrder/taskOrderCenter/components/AddFormModal.vue |    4 
 src/styles/index.css                                                       |    1 
 src/views/operations/attendance/plan/components/CopyScheduleModal.vue      |   48 -
 src/views/operations/taskOrder/taskOrderCenter/components/detailModal.vue  |  465 +++++++++++++++
 src/views/transaction/order/orderManagement/components/TableModal.vue      |   12 
 src/views/operations/attendance/plan/components/BatchSetModal.vue          |   70 +-
 src/views/operations/attendance/statistics/index.vue                       |   13 
 16 files changed, 1,260 insertions(+), 514 deletions(-)

diff --git a/service.config.ts b/service.config.ts
index 6c3cc32..41a3b1a 100644
--- a/service.config.ts
+++ b/service.config.ts
@@ -3,7 +3,7 @@
   dev: {
     // url: 'http://localhost:3000',
     url: 'http://221.182.45.100:8000',
-    // url: 'http://221.182.45.100:8000',
+    // url: 'http://192.168.110.80:8000',
   },
   production: {
     url: 'https://mock.apifox.cn/m1/4071143-0-default',
diff --git a/src/service/api/attendance.ts b/src/service/api/attendance.ts
index 0f9563a..4f7318b 100644
--- a/src/service/api/attendance.ts
+++ b/src/service/api/attendance.ts
@@ -95,6 +95,30 @@
   return method
 }
 
+/** 排班管理 */
+
+// 排班管理
+export function getSchedulingPageList(params: any) {
+  return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/scheduling/pageList', params)
+}
+
+// 导出排班
+export function exportScheduling(params: any) {
+  const method = request.Get<Service.ResponseResult<any>>('/maintenance/api/v1/scheduling/exportExcel?month=' + params.month, {}, { responseType: 'blob' })
+  method.meta = { ...method.meta, isBlob: true }
+  return method
+}
+
+// 保存排班
+export function saveScheduling(params: any) {
+  return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/scheduling/saveSchedulingDetail', params)
+}
+
+
+// 获取班次不分页列表
+export function getShiftListNoPage() {
+  return request.Get<Service.ResponseResult<any>>('/maintenance/xp-shift/api/v1/shift/queryList')
+}
 
 /** 运维人员管理 */
 
diff --git a/src/service/api/transaction.ts b/src/service/api/transaction.ts
index bcabb18..b9300da 100644
--- a/src/service/api/transaction.ts
+++ b/src/service/api/transaction.ts
@@ -40,6 +40,11 @@
   return request.Post<Service.ResponseResult<any>>('/vehicleConvoy/api/v1/fleet/list')
 }
 
+// 获取可用车辆列表
+export function getAvailableVehicleList(orderId: any) {
+  return request.Get<Service.ResponseResult<any>>('/vehicleConvoy/api/v1/vehicle/availableList?orderId=' + orderId)
+}
+
 /** 订单评价 */
 
 // 获取订单评价列表
diff --git a/src/styles/index.css b/src/styles/index.css
index 5c75a34..6a21caf 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -6,6 +6,7 @@
 body,
 #app {
   height: 100%;
+  background-color: #fff;
 }
 .color-weak {
   filter: invert(80%);
diff --git a/src/views/operations/attendance/plan/components/BatchSetModal.vue b/src/views/operations/attendance/plan/components/BatchSetModal.vue
index cd40bae..5a21f1f 100644
--- a/src/views/operations/attendance/plan/components/BatchSetModal.vue
+++ b/src/views/operations/attendance/plan/components/BatchSetModal.vue
@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import { ref, computed } from 'vue'
 import type { FormInst, FormRules } from 'naive-ui'
+import dayjs from 'dayjs'
 
 interface ShiftOption {
   name: string
@@ -15,6 +16,9 @@
 
 const props = defineProps<{
   show: boolean
+  teamOptions: any
+  shifts: any
+  selectedMonth: string | null
 }>()
 
 const emit = defineEmits<{
@@ -30,19 +34,20 @@
 const formRef = ref<FormInst | null>(null)
 const formModel = ref<FormModel>({
   team: null,
-  shifts: [
-    { name: '早', checked: false, dateRange: null },
-    { name: '中', checked: false, dateRange: null },
-    { name: '晚', checked: false, dateRange: null },
-    { name: '全天', checked: false, dateRange: null }
-  ]
+  shifts: []
 })
 
 // 生成1-31的日期选项
-const dateOptions = Array.from({ length: 31 }, (_, i) => ({
-  label: `${i + 1}`,
-  value: i + 1
-}))
+const dateOptions = computed(() => {
+  if (!props.selectedMonth) return []
+
+  const days = dayjs(props.selectedMonth).daysInMonth()
+
+  return Array.from({ length: days }, (_, i) => ({
+    label: `${i + 1}`,
+    value: i + 1
+  }))
+})
 
 const rules: FormRules = {
   team: [
@@ -51,11 +56,7 @@
 }
 
 // 模拟队伍数据
-const teamOptions = [
-  { label: '广州白云一队', value: '广州白云一队' },
-  { label: '广州白云二队', value: '广州白云二队' },
-  { label: '广州南沙三队', value: '广州南沙三队' }
-]
+const teamOptions = computed(() => props.teamOptions)
 
 const handleCancel = () => {
   show.value = false
@@ -76,22 +77,28 @@
 const resetForm = () => {
   formModel.value = {
     team: null,
-    shifts: [
-      { name: '早', checked: false, dateRange: null },
-      { name: '中', checked: false, dateRange: null },
-      { name: '晚', checked: false, dateRange: null },
-      { name: '全天', checked: false, dateRange: null }
-    ]
+    shifts: []
   }
 }
+
+watch(() => show.value, (newShow) => {
+  if (newShow) {
+    formModel.value.shifts = props.shifts.map((shift: any) => ({
+      ...shift,
+      name: shift.shiftName,
+      checked: false,
+      dateRange: null
+    }))
+  }
+})
 </script>
 
 <template>
-  <n-modal 
-    v-model:show="show" 
-    :mask-closable="false" 
-    preset="card" 
-    title="批量设置" 
+  <n-modal
+    v-model:show="show"
+    :mask-closable="false"
+    preset="card"
+    title="批量设置"
     class="w-500px"
     :segmented="{
       content: true,
@@ -106,17 +113,17 @@
       show-require-mark
     >
       <n-form-item label="队伍" path="team">
-        <n-select 
-          v-model:value="formModel.team" 
+        <n-select
+          v-model:value="formModel.team"
           :options="teamOptions"
           placeholder="选择"
         />
       </n-form-item>
 
       <n-form-item label="班次" path="shifts">
-        <div class="w-full flex flex-col gap-3">
-          <div 
-            v-for="shift in formModel.shifts" 
+        <div class="w-full flex flex-col gap-3" v-if="formModel.shifts.length > 0">
+          <div
+            v-for="shift in formModel.shifts"
             :key="shift.name"
             class="flex items-center gap-3"
           >
@@ -130,6 +137,7 @@
               :options="dateOptions"
               placeholder="选择"
               multiple
+              :max-tag-count="5"
               style="flex: 1"
             />
           </div>
diff --git a/src/views/operations/attendance/plan/components/CopyScheduleModal.vue b/src/views/operations/attendance/plan/components/CopyScheduleModal.vue
index f65232e..1ed8e00 100644
--- a/src/views/operations/attendance/plan/components/CopyScheduleModal.vue
+++ b/src/views/operations/attendance/plan/components/CopyScheduleModal.vue
@@ -7,7 +7,6 @@
 
 interface FormModel {
   month: number | null
-  role: string | null
 }
 
 const props = defineProps<{
@@ -26,25 +25,14 @@
 
 const formRef = ref<FormInst | null>(null)
 const formModel = ref<FormModel>({
-  month: null,
-  role: null
+  month: null
 })
 
 const rules: FormRules = {
   month: [
     { required: true, type: 'number', message: '请选择排班月份', trigger: 'change' }
-  ],
-  role: [
-    { required: true, message: '请选择角色', trigger: 'change' }
   ]
 }
-
-// 模拟角色数据
-const roleOptions = [
-  { label: '网格员', value: '网格员' },
-  { label: '管理员', value: '管理员' },
-  { label: '巡查员', value: '巡查员' }
-]
 
 const handleCancel = () => {
   show.value = false
@@ -54,15 +42,14 @@
 const handleConfirm = async () => {
   try {
     await formRef.value?.validate()
-    if (formModel.value.month && formModel.value.role) {
+    if (formModel.value.month) {
       emit('confirm', formModel.value)
       show.value = false
       // 跳转到复制页面,携带月份和角色参数
       router.push({
         path: '/operations/attendance/plan/copy',
-        query: { 
+        query: {
           month: String(formModel.value.month),
-          role: formModel.value.role
         }
       })
       resetForm()
@@ -74,18 +61,17 @@
 
 const resetForm = () => {
   formModel.value = {
-    month: null,
-    role: null
+    month: null
   }
 }
 </script>
 
 <template>
-  <n-modal 
-    v-model:show="show" 
-    :mask-closable="false" 
-    preset="card" 
-    title="复制排班" 
+  <n-modal
+    v-model:show="show"
+    :mask-closable="false"
+    preset="card"
+    title="复制排班"
     class="w-500px"
     :segmented="{
       content: true,
@@ -100,20 +86,12 @@
       show-require-mark
     >
       <n-form-item label="排班月份" path="month">
-        <n-date-picker 
-          v-model:value="formModel.month" 
-          type="month" 
+        <n-date-picker
+          v-model:value="formModel.month"
+          type="month"
           placeholder="请选择月份"
           style="width: 100%"
           format="yyyy-MM"
-        />
-      </n-form-item>
-
-      <n-form-item label="角色" path="role">
-        <n-select 
-          v-model:value="formModel.role" 
-          :options="roleOptions"
-          placeholder="请选择角色"
         />
       </n-form-item>
     </n-form>
@@ -121,7 +99,7 @@
     <template #action>
       <n-space justify="end">
         <n-button @click="handleCancel">取消</n-button>
-        <n-button type="primary" @click="handleConfirm">排班</n-button>
+        <n-button type="primary" @click="handleConfirm">复制</n-button>
       </n-space>
     </template>
   </n-modal>
diff --git a/src/views/operations/attendance/plan/components/EditMonthModal.vue b/src/views/operations/attendance/plan/components/EditMonthModal.vue
index 4f2e7bf..b442e1e 100644
--- a/src/views/operations/attendance/plan/components/EditMonthModal.vue
+++ b/src/views/operations/attendance/plan/components/EditMonthModal.vue
@@ -31,10 +31,10 @@
 
 const rules: FormRules = {
   month: [
-    { 
-      required: true, 
+    {
+      required: true,
       type: 'number',
-      message: '请选择排班月份', 
+      message: '请选择排班月份',
       trigger: 'change'
     }
   ]
@@ -77,11 +77,11 @@
 </script>
 
 <template>
-  <n-modal 
-    v-model:show="show" 
-    :mask-closable="false" 
-    preset="card" 
-    title="编辑排班" 
+  <n-modal
+    v-model:show="show"
+    :mask-closable="false"
+    preset="card"
+    title="编辑排班"
     class="w-500px"
     :segmented="{
       content: true,
@@ -96,9 +96,9 @@
       show-require-mark
     >
       <n-form-item label="排班月份" path="month">
-        <n-date-picker 
-          v-model:value="formModel.month" 
-          type="month" 
+        <n-date-picker
+          v-model:value="formModel.month"
+          type="month"
           placeholder="请选择月份"
           style="width: 100%"
           format="yyyy-MM"
@@ -109,7 +109,7 @@
     <template #action>
       <n-space justify="end">
         <n-button @click="handleCancel">取消</n-button>
-        <n-button type="primary" @click="handleConfirm">保存</n-button>
+        <n-button type="primary" @click="handleConfirm">排班</n-button>
       </n-space>
     </template>
   </n-modal>
diff --git a/src/views/operations/attendance/plan/copy.vue b/src/views/operations/attendance/plan/copy.vue
index 3ea9729..1b5f5be 100644
--- a/src/views/operations/attendance/plan/copy.vue
+++ b/src/views/operations/attendance/plan/copy.vue
@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import { ref, onMounted, onUnmounted, nextTick } from 'vue'
 import { useRoute } from 'vue-router'
+import { getSchedulingPageList, getShiftListNoPage, saveScheduling } from '@/service/api/attendance'
+import dayjs from 'dayjs'
 
 interface ShiftTag {
   name: string
@@ -15,17 +17,22 @@
 }
 
 const route = useRoute()
+const total = ref(0)
+const pageParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+})
 
-const currentDate = ref('2024/12/01')
-const selectedMonth = ref<number>(new Date(2024, 11, 1).valueOf())
-const selectedRole = ref<string>('')
+const selectedMonth = ref()
 
-const shiftTags = ref<ShiftTag[]>([
-  { name: '全班', time: '08:00-16:00', color: '#0068FF', bgColor: '#E6F4FF' },
-  { name: '早班', time: '08:00-20:00', color: '#16A60C', bgColor: '#DCF5DD' },
-  { name: '中班', time: '16:00-24:00', color: '#FF9000', bgColor: '#FFF8E6' },
-  { name: '晚班', time: '19:00-24:00', color: '#764DFF', bgColor: '#F6F0FF' }
-])
+const shiftTags = ref<any[]>([])
+
+const dayKeyMap = [
+  'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
+  'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty',
+  'twentyOne', 'twentyTwo', 'twentyThree', 'twentyFour', 'twentyFive', 'twentySix', 'twentySeven',
+  'twentyEight', 'twentyNine', 'thirty', 'thirtyOne'
+]
 
 // 生成日期列表
 const generateDates = (year: number, month: number) => {
@@ -43,33 +50,18 @@
   return dates
 }
 
-const dates = ref(generateDates(2024, 11))
+const dates = ref()
 
-const teamSchedules = ref<TeamSchedule[]>([
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云二队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州南沙三队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) }
-])
-
-// 初始化一些示例数据
-teamSchedules.value[0].schedules[3] = [shiftTags.value[0]]
-teamSchedules.value[1].schedules[1] = [shiftTags.value[1]]
+const teamSchedules = ref<any[]>([])
 
 const selectedCell = ref<{ teamIndex: number; dateIndex: number } | null>(null)
-const selectedShifts = ref<ShiftTag[]>([])
+const selectedShifts = ref<any[]>([])
 const selectorPosition = ref({ top: 0, left: 0 })
 
 const updateDates = () => {
   const date = new Date(selectedMonth.value)
   const year = date.getFullYear()
   const month = date.getMonth()
-  currentDate.value = `${year}/${String(month + 1).padStart(2, '0')}/01`
   dates.value = generateDates(year, month)
 
   // 重新初始化排班数据
@@ -87,7 +79,24 @@
 }
 
 const handleSave = () => {
-  console.log('保存')
+  const params = {
+    month: selectedMonth.value,
+    datas: teamSchedules.value.map((item: any) => ({
+      fleetId: item.fleetId,
+      days: item.schedules.map((shifts: any[], index: number) => {
+        return {
+          dayTime: `${dayjs(selectedMonth.value).year()}-${dayjs(selectedMonth.value).month() + 1 > 9 ? dayjs(selectedMonth.value).month() + 1 : '0' + (dayjs(selectedMonth.value).month() + 1)}-${index + 1 > 9 ? index + 1 : '0' + (index + 1)}`,
+          shiftId: Number(shifts[0]?.id) ? Number(shifts[0]?.id) : null
+        }
+      })
+    }))
+  }
+  saveScheduling(params).then(res => {
+    if (res.code === 200) {
+      window.$message.success('排班保存成功')
+      getList()
+    }
+  })
 }
 
 const getShiftCount = (schedules: ShiftTag[][], shiftName: string) => {
@@ -100,11 +109,11 @@
 
 const showScheduleModal = ref(false)
 
-const handleCellClick = (teamIndex: number, dateIndex: number, event: MouseEvent) => {
+const handleCellClick = (teamIndex: number, dateIndex: number | string , event: MouseEvent) => {
   event.stopPropagation() // 阻止事件冒泡
 
-  selectedCell.value = { teamIndex, dateIndex }
-  const currentShifts = teamSchedules.value[teamIndex].schedules[dateIndex]
+  selectedCell.value = { teamIndex, dateIndex: Number(dateIndex) }
+  const currentShifts = teamSchedules.value[teamIndex].schedules[Number(dateIndex)]
   selectedShifts.value = currentShifts ? [...currentShifts] : []
 
   // 计算盒子位置
@@ -129,13 +138,23 @@
   })
 }
 
-const handleShiftToggle = (shift: ShiftTag) => {
+const handleShiftToggle = (shift: any) => {
+  shift.name = shift.shiftName
   // 单选模式:直接设置为选中的班次
   selectedShifts.value = [shift]
 
   // 立即更新到表格
   if (selectedCell.value) {
     teamSchedules.value[selectedCell.value.teamIndex].schedules[selectedCell.value.dateIndex] = [shift]
+    teamSchedules.value[selectedCell.value.teamIndex].subtotal = teamSchedules.value[selectedCell.value.teamIndex].subtotal.map((item: any) => {
+      if (item.shiftId === shift.id) {
+        return {
+          ...item,
+          count: Number(item.count) + 1
+        }
+      }
+      return item
+    })
   }
 
   // 选择后直接关闭
@@ -159,6 +178,61 @@
   selectedCell.value = null
 }
 
+async function getList() {
+  const params = {
+    ...pageParams.value,
+    month: selectedMonth.value,
+  }
+  try {
+    const res = await getSchedulingPageList(params)
+    if (res.code === 200) {
+      const records = res.data.records || []
+      const date = dayjs(selectedMonth.value)
+      const daysInMonth = date.daysInMonth()
+      teamSchedules.value = records.map((item: any) => {
+        const schedules: any[] = []
+        for (let i = 0; i < daysInMonth; i++) {
+          const key = dayKeyMap[i]
+          const dayData = item[key]
+          if (dayData && dayData.shiftName) {
+            schedules.push([
+              {
+                id: dayData.shiftId,
+                name: dayData.shiftName,
+                color: dayData.shiftColor,
+                bgColor: dayData.shiftColor + '25',
+              }
+            ])
+          } else {
+            schedules.push([])
+          }
+        }
+        return {
+          fleetId: item.fleetId,
+          team: item.fleetName,
+          schedules,
+          subtotal: item.subtotal || []
+        }
+      })
+      total.value = res.data.total || teamSchedules.value.length
+    }
+  } finally {
+  }
+}
+
+const getShiftList = async () => {
+  const res = await getShiftListNoPage()
+  if (res.code === 200) {
+    shiftTags.value = res.data.map((item: any) => ({
+      id: item.id,
+      shiftName: item.shiftName,
+      time: `${item.startTime}-${item.endTime}`,
+      color: item.shiftColor,
+      bgColor: item.shiftColor + '25'
+    }))
+  }
+}
+
 onMounted(() => {
   document.addEventListener('click', handleClickOutside)
 
@@ -167,22 +241,23 @@
   if (monthParam) {
     const monthValue = typeof monthParam === 'string' ? Number(monthParam) : Array.isArray(monthParam) ? Number(monthParam[0]) : monthParam
     if (monthValue && !isNaN(monthValue)) {
-      selectedMonth.value = monthValue
+      selectedMonth.value = dayjs(monthValue).format('YYYY-MM')
       updateDates()
     }
   }
-
-  // 从路由参数读取角色
-  const roleParam = route.query.role
-  if (roleParam) {
-    selectedRole.value = typeof roleParam === 'string' ? roleParam : Array.isArray(roleParam) ? (roleParam[0] || '') : ''
-  }
+  getShiftList()
+  getList()
 })
 
 onUnmounted(() => {
   document.removeEventListener('click', handleClickOutside)
 })
 
+function changePage(page: number, size: number) {
+  pageParams.value.pageNum = page
+  pageParams.value.pageSize = size
+  getList()
+}
 </script>
 
 <template>
@@ -190,15 +265,12 @@
     <n-card style="border-radius: 6px; margin-top: 12px; overflow: hidden;"
       content-style="padding: 12px; height: 100%; display: flex; flex-direction: column;">
       <div class="flex items-center justify-between" style="margin-bottom: 12px;">
-        <div class="flex items-center gap-15">
-          <div class="text-lg font-bold">角色:{{ selectedRole }}</div>
-          <div class="text-lg font-bold">日期:{{ getDisplayDate() }}</div>
-        </div>
+        <div class="text-lg font-bold">日期:{{ getDisplayDate() }}</div>
       </div>
 
       <div class="flex items-center justify-between">
         <div class="flex items-center">
-          <div v-for="tag in shiftTags" :key="`${tag.name}-${tag.time}`" style="margin-right: 8px;"
+          <div v-for="tag in shiftTags" :key="`${tag.shiftName}-${tag.startTime}`" style="margin-right: 8px;"
             class="flex items-center gap-1">
             <span class="rounded text-xs" :style="{
               backgroundColor: tag.bgColor,
@@ -210,7 +282,7 @@
               alignItems: 'center',
               justifyContent: 'center'
             }">
-              {{ tag.name }} {{ tag.time }}
+              {{ tag.shiftName }} {{ tag.time }}
             </span>
           </div>
         </div>
@@ -218,43 +290,47 @@
           <n-button type="primary" style="width: 84px; height: 32px" @click="handleSave">保存</n-button>
         </div>
       </div>
-      <div class="schedule-table-container" style="overflow-x: auto; flex: 1;margin-top: 12px;">
-        <table class="schedule-table">
-          <thead>
-            <tr>
-              <th class="sticky-col">车队</th>
-              <th v-for="(date, index) in dates" :key="index">
-                <div>{{ date.month }}/{{ date.day }}</div>
-                <div class="text-xs text-gray-500" style="margin-top: 4px;">{{ date.weekDay }}</div>
-              </th>
-              <th class="summary-col">排班小计</th>
-            </tr>
-          </thead>
-          <tbody>
-            <tr v-for="(schedule, teamIndex) in teamSchedules" :key="teamIndex">
-              <td class="sticky-col team-name">{{ schedule.team }}</td>
-              <td v-for="(shifts, dateIndex) in schedule.schedules" :key="dateIndex" class="schedule-cell"
-                :class="{ 'schedule-cell-selected': selectedCell && selectedCell.teamIndex === teamIndex && selectedCell.dateIndex === dateIndex }"
-                @click="handleCellClick(teamIndex, dateIndex, $event)">
-                <div class="flex flex-wrap gap-1">
-                  <span v-for="(shift, shiftIndex) in shifts" :key="shiftIndex" class="whitespace-nowrap"
-                    :style="{ backgroundColor: shift.bgColor, color: shift.color }">
-                    {{ shift.name }}
-                  </span>
-                </div>
-              </td>
-              <td class="summary-cell">
-                <div class="flex flex-wrap justify-center" style="gap: 2px;">
-                  <span v-for="tag in shiftTags" :key="`${tag.name}-${tag.time}`" class="summary-tag"
-                    :style="{ backgroundColor: tag.bgColor, color: tag.color }">
-                    {{ tag.name }} {{ getShiftCount(schedule.schedules, tag.name) }}
-                  </span>
-                </div>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
+      <n-scrollbar x-scrollable class="flex-1 my-4">
+        <div class="schedule-table-wrapper flex-1">
+          <table class="schedule-table">
+            <thead>
+              <tr>
+                <th class="sticky-col">车队</th>
+                <th v-for="(date, index) in dates" :key="index">
+                  <div>{{ date.month }}/{{ date.day }}</div>
+                  <div class="text-xs text-gray-500" style="margin-top: 4px;">{{ date.weekDay }}</div>
+                </th>
+                <th class="summary-col">排班小计</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(schedule, teamIndex) in teamSchedules" :key="teamIndex">
+                <td class="sticky-col team-name">{{ schedule.team }}</td>
+                <td v-for="(shifts, dateIndex) in schedule.schedules" :key="dateIndex" class="schedule-cell"
+                  :class="{ 'schedule-cell-selected': selectedCell && selectedCell.teamIndex === teamIndex && selectedCell.dateIndex === dateIndex }"
+                  @click="handleCellClick(teamIndex, dateIndex, $event)">
+                  <div class="flex flex-wrap gap-1">
+                    <span v-for="(shift, shiftIndex) in shifts" :key="shiftIndex" class="whitespace-nowrap"
+                      :style="{ backgroundColor: shift.bgColor, color: shift.color }">
+                      {{ shift.name }}
+                    </span>
+                  </div>
+                </td>
+                <td class="summary-cell">
+                  <div class="flex flex-wrap justify-center" style="gap: 2px;">
+                    <span v-for="tag in schedule.subtotal" :key="`${tag.name}-${tag.time}`" class="summary-tag"
+                      :style="{ backgroundColor: tag.shiftColor + '25', color: tag.shiftColor }">
+                      {{ tag.shiftName }} {{ tag.count }}
+                    </span>
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </n-scrollbar>
+      <Pagination :count="total" @change="changePage" />
+
     </n-card>
 
     <!-- 排班选择盒子 -->
@@ -271,11 +347,11 @@
         </n-icon>
       </div>
       <div class="schedule-selector-content">
-        <div v-for="shift in shiftTags" :key="`${shift.name}-${shift.time}`" class="shift-option"
+        <div v-for="shift in shiftTags" :key="`${shift.shiftName}-${shift.time}`" class="shift-option"
           @click="handleShiftToggle(shift)">
-          <n-radio :checked="selectedShifts.some(s => s.name === shift.name && s.time === shift.time)" />
+          <n-radio :checked="selectedShifts.some(s => s.shiftName === shift.shiftName && s.time === shift.time)" />
           <span class="shift-tag" :style="{ backgroundColor: shift.bgColor, color: shift.color }">
-            {{ shift.name }} {{ shift.time }}
+            {{ shift.shiftName }} {{ shift.time }}
           </span>
         </div>
       </div>
diff --git a/src/views/operations/attendance/plan/edit.vue b/src/views/operations/attendance/plan/edit.vue
index 1aae154..100d8e4 100644
--- a/src/views/operations/attendance/plan/edit.vue
+++ b/src/views/operations/attendance/plan/edit.vue
@@ -2,30 +2,26 @@
 import { ref, onMounted, onUnmounted, nextTick } from 'vue'
 import { useRoute } from 'vue-router'
 import BatchSetModal from './components/BatchSetModal.vue'
-
-interface ShiftTag {
-  name: string
-  time: string
-  color: string
-  bgColor: string
-}
-
-interface TeamSchedule {
-  team: string
-  schedules: ShiftTag[][]
-}
+import { getSchedulingPageList, getShiftListNoPage, saveScheduling } from '@/service/api/attendance'
+import dayjs from 'dayjs'
 
 const route = useRoute()
+const total = ref(0)
+const pageParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+})
 
-const currentDate = ref('2024/12/01')
-const selectedMonth = ref<number>(new Date(2024, 11, 1).valueOf())
+const selectedMonth = ref()
 
-const shiftTags = ref<ShiftTag[]>([
-  { name: '全班', time: '08:00-16:00', color: '#0068FF', bgColor: '#E6F4FF' },
-  { name: '早班', time: '08:00-20:00', color: '#16A60C', bgColor: '#DCF5DD' },
-  { name: '中班', time: '16:00-24:00', color: '#FF9000', bgColor: '#FFF8E6' },
-  { name: '晚班', time: '19:00-24:00', color: '#764DFF', bgColor: '#F6F0FF' }
-])
+const shiftTags = ref<any[]>([])
+
+const dayKeyMap = [
+  'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
+  'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty',
+  'twentyOne', 'twentyTwo', 'twentyThree', 'twentyFour', 'twentyFive', 'twentySix', 'twentySeven',
+  'twentyEight', 'twentyNine', 'thirty', 'thirtyOne'
+]
 
 // 生成日期列表
 const generateDates = (year: number, month: number) => {
@@ -43,35 +39,20 @@
   return dates
 }
 
-const dates = ref(generateDates(2024, 11))
+const dates = ref()
 
-const teamSchedules = ref<TeamSchedule[]>([
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云二队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州南沙三队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) }
-])
-
-// 初始化一些示例数据
-teamSchedules.value[0].schedules[3] = [shiftTags.value[0]]
-teamSchedules.value[1].schedules[1] = [shiftTags.value[1]]
+const teamSchedules = ref<any[]>([])
 
 const selectedCell = ref<{ teamIndex: number; dateIndex: number } | null>(null)
-const selectedShifts = ref<ShiftTag[]>([])
+const selectedShifts = ref<any[]>([])
 const selectorPosition = ref({ top: 0, left: 0 })
 
 const updateDates = () => {
   const date = new Date(selectedMonth.value)
   const year = date.getFullYear()
   const month = date.getMonth()
-  currentDate.value = `${year}/${String(month + 1).padStart(2, '0')}/01`
   dates.value = generateDates(year, month)
-  
+
   // 重新初始化排班数据
   const daysInMonth = new Date(year, month + 1, 0).getDate()
   teamSchedules.value.forEach(schedule => {
@@ -91,58 +72,77 @@
 }
 
 const handleSave = () => {
-  console.log('保存')
-}
-
-const getShiftCount = (schedules: ShiftTag[][], shiftName: string) => {
-  let count = 0
-  schedules.forEach(shifts => {
-    count += shifts.filter(s => s.name === shiftName).length
+  const params = {
+    month: selectedMonth.value,
+    datas: teamSchedules.value.map((item: any) => ({
+      fleetId: item.fleetId,
+      days: item.schedules.map((shifts: any[], index: number) => {
+        return {
+          dayTime: `${dayjs(selectedMonth.value).year()}-${dayjs(selectedMonth.value).month() + 1 > 9 ? dayjs(selectedMonth.value).month() + 1 : '0' + (dayjs(selectedMonth.value).month() + 1)}-${index + 1 > 9 ? index + 1 : '0' + (index + 1)}`,
+          shiftId: Number(shifts[0]?.id) ? Number(shifts[0]?.id) : null
+        }
+      })
+    }))
+  }
+  saveScheduling(params).then(res => {
+    if (res.code === 200) {
+      window.$message.success('排班保存成功')
+      getList()
+    }
   })
-  return count
 }
 
 const showScheduleModal = ref(false)
 const showBatchSetModal = ref(false)
 
-const handleCellClick = (teamIndex: number, dateIndex: number, event: MouseEvent) => {
+const handleCellClick = (teamIndex: number, dateIndex: number | string, event: MouseEvent) => {
   event.stopPropagation() // 阻止事件冒泡
-  
-  selectedCell.value = { teamIndex, dateIndex }
-  const currentShifts = teamSchedules.value[teamIndex].schedules[dateIndex]
+
+  selectedCell.value = { teamIndex, dateIndex: Number(dateIndex) }
+  const currentShifts = teamSchedules.value[teamIndex].schedules[Number(dateIndex)]
   selectedShifts.value = currentShifts ? [...currentShifts] : []
-  
+
   // 计算盒子位置
   const target = event.currentTarget as HTMLElement
   const rect = target.getBoundingClientRect()
   const boxWidth = 240
   const windowWidth = window.innerWidth
-  
+
   // 判断右侧是否有足够空间,如果没有则显示在左侧
   let left = rect.right + 2
   if (left + boxWidth > windowWidth) {
     left = rect.left - boxWidth - 10
   }
-  
+
   selectorPosition.value = {
     top: rect.top,
     left: Math.max(10, left) // 确保不会超出左边界
   }
-  
+
   nextTick(() => {
     showScheduleModal.value = true
   })
 }
 
-const handleShiftToggle = (shift: ShiftTag) => {
+const handleShiftToggle = (shift: any) => {
+  shift.name = shift.shiftName
   // 单选模式:直接设置为选中的班次
   selectedShifts.value = [shift]
-  
+
   // 立即更新到表格
   if (selectedCell.value) {
     teamSchedules.value[selectedCell.value.teamIndex].schedules[selectedCell.value.dateIndex] = [shift]
+    teamSchedules.value[selectedCell.value.teamIndex].subtotal = teamSchedules.value[selectedCell.value.teamIndex].subtotal.map((item: any) => {
+      if (item.shiftId === shift.id) {
+        return {
+          ...item,
+          count: Number(item.count) + 1
+        }
+      }
+      return item
+    })
   }
-  
+
   // 选择后直接关闭
   closeScheduleModal()
 }
@@ -150,10 +150,10 @@
 // 点击外部关闭选择盒子
 const handleClickOutside = (event: MouseEvent) => {
   if (!showScheduleModal.value) return
-  
+
   const target = event.target as HTMLElement
   const selectorBox = document.querySelector('.schedule-selector-box')
-  
+
   if (selectorBox && !selectorBox.contains(target)) {
     closeScheduleModal()
   }
@@ -164,156 +164,234 @@
   selectedCell.value = null
 }
 
+async function getList() {
+  const params = {
+    ...pageParams.value,
+    month: selectedMonth.value,
+  }
+  try {
+    const res = await getSchedulingPageList(params)
+    if (res.code === 200) {
+      const records = res.data.records || []
+      const date = dayjs(selectedMonth.value)
+      const daysInMonth = date.daysInMonth()
+      teamSchedules.value = records.map((item: any) => {
+        const schedules: any[] = []
+        for (let i = 0; i < daysInMonth; i++) {
+          const key = dayKeyMap[i]
+          const dayData = item[key]
+          if (dayData && dayData.shiftName) {
+            schedules.push([
+              {
+                id: dayData.shiftId,
+                name: dayData.shiftName,
+                color: dayData.shiftColor,
+                bgColor: dayData.shiftColor + '25',
+              }
+            ])
+          } else {
+            schedules.push([])
+          }
+        }
+        return {
+          fleetId: item.fleetId,
+          team: item.fleetName,
+          schedules,
+          subtotal: item.subtotal || []
+        }
+      })
+      total.value = res.data.total || teamSchedules.value.length
+    }
+  } finally {
+  }
+}
+
+const getShiftList = async () => {
+  const res = await getShiftListNoPage()
+  if (res.code === 200) {
+    shiftTags.value = res.data.map((item: any) => ({
+      id: item.id,
+      shiftName: item.shiftName,
+      time: `${item.startTime}-${item.endTime}`,
+      color: item.shiftColor,
+      bgColor: item.shiftColor + '25'
+    }))
+  }
+}
+
 onMounted(() => {
   document.addEventListener('click', handleClickOutside)
-  
   // 从路由参数读取月份
   const monthParam = route.query.month
   if (monthParam) {
     const monthValue = typeof monthParam === 'string' ? Number(monthParam) : Array.isArray(monthParam) ? Number(monthParam[0]) : monthParam
     if (monthValue && !isNaN(monthValue)) {
-      selectedMonth.value = monthValue
+      selectedMonth.value = dayjs(monthValue).format('YYYY-MM')
       updateDates()
     }
   }
+  getShiftList()
+  getList()
 })
 
 onUnmounted(() => {
   document.removeEventListener('click', handleClickOutside)
 })
 
+function changePage(page: number, size: number) {
+  pageParams.value.pageNum = page
+  pageParams.value.pageSize = size
+  getList()
+}
+
+const handleBatchConfirm = (data: any) => {
+  const fleet = teamSchedules.value.find((item: any) => item.fleetId == data.team)
+  data.shifts.forEach((shift: any) => {
+    if (shift.checked) {
+      shift.dateRange.forEach((date: number) => {
+        fleet.schedules[date - 1] = [shift]
+      })
+    }
+  })
+  // 重新计算班次统计
+  const shiftCountMap: Record<string, number> = {}
+  fleet.schedules.forEach((shifts: any[]) => {
+    if (shifts && shifts.length > 0) {
+      shifts.forEach((shift: any) => {
+        const shiftId = String(shift.id)
+        shiftCountMap[shiftId] = (shiftCountMap[shiftId] || 0) + 1
+      })
+    }
+  })
+
+  // 更新subtotal数组
+  fleet.subtotal = Object.entries(shiftCountMap).map(([shiftId, count]) => {
+    const shiftInfo = shiftTags.value.find((tag: any) => tag.id == shiftId)
+    console.log('shiftInfo', shiftInfo)
+    return {
+      shiftId,
+      shiftName: shiftInfo?.shiftName,
+      count: count,
+      bgColor: shiftInfo?.bgColor,
+      color: shiftInfo?.color,
+      shiftColor: shiftInfo?.color
+    }
+  })
+}
 </script>
 
 <template>
   <div class="h-full flex flex-col">
-    <n-card style="border-radius: 6px; margin-top: 12px; overflow: hidden;" content-style="padding: 12px; height: 100%; display: flex; flex-direction: column;">
-        <div class="flex items-center justify-between" style="margin-bottom: 12px;">
+    <n-card style="border-radius: 6px; margin-top: 12px; overflow: hidden;"
+      content-style="padding: 12px; height: 100%; display: flex; flex-direction: column;">
+      <div class="flex items-center justify-between" style="margin-bottom: 12px;">
         <div class="text-lg font-bold">日期:{{ getDisplayDate() }}</div>
       </div>
 
       <div class="flex items-center justify-between">
         <div class="flex items-center">
-          <div v-for="tag in shiftTags" :key="`${tag.name}-${tag.time}`" style="margin-right: 8px;" class="flex items-center gap-1">
-            <span 
-              class="rounded text-xs"
-              :style="{ 
-                backgroundColor: tag.bgColor, 
-                color: tag.color,
-                width: '113px',
-                height: '28px',
-                borderRadius: '4px',
-                display: 'inline-flex',
-                alignItems: 'center',
-                justifyContent: 'center'
-              }"
-            >
-              {{ tag.name }} {{ tag.time }}
+          <div v-for="tag in shiftTags" :key="`${tag.shiftName}-${tag.startTime}`" style="margin-right: 8px;"
+            class="flex items-center gap-1">
+            <span class="rounded text-xs" :style="{
+              backgroundColor: tag.bgColor,
+              color: tag.color,
+              width: '113px',
+              height: '28px',
+              borderRadius: '4px',
+              display: 'inline-flex',
+              alignItems: 'center',
+              justifyContent: 'center'
+            }">
+              {{ tag.shiftName }} {{ tag.time }}
             </span>
           </div>
         </div>
         <div class="flex gap-2">
-          <n-button style="width: 84px; height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF" @click="handleBatchEdit">批量设置</n-button>
+          <n-button
+            style="width: 84px; height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF"
+            @click="handleBatchEdit">批量设置</n-button>
           <n-button type="primary" style="width: 84px; height: 32px" @click="handleSave">保存</n-button>
         </div>
       </div>
-      <div class="schedule-table-container" style="overflow-x: auto; flex: 1;margin-top: 12px;">
-        <table class="schedule-table">
-          <thead>
-            <tr>
-              <th class="sticky-col">车队</th>
-              <th 
-                v-for="(date, index) in dates" 
-                :key="index"
-              >
-                <div>{{ date.month }}/{{ date.day }}</div>
-                <div class="text-xs text-gray-500" style="margin-top: 4px;">{{ date.weekDay }}</div>
-              </th>
-              <th class="summary-col">排班小计</th>
-            </tr>
-          </thead>
-          <tbody>
-            <tr v-for="(schedule, teamIndex) in teamSchedules" :key="teamIndex">
-              <td class="sticky-col team-name">{{ schedule.team }}</td>
-              <td 
-                v-for="(shifts, dateIndex) in schedule.schedules" 
-                :key="dateIndex"
-                class="schedule-cell"
-                :class="{ 'schedule-cell-selected': selectedCell && selectedCell.teamIndex === teamIndex && selectedCell.dateIndex === dateIndex }"
-                @click="handleCellClick(teamIndex, dateIndex, $event)"
-              >
-                <div class="flex flex-wrap gap-1">
-                  <span
-                    v-for="(shift, shiftIndex) in shifts"
-                    :key="shiftIndex"
-                    class="whitespace-nowrap"
-                    :style="{ backgroundColor: shift.bgColor, color: shift.color }"
-                  >
-                    {{ shift.name }}
-                  </span>
-                </div>
-              </td>
-              <td class="summary-cell">
-                <div class="flex flex-wrap justify-center" style="gap: 2px;">
-                  <span
-                    v-for="tag in shiftTags"
-                    :key="`${tag.name}-${tag.time}`"
-                    class="summary-tag"
-                    :style="{ backgroundColor: tag.bgColor, color: tag.color }"
-                  >
-                    {{ tag.name }} {{ getShiftCount(schedule.schedules, tag.name) }}
-                  </span>
-                </div>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
+      <n-scrollbar x-scrollable class="flex-1 my-4">
+        <div class="schedule-table-wrapper flex-1">
+          <table class="schedule-table">
+            <thead>
+              <tr>
+                <th class="sticky-col">车队</th>
+                <th v-for="(date, index) in dates" :key="index">
+                  <div>{{ date.month }}/{{ date.day }}</div>
+                  <div class="text-xs text-gray-500" style="margin-top: 4px;">{{ date.weekDay }}</div>
+                </th>
+                <th class="summary-col">排班小计</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(schedule, teamIndex) in teamSchedules" :key="teamIndex">
+                <td class="sticky-col team-name">{{ schedule.team }}</td>
+                <td v-for="(shifts, dateIndex) in schedule.schedules" :key="dateIndex" class="schedule-cell"
+                  :class="{ 'schedule-cell-selected': selectedCell && selectedCell.teamIndex === teamIndex && selectedCell.dateIndex === dateIndex }"
+                  @click="handleCellClick(teamIndex, dateIndex, $event)">
+                  <div class="flex flex-wrap gap-1">
+                    <span v-for="(shift, shiftIndex) in shifts" :key="shiftIndex" class="whitespace-nowrap"
+                      :style="{ backgroundColor: shift.bgColor, color: shift.color }">
+                      {{ shift.name }}
+                    </span>
+                  </div>
+                </td>
+                <td class="summary-cell">
+                  <div class="flex flex-wrap justify-center" style="gap: 2px;">
+                    <span v-for="tag in schedule.subtotal" :key="`${tag.name}-${tag.time}`" class="summary-tag"
+                      :style="{ backgroundColor: tag.shiftColor + '25', color: tag.shiftColor }">
+                      {{ tag.shiftName }} {{ tag.count }}
+                    </span>
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </n-scrollbar>
+      <Pagination :count="total" @change="changePage" />
+
     </n-card>
 
     <!-- 排班选择盒子 -->
-    <div 
-      v-if="showScheduleModal" 
-      class="schedule-selector-box" 
-      :style="{ top: `${selectorPosition.top}px`, left: `${selectorPosition.left}px` }"
-    >
+    <div v-if="showScheduleModal" class="schedule-selector-box"
+      :style="{ top: `${selectorPosition.top}px`, left: `${selectorPosition.left}px` }">
       <div class="schedule-selector-header">
         <span class="schedule-selector-title">排班选择</span>
         <n-icon size="20" class="cursor-pointer" @click="closeScheduleModal">
-          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
+            stroke-linecap="round" stroke-linejoin="round">
             <line x1="18" y1="6" x2="6" y2="18"></line>
             <line x1="6" y1="6" x2="18" y2="18"></line>
           </svg>
         </n-icon>
       </div>
       <div class="schedule-selector-content">
-        <div
-          v-for="shift in shiftTags"
-          :key="`${shift.name}-${shift.time}`"
-          class="shift-option"
-          @click="handleShiftToggle(shift)"
-        >
-          <n-radio 
-            :checked="selectedShifts.some(s => s.name === shift.name && s.time === shift.time)"
-          />
-          <span 
-            class="shift-tag"
-            :style="{ backgroundColor: shift.bgColor, color: shift.color }"
-          >
-            {{ shift.name }} {{ shift.time }}
+        <div v-for="shift in shiftTags" :key="`${shift.shiftName}-${shift.time}`" class="shift-option"
+          @click="handleShiftToggle(shift)">
+          <n-radio :checked="selectedShifts.some(s => s.shiftName === shift.shiftName && s.time === shift.time)" />
+          <span class="shift-tag" :style="{ backgroundColor: shift.bgColor, color: shift.color }">
+            {{ shift.shiftName }} {{ shift.time }}
           </span>
         </div>
       </div>
     </div>
 
-    <BatchSetModal 
-      v-model:show="showBatchSetModal"
-    />
+    <BatchSetModal v-model:show="showBatchSetModal"
+      :teamOptions="teamSchedules.map(item => ({ label: item.team, value: item.fleetId }))" :shifts="shiftTags"
+      :selectedMonth="selectedMonth" @confirm="handleBatchConfirm" />
   </div>
 </template>
 
-<style scoped>
-.schedule-table-container {
-  position: relative;
+<style scoped lang="scss">
+.schedule-table-wrapper {
+  min-width: max-content;
+  // position: relative;
+  display: inline-block;
+  width: 100%;
 }
 
 .schedule-table {
diff --git a/src/views/operations/attendance/plan/index.vue b/src/views/operations/attendance/plan/index.vue
index fc56292..90552a6 100644
--- a/src/views/operations/attendance/plan/index.vue
+++ b/src/views/operations/attendance/plan/index.vue
@@ -2,28 +2,29 @@
 import { ref } from 'vue'
 import EditMonthModal from './components/EditMonthModal.vue'
 import CopyScheduleModal from './components/CopyScheduleModal.vue'
+import { getSchedulingPageList, getShiftListNoPage, exportScheduling } from '@/service/api/attendance'
+import { useBoolean } from '@/hooks'
+import { downloadFile } from '@/utils'
+import dayjs from 'dayjs'
 
-interface ShiftTag {
-  name: string
-  time: string
-  color: string
-  bgColor: string
-}
+const total = ref(0)
+const pageParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+})
 
-interface TeamSchedule {
-  team: string
-  schedules: (ShiftTag | null)[][]
-}
+const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
 
-const currentDate = ref('2024/12/01')
-const selectedMonth = ref<number>(new Date(2024, 11, 1).valueOf())
+const selectedMonth = ref()
 
-const shiftTags = ref<ShiftTag[]>([
-  { name: '全班', time: '08:00-16:00', color: '#0068FF', bgColor: '#E6F4FF' },
-  { name: '早班', time: '08:00-20:00', color: '#16A60C', bgColor: '#DCF5DD' },
-  { name: '中班', time: '16:00-24:00', color: '#FF9000', bgColor: '#FFF8E6' },
-  { name: '晚班', time: '19:00-24:00', color: '#764DFF', bgColor: '#F6F0FF' }
-])
+const shiftTags = ref<any[]>([])
+
+const dayKeyMap = [
+  'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
+  'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty',
+  'twentyOne', 'twentyTwo', 'twentyThree', 'twentyFour', 'twentyFive', 'twentySix', 'twentySeven',
+  'twentyEight', 'twentyNine', 'thirty', 'thirtyOne'
+]
 
 // 生成日期列表
 const generateDates = (year: number, month: number) => {
@@ -41,31 +42,23 @@
   return dates
 }
 
-const dates = ref(generateDates(2024, 11))
+const dates = ref(generateDates(dayjs().year(), dayjs().month()))
 
-const teamSchedules = ref<TeamSchedule[]>([
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云二队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州南沙三队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) },
-  { team: '广州白云一队', schedules: Array(31).fill(null).map(() => []) }
-])
+watch(selectedMonth, () => {
+  const date = dayjs(selectedMonth.value)
+  dates.value = generateDates(date.year(), date.month())
+  // getList()
+})
 
-// 初始化一些示例数据
-teamSchedules.value[0].schedules[3] = [shiftTags.value[0]]
-teamSchedules.value[1].schedules[1] = [shiftTags.value[1]]
-
+const teamSchedules = ref<any[]>([])
 const selectedCell = ref<{ teamIndex: number; dateIndex: number } | null>(null)
-const selectedShifts = ref<ShiftTag[]>([])
+const selectedShifts = ref<any[]>([])
 
 const handleMonthChange = (value: number | null) => {
   if (value) {
-    selectedMonth.value = value
-    updateDates()
+    selectedMonth.value = dayjs(value).format('YYYY-MM')
+    // updateDates()
+    getList()
   }
 }
 
@@ -73,9 +66,8 @@
   const date = new Date(selectedMonth.value)
   const year = date.getFullYear()
   const month = date.getMonth()
-  currentDate.value = `${year}/${String(month + 1).padStart(2, '0')}/01`
   dates.value = generateDates(year, month)
-  
+
   // 重新初始化排班数据
   const daysInMonth = new Date(year, month + 1, 0).getDate()
   teamSchedules.value.forEach(schedule => {
@@ -87,30 +79,15 @@
   showCopyModal.value = true
 }
 
-const handleDownload = () => {
-  console.log('下载表')
-}
-
-const getShiftCount = (schedules: (ShiftTag | null)[][], shiftName: string) => {
-  let count = 0
-  schedules.forEach(shifts => {
-    if (shifts) {
-      count += shifts.filter(s => s.name === shiftName).length
-    }
-  })
-  return count
-}
-
-const handleExport = () => {
-  console.log('导出')
-}
-
-const handleBatchSchedule = () => {
-  console.log('批量排班')
-}
-
-const handlePublish = () => {
-  console.log('发布')
+const handleDownload = async () => {
+  const params = {
+    month: selectedMonth.value,
+  }
+  const response = await exportScheduling(params)
+  if (response instanceof Blob) {
+    downloadFile(response, `排班计划列表_${new Date().getTime()}.xlsx`)
+    window.$message.success('导出成功')
+  }
 }
 
 const showScheduleModal = ref(false)
@@ -122,7 +99,7 @@
   selectedShifts.value = [...(teamSchedules.value[teamIndex].schedules[dateIndex] || [])]
 }
 
-const handleShiftToggle = (shift: ShiftTag) => {
+const handleShiftToggle = (shift: any) => {
   const index = selectedShifts.value.findIndex(s => s.name === shift.name && s.time === shift.time)
   if (index > -1) {
     selectedShifts.value.splice(index, 1)
@@ -142,11 +119,86 @@
   showEditModal.value = true
 }
 
-
-
 const handleConfirmEdit = (month: number) => {
   selectedMonth.value = month
   updateDates()
+}
+
+async function getList() {
+  startLoading()
+  const params = {
+    ...pageParams.value,
+    month: selectedMonth.value,
+  }
+  try {
+    const res = await getSchedulingPageList(params)
+    if (res.code === 200) {
+      const records = res.data.records || []
+      const date = dayjs(selectedMonth.value)
+      const daysInMonth = date.daysInMonth()
+      teamSchedules.value = records.map((item: any) => {
+        const schedules: any[] = []
+        for (let i = 0; i < daysInMonth; i++) {
+          const key = dayKeyMap[i]
+          const dayData = item[key]
+          if (dayData && dayData.shiftName) {
+            schedules.push([
+              {
+                id: dayData.shiftId,
+                name: dayData.shiftName,
+                color: dayData.shiftColor,
+                bgColor: dayData.shiftColor + '25',
+              }
+            ])
+          } else {
+            schedules.push([
+              {
+                id: 'rest',
+                name: '休',
+                color: '#999999',
+                bgColor: '#F5F5F5',
+                isRest: true
+              }
+            ])
+          }
+        }
+        return {
+          fleetId: item.fleetId,
+          team: item.fleetName,
+          schedules,
+          subtotal: item.subtotal || []
+        }
+      })
+      total.value = res.data.total || teamSchedules.value.length
+    }
+  } finally {
+    endLoading()
+  }
+}
+
+const getShiftList = async () => {
+  const res = await getShiftListNoPage()
+  if (res.code === 200) {
+    shiftTags.value = res.data.map((item: any) => ({
+      id: item.shiftId,
+      shiftName: item.shiftName,
+      time: `${item.startTime}-${item.endTime}`,
+      color: item.shiftColor,
+      bgColor: item.shiftColor + '25'
+    }))
+  }
+}
+
+onMounted(() => {
+  selectedMonth.value = dayjs(new Date()).format('YYYY-MM')
+  getShiftList()
+  getList()
+})
+
+function changePage(page: number, size: number) {
+  pageParams.value.pageNum = page
+  pageParams.value.pageSize = size
+  getList()
 }
 
 </script>
@@ -158,123 +210,98 @@
         <div class="text-lg font-bold">排班计划管理</div>
       </div>
 
-      <div class="flex items-center">
+      <div class="flex items-center" v-if="shiftTags.length > 0">
         <div class="flex items-center">
           <span class="text-sm">班次设置:</span>
-          <div v-for="tag in shiftTags" :key="`${tag.name}-${tag.time}`" style="margin-right: 8px;" class="flex items-center gap-1">
-            <span 
-              class="rounded text-xs"
-              :style="{ 
-                backgroundColor: tag.bgColor, 
-                color: tag.color,
-                width: '113px',
-                height: '28px',
-                borderRadius: '4px',
-                display: 'inline-flex',
-                alignItems: 'center',
-                justifyContent: 'center'
-              }"
-            >
-              {{ tag.name }} {{ tag.time }}
+          <div v-for="tag in shiftTags" :key="`${tag.shiftName}-${tag.startTime}`" style="margin-right: 8px;"
+            class="flex items-center gap-1">
+            <span class="rounded text-xs" :style="{
+              backgroundColor: tag.bgColor,
+              color: tag.color,
+              width: '113px',
+              height: '28px',
+              borderRadius: '4px',
+              display: 'inline-flex',
+              alignItems: 'center',
+              justifyContent: 'center'
+            }">
+              {{ tag.shiftName }} {{ tag.time }}
             </span>
           </div>
         </div>
-        
       </div>
     </n-card>
 
-    <n-card style="border-radius: 6px; margin-top: 12px; overflow: hidden;" content-style="padding: 12px; height: 100%; display: flex; flex-direction: column;">
+    <n-card style="border-radius: 6px; margin-top: 12px; overflow: hidden;"
+      content-style="padding: 12px; height: 100%; display: flex; flex-direction: column;">
       <div class="flex items-center justify-between mb-4">
-        <n-date-picker 
-          v-model:value="selectedMonth" 
-          placeholder="选择月份"
-          type="month"
-          style="width: 260px"
-          @update:value="handleMonthChange"
-        />
+        <n-date-picker v-model:value="selectedMonth" placeholder="选择月份" type="month" style="width: 260px"
+          @update:value="handleMonthChange" />
         <div class="flex gap-2">
-          <n-button style="width: 84px;height: 32px;background-color: #E6F4FF;color: #0068FF;border: 1px solid #7ABDFF;" @click="handleCopy">复制</n-button>
-          <n-button style="width: 84px;height: 32px;background-color: #E6F4FF;color: #0068FF;border: 1px solid #7ABDFF;" @click="handleDownload">下载表</n-button>
+          <n-button style="width: 84px;height: 32px;background-color: #E6F4FF;color: #0068FF;border: 1px solid #7ABDFF;"
+            @click="handleCopy">复制</n-button>
+          <n-button style="width: 84px;height: 32px;background-color: #E6F4FF;color: #0068FF;border: 1px solid #7ABDFF;"
+            @click="handleDownload">下载表</n-button>
           <n-button type="primary" style="width: 84px; height: 32px" @click="handleEdit">编辑</n-button>
         </div>
       </div>
-        
-      <div class="schedule-table-container" style="overflow-x: auto; flex: 1;">
-        <table class="schedule-table">
-          <thead>
-            <tr>
-              <th class="sticky-col">车队</th>
-              <th 
-                v-for="(date, index) in dates" 
-                :key="index"
-              >
-                <div>{{ date.month }}/{{ date.day }}</div>
-                <div class="text-xs text-gray-500" style="margin-top: 4px;">{{ date.weekDay }}</div>
-              </th>
-              <th class="summary-col">排班小计</th>
-            </tr>
-          </thead>
-          <tbody>
-            <tr v-for="(schedule, teamIndex) in teamSchedules" :key="teamIndex">
-              <td class="sticky-col team-name">{{ schedule.team }}</td>
-              <td 
-                v-for="(shifts, dateIndex) in schedule.schedules" 
-                :key="dateIndex"
-                class="schedule-cell"
-                @click="handleCellClick(teamIndex, dateIndex)"
-              >
-                <div class="flex flex-wrap gap-1">
-                  <span
-                    v-for="(shift, shiftIndex) in shifts"
-                    :key="shiftIndex"
-                    class="whitespace-nowrap"
-                    :style="{ backgroundColor: shift.bgColor, color: shift.color }"
-                  >
-                    {{ shift.name }}
-                  </span>
-                </div>
-              </td>
-              <td class="summary-cell">
-                <div class="flex flex-wrap justify-center" style="gap: 2px;">
-                  <span
-                    v-for="tag in shiftTags"
-                    :key="`${tag.name}-${tag.time}`"
-                    class="summary-tag"
-                    :style="{ backgroundColor: tag.bgColor, color: tag.color }"
-                  >
-                    {{ tag.name }} {{ getShiftCount(schedule.schedules, tag.name) }}
-                  </span>
-                </div>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
+
+      <n-scrollbar x-scrollable class="flex-1 mb-4">
+        <div class="schedule-table-wrapper">
+          <table class="schedule-table">
+            <thead>
+              <tr>
+                <th class="sticky-col">车队</th>
+                <th v-for="(date, index) in dates" :key="index">
+                  <div>{{ date.month }}/{{ date.day }}</div>
+                  <div class="text-xs text-gray-500" style="margin-top: 4px;">{{ date.weekDay }}</div>
+                </th>
+                <th class="summary-col">排班小计</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(schedule, teamIndex) in teamSchedules" :key="teamIndex">
+                <td class="sticky-col team-name">{{ schedule.team }}</td>
+                <td v-for="(shifts, dateIndex) in schedule.schedules" :key="dateIndex" class="schedule-cell"
+                  @click="handleCellClick(teamIndex, dateIndex)">
+                  <div class="flex flex-wrap gap-1">
+                    <span v-for="(shift, shiftIndex) in shifts" :key="shiftIndex" class="whitespace-nowrap"
+                      :style="{ backgroundColor: shift.bgColor, color: shift.color }">
+                      {{ shift.name }}
+                    </span>
+                  </div>
+                </td>
+                <td class="summary-cell">
+                  <div class="flex flex-wrap justify-center" style="gap: 2px;">
+                    <span v-for="tag in schedule.subtotal" :key="`${tag.shiftName}-${tag.time}`" class="summary-tag"
+                      :style="{ backgroundColor: tag.shiftColor + '25', color: tag.shiftColor }">
+                      {{ tag.shiftName }} {{ tag.count }}
+                    </span>
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </n-scrollbar>
+      <Pagination :count="total" @change="changePage" />
     </n-card>
 
     <!-- 排班弹窗 -->
     <n-modal v-model:show="showScheduleModal" preset="card" title="排班选择" class="w-400px">
       <div class="flex flex-col gap-3">
-        <div
-          v-for="shift in shiftTags"
-          :key="`${shift.name}-${shift.time}`"
+        <div v-for="shift in shiftTags" :key="`${shift.name}-${shift.time}`"
           class="flex items-center justify-between p-3 border rounded cursor-pointer hover:bg-gray-50"
           :class="{ 'border-blue-500 bg-blue-50': selectedShifts.some(s => s.name === shift.name && s.time === shift.time) }"
-          @click="handleShiftToggle(shift)"
-        >
+          @click="handleShiftToggle(shift)">
           <div class="flex items-center gap-2">
-            <span 
-              class="px-3 py-1 rounded text-xs"
-              :style="{ backgroundColor: shift.bgColor, color: shift.color }"
-            >
+            <span class="px-3 py-1 rounded text-xs" :style="{ backgroundColor: shift.bgColor, color: shift.color }">
               {{ shift.name }}
             </span>
             <span class="text-sm">{{ shift.time }}</span>
           </div>
-          <n-checkbox 
-            :checked="selectedShifts.some(s => s.name === shift.name && s.time === shift.time)"
-            @click.stop="handleShiftToggle(shift)"
-          />
+          <n-checkbox :checked="selectedShifts.some(s => s.name === shift.name && s.time === shift.time)"
+            @click.stop="handleShiftToggle(shift)" />
         </div>
       </div>
 
@@ -286,21 +313,18 @@
       </template>
     </n-modal>
 
-    <EditMonthModal 
-      v-model:show="showEditModal" 
-      :current-month="selectedMonth"
-      @confirm="handleConfirmEdit" 
-    />
+    <EditMonthModal v-model:show="showEditModal" :current-month="selectedMonth" @confirm="handleConfirmEdit" />
 
-    <CopyScheduleModal 
-      v-model:show="showCopyModal"
-    />
+    <CopyScheduleModal v-model:show="showCopyModal" />
   </div>
 </template>
 
-<style scoped>
-.schedule-table-container {
-  position: relative;
+<style scoped lang="scss">
+.schedule-table-wrapper {
+  min-width: max-content;
+  // position: relative;
+  display: inline-block;
+  width: 100%;
 }
 
 .schedule-table {
diff --git a/src/views/operations/attendance/statistics/index.vue b/src/views/operations/attendance/statistics/index.vue
index 9de2a6e..b8b2441 100644
--- a/src/views/operations/attendance/statistics/index.vue
+++ b/src/views/operations/attendance/statistics/index.vue
@@ -30,16 +30,7 @@
   { label: '角色1', value: '角色1' }
 ]
 
-const statisticsList = ref<any[]>([{
-  serialNumber: 1,
-  name: '王美丽',
-  roleName: '网格员',
-  teamName: '队伍1',
-  shouldClockInDays: 10,
-  clockInDays: 8,
-  notClockInDays: 2,
-  lateEarly: 1,
-}])
+const statisticsList = ref<any[]>([])
 
 const total = ref(0)
 const pageParams = ref({
@@ -122,7 +113,7 @@
     statisticDate: searchModel.value.statisticDate?.join('-'),
   }
   getClockInStatisticsList(params).then((res) => {
-    // statisticsList.value = res.data.records || []
+    statisticsList.value = res.data.records || []
     statisticsList.value.forEach((item: any, index: number) => {
       item.serialNumber = index + 1
     })
diff --git a/src/views/operations/taskOrder/taskOrderCenter/components/AddFormModal.vue b/src/views/operations/taskOrder/taskOrderCenter/components/AddFormModal.vue
index 464fa93..ce6e901 100644
--- a/src/views/operations/taskOrder/taskOrderCenter/components/AddFormModal.vue
+++ b/src/views/operations/taskOrder/taskOrderCenter/components/AddFormModal.vue
@@ -381,9 +381,9 @@
             :max-tag-count="1" />
         </n-form-item>
 
-        <n-form-item label="任务等级" path="taskTypeId">
+        <n-form-item label="任务等级" path="taskUrgencyLevel">
           <n-select v-model:value="formModel.taskUrgencyLevel" :options="taskUrgencyLevelOptions" placeholder="请选择"
-            clearable />
+            clearable  />
         </n-form-item>
 
         <n-form-item label="车辆" path="vehicleIds">
diff --git a/src/views/operations/taskOrder/taskOrderCenter/components/detailModal.vue b/src/views/operations/taskOrder/taskOrderCenter/components/detailModal.vue
new file mode 100644
index 0000000..a78bf9a
--- /dev/null
+++ b/src/views/operations/taskOrder/taskOrderCenter/components/detailModal.vue
@@ -0,0 +1,465 @@
+<script setup lang="ts">
+import { useBoolean } from '@/hooks'
+import { getTaskOrderDetail } from '@/service/api/attendance'
+import { NButton, NSpace, NTag } from 'naive-ui'
+import dayjs from 'dayjs'
+
+const emit = defineEmits<{
+  open: []
+  close: []
+}>()
+
+const { bool: modalVisible, setTrue: showModal, setFalse: hiddenModal } = useBoolean(false)
+
+const formDefault: any = {
+  id: '',
+  accidentDetailVO: {},
+  chargingDetailVO: {},
+  cleanDetailVO: {},
+  detectionDetailVO: {},
+  preparationDetailVO: {},
+  repairDetailVO: {},
+  routineDetailVO: {},
+}
+const formModel = ref({ ...formDefault })
+
+async function openModal(id: string) {
+  emit('open')
+  formModel.value.id = id
+  const res = await getTaskOrderDetail(id)
+  if (res.code === 200) {
+    formModel.value = { ...formModel.value, ...res.data }
+  }
+  showModal()
+}
+
+function closeModal() {
+  formModel.value = { ...formDefault }
+  hiddenModal()
+  emit('close')
+}
+
+defineExpose({
+  openModal,
+})
+const taskUrgencyLevelOptions = [
+  { label: '一级', value: 0, type: 'error' },
+  { label: '二级', value: 1, type: 'error' },
+  { label: '三级', value: 2, type: 'error' },
+  { label: '四级', value: 3, type: 'warning' },
+]
+const statusOptions = [
+  { label: '待到达', value: 0, type: 'default' },
+  { label: '待处理', value: 1, type: 'warning' },
+  { label: '已完成', value: 2, type: 'primary' },
+  { label: '已取消', value: 3, type: 'default' },
+]
+function formatCascadeData(data: any[]): string {
+  return data
+    .map(parent => {
+      const children = parent.child?.map((c: any) => c.errorName).join('、') ?? ''
+      return `${parent.errorName}>${children}`
+    })
+    .join(';')
+}
+</script>
+
+<template>
+  <n-modal v-model:show="modalVisible" :mask-closable="false" preset="card" title="任务详情" class="w-600px" :segmented="{
+    content: true,
+    action: true,
+  }">
+    <!-- 事故详情 -->
+    <div v-if="formModel.accidentDetailVO?.id">
+      <div class="content">
+        <!-- 任务信息 -->
+        <div>
+          <div class="mb-12px flex items-center gap-8px">
+            <div class="head">任务信息</div>
+            <NTag type="warning" class="w-40px" :bordered="false"
+              v-if="formModel.accidentDetailVO.taskUrgencyLevel === 3">{{taskUrgencyLevelOptions.find(it => it.value
+                === formModel.accidentDetailVO.taskUrgencyLevel)?.label || ''}}</NTag>
+            <NTag type="error" class="w-40px" :bordered="false" v-else>{{taskUrgencyLevelOptions.find(it => it.value
+              === formModel.accidentDetailVO.taskUrgencyLevel)?.label || ''}}</NTag>
+            <NTag :type="statusOptions.find(it => it.value === formModel.accidentDetailVO.status)?.type" class="w-52px"
+              :bordered="false">{{statusOptions.find(it => it.value ===
+                formModel.accidentDetailVO.status)?.label || ''}}</NTag>
+          </div>
+          <div class="mb-16px">{{ formatCascadeData(formModel.accidentDetailVO.errorTypeList) }}</div>
+          <div class="mb-16px flex items-center">
+            <img :src="formModel.accidentDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+              v-if="formModel.accidentDetailVO.vehiclePictures" />
+            <div>
+              <div class="plateNumber mb-8px">{{ formModel.accidentDetailVO.plateNumber }}</div>
+              <div class="value">{{ formModel.accidentDetailVO.vehicleColor }} | 车身编号:{{
+                formModel.accidentDetailVO.vehicleCode }} | VIN码:{{
+                  formModel.accidentDetailVO.vehicleVin }}</div>
+            </div>
+          </div>
+          <div class="mb-16px">
+            <div class="label mb-4px">上报地址</div>
+            <div class="value">{{ formModel.accidentDetailVO.vehicleLocation }}</div>
+          </div>
+          <div class="mb-16px">
+            <div class="label mb-4px">事故描述</div>
+            <div class="value mb-8px">{{ formModel.accidentDetailVO.taskDescription }}</div>
+            <div class="imgs">
+              <img v-for="(item, idx) in JSON.parse(formModel.accidentDetailVO.taskImage)" :key="idx" :src="item.url"
+                class="w-56px h-56px mr-16px" />
+            </div>
+          </div>
+          <div class="mb-16px flex gap-24px">
+            <div class="flex items-center flex-1">
+              <img :src="formModel.accidentDetailVO.reportUserAvatar" class="w-40px h-40px mr-8px"
+                v-if="formModel.accidentDetailVO.reportUserAvatar" />
+              <div>
+                <div class="label mb-4px">上报人</div>
+                <div class="value">{{ formModel.accidentDetailVO.reportUserName }} {{
+                  formModel.accidentDetailVO.reportUserPhone }}</div>
+              </div>
+            </div>
+            <div class="flex-1">
+              <div class="label mb-4px">上报时间</div>
+              <div class="value">{{ formModel.accidentDetailVO.createTime }}</div>
+            </div>
+          </div>
+        </div>
+        <!-- 处理情况 -->
+        <div>
+          <div class="head mb-12px">处理情况</div>
+          <div class="mb-16px flex gap-8px">
+            人员伤亡情况
+            <NTag type="primary" class="w-52px" :bordered="false">伤亡 {{ formModel.accidentDetailVO.deathCount || 0 }}
+            </NTag>
+            <NTag type="error" class="w-52px" :bordered="false">重伤 {{ formModel.accidentDetailVO.severeCount || 0 }}
+            </NTag>
+            <NTag type="warning" class="w-52px" :bordered="false">轻伤 {{ formModel.accidentDetailVO.minorCount || 0 }}
+            </NTag>
+          </div>
+          <div class="mb-16px flex gap-8px">
+            初版(交警)责任判断
+            <NTag type="error" class="w-64px" :bordered="false"
+              v-if="formModel.accidentDetailVO.responsibilityJudgment === 1">我方主责</NTag>
+            <NTag type="success" class="w-64px" :bordered="false"
+              v-if="formModel.accidentDetailVO.responsibilityJudgment === 2">我方无责</NTag>
+            <NTag type="primary" class="w-64px" :bordered="false"
+              v-if="formModel.accidentDetailVO.responsibilityJudgment === 3">双方同责</NTag>
+          </div>
+          <div class="mb-16px">
+            <div class="label mb-4px">现场情况</div>
+            <div class="value mb-8px">{{ formModel.accidentDetailVO.situationDescription }}</div>
+            <div class="imgs">
+              <img v-for="(item, idx) in JSON.parse(formModel.accidentDetailVO.situationImage)" :key="idx"
+                :src="item.url" class="w-56px h-56px mr-16px" />
+            </div>
+          </div>
+          <div class="mb-16px">
+            <div class="label mb-4px">处置情况</div>
+            <div class="value mb-8px">{{ formModel.accidentDetailVO.disposalDescription }}</div>
+            <div class="imgs">
+              <img v-for="(item, idx) in JSON.parse(formModel.accidentDetailVO.disposalImage)" :key="idx"
+                :src="item.url" class="w-56px h-56px mr-16px" />
+            </div>
+          </div>
+          <div class="mb-16px flex gap-24px">
+            <div class="flex items-center flex-1">
+              <img :src="formModel.accidentDetailVO.handleUserAvatar" class="w-40px h-40px mr-8px"
+                v-if="formModel.accidentDetailVO.handleUserAvatar" />
+              <div>
+                <div class="label mb-4px">处理人</div>
+                <div class="value">{{ formModel.accidentDetailVO.handleUserName }} {{
+                  formModel.accidentDetailVO.handleUserPhone }}</div>
+              </div>
+            </div>
+            <div class="flex-1">
+              <div class="label mb-4px">处理时间</div>
+              <div class="value">{{ formModel.accidentDetailVO.handleTime }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 检测详情 -->
+    <div v-if="formModel.detectionDetailVO?.id">
+      <div class="content">
+        <div class="mb-16px flex items-center">
+          <img :src="formModel.detectionDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+            v-if="formModel.detectionDetailVO?.vehiclePictures" />
+          <div>
+            <div class="plateNumber mb-8px">{{ formModel.detectionDetailVO.plateNumber }}</div>
+            <div class="value">{{ formModel.detectionDetailVO.vehicleColor }} | 车身编号:{{
+              formModel.detectionDetailVO.vehicleCode }} | VIN码:{{
+                formModel.detectionDetailVO.vehicleVin }}</div>
+          </div>
+        </div>
+        <div class="head mb-12px">系统检测</div>
+        <div class="label mb-16px">
+          <span>{{ formModel.detectionDetailVO.networkDetection === 1 ? '✔' : 'x' }}网络检测</span>
+          <span>{{ formModel.detectionDetailVO.versionVerification === 1 ? '✔' : 'x' }}软件版本核对</span>
+          <span>{{ formModel.detectionDetailVO.communicationSelfCheck === 1 ? '✔' : 'x' }}通讯自检</span>
+          <span>{{ formModel.detectionDetailVO.remoteSelfCheck === 1 ? '✔' : 'x' }}远程控制自检</span>
+          <span>{{ formModel.detectionDetailVO.collisionPrevention === 1 ? '✔' : 'x' }}防撞激活</span>
+          <span>{{ formModel.detectionDetailVO.sensorSelfCheck === 1 ? '✔' : 'x' }}传感器自检</span>
+        </div>
+        <div class="head mb-12px">人工检测</div>
+        <div class="mb-16px">
+          <div class="label mb-8px">传感器清洁:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.detectionDetailVO.sensorCleaningPicture)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">车内交互屏检查:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.detectionDetailVO.interactiveScreenPicture)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">紧急求助按钮/应答机:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.detectionDetailVO.helpButtonPicture)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">交付功能:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.detectionDetailVO.paymentFunctionPicture)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">订单系统检查:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.detectionDetailVO.systemCheckPicture)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">任务单号:{{ formModel.detectionDetailVO.id }}</div>
+        <div class="mb-16px">操作人:{{ formModel.detectionDetailVO.operationsUserName }}</div>
+        <div class="mb-16px">操作时间:{{ dayjs(formModel.detectionDetailVO.checkTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+      </div>
+    </div>
+    <!-- 充电详情 -->
+    <div v-if="formModel.chargingDetailVO?.id">
+      <div class="content">
+        <div class="mb-16px flex items-center">
+          <img :src="formModel.chargingDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+            v-if="formModel.chargingDetailVO?.vehiclePictures" />
+          <div>
+            <div class="plateNumber mb-8px">{{ formModel.chargingDetailVO.plateNumber }}</div>
+            <div class="value mb-8px">{{ formModel.chargingDetailVO.vehicleColor }} | VIN码:{{
+                formModel.chargingDetailVO.vehicleVin }}
+            </div>
+            <div class="value">车辆位置:{{ formModel.chargingDetailVO.vehicleLocation }}</div>
+          </div>
+        </div>
+        <div class="mb-16px">任务单号:{{ formModel.chargingDetailVO.id }}</div>
+        <div class="mb-16px">开始充电前电量:{{ formModel.chargingDetailVO.startRemainingBattery }}%</div>
+        <div class="mb-16px">开始充电时间:{{ dayjs(formModel.chargingDetailVO.startTime).format('YYYY-MM-DD HH:mm:ss') }}
+        </div>
+        <div class="mb-16px">开始充电操作人:{{ formModel.chargingDetailVO.startOperationsUserName }}</div>
+        <div class="mb-16px">
+          <div class="mb-8px">开始充电图片:</div>
+          <div class="imgs">
+            <div v-for="(item, idx) in JSON.parse(formModel.chargingDetailVO.startChargingPicture)" :key="idx">
+              <img :src="item.url" v-if="item?.url" class="w-56px h-56px mr-16px" />
+            </div>
+          </div>
+        </div>
+        <div class="mb-16px">充电后电量:{{ formModel.chargingDetailVO.endRemainingBattery }}%</div>
+        <div class="mb-16px">结束充电时间:{{ dayjs(formModel.chargingDetailVO.endTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+        <div class="mb-16px">结束充电操作人:{{ formModel.chargingDetailVO.endOperationsUserName }}</div>
+        <div class="mb-16px">
+          <div class="mb-8px">结束充电图片:</div>
+          <div class="imgs">
+            <div v-for="(item, idx) in JSON.parse(formModel.chargingDetailVO.endChargingPicture)" :key="idx">
+              <img :src="item.url" v-if="item?.url" class="w-56px h-56px mr-16px" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 整备详情 -->
+    <div v-if="formModel.preparationDetailVO?.id">
+      <div class="content">
+        <div class="mb-16px flex items-center">
+          <img :src="formModel.preparationDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+            v-if="formModel.preparationDetailVO?.vehiclePictures" />
+          <div>
+            <div class="plateNumber mb-8px">{{ formModel.chargingDetailVO.plateNumber }}</div>
+            <div class="value mb-8px">{{ formModel.chargingDetailVO.vehicleColor }} | VIN码:{{
+                formModel.chargingDetailVO.vehicleVin }}
+            </div>
+            <div class="value">车辆位置:{{ formModel.chargingDetailVO.vehicleLocation }}</div>
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">胎压:</div>
+          <div class="value mb-8px">左前轮:{{ formModel.preparationDetailVO.leftFrontWheel }} 右前轮:{{
+            formModel.preparationDetailVO.rightFrontWheel }} 左后轮:{{ formModel.preparationDetailVO.leftRearWheel }}
+            右后轮:{{ formModel.preparationDetailVO.rightRearWheel }}</div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">大灯情况:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.headLight)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">转向灯情况:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.turnSignal)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">刹车灯情况:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.brakeLight)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">雾灯情况:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.fogLight)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">车身标识及清洁:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.bodyEmblem)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">舱内清洁及消毒:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.cabinCleaning)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">遗失物品上报:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.preparationDetailVO.lostItems)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+          <div class="label mt-8px">遗失说明:<div v-html="formModel.preparationDetailVO.lostItemsDetail"></div>
+          </div>
+        </div>
+        <div class="mb-16px">任务单号:{{ formModel.preparationDetailVO.id }}</div>
+        <div class="mb-16px">操作人:{{ formModel.preparationDetailVO.operationsUserName }}</div>
+        <div class="mb-16px">操作时间:{{ dayjs(formModel.preparationDetailVO.handleTime).format('YYYY-MM-DD HH:mm:ss') }}
+        </div>
+      </div>
+    </div>
+    <!-- 洗车详情 -->
+    <div v-if="formModel.cleanDetailVO?.id">
+      <div class="content">
+        <div class="mb-16px flex items-center">
+          <img :src="formModel.cleanDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+            v-if="formModel.cleanDetailVO?.vehiclePictures" />
+          <div>
+            <div class="plateNumber mb-8px">{{ formModel.chargingDetailVO.plateNumber }}</div>
+            <div class="value mb-8px">{{ formModel.chargingDetailVO.vehicleColor }} | VIN码:{{
+                formModel.chargingDetailVO.vehicleVin }}
+            </div>
+            <div class="value">车辆位置:{{ formModel.chargingDetailVO.vehicleLocation }}</div>
+          </div>
+        </div>
+        <div class="mb-16px">
+          <div class="label mb-8px">图片:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.cleanDetailVO.taskImage)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">任务单号:{{ formModel.cleanDetailVO.id }}</div>
+        <div class="mb-16px">操作人:{{ formModel.cleanDetailVO.operationsUserName }}</div>
+        <div class="mb-16px">操作时间:{{ dayjs(formModel.cleanDetailVO.handleTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+      </div>
+    </div>
+    <!-- 维修详情 -->
+    <div v-if="formModel.repairDetailVO?.id">
+      <div class="content">
+        <div class="mb-16px flex items-center">
+          <img :src="formModel.repairDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+            v-if="formModel.repairDetailVO?.vehiclePictures" />
+          <div>
+            <div class="plateNumber mb-8px">{{ formModel.chargingDetailVO.plateNumber }}</div>
+            <div class="value mb-8px">{{ formModel.chargingDetailVO.vehicleColor }} | VIN码:{{
+                formModel.chargingDetailVO.vehicleVin }}
+            </div>
+            <div class="value">车辆位置:{{ formModel.chargingDetailVO.vehicleLocation }}</div>
+          </div>
+        </div>
+        <div class="mb-16px">维修停运时间:{{ dayjs(formModel.repairDetailVO.startTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+        <div class="mb-16px">维修原因:{{ formModel.repairDetailVO.repairReason }}</div>
+        <div class="mb-16px">
+          <div class="label mb-8px">维修前图片:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.repairDetailVO.startRepairPicture)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">送修时间:{{ dayjs(formModel.repairDetailVO.sendTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+        <div class="mb-16px">操作人:{{ formModel.repairDetailVO.sendUserName }}</div>
+        <div class="mb-16px">维修总费用:{{ formModel.repairDetailVO.repairMoney }}</div>
+        <div class="mb-16px">
+          <div class="label mb-8px">维修费用明细单:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.repairDetailVO.repairMoneyDetail)" :key="idx"
+              :src="item.url" class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">取回时间:{{ dayjs(formModel.repairDetailVO.returnTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+        <div class="mb-16px">操作人:{{ formModel.repairDetailVO.returnUserName }}</div>
+        <div class="mb-16px">任务单号:{{ formModel.repairDetailVO.id }}</div>
+      </div>
+    </div>
+    <!-- 保养详情 -->
+    <div v-if="formModel.routineDetailVO?.id">
+      <div class="content">
+        <div class="mb-16px flex items-center">
+          <img :src="formModel.routineDetailVO.vehiclePictures" class="w-112px h-64px mr-16px"
+            v-if="formModel.routineDetailVO?.vehiclePictures" />
+          <div>
+            <div class="plateNumber mb-8px">{{ formModel.chargingDetailVO.plateNumber }}</div>
+            <div class="value mb-8px">{{ formModel.chargingDetailVO.vehicleColor }} | VIN码:{{
+                formModel.chargingDetailVO.vehicleVin }}
+            </div>
+            <div class="value">车辆位置:{{ formModel.chargingDetailVO.vehicleLocation }}</div>
+          </div>
+        </div>
+        <div class="label mb-16px">任务完成情况:{{ formModel.routineDetailVO.taskDescription }}</div>
+        <div class="mb-16px">
+          <div class="label mb-8px">图片:</div>
+          <div class="imgs">
+            <img v-for="(item, idx) in JSON.parse(formModel.routineDetailVO.taskImage)" :key="idx" :src="item.url"
+              class="w-56px h-56px mr-16px" />
+          </div>
+        </div>
+        <div class="mb-16px">任务单号:{{ formModel.routineDetailVO.id }}</div>
+        <div class="mb-16px">操作人:{{ formModel.routineDetailVO.operationsUserName }}</div>
+        <div class="mb-16px">操作时间:{{ dayjs(formModel.routineDetailVO.checkTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
+      </div>
+    </div>
+    <template #action>
+      <n-space justify="end">
+        <n-button @click="closeModal">
+          返回
+        </n-button>
+      </n-space>
+    </template>
+  </n-modal>
+</template>
+
+<style scoped lang="scss">
+@use '../index.scss';
+</style>
diff --git a/src/views/operations/taskOrder/taskOrderCenter/index.scss b/src/views/operations/taskOrder/taskOrderCenter/index.scss
new file mode 100644
index 0000000..7340526
--- /dev/null
+++ b/src/views/operations/taskOrder/taskOrderCenter/index.scss
@@ -0,0 +1,41 @@
+.content {
+  .head {
+    font-family: PingFang SC;
+    font-size: 16px;
+    font-weight: 600;
+    line-height: 24px;
+    letter-spacing: normal;
+    color: #171F26;
+    position: relative;
+    text-indent: 11px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 50%;
+      left: 0;
+      transform: translateY(-50%);
+      width: 3px;
+      height: 14px;
+      z-index: 0;
+      border-radius: 3px;
+      background: #0068FF;
+    }
+  }
+
+  .plateNumber {
+    font-size: 20px;
+    line-height: 28px;
+    font-weight: 600;
+  }
+
+  .label {
+    color: rgba(23, 31, 38, 0.6);
+  }
+
+  .imgs {
+    img {
+      border-radius: 4px;
+    }
+  }
+}
diff --git a/src/views/operations/taskOrder/taskOrderCenter/index.vue b/src/views/operations/taskOrder/taskOrderCenter/index.vue
index 0da061d..899a205 100644
--- a/src/views/operations/taskOrder/taskOrderCenter/index.vue
+++ b/src/views/operations/taskOrder/taskOrderCenter/index.vue
@@ -7,6 +7,7 @@
 import SearchPanel from '@/components/custom/SearchPanel.vue'
 import TableModal from './components/TableModal.vue'
 import AddFormModal from './components/AddFormModal.vue'
+import DetailModal from './components/detailModal.vue'
 import dayjs from 'dayjs'
 
 const router = useRouter()
@@ -30,6 +31,8 @@
 
 const formRef = ref<FormInst | null>()
 const modalRef = ref()
+const detailModalRef = ref()
+
 const showFormModal = ref(false)
 const formMode = ref<'add' | 'edit'>('add')
 
@@ -186,18 +189,56 @@
   formMode.value = 'add'
 }
 
+/**
+ * 级联选中时自动补充父级 id
+ * @param tree 原始树结构
+ * @param selectedIds 已选中的 id 列表
+ */
+function addParentIds(
+  tree: any[],
+  selectedIds: string[]
+): string[] {
+  const result = new Set(selectedIds)
+
+  function traverse(nodes: any[], parent?: any) {
+    nodes.forEach(node => {
+      // 如果当前节点被选中
+      if (result.has(node.id) && parent) {
+        result.add(parent.id)
+      }
+
+      if (node.child && node.child.length) {
+        traverse(node.child, node)
+      }
+    })
+  }
+
+  traverse(tree)
+
+  return Array.from(result)
+}
+
 const handleConfirmForm = (data: any) => {
+  console.log(data)
+  const taskImage = data.taskImage.map((item: any) => {
+    return {
+      id: item.id,
+      name: item.name,
+      url: item.url
+    }
+  })
   const params = {
     ...data,
-    taskTypeId: data.taskTypeId.join(','),
+    taskTypeId: addParentIds(taskErrorIdOptions.value,data.taskTypeId).join(','),
+    taskImage: JSON.stringify(taskImage),
   }
-    createTaskAccident(params).then((res) => {
-      if (res.code === 200) {
-        window.$message.success('新增成功')
-      }
-    }).finally(() => {
-      getList()
-    })
+  createTaskAccident(params).then((res) => {
+    if (res.code === 200) {
+      window.$message.success('新增成功')
+    }
+  }).finally(() => {
+    getList()
+  })
 }
 
 
@@ -309,7 +350,7 @@
           <NButton
             text
             type='primary'
-            onClick={() => modalRef.value.openModal('edit', row.id)}
+            onClick={() => detailModalRef.value.openModal(row.id)}
           >
             详情
           </NButton>
@@ -360,6 +401,8 @@
       </NSpace>
     </n-card>
     <TableModal ref="modalRef" @refresh="getList" />
-    <AddFormModal v-model:show="showFormModal" :mode="formMode" @confirm="handleConfirmForm" :taskErrorIdOptions="taskErrorIdOptions" :taskUrgencyLevelOptions="taskUrgencyLevelOptions"/>
+    <DetailModal ref="detailModalRef" />
+    <AddFormModal v-model:show="showFormModal" :mode="formMode" @confirm="handleConfirmForm"
+      :taskErrorIdOptions="taskErrorIdOptions" :taskUrgencyLevelOptions="taskUrgencyLevelOptions" />
   </div>
 </template>
diff --git a/src/views/transaction/order/orderManagement/components/TableModal.vue b/src/views/transaction/order/orderManagement/components/TableModal.vue
index 80ce470..028a86f 100644
--- a/src/views/transaction/order/orderManagement/components/TableModal.vue
+++ b/src/views/transaction/order/orderManagement/components/TableModal.vue
@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { useBoolean } from '@/hooks'
+import { getAvailableVehicleList } from '@/service/api/transaction'
 
 interface Props {
   modalName?: string
@@ -28,6 +29,7 @@
 
 async function openModal(data: any) {
   emit('open')
+  await getOrder(data.id)
   showModal()
   formModel.value = { ...data }
 }
@@ -58,6 +60,16 @@
 }
 
 const options = ref()
+
+const getOrder = async (orderId: number) => {
+  const res = await getAvailableVehicleList(orderId)
+  if (res.code === 0) {
+    options.value = res.data.map((item: any) => ({
+      label: item.vehicleName,
+      value: item.vehicleId,
+    }))
+  }
+}
 </script>
 
 <template>

--
Gitblit v1.7.1