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