generated from Leo_Ding/web-template
Merge branch 'master' of https://gitlab.guxuan.icu/Leo_Ding/hahaPension_admin
This commit is contained in:
commit
c17f6794a4
22
src/apis/modules/workOrder.js
Normal file
22
src/apis/modules/workOrder.js
Normal file
@ -0,0 +1,22 @@
|
||||
// 工单模块
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 工单列表-- 全部
|
||||
export const getWorkOrderList = (params) => request.basic.get('/api/v1/orders', params)
|
||||
|
||||
// 异常工单列表
|
||||
export const getAbnormalWorkOrderList = (params) => request.basic.get('/api/v1/orders/errors_order', params)
|
||||
|
||||
// 我下的工单
|
||||
export const getMyWorkOrderList = (params) => request.basic.get('/api/v1/orders/my_order', params)
|
||||
|
||||
// 回访记录
|
||||
export const getBackRecordList = (params) => request.basic.get('/api/v1/orders/back_order', params)
|
||||
|
||||
// 工单回访列表
|
||||
export const getBackWorkOrderList = (params) => request.basic.get(`/api/v1/orders/noback_order`, params)
|
||||
|
||||
|
||||
// 工单详情
|
||||
export const getWorkOrderDetail = (id) => request.basic.get(`/api/v1/orders/${id}`)
|
||||
|
||||
@ -40,6 +40,8 @@ export default {
|
||||
mineWorderOrder: '我下的工单',
|
||||
invalidWzorkOrder: '无效工单',
|
||||
abnormalWorkOrder: '异常工单',
|
||||
visitWorkOrder: '工单回访',
|
||||
visitHistory: '回访记录',
|
||||
serviceWorkOrder: '服务工单',
|
||||
serviceMenu: '服务设施',
|
||||
serviceSites: '服务站点',
|
||||
|
||||
@ -57,6 +57,28 @@ export default [
|
||||
permission: '*',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'visitWorkOrder/index.vue',
|
||||
name: 'visitWorkOrder',
|
||||
component: 'workorderMenu/visitWorkOrder/index.vue',
|
||||
meta: {
|
||||
title: '工单回访',
|
||||
isMenu: true,
|
||||
keepAlive: true,
|
||||
permission: '*',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'visitHistory/index.vue',
|
||||
name: 'visitHistory',
|
||||
component: 'workorderMenu/visitHistory/index.vue',
|
||||
meta: {
|
||||
title: '回访记录',
|
||||
isMenu: true,
|
||||
keepAlive: true,
|
||||
permission: '*',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item :label="'站点类型'" name="type">
|
||||
<a-select v-model:value="searchFormData.type" placeholder="请选择站点类型" allow-clear>
|
||||
<a-select v-model:value="searchFormData.stationType" placeholder="请选择站点类型" allow-clear>
|
||||
<a-select-option v-for="item in dicsStore.dictOptions.Station_Type" :key="item.dval"
|
||||
:value="item.dval">
|
||||
{{ item.introduction }}
|
||||
@ -22,16 +22,13 @@
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item :label="'所在区域'" name="area">
|
||||
<AreaCascader v-model:value="searchFormData.area" @change="onAreaChange" />
|
||||
<AreaCascader v-model:value="searchFormData.areaCodes" @change="onAreaChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item :label="'所在节点'" name="node">
|
||||
<a-select v-model:value="searchFormData.node" @change="handleChange">
|
||||
<a-select-option value="jack">已结单</a-select-option>
|
||||
<a-select-option value="lucy">已作废</a-select-option>
|
||||
</a-select>
|
||||
<a-form-item label="组织所在区域" name="areaLabels">
|
||||
<NodeTree v-model:value="searchFormData.areaLabels" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
@ -180,7 +177,7 @@ import ExportRecordsModal from '@/components/ExportRecord/index.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import storage from '@/utils/storage'
|
||||
import AreaCascader from '@/components/AreaCascader/index.vue'
|
||||
|
||||
import NodeTree from '@/components/NodeTree/index.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'menu',
|
||||
@ -237,6 +234,7 @@ const { resetForm } = useForm()
|
||||
const editDialogRef = ref()
|
||||
import { useDicsStore } from '@/store'
|
||||
const dicsStore = useDicsStore()
|
||||
const areaCodes = ref([])
|
||||
|
||||
getList()
|
||||
|
||||
@ -248,10 +246,13 @@ async function getList() {
|
||||
try {
|
||||
showLoading()
|
||||
const { pageSize, current } = paginationState
|
||||
console.log("=====searchFormData",searchFormData.value.areaCodes)
|
||||
|
||||
const { success, data, total } = await apis.serviceMenu
|
||||
.getServiceSiteList({
|
||||
pageSize,
|
||||
current: current,
|
||||
// areaCodes:areaCodes,
|
||||
...searchFormData.value,
|
||||
})
|
||||
.catch(() => {
|
||||
@ -267,6 +268,7 @@ async function getList() {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
@ -631,6 +633,14 @@ const handleRetryExport = (record) => {
|
||||
message.error('重新导出失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 选择区域后
|
||||
// 如果你用 <script setup>
|
||||
const onAreaChange = (value, selectedOptions) => {
|
||||
areaCodes.value = value;
|
||||
console.log('value:', value);
|
||||
console.log('selectedOptions:', selectedOptions); // ← 这就是你想要的节点对象数组
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="920"
|
||||
:open="modal.open"
|
||||
:title="modal.title"
|
||||
:footer="null"
|
||||
@cancel="handleCancel"
|
||||
wrapClassName="order-detail-modal"
|
||||
>
|
||||
<!-- 服务轨迹 -->
|
||||
<a-descriptions bordered size="small" :column="2" class="mb-6">
|
||||
<template #title>
|
||||
<a-divider orientation="left" class="!mt-0">服务轨迹</a-divider>
|
||||
</template>
|
||||
|
||||
<a-descriptions-item label="签入时间">
|
||||
{{ detail.checkInTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="签入距离">
|
||||
{{ detail.checkInDistance }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="签入地址" :span="2">
|
||||
{{ detail.checkInAddress }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="签入图片" :span="2">
|
||||
<div class="image-preview-group">
|
||||
<a-space>
|
||||
<a-image
|
||||
v-for="(img, index) in detail.checkInImages"
|
||||
:key="'in-' + index"
|
||||
:src="img"
|
||||
:width="100"
|
||||
:height="100"
|
||||
:preview-src-list="detail.checkInImages"
|
||||
:preview-visible="false"
|
||||
class="rounded"
|
||||
/>
|
||||
</a-space>
|
||||
<span v-if="!detail.checkInImages?.length" class="text-gray-400">无</span>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="签出时间">
|
||||
{{ detail.checkOutTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="签出距离">
|
||||
{{ detail.checkOutDistance }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="签出地址" :span="2">
|
||||
{{ detail.checkOutAddress }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="签出图片" :span="2">
|
||||
<div class="image-preview-group">
|
||||
<a-space>
|
||||
<a-image
|
||||
v-for="(img, index) in detail.checkOutImages"
|
||||
:key="'out-' + index"
|
||||
:src="img"
|
||||
:width="100"
|
||||
:height="100"
|
||||
:preview-src-list="detail.checkOutImages"
|
||||
:preview-visible="false"
|
||||
class="rounded"
|
||||
/>
|
||||
</a-space>
|
||||
<span v-if="!detail.checkOutImages?.length" class="text-gray-400">无</span>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="签出备注" :span="2">
|
||||
{{ detail.checkOutRemark || '无' }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="实际服务时长" :span="2">
|
||||
{{ detail.serviceDuration }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<a-descriptions bordered size="small" :column="2" class="mb-6">
|
||||
<template #title>
|
||||
<a-divider orientation="left">用户信息</a-divider>
|
||||
</template>
|
||||
<a-descriptions-item label="工单号">{{ detail.orderNo }}</a-descriptions-item>
|
||||
<a-descriptions-item label="姓名">{{ detail.userName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">{{ detail.gender }}</a-descriptions-item>
|
||||
<a-descriptions-item label="年龄">{{ detail.age }} 岁</a-descriptions-item>
|
||||
<a-descriptions-item label="联系电话">{{ detail.phone }}</a-descriptions-item>
|
||||
<a-descriptions-item label="身份证号">{{ detail.idCard }}</a-descriptions-item>
|
||||
<a-descriptions-item label="评估等级" :span="2">
|
||||
{{ detail.assessmentLevel || '未填写' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<a-descriptions bordered size="small" :column="2" class="mb-6">
|
||||
<template #title>
|
||||
<a-divider orientation="left">任务信息</a-divider>
|
||||
</template>
|
||||
<a-descriptions-item label="护理员">{{ detail.caregiver }}</a-descriptions-item>
|
||||
<a-descriptions-item label="护理等级">{{ detail.careLevel || '未填写' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="计划服务时间">{{ detail.plannedTime }}</a-descriptions-item>
|
||||
<a-descriptions-item label="服务地址" :span="2">
|
||||
{{ detail.serviceAddress }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<!-- 服务项目 -->
|
||||
<div class="mb-6">
|
||||
<a-divider orientation="left">服务项目</a-divider>
|
||||
<a-table
|
||||
:dataSource="detail.serviceItems"
|
||||
:columns="serviceColumns"
|
||||
size="small"
|
||||
:pagination="false"
|
||||
bordered
|
||||
class="compact-table"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 异常工单处理信息 -->
|
||||
<a-descriptions bordered size="small" :column="2">
|
||||
<template #title>
|
||||
<a-divider orientation="left">异常工单处理信息</a-divider>
|
||||
</template>
|
||||
<a-descriptions-item label="处理状态">{{ detail.exceptionStatus }}</a-descriptions-item>
|
||||
<a-descriptions-item label="处理原因">{{ detail.exceptionReason || '无' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="处理人">{{ detail.exceptionHandler || '无' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="处理时间">{{ detail.exceptionTime || '无' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { useModal } from '@/hooks'
|
||||
|
||||
const { modal, hideModal } = useModal()
|
||||
|
||||
// 模拟数据(实际应由父组件传入)
|
||||
const detail = reactive({
|
||||
checkInTime: '2025-10-15 11:44:01',
|
||||
checkInDistance: '14996 m',
|
||||
checkInAddress: '江苏省南通市通州区十总镇南通市通州区十总镇五总居8-128西北775米',
|
||||
checkInImages: [
|
||||
'https://akt.obs.cn-east-3.myhuaweicloud.com:443/hahacloud/saas/1813150290048446464/6a4ddb23-8733-4ca4-a1f9-b0f061a4ca0a.jpg?AccessKeyId=EBJWO1KETPFJKBWNCG5V&Expires=1761122523&Signature=bygou7kYVrQ6f9bDDWlpebs15Nc%3D',
|
||||
'https://akt.obs.cn-east-3.myhuaweicloud.com:443/hahacloud/saas/1813150290048446464/11ce319c-705e-431a-a3a7-4a9bb8350fce.jpg?AccessKeyId=EBJWO1KETPFJKBWNCG5V&Expires=1761122523&Signature=cOHYxAxsTP0RQDgT2bNv2jjkWco%3D'
|
||||
],
|
||||
checkOutTime: '2025-10-15 12:44:43',
|
||||
checkOutDistance: '14984 m',
|
||||
checkOutAddress: '江苏省南通市通州区十总镇南通市通州区十总镇五总居8-128西北757米',
|
||||
checkOutImages: [
|
||||
'https://akt.obs.cn-east-3.myhuaweicloud.com:443/hahacloud/saas/1813150290048446464/3e7817e4-4d31-41d8-ba89-6532a5baea5d.jpg?AccessKeyId=EBJWO1KETPFJKBWNCG5V&Expires=1761122523&Signature=efu9s%2BjgIXdxZPU6sEH5OR0ipdE%3D',
|
||||
'https://akt.obs.cn-east-3.myhuaweicloud.com:443/hahacloud/saas/1813150290048446464/9902435e-fd61-423c-9681-7f11bcab7ee8.jpg?AccessKeyId=EBJWO1KETPFJKBWNCG5V&Expires=1761122523&Signature=y204ac9D%2BiI%2BWtNOaOu9UylAezg%3D',
|
||||
'https://akt.obs.cn-east-3.myhuaweicloud.com:443/hahacloud/saas/1813150290048446464/157a72f1-c081-4407-ab48-e38e25b38a24.jpg?AccessKeyId=EBJWO1KETPFJKBWNCG5V&Expires=1761122523&Signature=wvjINjdpPlG3QhkuDYIAISc20sY%3D'
|
||||
],
|
||||
checkOutRemark: '助洁理发,助餐剥花生',
|
||||
serviceDuration: '60 分钟',
|
||||
orderNo: '202510151043270345563994',
|
||||
userName: '顾美田',
|
||||
gender: '男',
|
||||
age: '90',
|
||||
phone: '18452439097',
|
||||
idCard: '320624193507244576',
|
||||
assessmentLevel: '',
|
||||
caregiver: '于圣霞',
|
||||
careLevel: '',
|
||||
plannedTime: '2025-10-15 10:42:00',
|
||||
serviceAddress: '江苏省南通市通州区十总镇五总社区居委会通州区五总居十七组3号',
|
||||
serviceItems: [
|
||||
{ itemCategoryName: '助乐服务', itemName: '精神关爱', careDuration: '60' }
|
||||
],
|
||||
exceptionStatus: '未处理',
|
||||
exceptionReason: '',
|
||||
exceptionHandler: '',
|
||||
exceptionTime: ''
|
||||
})
|
||||
|
||||
const serviceColumns = [
|
||||
{ title: '服务项目分类', dataIndex: 'itemCategoryName', width: '30%' },
|
||||
{ title: '服务项目名称', dataIndex: 'itemName', width: '40%' },
|
||||
{ title: '服务时长(分钟)', dataIndex: 'careDuration', width: '30%' }
|
||||
]
|
||||
|
||||
function handleCancel() {
|
||||
hideModal()
|
||||
}
|
||||
|
||||
function handleView() {
|
||||
modal.value.open = true
|
||||
modal.value.title = '工单详情'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleView
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局调整 modal 内部样式 */
|
||||
.order-detail-modal :deep(.ant-descriptions-title) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.order-detail-modal :deep(.ant-descriptions-item-label) {
|
||||
width: 120px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 图片预览区域 */
|
||||
.image-preview-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.image-preview-group .ant-image {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.image-preview-group .text-gray-400 {
|
||||
color: #8c8c8c;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 表格紧凑 */
|
||||
.compact-table :deep(.ant-table-tbody > tr > td),
|
||||
.compact-table :deep(.ant-table-thead > tr > th) {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* 区块间距 */
|
||||
.mb-6 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 覆盖 divider margin */
|
||||
.order-detail-modal :deep(.ant-divider-horizontal) {
|
||||
margin: 20px 0 16px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<x-search-bar class="mb-8-2">
|
||||
<template #default="{ gutter, colSpan }">
|
||||
<a-form :label-col="{ style: { width: '100px' } }" :model="searchFormData" layout="inline">
|
||||
<a-row :gutter="gutter">
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item :label="'姓名'" name="name">
|
||||
<a-input :placeholder="'请选择姓名'" v-model:value="searchFormData.name"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item name="code">
|
||||
<template #label>
|
||||
{{ '身份证号' }}
|
||||
<a-tooltip :title="'身份证号'">
|
||||
<question-circle-outlined class="ml-4-1 color-placeholder" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input :placeholder="'请输入身份证号'" v-model:value="searchFormData.code"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item :label="'工单号'" name="name">
|
||||
<a-input :placeholder="'请选择工单号'" v-model:value="searchFormData.name"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<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-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</x-search-bar>
|
||||
<a-card>
|
||||
<x-action-bar class="mb-8-2">
|
||||
<a-button v-action="'add'"
|
||||
type="primary"
|
||||
:disabled="selectedRowKeys.length === 0"
|
||||
@click="handleBatchProcess">
|
||||
批量处理(最多40条)
|
||||
</a-button>
|
||||
</x-action-bar>
|
||||
<a-table
|
||||
rowKey="id"
|
||||
:loading="loading"
|
||||
:pagination="true"
|
||||
:columns="columns"
|
||||
:data-source="listData"
|
||||
:row-selection="rowSelection"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="'menuType' === column.key">
|
||||
<!--菜单-->
|
||||
<a-tag v-if="menuTypeEnum.is('page', record.type)" color="processing">
|
||||
{{ menuTypeEnum.getDesc(record.type) }}
|
||||
</a-tag>
|
||||
<!--按钮-->
|
||||
<a-tag v-if="menuTypeEnum.is('button', record.type)" color="success">
|
||||
{{ menuTypeEnum.getDesc(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="'createAt' === column.key">
|
||||
{{ formatUtcDateTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<template v-if="'statusType' === column.key">
|
||||
<!--状态-->
|
||||
<a-tag v-if="statusTypeEnum.is('enabled', record.status)" color="processing">
|
||||
{{ statusTypeEnum.getDesc(record.status) }}
|
||||
</a-tag>
|
||||
<!--状态-->
|
||||
<a-tag v-if="statusTypeEnum.is('disabled', record.status)" color="processing">
|
||||
{{ statusTypeEnum.getDesc(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="'action' === column.key">
|
||||
<x-action-button @click="$refs.viewDialogRef.handleView(record)">
|
||||
<a-tooltip>
|
||||
<template #title>详情</template>
|
||||
详情
|
||||
</a-tooltip>
|
||||
</x-action-button>
|
||||
|
||||
<x-action-button @click="handleSingleProcess(record)">
|
||||
<a-tooltip>
|
||||
<template #title>处理</template>
|
||||
处理
|
||||
</a-tooltip>
|
||||
</x-action-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 处理弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="processModalVisible"
|
||||
title="处理异常"
|
||||
@ok="handleProcessSubmit"
|
||||
@cancel="processModalVisible = false"
|
||||
:confirm-loading="processSubmitting"
|
||||
>
|
||||
<a-form :model="processForm" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="处理结果" name="processStatus" required>
|
||||
<a-select v-model:value="processForm.processStatus" placeholder="请选择处理结果">
|
||||
<a-select-option value="handled">已处理</a-select-option>
|
||||
<a-select-option value="ignored">忽略</a-select-option>
|
||||
<!-- 可根据实际枚举扩展 -->
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="处理原因" name="processRemark">
|
||||
<a-textarea
|
||||
v-model:value="processForm.processRemark"
|
||||
placeholder="请输入处理原因"
|
||||
:rows="4"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<view-dialog @ok="onOk" ref="viewDialogRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
import { ref,reactive } from 'vue'
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons-vue'
|
||||
import apis from '@/apis'
|
||||
import { config } from '@/config'
|
||||
import { menuTypeEnum, statusTypeEnum } from '@/enums/system'
|
||||
import { usePagination, useForm } from '@/hooks'
|
||||
import { formatUtcDateTime } from '@/utils/util'
|
||||
import ViewDialog from './components/viewDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import storage from '@/utils/storage'
|
||||
import AreaCascader from '@/components/AreaCascader/index.vue'
|
||||
import dayjs from 'dayjs'
|
||||
defineOptions({
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
name: 'menu',
|
||||
})
|
||||
const { t } = useI18n() // 解构出t方法
|
||||
const columns = ref([
|
||||
{ title: '工单号', dataIndex: 'orderId', key: 'orderId', width: 180 },
|
||||
{ title: '姓名', dataIndex: 'customerName', key: 'customerName', width: 120 },
|
||||
{ title: '身份证号', dataIndex: 'identityNo', key: 'identityNo', width: 180 },
|
||||
{ title: '异常情况', dataIndex: 'abnormalStatus', key: 'abnormalStatus', width: 120 },
|
||||
{ title: '服务人员', dataIndex: 'serviceUserName', key: 'serviceUserName', width: 120 },
|
||||
{ title: '服务项目', dataIndex: 'itemNames', key: 'itemNames', width: 200 },
|
||||
{ title: '签入时间', dataIndex: 'realStartTime', key: 'realStartTime', width: 160 },
|
||||
{ title: '签出时间', dataIndex: 'realEndTime', key: 'realEndTime', width: 160 },
|
||||
{ title: '处理状态', dataIndex: 'processStatus', key: 'processStatus', width: 120 },
|
||||
{ title: '处理原因', dataIndex: 'processRemark', key: 'processRemark', width: 160 },
|
||||
{ title: '处理人', dataIndex: 'processUserName', key: 'processUserName', width: 120 },
|
||||
{ title: '处理时间', dataIndex: 'processTime', key: 'processTime', width: 160 },
|
||||
{ title: '服务地址', dataIndex: 'serviceAddress', key: 'serviceAddress', width: 200 },
|
||||
{ title: '操作', key: 'action', fixed: 'right', width: 340 },
|
||||
])
|
||||
const { listData, loading, showLoading, hideLoading, searchFormData, paginationState, resetPagination } =
|
||||
usePagination()
|
||||
const { resetForm } = useForm()
|
||||
const viewDialogRef = ref()
|
||||
const selectedRowKeys = ref([]) // 选中的 id 列表
|
||||
const selectedRows = ref([]) // 选中的完整记录
|
||||
const rowSelection = ref({
|
||||
onChange: (keys, rows) => {
|
||||
if (keys.length > 40) {
|
||||
message.warning('最多只能选择40条记录进行批量处理')
|
||||
// 保留前40条
|
||||
selectedRowKeys.value = keys.slice(0, 40)
|
||||
selectedRows.value = rows.slice(0, 40)
|
||||
return
|
||||
}
|
||||
selectedRowKeys.value = keys
|
||||
selectedRows.value = rows
|
||||
},
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 处理弹窗
|
||||
const processModalVisible = ref(false)
|
||||
const processSubmitting = ref(false)
|
||||
const processForm = reactive({
|
||||
processStatus: undefined,
|
||||
processRemark: '',
|
||||
})
|
||||
|
||||
|
||||
let currentProcessRecords = [] // 当前要处理的记录(单条或批量)
|
||||
|
||||
// 打开批量处理
|
||||
function handleBatchProcess() {
|
||||
if (selectedRowKeys.value.length === 0) return
|
||||
if (selectedRowKeys.value.length > 40) {
|
||||
message.warning('最多选择40条')
|
||||
return
|
||||
}
|
||||
currentProcessRecords = [...selectedRows.value]
|
||||
processForm.processStatus = undefined
|
||||
processForm.processRemark = ''
|
||||
processModalVisible.value = true
|
||||
}
|
||||
|
||||
getMenuList()
|
||||
|
||||
|
||||
/**
|
||||
* 获取菜单列表
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
|
||||
async function getMenuList() {
|
||||
try {
|
||||
showLoading()
|
||||
const { pageSize, current } = paginationState
|
||||
|
||||
const { success, data, total } = await apis.workOrder.getAbnormalWorkOrderList({
|
||||
pageSize,
|
||||
current: current,
|
||||
...searchFormData.value,
|
||||
})
|
||||
|
||||
// 字段映射
|
||||
const mappedData = data.map(item => ({
|
||||
id: item.orderNum || item.id, // 如果没有 id,可以用 orderNum 代替,或后端补充
|
||||
orderId: item.orderNum || '-', // 工单号
|
||||
customerName: item.customerName || '-', // 姓名
|
||||
identityNo: item.customerIdCard || '-', // 身份证号
|
||||
serviceUserName: item.serviceName || '-', // 服务人员
|
||||
itemNames: item.projects?.map(p => p.name).join('、') || '-', // 服务项目(多个用顿号连接)
|
||||
realStartTime: item.signInAt, // 签入时间(保持原格式,formatUtcDateTime 会处理)
|
||||
realEndTime: item.signOutAt, // 签出时间
|
||||
processStatus: item.processStatus === '1' ? '已处理' : item.processStatus === '0' ? '未处理' : '未知', // 处理状态(根据实际枚举调整)
|
||||
processRemark: item.reason || '-', // 处理原因
|
||||
processUserName: item.userName || '-', // 处理人
|
||||
processTime: dayjs(item.processAt).format('YYYY-MM-DD HH:mm:ss'), // 处理时间
|
||||
serviceAddress: item.detailAddress || '-', // 服务地址
|
||||
// 原始数据保留,便于后续操作(如提交处理时用 id)
|
||||
raw: item,
|
||||
}))
|
||||
|
||||
listData.value = mappedData
|
||||
paginationState.total = total
|
||||
hideLoading()
|
||||
} catch (error) {
|
||||
console.error('获取工单列表失败:', error)
|
||||
hideLoading()
|
||||
message.error('获取数据失败')
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
function handleSearch() {
|
||||
// resetForm()
|
||||
resetPagination()
|
||||
getMenuList()
|
||||
}
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function handleResetSearch() {
|
||||
searchFormData.value = {}
|
||||
resetPagination()
|
||||
getMenuList()
|
||||
}
|
||||
/**
|
||||
* 删除
|
||||
* @param id
|
||||
*/
|
||||
function handleDelete({ id }) {
|
||||
Modal.confirm({
|
||||
title: t('pages.system.menu.delTip'),
|
||||
content: t('button.confirm'),
|
||||
okText: t('button.confirm'),
|
||||
onOk: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
; (async () => {
|
||||
try {
|
||||
const { success } = await apis.menu.delMenu(id).catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
if (config('http.code.success') === success) {
|
||||
resolve()
|
||||
message.success(t('component.message.success.delete'))
|
||||
await getMenuList()
|
||||
}
|
||||
} catch (error) {
|
||||
reject()
|
||||
}
|
||||
})()
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 编辑完成
|
||||
*/
|
||||
async function onOk() {
|
||||
await getMenuList()
|
||||
}
|
||||
|
||||
|
||||
// 打开单条处理
|
||||
function handleSingleProcess(record) {
|
||||
currentProcessRecords = [record]
|
||||
processForm.processStatus = record.processStatus || undefined
|
||||
processForm.processRemark = record.processRemark || ''
|
||||
processModalVisible.value = true
|
||||
}
|
||||
|
||||
// 提交处理
|
||||
async function handleProcessSubmit() {
|
||||
const { processStatus } = processForm
|
||||
if (!processStatus ) {
|
||||
message.warning('请选择处理结果')
|
||||
return
|
||||
}
|
||||
|
||||
processSubmitting.value = true
|
||||
// try {
|
||||
// // 假设你的 API 是批量处理:apis.order.batchProcess({ ids, processStatus, processRemark })
|
||||
// const ids = currentProcessRecords.map(r => r.id)
|
||||
// const { success } = await apis.order.batchProcess({
|
||||
// ids,
|
||||
// processStatus,
|
||||
// processRemark,
|
||||
// })
|
||||
|
||||
// if (config('http.code.success') === success) {
|
||||
// message.success('处理成功')
|
||||
// processModalVisible.value = false
|
||||
// await getOrderList() // 刷新列表
|
||||
// }
|
||||
// } catch (error) {
|
||||
// message.error('处理失败')
|
||||
// } finally {
|
||||
// processSubmitting.value = false
|
||||
// }
|
||||
}
|
||||
|
||||
async function handleView(record = {}) {
|
||||
showModal({
|
||||
type: 'view',
|
||||
title: '查看详情',
|
||||
})
|
||||
const { data } = await apis.menu.getMenu(record.id).catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
formData.value = cloneDeep(data)
|
||||
formData.value.properties = formData.value.properties ? JSON.parse(formData.value.properties) : ''
|
||||
formData.value.resources = formData.value.resources || (formData.value.resources = [])
|
||||
platform.value=data.platform
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item :label="'所在区域'" name="name">
|
||||
<a-input :placeholder="'请选择区域'" v-model:value="searchFormData.name"></a-input>
|
||||
<AreaCascader v-model:value="searchFormData.currentNode" @change="onAreaChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
@ -134,6 +134,7 @@ import { formatUtcDateTime } from '@/utils/util'
|
||||
import EditDialog from './components/EditDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import storage from '@/utils/storage'
|
||||
import AreaCascader from '@/components/AreaCascader/index.vue'
|
||||
defineOptions({
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
name: 'menu',
|
||||
@ -169,25 +170,27 @@ getMenuList()
|
||||
async function getMenuList() {
|
||||
try {
|
||||
showLoading()
|
||||
// const { current } = paginationState
|
||||
const platform = storage.local.getItem('platform')
|
||||
const { data, success, total } = await apis.menu
|
||||
.getMenuList({
|
||||
...searchFormData.value,
|
||||
platform
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
hideLoading()
|
||||
const { pageSize, current } = paginationState
|
||||
const params = {
|
||||
pageSize,
|
||||
current,
|
||||
...searchFormData.value
|
||||
}
|
||||
|
||||
params.status = 'Invalid_WorkerOrder'
|
||||
|
||||
console.log("=====search params", params)
|
||||
|
||||
const { success, data, total } = await apis.workOrder.getWorkOrderList(params)
|
||||
|
||||
if (config('http.code.success') === success) {
|
||||
data.forEach((item) => {
|
||||
item.name = t(item.code) || item.name
|
||||
})
|
||||
listData.value = data
|
||||
paginationState.total = total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取工单列表失败:', error)
|
||||
message.error('获取数据失败')
|
||||
} finally {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,15 +41,15 @@
|
||||
</template>
|
||||
</x-search-bar>
|
||||
<a-card>
|
||||
<x-action-bar class="mb-8-2">
|
||||
<!-- <x-action-bar class="mb-8-2">
|
||||
<a-button v-action="'add'" type="primary" @click="$refs.editDialogRef.handleCreate()">
|
||||
<template #icon>
|
||||
<plus-outlined></plus-outlined>
|
||||
</template>
|
||||
{{ $t('pages.system.menu.add') }}
|
||||
</a-button>
|
||||
</x-action-bar>
|
||||
<a-table rowKey="id" :loading="loading" :pagination="true" :columns="columns" :data-source="listData">
|
||||
</x-action-bar> -->
|
||||
<a-table rowKey="id" :loading="loading" :pagination="true" :columns="columns" :data-source="listData" :scroll="{ x: 'max-content' }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="'menuType' === column.key">
|
||||
<!--菜单-->
|
||||
@ -123,21 +123,19 @@ defineOptions({
|
||||
})
|
||||
const { t } = useI18n() // 解构出t方法
|
||||
const columns = ref([
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '身份证号', dataIndex: 'type', key: 'menuType', width: 240 },
|
||||
{ title: '联系方式1', dataIndex: 'status', key: 'statusType', width: 240 },
|
||||
{ title: '联系方式2', dataIndex: 'sequence', width: 240 },
|
||||
{ title: '计划服务时间', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '计划服务时常(分钟)', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '服务项目', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '所属区域', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '服务地址', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '服务组织', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '服务人员', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '下单员', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
{ title: '下单时间', dataIndex: 'created_at', key: 'createAt', width: 240 },
|
||||
])
|
||||
{ title: '工单号', dataIndex: 'orderId', key: 'orderId', width: 180 },
|
||||
{ title: '服务对象', dataIndex: 'customerName', key: 'customerName', width: 150 },
|
||||
{ title: '身份证号', dataIndex: 'identityNo', key: 'identityNo', width: 300 },
|
||||
{ title: '联系方式1', dataIndex: 'contact1', key: 'contact1', width: 150 },
|
||||
{ title: '联系方式2', dataIndex: 'contact2', key: 'contact2', width: 150 },
|
||||
{ title: '计划服务时常(分钟)', dataIndex: 'workDuration', key: 'workDuration', width: 160 },
|
||||
{ title: '服务项目', dataIndex: 'itemNames', key: 'itemNames', width: 200 },
|
||||
{ title: '所属区域', dataIndex: 'areaLabels', key: 'areaLabels', width: 200, customRender: ({ text }) => text?.join('、') || '-' },
|
||||
{ title: '服务地址', dataIndex: 'serviceAddress', key: 'serviceAddress', width: 200 },
|
||||
{ title: '服务人员', dataIndex: 'serviceUserName', key: 'serviceUserName', width: 150 },
|
||||
{ title: '下单员', dataIndex: 'processUserName', key: 'processUserName', width: 150 },
|
||||
{ title: '下单时间', dataIndex: 'createAt', key: 'createAt', width: 180, customRender: ({ record }) => formatUtcDateTime(record.raw.createAt) },
|
||||
]);
|
||||
const { listData, loading, showLoading, hideLoading, searchFormData, paginationState, resetPagination } =
|
||||
usePagination()
|
||||
const { resetForm } = useForm()
|
||||
@ -152,26 +150,44 @@ getMenuList()
|
||||
async function getMenuList() {
|
||||
try {
|
||||
showLoading()
|
||||
// const { current } = paginationState
|
||||
const platform = storage.local.getItem('platform')
|
||||
const { data, success, total } = await apis.menu
|
||||
.getMenuList({
|
||||
const { pageSize, current } = paginationState
|
||||
|
||||
const { success, data, total } = await apis.workOrder.getMyWorkOrderList({
|
||||
pageSize,
|
||||
current: current,
|
||||
...searchFormData.value,
|
||||
platform
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
hideLoading()
|
||||
if (config('http.code.success') === success) {
|
||||
data.forEach((item) => {
|
||||
item.name = t(item.code) || item.name
|
||||
})
|
||||
listData.value = data
|
||||
|
||||
// 字段映射
|
||||
const mappedData = data.map(item => {
|
||||
// 联系方式2:优先 otherPhone1,若为空则用 otherPhone2
|
||||
const contact2 = item.otherPhone1 || item.otherPhone2 || '-';
|
||||
|
||||
return {
|
||||
id: item.orderNum || item.customerIdCard, // 唯一标识,可调整
|
||||
orderId: item.orderNum || '-',
|
||||
customerName: item.customerName || '-',
|
||||
identityNo: item.customerIdCard || '-',
|
||||
contact1: item.contact1 || '-',
|
||||
contact2: contact2,
|
||||
workDuration: item.workDuration ? `${item.workDuration} 分钟` : '-',
|
||||
itemNames: item.projects?.map(p => p.name).join('、') || '-',
|
||||
areaLabels: item.areaLabels || [],
|
||||
serviceAddress: item.detailAddress || '-',
|
||||
serviceUserName: item.serviceName || '-',
|
||||
processUserName: item.userName || '-',
|
||||
createAt: item.createAt, // 保留原始时间用于格式化
|
||||
raw: item, // 保留原始数据
|
||||
};
|
||||
});
|
||||
|
||||
listData.value = mappedData
|
||||
paginationState.total = total
|
||||
}
|
||||
} catch (error) {
|
||||
hideLoading()
|
||||
} catch (error) {
|
||||
console.error('获取工单列表失败:', error)
|
||||
hideLoading()
|
||||
message.error('获取数据失败')
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="520"
|
||||
:open="modal.open"
|
||||
:title="modal.title"
|
||||
:confirm-loading="modal.confirmLoading"
|
||||
:after-close="onAfterClose"
|
||||
:cancel-text="cancelText"
|
||||
:ok-text="okText"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form layout="vertical" ref="formRef" :model="formData" :rules="formRules">
|
||||
<!-- 签入时间 -->
|
||||
<a-form-item
|
||||
label="签入时间"
|
||||
name="signTime"
|
||||
required
|
||||
>
|
||||
<a-date-picker
|
||||
v-model:value="formData.signTime"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 代签入原因 -->
|
||||
<a-form-item
|
||||
label="代签入原因"
|
||||
name="reason"
|
||||
required
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="formData.reason"
|
||||
placeholder="请填写1-100字的原因"
|
||||
:maxlength="100"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 代签入图片 -->
|
||||
<a-form-item
|
||||
label="代签入图片"
|
||||
name="images"
|
||||
>
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="dummyRequest"
|
||||
:multiple="true"
|
||||
accept="image/*"
|
||||
>
|
||||
<div>
|
||||
<PlusOutlined />
|
||||
<div style="margin-top: 8px">选择图片</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { useForm } from '@/hooks'
|
||||
import { useModal } from '@/hooks'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const emit = defineEmits(['ok'])
|
||||
|
||||
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
|
||||
const { formData, formRef, formRules, resetForm } = useForm()
|
||||
|
||||
// 文件列表
|
||||
const fileList = ref([])
|
||||
|
||||
// 表单数据初始化
|
||||
formData.value = reactive({
|
||||
signTime: null,
|
||||
reason: '',
|
||||
images: [] // 存储 File 对象或 URL
|
||||
})
|
||||
|
||||
// 表单校验规则
|
||||
formRules.value = {
|
||||
signTime: [
|
||||
{ required: true, message: '请选择签入时间' }
|
||||
],
|
||||
reason: [
|
||||
{ required: true, message: '请填写代签入原因' },
|
||||
{ max: 100, message: '原因不能超过100字' }
|
||||
]
|
||||
}
|
||||
|
||||
// 按钮文案
|
||||
const cancelText = ref('取消')
|
||||
const okText = ref('确定')
|
||||
|
||||
// 当前工单记录(可选)
|
||||
const currentRecord = ref(null)
|
||||
|
||||
// 显示弹窗方法(供父组件调用)
|
||||
function showCheckModal(record) {
|
||||
currentRecord.value = record
|
||||
showModal({
|
||||
type: 'cancel',
|
||||
title: '代签入工单'
|
||||
})
|
||||
formData.value.signTime = null
|
||||
formData.value.reason = ''
|
||||
fileList.value = []
|
||||
}
|
||||
|
||||
// 图片上传前校验(限制大小、类型等)
|
||||
function beforeUpload(file) {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type.startsWith('image/')
|
||||
if (!isJpgOrPng) {
|
||||
message.error('只能上传 JPG/PNG 等图片文件!')
|
||||
return false
|
||||
}
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isLt5M) {
|
||||
message.error('图片大小不能超过 5MB!')
|
||||
return false
|
||||
}
|
||||
// 将文件加入 fileList(Ant Upload 会自动处理,但这里确保能拿到)
|
||||
return true
|
||||
}
|
||||
|
||||
// 阻止 Ant Design 自动上传(因为我们可能只收集文件,由父组件或 API 处理)
|
||||
function dummyRequest(options) {
|
||||
const { onSuccess, file } = options
|
||||
setTimeout(() => {
|
||||
onSuccess('ok', file)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 确定提交
|
||||
async function handleOk() {
|
||||
try {
|
||||
const values = await formRef.value.validateFields()
|
||||
showLoading()
|
||||
|
||||
// 收集文件(如果需要传给父组件)
|
||||
const files = fileList.value
|
||||
.filter(f => f.status === 'done')
|
||||
.map(f => f.originFileObj || f)
|
||||
|
||||
// 此处可调用 API 提交:签入时间、原因、图片
|
||||
// await apis.workOrder.proxySignIn({
|
||||
// id: currentRecord.value?.id,
|
||||
// signTime: dayjs(formData.value.signTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
// reason: formData.value.reason,
|
||||
// images: files
|
||||
// })
|
||||
|
||||
hideLoading()
|
||||
hideModal()
|
||||
emit('ok', {
|
||||
signTime: formData.value.signTime,
|
||||
reason: formData.value.reason,
|
||||
images: files
|
||||
})
|
||||
} catch (error) {
|
||||
hideLoading()
|
||||
console.error('表单验证失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
hideModal()
|
||||
}
|
||||
|
||||
// 关闭后重置
|
||||
function onAfterClose() {
|
||||
resetForm()
|
||||
fileList.value = []
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
showCheckModal
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 可选:调整上传区域样式 */
|
||||
:deep(.ant-upload-select-picture-card) {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="780"
|
||||
:open="modal.open"
|
||||
:title="modal.title"
|
||||
:confirm-loading="modal.confirmLoading"
|
||||
:after-close="onAfterClose"
|
||||
:cancel-text="cancelText"
|
||||
:ok-text="okText"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
>
|
||||
<!-- 用 Row + Col 实现两列排版 -->
|
||||
<a-row :gutter="24">
|
||||
<!-- 服务对象(只读) -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="服务对象" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-input v-model:value="formData.serviceTarget" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 服务人员(下拉选择) -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="服务人员" name="serviceStaff" required :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-select
|
||||
v-model:value="formData.serviceStaff"
|
||||
placeholder="请选择服务人员"
|
||||
:options="staffOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 服务项目(单选下拉) -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="服务项目" name="serviceItems" required :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-select
|
||||
v-model:value="formData.serviceItems"
|
||||
placeholder="请选择服务项目"
|
||||
:options="serviceItemOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 服务费用(普通输入框) -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="服务费用" name="serviceFee" required :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-input
|
||||
v-model:value="formData.serviceFee"
|
||||
placeholder="请填写服务费用"
|
||||
type="number"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 计划日期范围(占满一行) -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计划开始日期" name="planDateRange" required :label-col="{ span:8}" :wrapper-col="{ span: 16 }">
|
||||
<a-range-picker
|
||||
v-model:value="formData.planDateRange"
|
||||
format="YYYY-MM-DD"
|
||||
:placeholder="['开始日期', '结束日期']"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 计划开始时间 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计划开始时间" name="planStartTime" required :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }">
|
||||
<a-time-picker
|
||||
v-model:value="formData.planStartTime"
|
||||
format="HH:mm:ss"
|
||||
placeholder="请选择计划开始时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 要求工单时长(分钟) -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="要求工单时长" name="requiredDuration" required :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }">
|
||||
<a-input-number
|
||||
v-model:value="formData.requiredDuration"
|
||||
:min="0"
|
||||
:step="1"
|
||||
style="width: 80%"
|
||||
placeholder="请填写要求工单时长"
|
||||
/>
|
||||
<span style="margin-left: 8px">分钟</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 服务地址(级联选择,占满一行) -->
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务地址" name="serviceArea" required :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
|
||||
<a-cascader
|
||||
v-model:value="formData.serviceArea"
|
||||
:options="areaOptions"
|
||||
change-on-select
|
||||
placeholder="请选择服务地址"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 详细地址(占满一行) -->
|
||||
<a-col :span="24">
|
||||
<a-form-item label="详细地址" name="detailAddress" required :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
|
||||
<a-input v-model:value="formData.detailAddress" placeholder="请填写详细地址" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 地图定位地址(只读+图标,占满一行) -->
|
||||
<a-col :span="24">
|
||||
<a-form-item label="地图定位地址" :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<span style="margin-right: 8px; cursor: pointer;" @click="openMap">📍</span>
|
||||
<a-input v-model:value="formData.mapAddress" readonly />
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 备注(占满一行) -->
|
||||
<a-col :span="24">
|
||||
<a-form-item label="备注" :label-col="{ span: 3 }" :wrapper-col="{ span: 21 }">
|
||||
<a-textarea
|
||||
v-model:value="formData.remark"
|
||||
placeholder="请填写备注"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useForm } from '@/hooks'
|
||||
import { useModal } from '@/hooks'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 模拟下拉选项(实际从接口获取)
|
||||
const staffOptions = [
|
||||
{ value: '邢光平', label: '邢光平' },
|
||||
{ value: '张三', label: '张三' },
|
||||
{ value: '李四', label: '李四' }
|
||||
]
|
||||
|
||||
const serviceItemOptions = [
|
||||
{ value: '兴趣活动', label: '兴趣活动' },
|
||||
{ value: '生活照料', label: '生活照料' },
|
||||
{ value: '康复训练', label: '康复训练' }
|
||||
]
|
||||
|
||||
const areaOptions = [
|
||||
{
|
||||
value: '江苏省',
|
||||
label: '江苏省',
|
||||
children: [
|
||||
{
|
||||
value: '南通市',
|
||||
label: '南通市',
|
||||
children: [
|
||||
{
|
||||
value: '通州区',
|
||||
label: '通州区',
|
||||
children: [
|
||||
{
|
||||
value: '金新街道',
|
||||
label: '金新街道',
|
||||
children: [
|
||||
{ value: '大石桥村委会', label: '大石桥村委会' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 表单 & 弹窗逻辑
|
||||
const emit = defineEmits(['ok'])
|
||||
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
|
||||
const { formData, formRef, formRules, resetForm } = useForm()
|
||||
|
||||
// 初始化表单数据(与图片展示内容一致)
|
||||
formData.value = reactive({
|
||||
serviceTarget: '袁正芬', // 服务对象(只读)
|
||||
serviceStaff: '邢光平', // 服务人员
|
||||
serviceItems: '兴趣活动', // 服务项目(单选)
|
||||
serviceFee: '40', // 服务费用
|
||||
planDateRange: [dayjs('2025-10-17'), dayjs('2025-10-31')], // 计划日期范围
|
||||
planStartTime: dayjs('00:03:00', 'HH:mm:ss'), // 计划开始时间
|
||||
requiredDuration: 90, // 要求工单时长(分钟)
|
||||
serviceArea: ['江苏省', '南通市', '通州区', '金新街道', '大石桥村委会'], // 服务地址(级联)
|
||||
detailAddress: '南通市通州区川姜镇大石桥村九组131号', // 详细地址
|
||||
mapAddress: '', // 地图定位地址(暂空)
|
||||
remark: '' // 备注
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
formRules.value = {
|
||||
serviceStaff: [{ required: true, message: '请选择服务人员' }],
|
||||
serviceItems: [{ required: true, message: '请选择服务项目' }],
|
||||
serviceFee: [
|
||||
{ required: true, message: '请填写服务费用' },
|
||||
{ pattern: /^\d+$/, message: '请输入有效数字' }
|
||||
],
|
||||
planDateRange: [{ required: true, message: '请选择计划日期范围' }],
|
||||
planStartTime: [{ required: true, message: '请选择计划开始时间' }],
|
||||
requiredDuration: [{ required: true, message: '请填写要求工单时长' }],
|
||||
serviceArea: [{ required: true, message: '请选择服务地址' }],
|
||||
detailAddress: [{ required: true, message: '请填写详细地址' }]
|
||||
}
|
||||
|
||||
// 按钮文案
|
||||
const cancelText = ref('取消')
|
||||
const okText = ref('保存')
|
||||
|
||||
// 打开地图(示例方法,可扩展真实逻辑)
|
||||
function openMap() {
|
||||
message.info('地图定位功能待实现')
|
||||
}
|
||||
|
||||
// 确定保存
|
||||
async function handleOk() {
|
||||
try {
|
||||
const values = await formRef.value.validateFields()
|
||||
showLoading()
|
||||
|
||||
// 构造提交数据(根据后端需求调整格式,如日期转字符串)
|
||||
const payload = {
|
||||
...values,
|
||||
planStartDate: values.planDateRange?.[0]?.format('YYYY-MM-DD') || '',
|
||||
planEndDate: values.planDateRange?.[1]?.format('YYYY-MM-DD') || '',
|
||||
planStartTime: values.planStartTime?.format('HH:mm:ss') || ''
|
||||
}
|
||||
|
||||
// 模拟接口请求:await apis.workOrder.save(payload)
|
||||
setTimeout(() => {
|
||||
hideLoading()
|
||||
hideModal()
|
||||
emit('ok', payload)
|
||||
message.success('保存成功')
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
hideModal()
|
||||
}
|
||||
|
||||
// 关闭后重置表单
|
||||
function onAfterClose() {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 暴露“显示编辑弹窗”方法(若需外部调用)
|
||||
defineExpose({
|
||||
showEditModal(record) {
|
||||
// 若为“编辑”场景,需将 record 数据映射到 formData(示例逻辑)
|
||||
formData.value = reactive({
|
||||
serviceTarget: record?.serviceTarget || '袁正芬',
|
||||
serviceStaff: record?.serviceStaff || '邢光平',
|
||||
serviceItems: record?.serviceItems || '兴趣活动',
|
||||
serviceFee: record?.serviceFee || '40',
|
||||
planDateRange: record?.planDateRange
|
||||
? [dayjs(record.planStartDate), dayjs(record.planEndDate)]
|
||||
: [dayjs('2025-10-17'), dayjs('2025-10-31')],
|
||||
planStartTime: record?.planStartTime
|
||||
? dayjs(record.planStartTime, 'HH:mm:ss')
|
||||
: dayjs('00:03:00', 'HH:mm:ss'),
|
||||
requiredDuration: record?.requiredDuration || 90,
|
||||
serviceArea: record?.serviceArea || ['江苏省', '南通市', '通州区', '金新街道', '大石桥村委会'],
|
||||
detailAddress: record?.detailAddress || '南通市通州区川姜镇大石桥村九组131号',
|
||||
mapAddress: record?.mapAddress || '',
|
||||
remark: record?.remark || ''
|
||||
})
|
||||
|
||||
showModal({
|
||||
type: 'edit',
|
||||
title: '编辑工单'
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="520"
|
||||
:open="modal.open"
|
||||
:title="modal.title"
|
||||
:confirm-loading="modal.confirmLoading"
|
||||
:after-close="onAfterClose"
|
||||
:cancel-text="cancelText"
|
||||
:ok-text="okText"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form layout="vertical" ref="formRef" :model="formData" :rules="formRules">
|
||||
<a-form-item
|
||||
:label="'作废原因'"
|
||||
name="reason"
|
||||
required
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="formData.reason"
|
||||
:placeholder="'请填写作废原因'"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useForm } from '@/hooks'
|
||||
import { useModal } from '@/hooks'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const emit = defineEmits(['ok'])
|
||||
|
||||
const { t } = useI18n()
|
||||
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
|
||||
const { formData, formRef, formRules, resetForm } = useForm()
|
||||
|
||||
// 表单规则
|
||||
formRules.value = {
|
||||
reason: [
|
||||
{ required: true, message: '请填写作废原因' },
|
||||
{ max: 500, message:'请填写作废原因' }
|
||||
]
|
||||
}
|
||||
|
||||
// 按钮文案
|
||||
const cancelText = ref('取消')
|
||||
const okText = ref('确定')
|
||||
|
||||
// 添加 props 或内部状态来保存当前工单
|
||||
const currentRecord = ref(null)
|
||||
|
||||
// 修改 showCancelModal 方法,接收 record
|
||||
function showCancelModal(record) {
|
||||
currentRecord.value = record // 保存当前工单
|
||||
showModal({
|
||||
type: 'cancel',
|
||||
title:'作废工单'
|
||||
})
|
||||
formData.value.reason = ''
|
||||
}
|
||||
|
||||
|
||||
// 确定
|
||||
async function handleOk() {
|
||||
try {
|
||||
await formRef.value.validateFields()
|
||||
showLoading()
|
||||
|
||||
// 此处可调用 API 提交作废原因
|
||||
// await apis.workOrder.cancelWorkOrder({ id: props.workOrderId, reason: formData.value.reason })
|
||||
|
||||
hideLoading()
|
||||
hideModal()
|
||||
emit('ok', formData.value.reason) // 可选:将原因传回父组件
|
||||
} catch (error) {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
hideModal()
|
||||
}
|
||||
|
||||
// 关闭后重置
|
||||
function onAfterClose() {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showCancelModal
|
||||
})
|
||||
</script>
|
||||
@ -0,0 +1,593 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="900"
|
||||
:open="modal.open"
|
||||
title="查看工单"
|
||||
:confirm-loading="modal.confirmLoading"
|
||||
:after-close="onAfterClose"
|
||||
cancel-text="关闭"
|
||||
ok-text="确认"
|
||||
:ok-button-props="{ disabled: true }"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-tabs default-active-key="1" :tab-bar-style="{ marginBottom: '16px' }">
|
||||
<!-- 工单信息页签(已替换为现有布局与字段) -->
|
||||
<a-tab-pane key="1" tab="工单信息">
|
||||
<a-form layout="vertical" ref="formRef" :model="formData">
|
||||
<!-- 1. 工单信息 -->
|
||||
<a-card class="mb-4" title="工单信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="工单号" name="workOrderNo">
|
||||
<a-input v-model:value="formData.workOrderNo" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="工单状态" name="status">
|
||||
<a-input v-model:value="formData.status" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="工单类型" name="type">
|
||||
<div class="flex items-center">
|
||||
<a-input v-model:value="formData.type" disabled style="flex: 1" />
|
||||
<a-button type="link" disabled class="ml-2">修改</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 2. 用户信息 -->
|
||||
<a-card class="mb-4" title="用户信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="姓名" name="userName">
|
||||
<a-input v-model:value="formData.userName" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-select v-model:value="formData.gender" disabled>
|
||||
<a-select-option value="男">男</a-select-option>
|
||||
<a-select-option value="女">女</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="年龄" name="age">
|
||||
<a-input-number v-model:value="formData.age" disabled style="width: 100%" :min="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="身份证号" name="idCard">
|
||||
<a-input v-model:value="formData.idCard" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" name="phone">
|
||||
<a-input v-model:value="formData.phone" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="评估等级" name="assessmentLevel">
|
||||
<a-input v-model:value="formData.assessmentLevel" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 3. 服务信息 -->
|
||||
<a-card class="mb-4" title="服务信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="护理员" name="nurseName">
|
||||
<a-input v-model:value="formData.nurseName" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="护理等级" name="nurseLevel">
|
||||
<a-input v-model:value="formData.nurseLevel" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="计划开始日期" name="planStartDate">
|
||||
<a-date-picker v-model:value="formData.planStartDate" format="YYYY-MM-DD" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="计划结束日期" name="planEndDate">
|
||||
<a-date-picker v-model:value="formData.planEndDate" format="YYYY-MM-DD" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="计划服务开始时间" name="planStartTime">
|
||||
<a-time-picker v-model:value="formData.planStartTime" format="HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务地址" name="serviceAddress">
|
||||
<a-input v-model:value="formData.serviceAddress" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务项目">
|
||||
<a-button type="link" @click="showServiceItems" disabled>项目清单</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="服务内容" name="serviceContent">
|
||||
<a-input v-model:value="formData.serviceContent" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="下单人" name="orderer">
|
||||
<a-input v-model:value="formData.orderer" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="下单时间" name="orderTime">
|
||||
<a-date-picker v-model:value="formData.orderTime" show-time format="YYYY-MM-DD HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 4. 签到信息(新增模块,匹配现有布局) -->
|
||||
<a-card class="mb-4" title="签到信息">
|
||||
<a-row :gutter="16">
|
||||
<!-- 签入基础信息 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="签入时间" name="checkInTime">
|
||||
<a-input v-model:value="formData.checkInTime" disabled style="color: red" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="签入距离" name="checkInDistance">
|
||||
<a-input v-model:value="formData.checkInDistance" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="签入地址" name="checkInAddress">
|
||||
<a-input v-model:value="formData.checkInAddress" disabled style="color: red" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 签入图片 -->
|
||||
<a-col :span="24">
|
||||
<a-form-item label="签入图片" name="checkInImage">
|
||||
<div class="flex items-center">
|
||||
<a-link v-if="formData.checkInImage" :href="formData.checkInImage" target="_blank" rel="noopener noreferrer">
|
||||
查看图片
|
||||
</a-link>
|
||||
<span v-else>-</span>
|
||||
<a-button type="primary" size="small" class="ml-4" disabled>编 辑</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 签入备注与代签入 -->
|
||||
<a-col :span="24">
|
||||
<a-form-item label="签入备注" name="checkInRemark">
|
||||
<a-input v-model:value="formData.checkInRemark" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="代签入操作人" name="proxyCheckInOperator">
|
||||
<a-input v-model:value="formData.proxyCheckInOperator" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="代签入时间" name="proxyCheckInTime">
|
||||
<a-date-picker v-model:value="formData.proxyCheckInTime" show-time format="YYYY-MM-DD HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="代签入原因" name="proxyCheckInReason">
|
||||
<a-input v-model:value="formData.proxyCheckInReason" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 录音与过程文件 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="签入录音" name="checkInRecording">
|
||||
<a-input v-model:value="formData.checkInRecording" disabled placeholder="无" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="进行中的图片" name="inProgressImage">
|
||||
<div class="flex items-center">
|
||||
<span v-if="!formData.inProgressImage">-</span>
|
||||
<a-button type="primary" size="small" class="ml-2" disabled>编 辑</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="进行中的视频" name="inProgressVideo">
|
||||
<a-input v-model:value="formData.inProgressVideo" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<!-- 签出与服务时长 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="代签出操作人" name="proxyCheckOutOperator">
|
||||
<a-input v-model:value="formData.proxyCheckOutOperator" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="代签出时间" name="proxyCheckOutTime">
|
||||
<a-date-picker v-model:value="formData.proxyCheckOutTime" show-time format="YYYY-MM-DD HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="代签出原因" name="proxyCheckOutReason">
|
||||
<a-input v-model:value="formData.proxyCheckOutReason" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计划服务时长" name="plannedServiceDuration">
|
||||
<a-input v-model:value="formData.plannedServiceDuration" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="标准服务时长" name="standardServiceDuration">
|
||||
<a-input v-model:value="formData.standardServiceDuration" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="标准服务费用" name="standardServiceFee">
|
||||
<a-input v-model:value="formData.standardServiceFee" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="实际服务时长" name="actualServiceDuration">
|
||||
<a-input v-model:value="formData.actualServiceDuration" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="签出录音" name="checkOutRecording">
|
||||
<a-input v-model:value="formData.checkOutRecording" disabled placeholder="无" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="全程录音" name="fullRecording">
|
||||
<a-input v-model:value="formData.fullRecording" disabled placeholder="无" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 5. 服务小结与反馈 -->
|
||||
<a-card class="mb-4" title="服务小结与反馈">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="服务小结" name="summary">
|
||||
<a-input v-model:value="formData.summary" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户需求反馈" name="feedback">
|
||||
<a-input v-model:value="formData.feedback" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 6. 异常工单处理信息 -->
|
||||
<a-card class="mb-4" title="异常工单处理信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理状态" name="exceptionStatus">
|
||||
<a-input v-model:value="formData.exceptionStatus" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理原因" name="exceptionReason">
|
||||
<a-input v-model:value="formData.exceptionReason" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理人" name="exceptionHandler">
|
||||
<a-input v-model:value="formData.exceptionHandler" disabled placeholder="" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理时间" name="exceptionTime">
|
||||
<a-date-picker v-model:value="formData.exceptionTime" show-time format="YYYY-MM-DD HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 服务评价页签(完全保留原内容,未修改) -->
|
||||
<a-tab-pane key="2" tab="服务评价">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<!-- 回访评价 -->
|
||||
<a-card title="回访评价" size="small">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 2 }" bordered>
|
||||
<a-descriptions-item label="服务满意度">
|
||||
{{ formData.visitSatisfaction || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="服务人员满意度">
|
||||
{{ formData.staffSatisfaction || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="服务反馈" :span="2">
|
||||
{{ formData.visitFeedback || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="回访人">
|
||||
{{ formData.visitor || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="回访时间">
|
||||
{{
|
||||
formData.visitTime
|
||||
? dayjs(formData.visitTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<!-- 服务评价 -->
|
||||
<a-card title="服务评价" size="small" style="margin-top: 16px">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 2 }" bordered>
|
||||
<a-descriptions-item label="服务满意度">
|
||||
{{ formData.serviceSatisfaction || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价渠道">
|
||||
{{ formData.evaluationChannel || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价内容" :span="2">
|
||||
{{ formData.evaluationContent || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价人">
|
||||
{{ formData.evaluator || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价时间">
|
||||
{{
|
||||
formData.evaluationTime
|
||||
? dayjs(formData.evaluationTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useModal, useForm } from '@/hooks'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const emit = defineEmits(['ok'])
|
||||
const { modal, showModal, hideModal } = useModal()
|
||||
const { formData, formRef, resetForm } = useForm()
|
||||
|
||||
// 初始化表单数据(新增签到信息相关字段,匹配现有页面)
|
||||
const initFormData = () => ({
|
||||
// 原有基础字段
|
||||
workOrderNo: '',
|
||||
status: '',
|
||||
type: '',
|
||||
userName: '',
|
||||
gender: '',
|
||||
age: null,
|
||||
idCard: '',
|
||||
phone: '',
|
||||
assessmentLevel: '',
|
||||
nurseName: '',
|
||||
nurseLevel: '',
|
||||
planStartDate: null,
|
||||
planEndDate: null,
|
||||
planStartTime: null,
|
||||
serviceAddress: '',
|
||||
serviceContent: '',
|
||||
orderer: '',
|
||||
orderTime: null,
|
||||
summary: '',
|
||||
feedback: '',
|
||||
exceptionStatus: '',
|
||||
exceptionReason: '',
|
||||
exceptionHandler: '',
|
||||
exceptionTime: null,
|
||||
|
||||
// 新增:签到信息字段(与现有页面一一对应)
|
||||
checkInTime: '', // 格式:2025-10-17 11:19:59(字符串,含红色样式)
|
||||
checkInDistance: '', // 格式:12163 m
|
||||
checkInAddress: '', // 格式:江苏省南通市...(字符串,含红色样式)
|
||||
checkInImage: '', // 图片预览链接
|
||||
checkInRemark: '', // 签入备注
|
||||
proxyCheckInOperator: '', // 代签入操作人
|
||||
proxyCheckInTime: null, // 代签入时间(日期时间类型)
|
||||
proxyCheckInReason: '', // 代签入原因
|
||||
checkInRecording: '无', // 签入录音(默认无)
|
||||
inProgressImage: '', // 进行中的图片(编辑按钮控制)
|
||||
inProgressVideo: '', // 进行中的视频
|
||||
proxyCheckOutOperator: '', // 代签出操作人
|
||||
proxyCheckOutTime: null, // 代签出时间(日期时间类型)
|
||||
proxyCheckOutReason: '', // 代签出原因
|
||||
plannedServiceDuration: '', // 格式:90 分钟
|
||||
standardServiceDuration: '', // 标准服务时长
|
||||
standardServiceFee: '', // 标准服务费用
|
||||
actualServiceDuration: '', // 实际服务时长
|
||||
checkOutRecording: '无', // 签出录音(默认无)
|
||||
fullRecording: '无', // 全程录音(默认无)
|
||||
|
||||
// 原有:服务评价相关字段(完全保留)
|
||||
visitSatisfaction: '',
|
||||
staffSatisfaction: '',
|
||||
visitFeedback: '',
|
||||
visitor: '',
|
||||
visitTime: null,
|
||||
serviceSatisfaction: '',
|
||||
evaluationContent: '',
|
||||
evaluator: '',
|
||||
evaluationTime: null,
|
||||
evaluationChannel: '',
|
||||
})
|
||||
|
||||
// 初始化表单
|
||||
formData.value = initFormData()
|
||||
|
||||
// ================== Methods ==================
|
||||
|
||||
/**
|
||||
* 查看工单(唯一打开弹框的方法)
|
||||
* @param {Object} record - 工单数据(需包含新增的签到信息字段)
|
||||
*/
|
||||
function handleView(record = {}) {
|
||||
showModal({ mode: 'view', title: '查看工单' })
|
||||
loadRecord(record)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载工单数据到表单(新增签到信息字段的赋值逻辑)
|
||||
* @param {Object} record - 工单数据
|
||||
*/
|
||||
function loadRecord(record) {
|
||||
formData.value = {
|
||||
...initFormData(),
|
||||
// 原有字段赋值(保持不变)
|
||||
workOrderNo: record.workOrderNo || '',
|
||||
status: record.status || '',
|
||||
type: record.type || '',
|
||||
userName: record.userName || '',
|
||||
gender: record.gender || '',
|
||||
age: record.age || null,
|
||||
idCard: record.idCard || '',
|
||||
phone: record.phone || '',
|
||||
assessmentLevel: record.assessmentLevel || '',
|
||||
nurseName: record.nurseName || '',
|
||||
nurseLevel: record.nurseLevel || '',
|
||||
planStartDate: record.planStartDate ? dayjs(record.planStartDate) : null,
|
||||
planEndDate: record.planEndDate ? dayjs(record.planEndDate) : null,
|
||||
planStartTime: record.planStartTime ? dayjs(`1970-01-01 ${record.planStartTime}`) : null,
|
||||
serviceAddress: record.serviceAddress || '',
|
||||
serviceContent: record.serviceContent || '',
|
||||
orderer: record.orderer || '',
|
||||
orderTime: record.orderTime ? dayjs(record.orderTime) : null,
|
||||
summary: record.summary || '',
|
||||
feedback: record.feedback || '',
|
||||
exceptionStatus: record.exceptionStatus || '',
|
||||
exceptionReason: record.exceptionReason || '',
|
||||
exceptionHandler: record.exceptionHandler || '',
|
||||
exceptionTime: record.exceptionTime ? dayjs(record.exceptionTime) : null,
|
||||
|
||||
// 新增:签到信息字段赋值(匹配record数据格式)
|
||||
checkInTime: record.checkInTime || '', // 直接接收字符串格式(如:2025-10-17 11:19:59)
|
||||
checkInDistance: record.checkInDistance || '', // 直接接收字符串格式(如:12163 m)
|
||||
checkInAddress: record.checkInAddress || '', // 直接接收字符串格式
|
||||
checkInImage: record.checkInImage || '', // 接收图片预览链接
|
||||
checkInRemark: record.checkInRemark || '',
|
||||
proxyCheckInOperator: record.proxyCheckInOperator || '',
|
||||
proxyCheckInTime: record.proxyCheckInTime ? dayjs(record.proxyCheckInTime) : null,
|
||||
proxyCheckInReason: record.proxyCheckInReason || '',
|
||||
inProgressImage: record.inProgressImage || '',
|
||||
inProgressVideo: record.inProgressVideo || '',
|
||||
proxyCheckOutOperator: record.proxyCheckOutOperator || '',
|
||||
proxyCheckOutTime: record.proxyCheckOutTime ? dayjs(record.proxyCheckOutTime) : null,
|
||||
proxyCheckOutReason: record.proxyCheckOutReason || '',
|
||||
plannedServiceDuration: record.plannedServiceDuration || '', // 直接接收字符串格式(如:90 分钟)
|
||||
standardServiceDuration: record.standardServiceDuration || '',
|
||||
standardServiceFee: record.standardServiceFee || '',
|
||||
actualServiceDuration: record.actualServiceDuration || '',
|
||||
|
||||
// 原有:服务评价字段赋值(保持不变)
|
||||
visitSatisfaction: record.visitSatisfaction || '',
|
||||
staffSatisfaction: record.staffSatisfaction || '',
|
||||
visitFeedback: record.visitFeedback || '',
|
||||
visitor: record.visitor || '',
|
||||
visitTime: record.visitTime ? dayjs(record.visitTime) : null,
|
||||
serviceSatisfaction: record.serviceSatisfaction || '',
|
||||
evaluationContent: record.evaluationContent || '',
|
||||
evaluator: record.evaluator || '',
|
||||
evaluationTime: record.evaluationTime ? dayjs(record.evaluationTime) : null,
|
||||
evaluationChannel: record.evaluationChannel || '',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认按钮(禁用状态,仅保留逻辑)
|
||||
*/
|
||||
function handleOk() {
|
||||
hideModal()
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消/关闭按钮
|
||||
*/
|
||||
function handleCancel() {
|
||||
hideModal()
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹框关闭后重置表单
|
||||
*/
|
||||
function onAfterClose() {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务项目清单(查看模式下禁用,保留提示)
|
||||
*/
|
||||
function showServiceItems() {
|
||||
message.info('服务项目清单')
|
||||
}
|
||||
|
||||
// 仅暴露查看方法
|
||||
defineExpose({
|
||||
handleView,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ant-descriptions-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ant-descriptions-view table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.ant-descriptions-item-label {
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.ant-descriptions-item-content {
|
||||
display: inline-block;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* 适配新增的签入信息模块样式 */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.ml-4 {
|
||||
margin-left: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,585 @@
|
||||
<template>
|
||||
<!-- 用a-card包裹Tabs和搜索栏,形成统一白色区域 -->
|
||||
<a-card class="tab-search-container" :bordered="false">
|
||||
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange" style="margin-bottom: 16px;">
|
||||
<a-tab-pane key="all" tab="全部" />
|
||||
<a-tab-pane key="initialization" tab="初始化" />
|
||||
<a-tab-pane key="pendingCheckin" tab="待签入" />
|
||||
<a-tab-pane key="completed" tab="已结单" />
|
||||
<a-tab-pane key="cancelled" tab="已作废" />
|
||||
<a-tab-pane key="expiredUncheckedIn" tab="过期未签入" />
|
||||
<a-tab-pane key="expiredUncheckedOut" tab="过期未签出" />
|
||||
</a-tabs>
|
||||
|
||||
<x-search-bar class="mb-4">
|
||||
<template #default="{ gutter, colSpan }">
|
||||
<a-form :label-col="{ style: { width: '100px' } }" :model="searchFormData" layout="inline">
|
||||
<!-- 基础查询字段 -->
|
||||
<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 name="code">
|
||||
<template #label>
|
||||
身份证号
|
||||
<a-tooltip title="身份证号">
|
||||
<question-circle-outlined class="ml-4-1 color-placeholder" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model:value="searchFormData.code" placeholder="请输入身份证号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="实际服务日期" name="actualServiceTime">
|
||||
<a-range-picker v-model:value="searchFormData.actualServiceTime" />
|
||||
</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="workOrderNo">
|
||||
<a-input v-model:value="searchFormData.workOrderNo" placeholder="请输入工单号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="所在区域" name="area">
|
||||
<AreaCascader v-model:value="searchFormData.currentNode"
|
||||
@change="onAreaChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="所在节点" name="node">
|
||||
<a-select v-model:value="searchFormData.node" placeholder="请选择所在节点">
|
||||
<a-select-option value="node1">节点1</a-select-option>
|
||||
<a-select-option value="node2">节点2</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="服务项目" name="serviceProject">
|
||||
<a-input v-model:value="searchFormData.serviceProject" placeholder="请输入服务项目" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="服务人员" name="staff">
|
||||
<a-select v-model:value="searchFormData.staff" placeholder="请选择服务人员">
|
||||
<a-select-option value="staff1">张三</a-select-option>
|
||||
<a-select-option value="staff2">李四</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<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="projectCode">
|
||||
<a-select v-model:value="searchFormData.projectCode" placeholder="请选择项目编号">
|
||||
<a-select-option value="P001">P001</a-select-option>
|
||||
<a-select-option value="P002">P002</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="服务对象分类" name="clientCategory">
|
||||
<a-select v-model:value="searchFormData.clientCategory" placeholder="请选择分类">
|
||||
<a-select-option value="elderly">老年人</a-select-option>
|
||||
<a-select-option value="disabled">残障人士</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-card>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<a-card style="margin-top: 16px;">
|
||||
<x-action-bar class="mb-8-2">
|
||||
<a-button v-action="'add'" type="primary" @click="$refs.editDialogRef.handleCreate()"
|
||||
style="margin-right: 20px;">
|
||||
<template #icon>
|
||||
<!-- <plus-outlined /> -->
|
||||
</template>
|
||||
{{ '导出' }}
|
||||
</a-button>
|
||||
|
||||
<a-button v-action="'add'" type="primary" @click="$refs.editDialogRef.handleCreate()">
|
||||
<template #icon>
|
||||
<!-- <plus-outlined /> -->
|
||||
</template>
|
||||
{{ '导出记录' }}
|
||||
</a-button>
|
||||
</x-action-bar>
|
||||
|
||||
<a-table rowKey="id" :loading="loading" :pagination="true" :columns="currentColumns" :data-source="listData"
|
||||
:scroll="{ x: 'max-content' }">
|
||||
<!-- 表格列渲染逻辑保持不变 -->
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="'menuType' === column.key">
|
||||
<a-tag v-if="menuTypeEnum.is('page', record.type)" color="processing">
|
||||
{{ menuTypeEnum.getDesc(record.type) }}
|
||||
</a-tag>
|
||||
<a-tag v-if="menuTypeEnum.is('button', record.type)" color="success">
|
||||
{{ menuTypeEnum.getDesc(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="'createAt' === column.key">
|
||||
{{ formatUtcDateTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<template v-if="'statusType' === column.key">
|
||||
<a-tag v-if="statusTypeEnum.is('enabled', record.status)" color="processing">
|
||||
{{ statusTypeEnum.getDesc(record.status) }}
|
||||
</a-tag>
|
||||
<a-tag v-if="statusTypeEnum.is('disabled', record.status)" color="default">
|
||||
{{ statusTypeEnum.getDesc(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="'action' === column.key">
|
||||
<template v-if="['all', 'initialization', 'pendingCheckin'].includes(activeTabKey)">
|
||||
<x-action-button @click="$refs.cancelDialogRef.showCancelModal(record)">
|
||||
<a-tooltip><template #title>
|
||||
{{ '作废' }}</template>作废
|
||||
</a-tooltip>
|
||||
</x-action-button>
|
||||
</template>
|
||||
|
||||
<template v-if="['all', 'initialization'].includes(activeTabKey)">
|
||||
<x-action-button @click="$refs.editDialogRef.showEditModal(record)">
|
||||
<a-tooltip><template #title>编辑</template>编辑</a-tooltip>
|
||||
</x-action-button>
|
||||
</template>
|
||||
|
||||
<template v-if="['all', 'completed', 'cancelled'].includes(activeTabKey)">
|
||||
<x-action-button @click="$refs.viewDialogRef.handleView(record)">
|
||||
<a-tooltip><template #title>详情</template>详情</a-tooltip>
|
||||
</x-action-button>
|
||||
</template>
|
||||
|
||||
<template v-if="['all', 'expiredUncheckedIn', 'expiredUncheckedOut'].includes(activeTabKey)">
|
||||
<x-action-button @click="$refs.checkDialogRef.showCheckModal(record)">
|
||||
<a-tooltip><template #title>待签入</template>待签入</a-tooltip>
|
||||
</x-action-button>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<edit-dialog @ok="onOk" ref="editDialogRef" />
|
||||
<CancelWorkOrder @ok="onOk" ref="cancelDialogRef" />
|
||||
<view-dialog @ok="onOk" ref="viewDialogRef" />
|
||||
<check-dialog @ok="onOk" ref="checkDialogRef" />
|
||||
|
||||
<!-- 作废确认模态 框 -->
|
||||
<!-- <a-modal
|
||||
v-model:open="cancelModalVisible"
|
||||
title="作废确认"
|
||||
@ok="confirmCancel"
|
||||
@cancel="cancelModalVisible = false"
|
||||
>
|
||||
<p>确定要作废该工单吗?此操作不可撤销。</p>
|
||||
<a-textarea
|
||||
v-model:value="cancelReason"
|
||||
placeholder="请输入作废原因(可选)"
|
||||
:rows="4"
|
||||
/>
|
||||
</a-modal> -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import apis from '@/apis'
|
||||
import { config } from '@/config'
|
||||
import { menuTypeEnum, statusTypeEnum } from '@/enums/system'
|
||||
import { usePagination, useForm } from '@/hooks'
|
||||
import { formatUtcDateTime } from '@/utils/util'
|
||||
import EditDialog from './components/EditDialog.vue'
|
||||
import CancelWorkOrder from './components/InvalidatedDialog.vue'
|
||||
import ViewDialog from './components/ViewDialog.vue'
|
||||
import CheckDialog from './components/CheckDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import storage from '@/utils/storage'
|
||||
import AreaCascader from '@/components/AreaCascader/index.vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
defineOptions({
|
||||
name: 'menu',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const activeTabKey = ref('all') // 默认选中"全部"标签页
|
||||
|
||||
// 作废相关状态
|
||||
const cancelModalVisible = ref(false)
|
||||
const cancelRecord = ref(null)
|
||||
const cancelReason = ref('')
|
||||
|
||||
// 不同标签页对应的表格列配置
|
||||
const columnConfigs = ref({
|
||||
all: [
|
||||
|
||||
// { title: '工单号', dataIndex: 'orderNum', key: 'orderNum', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'customerName', key: 'customerName', width: 140 },
|
||||
{ title: '服务对象身份证', dataIndex: 'customerIdCard', key: 'customerIdCard', width: 240 },
|
||||
{
|
||||
title: '服务对象分类', dataIndex: 'labels', key: 'labels', width: 140,
|
||||
render: (labels) => Array.isArray(labels) ? labels.join(', ') : ''
|
||||
},
|
||||
{ title: '服务人员', dataIndex: 'serviceName', key: 'serviceName', width: 140 },
|
||||
{
|
||||
title: '计划日期',
|
||||
dataIndex: 'plannedStartDate',
|
||||
key: 'plannedStartDate',
|
||||
width: 240,
|
||||
customRender: ({ text, record }) => {
|
||||
if (!text) return '';
|
||||
try {
|
||||
// 确保 dayjs 已导入
|
||||
const formatted = dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
||||
return formatted;
|
||||
} catch (e) {
|
||||
console.error('日期格式化失败:', text, e);
|
||||
return text; // 或 return '无效日期'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '服务项目分类',
|
||||
// 假设取 projects[0].categoryType,可根据实际字段调整
|
||||
render: (_, record) => record.projects?.[0]?.categoryType || record.projects?.[0]?.categoryMethod || '',
|
||||
key: 'serviceCategory',
|
||||
width: 240
|
||||
},
|
||||
{
|
||||
title: '服务项目',
|
||||
render: (_, record) => record.projects?.[0]?.name || '',
|
||||
key: 'serviceNameProject',
|
||||
width: 240
|
||||
},
|
||||
{
|
||||
title: '服务费用',
|
||||
render: (_, record) => record.projects?.[0]?.price ?? 0,
|
||||
key: 'servicePrice',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '实际支付费用',
|
||||
// 若无单独字段,可先与服务费用一致;后续可替换为 actualPaid 等字段
|
||||
render: (_, record) => record.projects?.[0]?.price ?? 0,
|
||||
key: 'actualPaid',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '计划服务时间(分钟)',
|
||||
|
||||
dataIndex: 'workDuration',
|
||||
key: 'workDuration',
|
||||
width: 240
|
||||
},
|
||||
{ title: '服务状态', dataIndex: 'status', key: 'status', width: 180 },
|
||||
{ title: '操作', key: 'action', width: 440, fixed: 'right' }
|
||||
|
||||
],
|
||||
initialization: [
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '服务项目', dataIndex: 'project', key: 'project', width: 240 },
|
||||
{ title: '计划服务时间', dataIndex: 'scheduledTime', key: 'scheduledTime', width: 240 },
|
||||
{ title: '服务组织', dataIndex: 'organization', key: 'organization', width: 240 },
|
||||
{ title: '操作', key: 'action', width: 240, fixed: 'right' }
|
||||
],
|
||||
pendingCheckin: [
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '服务地址', dataIndex: 'address', key: 'address', width: 300 },
|
||||
{ title: '计划服务时间', dataIndex: 'scheduledTime', key: 'scheduledTime', width: 240 },
|
||||
{ title: '服务人员', dataIndex: 'staff', key: 'staff', width: 240 },
|
||||
{ title: '操作', key: 'action', width: 240, fixed: 'right' }
|
||||
],
|
||||
completed: [
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '服务项目', dataIndex: 'project', key: 'project', width: 240 },
|
||||
{ title: '实际服务时间', dataIndex: 'actualTime', key: 'actualTime', width: 240 },
|
||||
{ title: '服务人员', dataIndex: 'staff', key: 'staff', width: 240 },
|
||||
{ title: '操作', key: 'action', width: 240, fixed: 'right' }
|
||||
],
|
||||
cancelled: [
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '作废原因', dataIndex: 'cancelReason', key: 'cancelReason', width: 300 },
|
||||
{ title: '作废时间', dataIndex: 'cancelTime', key: 'cancelTime', width: 240 },
|
||||
{ title: '操作人', dataIndex: 'operator', key: 'operator', width: 240 },
|
||||
{ title: '操作', key: 'action', width: 240, fixed: 'right' }
|
||||
],
|
||||
expiredUncheckedIn: [
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '计划服务时间', dataIndex: 'scheduledTime', key: 'scheduledTime', width: 240 },
|
||||
{ title: '逾期天数', dataIndex: 'overdueDays', key: 'overdueDays', width: 180 },
|
||||
{ title: '服务人员', dataIndex: 'staff', key: 'staff', width: 240 },
|
||||
{ title: '操作', key: 'action', width: 240, fixed: 'right' }
|
||||
],
|
||||
expiredUncheckedOut: [
|
||||
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
|
||||
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
|
||||
{ title: '签入时间', dataIndex: 'checkinTime', key: 'checkinTime', width: 240 },
|
||||
{ title: '逾期天数', dataIndex: 'overdueDays', key: 'overdueDays', width: 180 },
|
||||
{ title: '服务人员', dataIndex: 'staff', key: 'staff', width: 240 },
|
||||
{ title: '操作', key: 'action', width: 240, fixed: 'right' }
|
||||
]
|
||||
})
|
||||
|
||||
// 根据当前选中的标签页获取对应的表格列
|
||||
const currentColumns = computed(() => {
|
||||
return columnConfigs.value[activeTabKey.value] || columnConfigs.value.all
|
||||
})
|
||||
|
||||
const { listData, loading, showLoading, hideLoading, searchFormData, paginationState, resetPagination } =
|
||||
usePagination()
|
||||
const { resetForm } = useForm()
|
||||
const editDialogRef = ref()
|
||||
const viewDialogRef = ref()
|
||||
const checkDialogRef = ref()
|
||||
// 控制高级查询面板是否展开
|
||||
const advancedSearchVisible = ref([]) // 默认收起
|
||||
const tabKeyToStatusMap = {
|
||||
all: undefined, // 全部不传 status
|
||||
initialization: 'Initialize',
|
||||
pendingCheckin: 'Pending_Check-in',
|
||||
completed: 'Closed',
|
||||
cancelled: 'Cancelled',
|
||||
expiredUncheckedIn: 'Overdue_Unchecked-in',
|
||||
expiredUncheckedOut: 'Overdue_Unchecked-out'
|
||||
}
|
||||
|
||||
// 初始加载数据
|
||||
getMenuList()
|
||||
|
||||
/**
|
||||
* 获取菜单列表
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function getMenuList() {
|
||||
try {
|
||||
showLoading()
|
||||
const { pageSize, current } = paginationState
|
||||
|
||||
// 获取当前 tab 对应的 status
|
||||
const status = tabKeyToStatusMap[activeTabKey.value]
|
||||
|
||||
const params = {
|
||||
pageSize,
|
||||
current,
|
||||
...searchFormData.value
|
||||
}
|
||||
|
||||
// 只有 status 不为 undefined 时才加入参数
|
||||
if (status !== undefined) {
|
||||
params.status = status
|
||||
}
|
||||
|
||||
console.log("=====search params", params)
|
||||
|
||||
const { success, data, total } = await apis.workOrder.getWorkOrderList(params)
|
||||
|
||||
if (config('http.code.success') === success) {
|
||||
listData.value = data
|
||||
paginationState.total = total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取工单列表失败:', error)
|
||||
message.error('获取数据失败')
|
||||
} finally {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签页切换事件
|
||||
*/
|
||||
|
||||
|
||||
function handleTabChange(key) {
|
||||
// 全部不用传
|
||||
// 初始化 Initialize
|
||||
// 待签入 Pending_Check-in
|
||||
// 待签出 Pending_Check-out
|
||||
// 已结单 Closed
|
||||
// 已作废 Cancelled
|
||||
// 过期未签入 Overdue_Unchecked-in
|
||||
// 过期未签出 Overdue_Unchecked-out
|
||||
console.log('active tab key:', key)
|
||||
activeTabKey.value = key
|
||||
resetPagination()
|
||||
getMenuList() // 切换标签页时重新加载数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
function handleSearch() {
|
||||
resetPagination()
|
||||
getMenuList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function handleResetSearch() {
|
||||
searchFormData.value = {}
|
||||
resetPagination()
|
||||
getMenuList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示作废确认模态框
|
||||
*/
|
||||
function showCancelModal(record) {
|
||||
cancelRecord.value = record
|
||||
cancelReason.value = ''
|
||||
cancelModalVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认作废操作
|
||||
*/
|
||||
async function confirmCancel() {
|
||||
if (!cancelRecord.value) return
|
||||
|
||||
try {
|
||||
const { success } = await apis.menu.cancelMenu({
|
||||
id: cancelRecord.value.id,
|
||||
reason: cancelReason.value
|
||||
}).catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
if (config('http.code.success') === success) {
|
||||
message.success('工单已作废')
|
||||
cancelModalVisible.value = false
|
||||
await getMenuList()
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('作废失败')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
* @param record
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
console.log('查看详情:', record)
|
||||
// 实现详情查看逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 续期操作(针对过期工单)
|
||||
* @param record
|
||||
*/
|
||||
function handleRenew(record) {
|
||||
Modal.confirm({
|
||||
title: t('pages.system.renewConfirm'),
|
||||
content: t('pages.system.renewTip'),
|
||||
okText: t('button.confirm'),
|
||||
cancelText: t('button.cancel'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
const { success } = await apis.menu.renewMenu(record.id)
|
||||
if (config('http.code.success') === success) {
|
||||
message.success(t('component.message.success.renew'))
|
||||
await getMenuList()
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(t('component.message.error.renew'))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑完成
|
||||
*/
|
||||
async function onOk() {
|
||||
await getMenuList()
|
||||
}
|
||||
|
||||
// 选择器变更事件
|
||||
function handleChange(value) {
|
||||
console.log('Selected value:', value)
|
||||
}
|
||||
|
||||
// 区域变更事件
|
||||
function onAreaChange(value) {
|
||||
console.log('Area changed:', value)
|
||||
}
|
||||
|
||||
// 切换高级查询
|
||||
function toggleAdvancedSearch() {
|
||||
advancedSearchVisible.value = advancedSearchVisible.value.length ? [] : ['1']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-search-container {
|
||||
background: #fff; // 白色背景
|
||||
padding: 16px; // 内边距
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); // 可选:添加轻微阴影增强层次
|
||||
margin-bottom: 16px; // 与下方表格区域保持间距
|
||||
}
|
||||
|
||||
// 在 style 中添加
|
||||
.ant-btn-link:hover {
|
||||
color: #0050b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
338
src/views/workorderMenu/visitHistory/index.vue
Normal file
338
src/views/workorderMenu/visitHistory/index.vue
Normal file
@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<x-search-bar class="mb-4">
|
||||
<template #default="{ gutter, colSpan }">
|
||||
<a-form :label-col="{ style: { width: '130px' } }" :model="searchFormData" layout="inline">
|
||||
<!-- 基础查询字段 -->
|
||||
<a-row :gutter="gutter">
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="所在区域" name="area">
|
||||
<AreaCascader v-model:value="searchFormData.currentNode" @change="onAreaChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="服务满意度" name="node">
|
||||
<a-select v-model:value="searchFormData.node" placeholder="请选择满意度">
|
||||
<a-select-option v-for="item in dicsStore.dictOptions.Satisfaction_Type"
|
||||
:key="item.dval" :value="item.dval">
|
||||
{{ item.introduction }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="服务人员满意度" name="node">
|
||||
<a-select v-model:value="searchFormData.node" placeholder="请选择服务人员满意度">
|
||||
<a-select-option v-for="item in dicsStore.dictOptions.Satisfaction_Type"
|
||||
:key="item.dval" :value="item.dval">
|
||||
{{ item.introduction }}
|
||||
</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-form-item label="回访时间" name="plannedDate" style="white-space: nowrap;">
|
||||
<a-range-picker v-model:value="searchFormData.plannedDate" style="width: 220px;" />
|
||||
</a-form-item>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="回访类型" name="node">
|
||||
<a-select v-model:value="searchFormData.node" placeholder="请选择所在节点">
|
||||
<a-select-option v-for="item in dicsStore.dictOptions.Visit_Type"
|
||||
:key="item.dval" :value="item.dval">
|
||||
{{ item.introduction }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col v-bind="colSpan">
|
||||
<a-form-item label="回访人" name="serviceProject">
|
||||
<a-input v-model:value="searchFormData.serviceProject" placeholder="请输入服务项目" />
|
||||
</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">
|
||||
<a-col flex="auto">
|
||||
<a-card title="服务人员列表">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<a-space>
|
||||
<!-- <a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button> -->
|
||||
<a-button type="primary">导出</a-button>
|
||||
<a-button type="dashed">导出记录</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
|
||||
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
|
||||
<template #bodyCell="{ index, column, record }">
|
||||
<template v-if="column.key === 'serialNumber'">
|
||||
<span>{{ index + 1 }}</span>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<!-- <edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
|
||||
<detail ref="detailRef"></detail> -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'
|
||||
import AreaCascader from '@/components/AreaCascader/index.vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { ref } from 'vue'
|
||||
import apis from '@/apis'
|
||||
import { config } from '@/config'
|
||||
import { usePagination } from '@/hooks'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDicsStore } from '@/store'
|
||||
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import NodeTree from '@/components/NodeTree/index.vue'
|
||||
defineOptions({
|
||||
name: 'allocation',
|
||||
})
|
||||
const dicsStore = useDicsStore()
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'serialNumber',
|
||||
key: 'serialNumber',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
key: 'gender',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '身份证号',
|
||||
dataIndex: 'idCard',
|
||||
key: 'idCard',
|
||||
align: 'center',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '护理人员类型',
|
||||
dataIndex: 'serviceType',
|
||||
key: 'serviceType',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '用工形式',
|
||||
dataIndex: 'workType',
|
||||
key: 'workType',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
},
|
||||
// --- 联系方式 ---
|
||||
{
|
||||
title: '护理人员类型',
|
||||
dataIndex: 'serviceType',
|
||||
key: 'serviceType',
|
||||
align: 'center',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
title: '所属服务组织',
|
||||
dataIndex: 'stationName',
|
||||
key: 'stationName',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
},
|
||||
// {
|
||||
// title: '组织所在区域',
|
||||
// dataIndex: 'areaLabels',
|
||||
// key: 'areaLabels',
|
||||
// align: 'center',
|
||||
// width: 120,
|
||||
// },
|
||||
];
|
||||
const { t } = useI18n() // 解构出t方法
|
||||
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
|
||||
const editDialogRef = ref()
|
||||
const detailRef = ref()
|
||||
// 控制高级查询面板是否展开
|
||||
const advancedSearchVisible = ref([]) // 默认收起
|
||||
|
||||
getPageList()
|
||||
|
||||
async function getPageList() {
|
||||
console.log("===1")
|
||||
try {
|
||||
const { pageSize, current } = paginationState
|
||||
const { success, data, total } = await apis.workOrder
|
||||
.getBackRecordList({
|
||||
pageSize,
|
||||
current: current,
|
||||
...searchFormData.value,
|
||||
})
|
||||
|
||||
listData.value = data
|
||||
paginationState.total = total
|
||||
console.log("===",data)
|
||||
.catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
if (config('http.code.success') === success) {
|
||||
listData.value = data
|
||||
paginationState.total = total
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
/**核销 */
|
||||
const checkHandler = (record) => {
|
||||
Modal.confirm({
|
||||
title: '即将核销是否继续',
|
||||
content: t('button.confirm'),
|
||||
okText: t('button.confirm'),
|
||||
onOk: async () => {
|
||||
const params = {
|
||||
...record,
|
||||
status: 'success'
|
||||
}
|
||||
const { success } = await apis.productOrder.updateItem(params.id, params).catch(() => {
|
||||
// throw new Error()
|
||||
})
|
||||
if (config('http.code.success') === success) {
|
||||
// resolve()
|
||||
message.success('核销成功')
|
||||
await getPageList()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
function handleDelete({ id }) {
|
||||
Modal.confirm({
|
||||
title: t('pages.system.user.delTip'),
|
||||
content: t('button.confirm'),
|
||||
okText: t('button.confirm'),
|
||||
onOk: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
; (async () => {
|
||||
try {
|
||||
const { success } = await apis.productOrder.delItem(id).catch(() => {
|
||||
throw new Error()
|
||||
})
|
||||
if (config('http.code.success') === success) {
|
||||
resolve()
|
||||
message.success(t('component.message.success.delete'))
|
||||
await getPageList()
|
||||
}
|
||||
} catch (error) {
|
||||
reject()
|
||||
}
|
||||
})()
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
function onTableChange({ current, pageSize }) {
|
||||
paginationState.current = current
|
||||
paginationState.pageSize = pageSize
|
||||
getPageList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
function handleSearch() {
|
||||
resetPagination()
|
||||
getPageList()
|
||||
}
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function handleResetSearch() {
|
||||
searchFormData.value = {}
|
||||
resetPagination()
|
||||
getPageList()
|
||||
}
|
||||
/**
|
||||
* 编辑完成
|
||||
*/
|
||||
async function onOk() {
|
||||
await getPageList()
|
||||
}
|
||||
|
||||
// 切换高级查询
|
||||
function toggleAdvancedSearch() {
|
||||
advancedSearchVisible.value = advancedSearchVisible.value.length ? [] : ['1']
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
404
src/views/workorderMenu/visitWorkOrder/components/EditDialog.vue
Normal file
404
src/views/workorderMenu/visitWorkOrder/components/EditDialog.vue
Normal file
@ -0,0 +1,404 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="900"
|
||||
:open="modal.open"
|
||||
title="查看工单"
|
||||
:confirm-loading="modal.confirmLoading"
|
||||
:after-close="onAfterClose"
|
||||
cancel-text="关闭"
|
||||
ok-text="确认"
|
||||
:ok-button-props="{ disabled: true }"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-tabs default-active-key="1" :tab-bar-style="{ marginBottom: '16px' }">
|
||||
<!-- 工单信息页签 -->
|
||||
<a-tab-pane key="1" tab="工单信息">
|
||||
<a-form layout="vertical" ref="formRef" :model="formData">
|
||||
<!-- 工单信息 -->
|
||||
<a-card class="mb-4" title="工单信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工单号" name="workOrderNo">
|
||||
<a-input v-model:value="formData.workOrderNo" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工单状态" name="status">
|
||||
<a-input v-model:value="formData.status" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工单类型" name="type">
|
||||
<a-input v-model:value="formData.type" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<a-card class="mb-4" title="用户信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="姓名" name="userName">
|
||||
<a-input v-model:value="formData.userName" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-select v-model:value="formData.gender" disabled>
|
||||
<a-select-option value="男">男</a-select-option>
|
||||
<a-select-option value="女">女</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="年龄" name="age">
|
||||
<a-input-number v-model:value="formData.age" disabled style="width: 100%" :min="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="身份证号" name="idCard">
|
||||
<a-input v-model:value="formData.idCard" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系电话" name="phone">
|
||||
<a-input v-model:value="formData.phone" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="评估等级" name="assessmentLevel">
|
||||
<a-input v-model:value="formData.assessmentLevel" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 服务信息 -->
|
||||
<a-card class="mb-4" title="服务信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="护理员" name="nurseName">
|
||||
<a-input v-model:value="formData.nurseName" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="护理等级" name="nurseLevel">
|
||||
<a-input v-model:value="formData.nurseLevel" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计划开始日期" name="planStartDate">
|
||||
<a-date-picker v-model:value="formData.planStartDate" format="YYYY-MM-DD" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计划结束日期" name="planEndDate">
|
||||
<a-date-picker v-model:value="formData.planEndDate" format="YYYY-MM-DD" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="计划服务开始时间" name="planStartTime">
|
||||
<a-time-picker v-model:value="formData.planStartTime" format="HH:mm" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务地址" name="serviceAddress">
|
||||
<a-input v-model:value="formData.serviceAddress" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务项目">
|
||||
<a-button type="link" @click="showServiceItems" disabled>项目清单</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务内容" name="serviceContent">
|
||||
<a-textarea v-model:value="formData.serviceContent" disabled rows="3" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="下单人" name="orderer">
|
||||
<a-input v-model:value="formData.orderer" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="下单时间" name="orderTime">
|
||||
<a-date-picker v-model:value="formData.orderTime" show-time format="YYYY-MM-DD HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 服务小结与反馈 -->
|
||||
<a-card class="mb-4" title="服务小结与反馈">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="服务小结" name="summary">
|
||||
<a-textarea v-model:value="formData.summary" disabled rows="3" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="用户需求反馈" name="feedback">
|
||||
<a-textarea v-model:value="formData.feedback" disabled rows="3" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<!-- 异常处理信息 -->
|
||||
<a-card class="mb-4" title="异常工单处理信息">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理状态" name="exceptionStatus">
|
||||
<a-input v-model:value="formData.exceptionStatus" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理原因" name="exceptionReason">
|
||||
<a-input v-model:value="formData.exceptionReason" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理人" name="exceptionHandler">
|
||||
<a-input v-model:value="formData.exceptionHandler" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="处理时间" name="exceptionTime">
|
||||
<a-date-picker v-model:value="formData.exceptionTime" show-time format="YYYY-MM-DD HH:mm:ss" disabled style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 服务评价页签 -->
|
||||
<a-tab-pane key="2" tab="服务评价">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<!-- 回访评价 -->
|
||||
<a-card title="回访评价" size="small">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 2 }" bordered>
|
||||
<a-descriptions-item label="服务满意度">
|
||||
{{ formData.visitSatisfaction || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="服务人员满意度">
|
||||
{{ formData.staffSatisfaction || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="服务反馈" :span="2">
|
||||
{{ formData.visitFeedback || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="回访人">
|
||||
{{ formData.visitor || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="回访时间">
|
||||
{{
|
||||
formData.visitTime
|
||||
? dayjs(formData.visitTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<!-- 服务评价 -->
|
||||
<a-card title="服务评价" size="small" style="margin-top: 16px">
|
||||
<a-descriptions :column="{ xs: 1, sm: 2, md: 2 }" bordered>
|
||||
<a-descriptions-item label="服务满意度">
|
||||
{{ formData.serviceSatisfaction || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价渠道">
|
||||
{{ formData.evaluationChannel || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价内容" :span="2">
|
||||
{{ formData.evaluationContent || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价人">
|
||||
{{ formData.evaluator || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="评价时间">
|
||||
{{
|
||||
formData.evaluationTime
|
||||
? dayjs(formData.evaluationTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useModal, useForm } from '@/hooks'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const emit = defineEmits(['ok'])
|
||||
const { modal, showModal, hideModal } = useModal()
|
||||
const { formData, formRef, resetForm } = useForm()
|
||||
|
||||
// 初始化表单数据(含服务评价字段)
|
||||
const initFormData = () => ({
|
||||
workOrderNo: '',
|
||||
status: '',
|
||||
type: '',
|
||||
userName: '',
|
||||
gender: '',
|
||||
age: null,
|
||||
idCard: '',
|
||||
phone: '',
|
||||
assessmentLevel: '',
|
||||
nurseName: '',
|
||||
nurseLevel: '',
|
||||
planStartDate: null,
|
||||
planEndDate: null,
|
||||
planStartTime: null,
|
||||
serviceAddress: '',
|
||||
serviceContent: '',
|
||||
orderer: '',
|
||||
orderTime: null,
|
||||
summary: '',
|
||||
feedback: '',
|
||||
exceptionStatus: '',
|
||||
exceptionReason: '',
|
||||
exceptionHandler: '',
|
||||
exceptionTime: null,
|
||||
// 服务评价相关字段
|
||||
visitSatisfaction: '',
|
||||
staffSatisfaction: '',
|
||||
visitFeedback: '',
|
||||
visitor: '',
|
||||
visitTime: null,
|
||||
serviceSatisfaction: '',
|
||||
evaluationContent: '',
|
||||
evaluator: '',
|
||||
evaluationTime: null,
|
||||
evaluationChannel: '',
|
||||
})
|
||||
|
||||
// 初始化表单
|
||||
formData.value = initFormData()
|
||||
|
||||
// ================== Methods ==================
|
||||
|
||||
/**
|
||||
* 查看工单(唯一打开弹框的方法)
|
||||
* @param {Object} record - 工单数据
|
||||
*/
|
||||
function handleView(record = {}) {
|
||||
showModal({ mode: 'view', title: '查看工单' })
|
||||
loadRecord(record)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载工单数据到表单
|
||||
* @param {Object} record - 工单数据
|
||||
*/
|
||||
function loadRecord(record) {
|
||||
formData.value = {
|
||||
...initFormData(),
|
||||
workOrderNo: record.workOrderNo || '',
|
||||
status: record.status || '',
|
||||
type: record.type || '',
|
||||
userName: record.userName || '',
|
||||
gender: record.gender || '',
|
||||
age: record.age || null,
|
||||
idCard: record.idCard || '',
|
||||
phone: record.phone || '',
|
||||
assessmentLevel: record.assessmentLevel || '',
|
||||
nurseName: record.nurseName || '',
|
||||
nurseLevel: record.nurseLevel || '',
|
||||
planStartDate: record.planStartDate ? dayjs(record.planStartDate) : null,
|
||||
planEndDate: record.planEndDate ? dayjs(record.planEndDate) : null,
|
||||
planStartTime: record.planStartTime ? dayjs(`1970-01-01 ${record.planStartTime}`) : null,
|
||||
serviceAddress: record.serviceAddress || '',
|
||||
serviceContent: record.serviceContent || '',
|
||||
orderer: record.orderer || '',
|
||||
orderTime: record.orderTime ? dayjs(record.orderTime) : null,
|
||||
summary: record.summary || '',
|
||||
feedback: record.feedback || '',
|
||||
exceptionStatus: record.exceptionStatus || '',
|
||||
exceptionReason: record.exceptionReason || '',
|
||||
exceptionHandler: record.exceptionHandler || '',
|
||||
exceptionTime: record.exceptionTime ? dayjs(record.exceptionTime) : null,
|
||||
// 服务评价字段
|
||||
visitSatisfaction: record.visitSatisfaction || '',
|
||||
staffSatisfaction: record.staffSatisfaction || '',
|
||||
visitFeedback: record.visitFeedback || '',
|
||||
visitor: record.visitor || '',
|
||||
visitTime: record.visitTime ? dayjs(record.visitTime) : null,
|
||||
serviceSatisfaction: record.serviceSatisfaction || '',
|
||||
evaluationContent: record.evaluationContent || '',
|
||||
evaluator: record.evaluator || '',
|
||||
evaluationTime: record.evaluationTime ? dayjs(record.evaluationTime) : null,
|
||||
evaluationChannel: record.evaluationChannel || '',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认按钮(禁用状态,仅保留逻辑)
|
||||
*/
|
||||
function handleOk() {
|
||||
hideModal()
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消/关闭按钮
|
||||
*/
|
||||
function handleCancel() {
|
||||
hideModal()
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹框关闭后重置表单
|
||||
*/
|
||||
function onAfterClose() {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务项目清单(查看模式下禁用,保留提示)
|
||||
*/
|
||||
function showServiceItems() {
|
||||
message.info('服务项目清单')
|
||||
}
|
||||
|
||||
// 仅暴露查看方法
|
||||
defineExpose({
|
||||
handleView,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ant-descriptions-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ant-descriptions-view table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.ant-descriptions-item-label {
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.ant-descriptions-item-content {
|
||||
display: inline-block;
|
||||
padding-left: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible" title="工单回访" @cancel="handleCancel" @ok="handleOk"
|
||||
:confirm-loading="confirmLoading">
|
||||
<a-form :model="formData" ref="formRef" :rules="formRules">
|
||||
<!-- 满意度(带 tooltip 提示) -->
|
||||
<!-- 满意度 -->
|
||||
<a-form-item label="满意度" name="satisfaction" :required="true">
|
||||
<a-tooltip title="对服务整体满意程度的评价" placement="right" trigger="hover">
|
||||
<span>
|
||||
<a-rate v-model:value="formData.satisfaction" allow-half />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 服务人员评分 -->
|
||||
<a-form-item label="服务人员" name="staffSatisfaction" :required="true">
|
||||
<a-tooltip title="对服务人员态度、专业能力等的评价" placement="right" trigger="hover">
|
||||
<span>
|
||||
<a-rate v-model:value="formData.staffSatisfaction" allow-half />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 服务反馈(非必填) -->
|
||||
<a-form-item label="服务反馈">
|
||||
<a-textarea v-model:value="formData.feedback" placeholder="客观地评价及记录有助于用户决策"
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
// 弹框显隐
|
||||
const visible = ref(false)
|
||||
// 提交加载状态
|
||||
const confirmLoading = ref(false)
|
||||
// 表单引用(用于验证)
|
||||
const formRef = ref()
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
satisfaction: null, // 满意度星级
|
||||
staffSatisfaction: null, // 服务人员星级
|
||||
feedback: '' // 服务反馈
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
satisfaction: [
|
||||
{ required: true, message: '请选择满意度', trigger: 'change' }
|
||||
],
|
||||
staffSatisfaction: [
|
||||
{ required: true, message: '请选择服务人员满意度', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 向父组件暴露事件
|
||||
const emit = defineEmits(['ok', 'cancel'])
|
||||
|
||||
// 确定提交
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
// 表单验证
|
||||
await formRef.value.validate()
|
||||
confirmLoading.value = true
|
||||
|
||||
// 模拟接口提交(实际项目替换为真实接口请求)
|
||||
setTimeout(() => {
|
||||
confirmLoading.value = false
|
||||
visible.value = false
|
||||
emit('ok', formData) // 向父组件传递表单数据
|
||||
message.success('回访提交成功')
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消/关闭弹框
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
// 暴露给父组件的“打开弹框”方法
|
||||
defineExpose({
|
||||
handleOpen: (record) => {
|
||||
formRef.value?.resetFields() // 重置表单
|
||||
formData.satisfaction = null
|
||||
formData.staffSatisfaction = null
|
||||
formData.feedback = ''
|
||||
visible.value = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
333
src/views/workorderMenu/visitWorkOrder/index.vue
Normal file
333
src/views/workorderMenu/visitWorkOrder/index.vue
Normal file
@ -0,0 +1,333 @@
|
||||
<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="true" :columns="columns" :data-source="listData"
|
||||
:scroll="{ x: 'max-content' }">
|
||||
|
||||
<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'">
|
||||
<x-action-button @click="$refs.editDialogRef.handleView(record)">
|
||||
<span>详情</span>
|
||||
</x-action-button>
|
||||
<x-action-button @click="handleVisit(record)">
|
||||
回访
|
||||
</x-action-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<edit-dialog @ok="onOk" ref="editDialogRef" />
|
||||
<!-- 新增回访弹框 -->
|
||||
<visit-dialog @ok="onVisitOk" ref="visitDialogRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'
|
||||
import AreaCascader from '@/components/AreaCascader/index.vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { ref } from 'vue'
|
||||
import apis from '@/apis'
|
||||
import { config } from '@/config'
|
||||
import { usePagination } from '@/hooks'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDicsStore } from '@/store'
|
||||
import EditDialog from './components/EditDialog.vue'
|
||||
// 引入回访弹框组件
|
||||
import VisitDialog from './components/VisitDialog.vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
defineOptions({
|
||||
name: 'allocation',
|
||||
})
|
||||
|
||||
const dicsStore = useDicsStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 表格列
|
||||
const columns = [
|
||||
{ title: '订单号', dataIndex: 'orderNum', key: 'orderNum', align: 'center', width: 80 },
|
||||
{ title: '服务对象姓名', dataIndex: 'customerName', key: 'customerName', align: 'center', width: 100 },
|
||||
{ title: '电话', dataIndex: 'contact1', key: 'contact1', align: 'center', width: 120 },
|
||||
{ title: '电话1', dataIndex: 'otherPhone1', key: 'otherPhone1', align: 'center', width: 120 },
|
||||
{ title: '电话2', dataIndex: 'otherPhone2', key: 'otherPhone2', align: 'center', width: 120 },
|
||||
{ title: '身份证号', dataIndex: 'customerIdCard', key: 'customerIdCard', align: 'center', width: 240 },
|
||||
{ title: '地址', dataIndex: 'detailAddress', key: 'detailAddress', align: 'center', width: 240 },
|
||||
{ title: '服务人员姓名', dataIndex: 'serviceName', key: 'serviceName', align: 'center', width: 120 },
|
||||
{ title: '服务项目名称', dataIndex: 'workType', key: 'workType', align: 'center', width: 120 },
|
||||
{ title: '计划服务开始时间', dataIndex: 'plannedServiceStartDate', key: 'plannedServiceStartDate', align: 'center', width: 100 },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', align: 'center', width: 150 },
|
||||
{ title: '操作', key: 'action', width: 140, fixed: 'right' }
|
||||
]
|
||||
// 分页与搜索数据
|
||||
const {
|
||||
listData,
|
||||
loading,
|
||||
paginationState,
|
||||
resetPagination,
|
||||
searchFormData,
|
||||
} = usePagination()
|
||||
|
||||
// 初始化 searchFormData 结构(重要!)
|
||||
searchFormData.value = {
|
||||
name: '',
|
||||
idCard: '',
|
||||
exceptionType: undefined,
|
||||
serviceOrderNo: '',
|
||||
serviceOrg: undefined,
|
||||
satisfaction: undefined,
|
||||
staffSatisfaction: undefined,
|
||||
isVisited: undefined,
|
||||
plannedDate: [],
|
||||
serviceDuration: undefined,
|
||||
currentNode: [],
|
||||
}
|
||||
|
||||
const editDialogRef = ref()
|
||||
const detailRef = ref()
|
||||
const advancedSearchVisible = ref([]) // 高级查询默认收起
|
||||
// 回访弹框引用
|
||||
const visitDialogRef = ref()
|
||||
// 计算所有列宽度总和
|
||||
const totalWidth = columns.reduce((sum, col) => sum + (col.width || 100), 0);
|
||||
|
||||
// 获取列表(mock 数据)
|
||||
async function getPageList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { pageSize, current } = paginationState
|
||||
const params = {
|
||||
pageSize,
|
||||
current,
|
||||
...searchFormData.value,
|
||||
}
|
||||
if (Array.isArray(params.plannedDate) && params.plannedDate.length === 0) {
|
||||
delete params.plannedDate
|
||||
}
|
||||
const { success, data, total } = await apis.workOrder.getBackWorkOrderList(params)
|
||||
listData.value = data
|
||||
paginationState.total = total
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
message.error('加载数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
resetPagination()
|
||||
getPageList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
function handleResetSearch() {
|
||||
searchFormData.value = {
|
||||
name: '',
|
||||
idCard: '',
|
||||
exceptionType: undefined,
|
||||
serviceOrderNo: '',
|
||||
serviceOrg: undefined,
|
||||
satisfaction: undefined,
|
||||
staffSatisfaction: undefined,
|
||||
isVisited: undefined,
|
||||
plannedDate: [],
|
||||
serviceDuration: undefined,
|
||||
currentNode: [],
|
||||
}
|
||||
resetPagination()
|
||||
getPageList()
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
function onTableChange(pagination) {
|
||||
paginationState.current = pagination.current
|
||||
paginationState.pageSize = pagination.pageSize
|
||||
getPageList()
|
||||
}
|
||||
|
||||
// 切换高级查询
|
||||
function toggleAdvancedSearch() {
|
||||
advancedSearchVisible.value = advancedSearchVisible.value.length ? [] : ['1']
|
||||
}
|
||||
|
||||
// 区域选择回调
|
||||
function onAreaChange(value) {
|
||||
console.log('区域变更:', value)
|
||||
}
|
||||
|
||||
// 操作:详情
|
||||
function handleDetail(record) {
|
||||
console.log('查看详情:', record)
|
||||
message.info(`查看【${record.name}】的详情`)
|
||||
// 示例:打开详情弹窗
|
||||
// detailRef.value?.open(record)
|
||||
}
|
||||
|
||||
|
||||
// 操作:回访(点击行内“回访”按钮时触发)
|
||||
function handleVisit(record) {
|
||||
visitDialogRef.value.handleOpen(record) // 打开回访弹框
|
||||
}
|
||||
|
||||
// 回访提交成功后的回调
|
||||
function onVisitOk(formData) {
|
||||
console.log('回访表单数据:', formData)
|
||||
message.success('回访提交成功')
|
||||
getPageList() // 重新获取列表,更新状态
|
||||
}
|
||||
|
||||
// 页面初始化
|
||||
getPageList()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
Loading…
x
Reference in New Issue
Block a user