代码修改

This commit is contained in:
qiuyuan 2025-10-22 09:40:27 +08:00
parent d428ab01d7
commit 34208a9a31
3 changed files with 709 additions and 0 deletions

View File

@ -0,0 +1,95 @@
import { ProfileOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'workorderYunying',
name: 'workorderYunying',
component: 'RouteViewLayout',
meta: {
icon: ProfileOutlined,
title: '工单管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'mineWorderOrder/index.vue',
name: 'mineWorderOrder',
component: 'workorderYunying/mineWorderOrder/index.vue',
meta: {
title: '我的工单',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'invalidWzorkOrder/index.vue',
name: 'invalidWzorkOrder',
component: 'workorderYunying/invalidWzorkOrder/index.vue',
meta: {
title: '无效工单',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'abnormalWorkOrder/index.vue',
name: 'abnormalWorkOrder',
component: 'workorderYunying/abnormalWorkOrder/index.vue',
meta: {
title: '异常工单',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'serviceWorkOrder/index.vue',
name: 'serviceWorkOrder',
component: 'workorderYunying/serviceWorkOrder/index.vue',
meta: {
title: '服务工单',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'visitWorkOrder/index.vue',
name: 'visitWorkOrder',
component: 'workorderYunying/visitWorkOrder/index.vue',
meta: {
title: '工单回访',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'visitHistory/index.vue',
name: 'visitHistory',
component: 'workorderYunying/visitHistory/index.vue',
meta: {
title: '回访记录',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'waitWorkOrder/index.vue',
name: 'waitWorkOrder',
component: 'workorderMenu/waitWorkOrder/index.vue',
meta: {
title: '待派单',
isMenu: true,
keepAlive: true,
permission: '*',
},
}
],
},
]

View File

@ -0,0 +1,254 @@
<!-- src/components/DispatchDrawer.vue -->
<template>
<a-drawer
title="派单"
:open="visible"
:width="600"
@close="handleClose"
:destroy-on-close="true"
>
<a-form
:model="formModel"
:rules="rules"
ref="formRef"
layout="vertical"
class="form-mode"
>
<!-- 服务对象 -->
<a-row :gutter="16">
<a-col :xs="12">
<a-form-item label="服务对象">
<a-input
v-model:value="formModel.serviceTarget"
placeholder="请输入服务对象名称"
disabled
/>
</a-form-item>
</a-col>
</a-row>
<!-- 服务项目和服务费用 -->
<a-row :gutter="16">
<a-col :xs="12">
<a-form-item label="服务项目" name="serviceItem">
<a-select
v-model:value="formModel.serviceItem"
placeholder="请选择服务项目"
mode="multiple"
>
<a-select-option value="兴趣活动">兴趣活动</a-select-option>
<a-select-option value="居家照护">居家照护</a-select-option>
<a-select-option value="医疗护理">医疗护理</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :xs="12">
<a-form-item label="服务费用">
<a-input-number
v-model:value="formModel.serviceFee"
placeholder="由关联服务项目计算得出"
style="width: 100%"
:min="0"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 服务人员 -->
<a-row :gutter="16">
<a-col :xs="12">
<a-form-item label="服务人员" name="serviceStaff">
<a-select
v-model:value="formModel.serviceStaff"
placeholder="请选择服务人员"
>
<a-select-option
v-for="user in assignees"
:key="user.id"
:value="user.id"
>
{{ user.name }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<!-- 计划日期 -->
<a-row :gutter="16">
<a-col :xs="24">
<a-form-item label="计划日期" name="plannedDate">
<a-range-picker
v-model:value="formModel.plannedDate"
style="width: 100%"
:placeholder="['开始日期', '结束日期']"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 计划开始时间和要求工单时长 -->
<a-row :gutter="16">
<a-col :xs="12">
<a-form-item label="计划开始时间" name="plannedStartTime">
<a-time-picker
v-model:value="formModel.plannedStartTime"
placeholder="请选择计划开始时间"
style="width: 100%"
format="HH:mm"
:minute-step="5"
/>
</a-form-item>
</a-col>
<a-col :xs="12">
<a-form-item label="要求工单时长" name="requiredDuration">
<a-input
v-model:value="formModel.requiredDuration"
placeholder="请输入要求工单时长"
>
<template #suffix>分钟</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<!-- 服务地址 -->
<a-row :gutter="16">
<a-col :xs="24">
<a-form-item label="服务地址" name="serviceArea">
<AreaCascader
v-model:value="formModel.serviceArea"
style="width: 100%; margin-bottom: 8px;"
/>
<a-input
v-model:value="formModel.detailedAddress"
placeholder="详细地址"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 服务内容 -->
<a-row :gutter="16">
<a-col :xs="24">
<a-form-item label="服务内容">
<a-input
v-model:value="formModel.serviceContent"
placeholder="填写服务内容充当备注作用此字段数据会在app工单中显示"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<div class="select-btn" style="display: flex; justify-content: flex-end; gap: 8px;">
<a-button @click="handleClose">取消</a-button>
<a-button type="primary" @click="handleSubmit">确定</a-button>
</div>
</template>
</a-drawer>
</template>
<script setup>
import { ref, reactive, watch, nextTick } from 'vue'
import AreaCascader from '@/components/AreaCascader/index.vue'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
assignees: {
type: Array,
default: () => []
},
initialValues: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['close', 'submit'])
const formRef = ref()
const formModel = reactive({
serviceTarget: '',
serviceItem: [],
serviceFee: undefined,
serviceStaff: undefined,
plannedDate: [],
plannedStartTime: null,
requiredDuration: '',
serviceArea: [],
detailedAddress: '',
serviceContent: ''
})
const rules = {
serviceItem: [{ required: true, message: '请选择服务项目', trigger: 'change' }],
serviceStaff: [{ required: true, message: '请选择服务人员', trigger: 'change' }],
plannedDate: [{ required: true, message: '请选择计划日期', trigger: 'change' }],
plannedStartTime: [{ required: true, message: '请选择计划开始时间', trigger: 'change' }],
requiredDuration: [{ required: true, message: '请输入要求工单时长', trigger: 'blur' }],
serviceArea: [{ required: true, message: '请选择服务区域', trigger: 'change' }],
detailedAddress: [{ required: true, message: '请输入详细地址', trigger: 'blur' }]
}
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
nextTick(() => {
if (Object.keys(props.initialValues).length > 0) {
Object.assign(formModel, props.initialValues)
} else {
//
for (const key in formModel) {
if (Array.isArray(formModel[key])) {
formModel[key] = []
} else if (typeof formModel[key] === 'string') {
formModel[key] = ''
} else {
formModel[key] = undefined
}
}
formRef.value?.resetFields()
}
})
}
}
)
const handleClose = () => {
formRef.value?.resetFields()
emit('close')
}
const handleSubmit = async () => {
try {
const values = await formRef.value.validateFields()
// model
emit('submit', { ...formModel })
handleClose()
} catch (error) {
console.log('校验失败:', error)
}
}
</script>
<style scoped>
.form-mode {
padding: 0 16px;
}
.select-btn {
margin-top: 16px;
text-align: right;
}
.ant-form-item {
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,360 @@
<template>
<x-search-bar class="mb-4">
<template #default="{ gutter, colSpan }">
<a-form :label-col="{ style: { width: '130px' } }" :model="searchFormData">
<!-- 基础查询字段 -->
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="姓名" name="name">
<a-input v-model:value="searchFormData.name" placeholder="请输入姓名" />
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="身份证号" name="idCard">
<a-input v-model:value="searchFormData.idCard" placeholder="请输入身份证号" />
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="异常类型" name="exceptionType">
<a-select v-model:value="searchFormData.exceptionType" placeholder="请选择异常类型">
<a-select-option value="type1">类型1</a-select-option>
<a-select-option value="type2">类型2</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<!-- 高级查询折叠面板 -->
<a-collapse v-model:activeKey="advancedSearchVisible" ghost>
<a-collapse-panel key="1" :showArrow="false" style="padding: 0; border: none;">
<template #header></template>
<a-row :gutter="gutter" style="margin-top: 16px;">
<a-col v-bind="colSpan">
<a-form-item label="实际服务日期" name="plannedDate">
<a-range-picker v-model:value="searchFormData.plannedDate" />
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="实际服务时长(小时)" name="serviceDuration" style="white-space: nowrap;">
<a-input-number v-model:value="searchFormData.serviceDuration" :min="0"
placeholder="请输入时长" style="width: 100%" />
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="工单号" name="serviceOrderNo">
<a-input v-model:value="searchFormData.serviceOrderNo" placeholder="请输入工单号" />
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="所属服务组织" name="serviceOrg">
<a-select v-model:value="searchFormData.serviceOrg" placeholder="请选择服务组织">
<a-select-option value="org1">组织1</a-select-option>
<a-select-option value="org2">组织2</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="所在区域" name="currentNode">
<AreaCascader v-model:value="searchFormData.currentNode" @change="onAreaChange" />
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="服务满意度" name="satisfaction">
<a-select v-model:value="searchFormData.satisfaction" placeholder="请选择满意度">
<a-select-option value="5">非常满意</a-select-option>
<a-select-option value="4">满意</a-select-option>
<a-select-option value="3">一般</a-select-option>
<a-select-option value="2">不满意</a-select-option>
<a-select-option value="1">非常不满意</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="服务人员满意度" name="staffSatisfaction">
<a-select v-model:value="searchFormData.staffSatisfaction" placeholder="请选择满意度">
<a-select-option value="5">非常满意</a-select-option>
<a-select-option value="4">满意</a-select-option>
<a-select-option value="3">一般</a-select-option>
<a-select-option value="2">不满意</a-select-option>
<a-select-option value="1">非常不满意</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="是否回访" name="isVisited">
<a-select v-model:value="searchFormData.isVisited" placeholder="请选择">
<a-select-option :value="true"></a-select-option>
<a-select-option :value="false"></a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-collapse-panel>
</a-collapse>
<!-- 操作按钮 -->
<a-row :gutter="gutter" style="margin-top: 16px;">
<a-col :span="24" style="text-align: right;">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
<a-button type="link" @click="toggleAdvancedSearch"
style="color: #1890ff; border: none; padding: 0; font-size: 14px;">
{{ advancedSearchVisible.length ? '收起' : '高级查询' }}
<template #icon>
<down-outlined v-if="advancedSearchVisible.length" />
<up-outlined v-else />
</template>
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-row :gutter="8" :wrap="false" style="margin-top: 20px;">
<a-col flex="auto">
<a-card title="回访列表">
<div style="margin-bottom: 20px;">
<a-space>
<a-button type="primary">导入</a-button>
<a-button type="primary">导入记录</a-button>
<a-button type="primary">导出</a-button>
<a-button type="primary">导出记录</a-button>
</a-space>
</div>
<a-table rowKey="id" :loading="loading" :pagination="paginationConfig" :columns="columns"
:data-source="listData" :scroll="{ x: totalWidth }" @change="onTableChange">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-else-if="column.dataIndex === 'otherPhone1'">
<span>{{ record.otherPhone1 || record.otherPhone2 || '—' }}</span>
</template>
<template v-else-if="column.dataIndex === 'plannedServiceStartDate'">
<span>{{ formatDate(record.plannedServiceStartDate) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<!-- 只保留派单按钮 -->
<a-button type="link" @click="openDispatchDrawer(record)">派单</a-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<!-- 派单抽屉右侧弹出 -->
<DispatchDrawer :visible="showDrawer" :assignees="staffList" :initial-values="initialValues"
@close="showDrawer = false" @submit="handleDispatchSubmit" />
</template>
<script setup>
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'
import AreaCascader from '@/components/AreaCascader/index.vue'
import { message } from 'ant-design-vue'
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import DispatchDrawer from './components/DispatchDrawer.vue';
defineOptions({
name: 'allocation',
})
const { t } = useI18n()
// columns
const columns = [
{ title: '序号', key: 'serialNumber', align: 'center', width: 60 },
{ title: '工单号', dataIndex: 'orderNum', key: 'orderNum', align: 'center', width: 120 },
{ title: '姓名', dataIndex: 'customerName', key: 'customerName', align: 'center', width: 100 },
{ title: '身份证号', dataIndex: 'idCard', key: 'idCard', align: 'center', width: 150 },
{ title: '联系方式1', dataIndex: 'otherPhone1', key: 'otherPhone1', align: 'center', width: 120 },
{ title: '联系方式2', dataIndex: 'otherPhone2', key: 'otherPhone2', align: 'center', width: 120 },
{ title: '计划服务时间', dataIndex: 'plannedServiceStartDate', key: 'plannedServiceStartDate', align: 'center', width: 180 },
{ title: '计划服务时长(分钟)', dataIndex: 'serviceDurationMinutes', key: 'serviceDurationMinutes', align: 'center', width: 150 },
{ title: '服务项目', dataIndex: 'serviceName', key: 'serviceName', align: 'center', width: 120 },
{ title: '所属区域', dataIndex: 'area', key: 'area', align: 'center', width: 120 },
{ title: '服务地址', dataIndex: 'serviceAddress', key: 'serviceAddress', align: 'center', width: 200 },
{ title: '下单员', dataIndex: 'creator', key: 'creator', align: 'center', width: 100 },
{ title: '下单时间', dataIndex: 'createTime', key: 'createTime', align: 'center', width: 180 },
{ title: '操作', key: 'action', width: 100, fixed: 'right' }
]
//
const paginationState = ref({
current: 1,
pageSize: 10,
total: 0,
})
const loading = ref(false)
const listData = ref([])
//
const searchFormData = ref({
name: '',
idCard: '',
exceptionType: undefined,
serviceOrderNo: '',
serviceOrg: undefined,
satisfaction: undefined,
staffSatisfaction: undefined,
isVisited: undefined,
plannedDate: [],
serviceDuration: undefined,
currentNode: [],
})
//
const advancedSearchVisible = ref([])
//
const totalWidth = computed(() => {
return columns.reduce((sum, col) => sum + (col.width || 100), 0)
})
// a-table
const paginationConfig = computed(() => ({
current: paginationState.value.current,
pageSize: paginationState.value.pageSize,
total: paginationState.value.total,
showSizeChanger: true,
showQuickJumper: true,
}))
//
function mockData() {
return Array.from({ length: 5 }, (_, i) => ({
id: i + 1,
orderNum: `GD20251021${String(i + 1).padStart(4, '0')}`,
customerName: `客户${i + 1}`,
idCard: `11010119900101${String(i + 1).padStart(4, '0')}`,
otherPhone1: `1380013800${i}`,
otherPhone2: i % 2 === 0 ? `1390013900${i}` : null,
plannedServiceStartDate: '2025-10-25 09:00:00',
serviceDurationMinutes: 120 + i * 10,
serviceName: '居家照护',
area: '北京市朝阳区',
serviceAddress: '朝阳区某某街道XX号',
creator: '管理员',
createTime: '2025-10-20 14:30:00',
}))
}
// 使 mock
async function getPageList() {
loading.value = true
try {
//
await new Promise(resolve => setTimeout(resolve, 300))
listData.value = mockData()
paginationState.value.total = listData.value.length
} catch (error) {
console.error('获取列表失败:', error)
message.error('加载数据失败')
} finally {
loading.value = false
}
}
// &
function handleSearch() {
paginationState.value.current = 1
getPageList()
}
function handleResetSearch() {
searchFormData.value = {
name: '',
idCard: '',
exceptionType: undefined,
serviceOrderNo: '',
serviceOrg: undefined,
satisfaction: undefined,
staffSatisfaction: undefined,
isVisited: undefined,
plannedDate: [],
serviceDuration: undefined,
currentNode: [],
}
handleSearch()
}
//
function onTableChange(pagination) {
paginationState.value.current = pagination.current
paginationState.value.pageSize = pagination.pageSize
getPageList()
}
//
function toggleAdvancedSearch() {
advancedSearchVisible.value = advancedSearchVisible.value.length ? [] : ['1']
}
function onAreaChange(value) {
console.log('区域变更:', value)
}
//
function formatDate(dateStr) {
return dateStr || '—'
}
// ====== ======
const showDrawer = ref(false)
const currentRecord = ref(null)
const staffList = ref([
{ id: '1', name: '张三' },
{ id: '2', name: '李四' },
{ id: '3', name: '王五' },
])
const initialValues = computed(() => {
return {}
})
// ====== ======
function openDispatchDrawer(record) {
currentRecord.value = record
showDrawer.value = true
}
function closeDispatchDrawer() {
showDrawer.value = false
currentRecord.value = null
}
function handleDispatchSubmit(values) {
console.log('派单数据:', values)
console.log('派给工单:', currentRecord.value)
message.success('派单成功')
closeDispatchDrawer()
}
//
getPageList()
</script>
<style lang="less" scoped></style>