<script setup lang="tsx">
|
import { ref, h, onMounted } from "vue";
|
import type { DataTableColumns } from "naive-ui";
|
import { NButton, NTag } from "naive-ui";
|
import { useRouter } from "vue-router";
|
import { useBoolean } from "@/hooks";
|
import {
|
fetchWorkOrderPageList,
|
fetchFeedbackTypeList,
|
fetchOperationsUserListAll,
|
saveWorkOrder,
|
updateWorkOrder,
|
deleteWorkOrder,
|
handleWorkOrder,
|
} from "@/service/api/serviceCenter";
|
import WorkOrderModal from "./components/WorkOrderModal.vue";
|
import WorkOrderHandleModal from "./components/WorkOrderHandleModal.vue";
|
import dayjs from "dayjs";
|
|
interface WorkOrderItem {
|
id: number;
|
feedbackUserPhone?: string;
|
problemSource?: number;
|
feedbackTypeId?: number;
|
feedbackTypeName?: string;
|
problem?: string;
|
problemDescription?: string;
|
problemImage?: string;
|
priority?: number;
|
handleStatus?: number;
|
handleUserId?: number | string;
|
handleUserName?: string;
|
createTime?: string;
|
}
|
|
const {
|
bool: loading,
|
setTrue: startLoading,
|
setFalse: endLoading,
|
} = useBoolean(false);
|
const router = useRouter();
|
|
const initialSearchForm = {
|
feedbackUserPhone: "",
|
problemSource: null as number | null,
|
feedbackTypeId: null as number | null,
|
priority: null as number | null,
|
handleStatus: null as number | null,
|
};
|
const searchForm = ref({ ...initialSearchForm });
|
|
const tableData = ref<WorkOrderItem[]>([]);
|
const currentRow = ref<WorkOrderItem | null>(null);
|
const modalVisible = ref(false);
|
const modalMode = ref<"add" | "edit">("add");
|
const handleModalVisible = ref(false);
|
const handleRow = ref<WorkOrderItem | null>(null);
|
|
const pagination = ref({
|
page: 1,
|
pageSize: 10,
|
});
|
const count = ref(0);
|
|
const typeOptions = ref<{ label: string; value: number }[]>([]);
|
const handleUserOptions = ref<{ label: string; value: number | string }[]>([]);
|
|
const problemSourceOptions = [{ label: "电话客服", value: 1 }];
|
|
const priorityOptions = [
|
{ label: "低", value: 1 },
|
{ label: "中", value: 2 },
|
{ label: "高", value: 3 },
|
];
|
|
const statusOptions = [
|
{ label: "待处理", value: 1 },
|
{ label: "处理中", value: 2 },
|
{ label: "已处理", value: 3 },
|
];
|
|
const priorityLabelMap: Record<number, string> = {
|
1: "低",
|
2: "中",
|
3: "高",
|
};
|
|
const statusLabelMap: Record<number, string> = {
|
1: "未处理",
|
2: "处理中",
|
3: "已完结",
|
};
|
|
const problemSourceLabelMap: Record<number, string> = {
|
1: "电话客服",
|
};
|
|
const columns: DataTableColumns<WorkOrderItem> = [
|
{
|
title: "序号",
|
key: "index",
|
width: 80,
|
resizable: true,
|
render: (_row, index) =>
|
h(
|
"span",
|
(pagination.value.page - 1) * pagination.value.pageSize + index + 1
|
),
|
},
|
{
|
title: "提交用户",
|
key: "feedbackUserPhone",
|
width: 100,
|
resizable: true,
|
render: (row) => row.feedbackUserPhone || "-",
|
},
|
{
|
title: "来源",
|
key: "problemSource",
|
width: 120,
|
resizable: true,
|
render: (row) => problemSourceLabelMap[row.problemSource || 0] || "-",
|
},
|
{
|
title: "类型",
|
key: "feedbackTypeName",
|
width: 100,
|
resizable: true,
|
render: (row) => row.feedbackTypeName || "-",
|
},
|
{
|
title: "问题",
|
key: "problem",
|
width: 100,
|
resizable: true,
|
render: (row) => row.problem || "-",
|
},
|
{
|
title: "优先级",
|
key: "priority",
|
width: 80,
|
resizable: true,
|
render: (row) => {
|
const priority = row.priority || 0;
|
const tagType = priority === 3 ? "error" : priority === 2 ? "warning" : "success";
|
const statusText = priorityLabelMap[priority] || "-";
|
return (
|
<NTag type={tagType as any} bordered={false} size="small">
|
{statusText}
|
</NTag>
|
);
|
},
|
},
|
{
|
title: "处理状态",
|
key: "handleStatus",
|
width: 80,
|
resizable: true,
|
render: (row) => {
|
const handleStatus = row.handleStatus || 0;
|
const tagType = handleStatus === 3 ? "default" : handleStatus === 2 ? "warning" : "error";
|
const statusText = statusLabelMap[handleStatus] || "-";
|
return (
|
<NTag type={tagType as any} bordered={false} size="small">
|
{statusText}
|
</NTag>
|
);
|
},
|
},
|
{
|
title: "处理人",
|
key: "handleUserName",
|
width: 80,
|
resizable: true,
|
render: (row) => row.handleUserName || "-",
|
},
|
{
|
title: "添加时间",
|
key: "createTime",
|
width: 150,
|
resizable: true,
|
render: (row) => dayjs(row.createTime).format("YYYY/MM/DD HH:mm") || "-",
|
},
|
{
|
title: "操作",
|
key: "actions",
|
width: 140,
|
resizable: true,
|
render: (row) => {
|
return (
|
<div class="flex items-center gap-2">
|
<NButton
|
v-permission="80212"
|
text
|
type="primary"
|
onClick={() => handleView(row)}
|
>
|
详情
|
</NButton>
|
{[1, 2].includes(row.handleStatus || 0) ? (
|
<NButton
|
v-permission="80213"
|
text
|
type="primary"
|
onClick={() => handleStart(row)}
|
>
|
处理
|
</NButton>
|
) : null}
|
<NButton
|
v-permission="80215"
|
text
|
type="primary"
|
onClick={() => handleEdit(row)}
|
>
|
编辑
|
</NButton>
|
<NButton
|
v-permission="80216"
|
text
|
type="primary"
|
onClick={() => handleDelete(row)}
|
>
|
删除
|
</NButton>
|
</div>
|
);
|
},
|
},
|
];
|
|
function handleSearch() {
|
pagination.value.page = 1;
|
getWorkOrderList();
|
}
|
|
function handleReset() {
|
searchForm.value = { ...initialSearchForm };
|
pagination.value.page = 1;
|
getWorkOrderList();
|
}
|
|
function changePage(page: number, pageSize: number) {
|
pagination.value.page = page;
|
pagination.value.pageSize = pageSize;
|
getWorkOrderList();
|
}
|
|
function handleCreate() {
|
modalMode.value = "add";
|
currentRow.value = null;
|
modalVisible.value = true;
|
}
|
|
function handleEdit(row: WorkOrderItem) {
|
modalMode.value = "edit";
|
currentRow.value = row;
|
modalVisible.value = true;
|
}
|
|
function handleView(row: WorkOrderItem) {
|
router.push({
|
path: "/service/ticket/workOrder/detail",
|
query: { id: row.id },
|
});
|
}
|
|
function handleStart(row: WorkOrderItem) {
|
handleRow.value = row;
|
handleModalVisible.value = true;
|
}
|
|
async function handleSave(formData: any) {
|
const handleUser = handleUserOptions.value.find(
|
(item) => item.value === formData.handleUserId
|
);
|
const savePayload = {
|
feedbackTypeId: formData.feedbackTypeId || undefined,
|
feedbackUserPhone: formData.feedbackUserPhone || undefined,
|
priority: formData.priority ?? undefined,
|
problemSource: formData.problemSource ?? undefined,
|
problem: formData.problem || undefined,
|
problemDescription: formData.problemDescription || undefined,
|
problemImage: formData.problemImage,
|
handleUserId: formData.handleUserId ?? undefined,
|
handleUserName: handleUser?.label || undefined,
|
};
|
if (modalMode.value === "add") {
|
const { code } = await saveWorkOrder(savePayload);
|
if (code === 200) {
|
window.$message?.success("新增成功");
|
modalVisible.value = false;
|
getWorkOrderList();
|
} else {
|
window.$message?.error("新增失败");
|
}
|
return;
|
}
|
const { code } = await updateWorkOrder({
|
id: formData.id as number,
|
...savePayload,
|
});
|
if (code === 200) {
|
window.$message?.success("编辑成功");
|
modalVisible.value = false;
|
getWorkOrderList();
|
} else {
|
window.$message?.error("编辑失败");
|
}
|
}
|
|
async function handleSubmitHandle(payload: {
|
handleType: number;
|
handleLog: string;
|
handleUserId: number | string | null;
|
handleFile: { id: string; name: string; url: string }[];
|
}) {
|
if (!handleRow.value?.id) return;
|
const selectedUser = handleUserOptions.value.find(
|
(item) => item.value === payload.handleUserId
|
);
|
const handleUserId =
|
payload.handleUserId ?? handleRow.value.handleUserId ?? undefined;
|
const handleUserName =
|
selectedUser?.label || handleRow.value.handleUserName || undefined;
|
const submitData = {
|
handleFile: payload.handleFile || undefined,
|
handleLog: payload.handleLog,
|
handleType: payload.handleType,
|
handleUserId,
|
handleUserName,
|
workOrderId: handleRow.value.id,
|
};
|
const execSubmit = async () => {
|
const { code } = await handleWorkOrder(submitData);
|
if (code === 200) {
|
window.$message?.success("操作成功");
|
handleModalVisible.value = false;
|
getWorkOrderList();
|
} else {
|
window.$message?.error("操作失败");
|
}
|
};
|
if (payload.handleType === 2) {
|
window.$dialog?.warning({
|
title: "是否确认结办?",
|
content: "",
|
positiveText: "确定",
|
negativeText: "取消",
|
onPositiveClick: execSubmit,
|
});
|
return;
|
}
|
execSubmit();
|
}
|
|
function handleDelete(row: WorkOrderItem) {
|
window.$dialog?.warning({
|
title: "确认删除",
|
content: "确定要删除该工单吗?",
|
positiveText: "确定",
|
negativeText: "取消",
|
onPositiveClick: async () => {
|
const res = await deleteWorkOrder(row.id);
|
if (res.code === 200) {
|
window.$message?.success("删除成功");
|
getWorkOrderList();
|
} else {
|
window.$message?.error("删除失败");
|
}
|
},
|
});
|
}
|
|
async function getTypeOptions() {
|
const res = await fetchFeedbackTypeList({ pageNum: 1, pageSize: 9999 });
|
if (res.code === 200 && res.data) {
|
const list = Array.isArray(res.data) ? res.data : res.data.records || [];
|
typeOptions.value = list.map((item: any) => ({
|
label: item.feedbackType,
|
value: item.id,
|
}));
|
} else {
|
typeOptions.value = [];
|
}
|
}
|
|
async function getHandleUserOptions() {
|
const res = await fetchOperationsUserListAll();
|
if (res.code === 200 && res.data) {
|
const list = Array.isArray(res.data) ? res.data : res.data.records || [];
|
handleUserOptions.value = list
|
.filter((item: any) => item.nickName)
|
.map((item: any) => ({
|
label: item.nickName,
|
value: item.id,
|
}));
|
} else {
|
handleUserOptions.value = [];
|
}
|
}
|
|
async function getWorkOrderList() {
|
startLoading();
|
try {
|
const res = await fetchWorkOrderPageList({
|
feedbackTypeId: searchForm.value.feedbackTypeId || undefined,
|
feedbackUserPhone: searchForm.value.feedbackUserPhone || undefined,
|
handleStatus: searchForm.value.handleStatus ?? undefined,
|
pageNum: pagination.value.page,
|
pageSize: pagination.value.pageSize,
|
priority: searchForm.value.priority ?? undefined,
|
problemSource: searchForm.value.problemSource ?? undefined,
|
});
|
if (res.code === 200 && res.data) {
|
tableData.value = res.data.records || [];
|
count.value = res.data.total || res.data.records?.length || 0;
|
} else {
|
tableData.value = [];
|
count.value = 0;
|
}
|
} finally {
|
endLoading();
|
}
|
}
|
|
onMounted(() => {
|
getTypeOptions();
|
getHandleUserOptions();
|
getWorkOrderList();
|
});
|
|
|
const isProblemSourceSelectOpen = ref(false);
|
const isFeedbackTypeSelectOpen = ref(false);
|
const isPrioritySelectOpen = ref(false);
|
const isHandleStatusSelectOpen = ref(false);
|
|
const filterItems = computed(() => [
|
{
|
label: '用户',
|
path: 'feedbackUserPhone',
|
render: () => <n-input v-model:value={searchForm.value.feedbackUserPhone} placeholder='请输入' />,
|
},
|
{
|
label: '问题来源',
|
path: 'problemSource',
|
render: () => (
|
<n-select
|
v-model:value={searchForm.value.problemSource}
|
options={problemSourceOptions}
|
placeholder='请选择'
|
clearable
|
onUpdate:show={val => isProblemSourceSelectOpen.value = val}
|
>
|
{{
|
arrow: () => (
|
<img
|
src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
|
class={`arrow-icon ${isProblemSourceSelectOpen.value ? 'arrow-icon--open' : ''}`}
|
style={{ width: '16px', height: '16px' }}
|
alt="arrow"
|
/>
|
),
|
}}
|
</n-select>
|
),
|
},
|
{
|
label: '类型',
|
path: 'feedbackTypeId',
|
render: () => (
|
<n-select
|
v-model:value={searchForm.value.feedbackTypeId}
|
options={typeOptions.value}
|
placeholder='请选择'
|
clearable
|
onUpdate:show={val => isFeedbackTypeSelectOpen.value = val}
|
>
|
{{
|
arrow: () => (
|
<img
|
src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
|
class={`arrow-icon ${isFeedbackTypeSelectOpen.value ? 'arrow-icon--open' : ''}`}
|
style={{ width: '16px', height: '16px' }}
|
alt="arrow"
|
/>
|
),
|
}}
|
</n-select>
|
),
|
},
|
{
|
label: '优先级',
|
path: 'priority',
|
render: () => (
|
<n-select
|
v-model:value={searchForm.value.priority}
|
options={priorityOptions}
|
placeholder='请选择'
|
clearable
|
onUpdate:show={val => isPrioritySelectOpen.value = val}
|
>
|
{{
|
arrow: () => (
|
<img
|
src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
|
class={`arrow-icon ${isPrioritySelectOpen.value ? 'arrow-icon--open' : ''}`}
|
style={{ width: '16px', height: '16px' }}
|
alt="arrow"
|
/>
|
),
|
}}
|
</n-select>
|
),
|
},
|
{
|
label: '状态',
|
path: 'handleStatus',
|
render: () => (
|
<n-select
|
v-model:value={searchForm.value.handleStatus}
|
options={statusOptions}
|
placeholder='请选择'
|
clearable
|
onUpdate:show={val => isHandleStatusSelectOpen.value = val}
|
>
|
{{
|
arrow: () => (
|
<img
|
src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
|
class={`arrow-icon ${isHandleStatusSelectOpen.value ? 'arrow-icon--open' : ''}`}
|
style={{ width: '16px', height: '16px' }}
|
alt="arrow"
|
/>
|
),
|
}}
|
</n-select>
|
),
|
},
|
])
|
</script>
|
|
<template>
|
<div class="roboTaxi">
|
<div ref="formRef" class="roboTaxi-form-card mb-12px">
|
<n-form
|
:model="searchForm"
|
label-placement="left"
|
inline
|
:show-feedback="false"
|
>
|
<SearchPanel>
|
<n-gi v-for="item in filterItems" :key="item.path">
|
<n-form-item :label="item.label" :path="item.path">
|
<component :is="item.render" />
|
</n-form-item>
|
</n-gi>
|
<template #action>
|
<NButton strong @click="handleReset">
|
{{ $t("common.reset") }}
|
</NButton>
|
<NButton type="primary" @click="handleSearch">
|
{{ $t("common.search") }}
|
</NButton>
|
</template>
|
</SearchPanel>
|
</n-form>
|
</div>
|
|
<n-card
|
class="flex-1 robotaxi-table-card"
|
content-style="display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0;"
|
>
|
<template #header>
|
<n-flex v-permission="80211" justify="flex-end"
|
><n-button type="primary" secondary @click="handleCreate"
|
>创建工单</n-button
|
>
|
</n-flex>
|
</template>
|
|
<div
|
class="flex-1 flex flex-col h-full"
|
style="min-width: 0; overflow: hidden"
|
>
|
<n-data-table
|
class="flex-1"
|
flex-height
|
:columns="columns"
|
:data="tableData"
|
:loading="loading"
|
:scroll-x="1600"
|
:row-key="(row: WorkOrderItem) => row.id"
|
/>
|
<Pagination
|
:count="count"
|
:page="pagination.page"
|
:page-size="pagination.pageSize"
|
class="robotaxi-pagination mt-12px"
|
@change="changePage"
|
/>
|
</div>
|
</n-card>
|
|
<WorkOrderModal
|
v-model:visible="modalVisible"
|
:mode="modalMode"
|
:row-data="currentRow"
|
:type-options="typeOptions"
|
:problem-source-options="problemSourceOptions"
|
:priority-options="priorityOptions"
|
:handle-user-options="handleUserOptions"
|
@submit="handleSave"
|
/>
|
|
<WorkOrderHandleModal
|
v-model:visible="handleModalVisible"
|
:row-data="handleRow"
|
:type-options="typeOptions"
|
:problem-source-options="problemSourceOptions"
|
:priority-options="priorityOptions"
|
:handle-user-options="handleUserOptions"
|
@submit="handleSubmitHandle"
|
/>
|
</div>
|
</template>
|
|
<style scoped></style>
|