|
|
@@ -0,0 +1,313 @@
|
|
|
+<template>
|
|
|
+ <ele-modal :title="title" v-model="visible" width="1100px" @closed="handleClose" :close-on-click-modal="false"
|
|
|
+ destroy-on-close :body-style="{ padding: '20px' }">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <!-- Left Column: Form -->
|
|
|
+ <el-col :span="14" class="border-r border-gray-200 pr-5">
|
|
|
+ <ProForm ref="proFormRef" :model="form" :rules="rules" :items="formItems" label-width="100px"
|
|
|
+ :footer="false" @updateValue="(prop, val) => form[prop] = val">
|
|
|
+ <template #imageUpload="{ proForm }">
|
|
|
+ <ImageUpload v-model="proForm.model.fileList" :limit="5" />
|
|
|
+ </template>
|
|
|
+ </ProForm>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <!-- Right Column: Info & Timeline -->
|
|
|
+ <el-col :span="10" class="pl-2">
|
|
|
+ <TrackingTimeline :logs="logs" />
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-center space-x-4">
|
|
|
+ <el-button type="warning" plain @click="handleFollowUp" v-if="form.id">待跟进</el-button>
|
|
|
+ <el-button type="success" plain @click="handleFeedback" v-if="form.id">反馈</el-button>
|
|
|
+ <el-button @click="handleClose">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmit" :loading="loading">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </ele-modal>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed, watch, getCurrentInstance } from 'vue';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import ImageUpload from '@/components/ImageUpload/index.vue';
|
|
|
+import ProForm from '@/components/ProForm/index.vue';
|
|
|
+import TrackingTimeline from './tracking-timeline.vue';
|
|
|
+import { pageUsers } from '@/api/system/user';
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance();
|
|
|
+
|
|
|
+const emit = defineEmits(['submit', 'success']);
|
|
|
+
|
|
|
+const visible = ref(false);
|
|
|
+const title = ref('工单信息');
|
|
|
+const loading = ref(false);
|
|
|
+// Use proFormRef instead of formRef
|
|
|
+const proFormRef = ref(null);
|
|
|
+
|
|
|
+const form = reactive({
|
|
|
+ id: undefined,
|
|
|
+ taskType: undefined,
|
|
|
+ handleUserId: [], // Changed from undefined to []
|
|
|
+ detail: '',
|
|
|
+ fileList: '',
|
|
|
+ carrierId: undefined,
|
|
|
+ waybillCode: '',
|
|
|
+ inspectionStatus: undefined,
|
|
|
+ orderNo: '',
|
|
|
+ type: 1,// 1-卖书 2-收书
|
|
|
+});
|
|
|
+
|
|
|
+const logs = ref([]);
|
|
|
+const userList = ref([]);
|
|
|
+const userLoading = ref(false);
|
|
|
+
|
|
|
+const rules = {
|
|
|
+ taskType: [
|
|
|
+ { required: true, message: '请选择任务类型', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ handleUserId: [
|
|
|
+ { required: true, message: '请选择指派人', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ detail: [{ required: true, message: '请输入详情', trigger: 'blur' }],
|
|
|
+ inspectionStatus: [
|
|
|
+ { required: true, message: '请选择验货状态', trigger: 'change' }
|
|
|
+ ]
|
|
|
+};
|
|
|
+
|
|
|
+// Form Items Configuration
|
|
|
+const formItems = computed(() => [
|
|
|
+ {
|
|
|
+ label: '任务类型',
|
|
|
+ prop: 'taskType',
|
|
|
+ type: 'dictSelect',
|
|
|
+ props: { class: 'w-full', placeholder: '请选择任务类型', code: 'task_type' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '指派给',
|
|
|
+ prop: 'handleUserId',
|
|
|
+ type: 'select',
|
|
|
+ options: userList.value.map(user => ({
|
|
|
+ label: user.nickName || user.userName,
|
|
|
+ value: user.userId
|
|
|
+ })),
|
|
|
+ props: {
|
|
|
+ class: 'w-full',
|
|
|
+ placeholder: '请选择',
|
|
|
+ filterable: true,
|
|
|
+ remote: true,
|
|
|
+ multiple: true, // Enable multiple selection
|
|
|
+ remoteMethod: searchUsers,
|
|
|
+ loading: userLoading.value
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '任务详情',
|
|
|
+ prop: 'detail',
|
|
|
+ type: 'textarea',
|
|
|
+ props: { rows: 4, placeholder: '请输入' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '图片/附件',
|
|
|
+ prop: 'fileList',
|
|
|
+ type: 'imageUpload',
|
|
|
+ itemType: 'view' // Triggers the slot mechanism
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '承运商',
|
|
|
+ prop: 'carrierId',
|
|
|
+ type: 'dictSelect',
|
|
|
+ props: {
|
|
|
+ class: 'w-full',
|
|
|
+ placeholder: '请选择发货快递',
|
|
|
+ filterable: true,
|
|
|
+ code: 'final_express'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '发出快递单号',
|
|
|
+ prop: 'waybillCode',
|
|
|
+ type: 'input',
|
|
|
+ props: { placeholder: '请输入' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '验货状态',
|
|
|
+ prop: 'inspectionStatus',
|
|
|
+ type: 'dictSelect',
|
|
|
+ props: { class: 'w-full', placeholder: '请选择验货状态', code: 'inspection_status' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '订单号',
|
|
|
+ prop: 'orderNo',
|
|
|
+ type: 'input',
|
|
|
+ props: { placeholder: '请输入' }
|
|
|
+ }
|
|
|
+]);
|
|
|
+
|
|
|
+// 搜索用户
|
|
|
+const searchUsers = async (query = '') => {
|
|
|
+ userLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await pageUsers({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 50,
|
|
|
+ nickName: query
|
|
|
+ });
|
|
|
+ if (res.code === 200) {
|
|
|
+ userList.value = res.rows || res.data;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ } finally {
|
|
|
+ userLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleOpen = async (data = {}) => {
|
|
|
+ visible.value = true;
|
|
|
+ title.value = data.id ? '编辑工单' : '新增工单';
|
|
|
+
|
|
|
+ // Initialize lists
|
|
|
+ if (userList.value.length === 0) searchUsers();
|
|
|
+
|
|
|
+ // Reset form
|
|
|
+ Object.assign(form, {
|
|
|
+ id: undefined,
|
|
|
+ taskType: undefined,
|
|
|
+ handleUserId: [], // Reset to empty array
|
|
|
+ detail: '',
|
|
|
+ fileList: '',
|
|
|
+ carrierId: undefined,
|
|
|
+ waybillCode: '',
|
|
|
+ inspectionStatus: undefined,
|
|
|
+ orderNo: '',
|
|
|
+ type: 1,// 1-卖书 2-收书
|
|
|
+ });
|
|
|
+ logs.value = [];
|
|
|
+
|
|
|
+ if (data.id) {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ // Fetch full details
|
|
|
+ const res = await proxy.$http.get('/workorder/workorderinfo/getInfo/' + data.id);
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ const detail = res.data.data;
|
|
|
+
|
|
|
+ // Map backend data to form
|
|
|
+ form.id = detail.id;
|
|
|
+ form.taskType = String(detail.taskType); // Convert to number
|
|
|
+ form.inspectionStatus = String(detail.inspectionStatus); // Convert to number
|
|
|
+ form.waybillCode = detail.waybillCode;
|
|
|
+
|
|
|
+ // Map mismatched fields
|
|
|
+ form.detail = detail.deatil; // deatil -> detail
|
|
|
+ form.orderNo = detail.orderId; // orderId -> orderNo
|
|
|
+ form.carrierId = detail.expressType ? String(detail.expressType) : undefined; // expressType -> carrierId (String for dict match)
|
|
|
+
|
|
|
+ // handleUsers array of objects -> array of IDs
|
|
|
+ if (detail.handleUsers && detail.handleUsers.length > 0) {
|
|
|
+ form.handleUserId = detail.handleUsers.map(user => {
|
|
|
+ // Ensure user is in list for display
|
|
|
+ if (typeof user === 'object' && user.userId) {
|
|
|
+ if (!userList.value.find(u => u.userId === user.userId)) {
|
|
|
+ userList.value.push(user);
|
|
|
+ }
|
|
|
+ return user.userId;
|
|
|
+ }
|
|
|
+ return user;
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ form.handleUserId = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // imgInfo object -> fileList JSON string
|
|
|
+ if (detail.imgInfo && detail.imgInfo.imgUrlList && Array.isArray(detail.imgInfo.imgUrlList)) {
|
|
|
+ form.fileList = JSON.stringify(detail.imgInfo.imgUrlList);
|
|
|
+ } else {
|
|
|
+ form.fileList = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Logs
|
|
|
+ if (detail.changeRecords) {
|
|
|
+ logs.value = detail.changeRecords.map(item => ({
|
|
|
+ createTime: item.createTime,
|
|
|
+ createName: item.createUserName,
|
|
|
+ content: item.remark
|
|
|
+ }));
|
|
|
+ } else {
|
|
|
+ logs.value = [];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Fallback mapping if API fails (unlikely to work well but safe)
|
|
|
+ Object.assign(form, data);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ Object.assign(form, data);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleClose = () => {
|
|
|
+ visible.value = false;
|
|
|
+ // Use proFormRef to reset
|
|
|
+ proFormRef.value?.resetFields();
|
|
|
+};
|
|
|
+
|
|
|
+const handleSubmit = () => {
|
|
|
+ // Use proFormRef to validate
|
|
|
+ proFormRef.value?.validate((valid) => {
|
|
|
+ if (valid) {
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ // Map form data to API structure
|
|
|
+ const submitData = {
|
|
|
+ id: form.id,
|
|
|
+ taskType: form.taskType,
|
|
|
+ inspectionStatus: form.inspectionStatus,
|
|
|
+ waybillCode: form.waybillCode,
|
|
|
+
|
|
|
+ deatil: form.detail, // detail -> deatil
|
|
|
+ orderId: form.orderNo, // orderNo -> orderId
|
|
|
+ expressType: form.carrierId, // carrierId -> expressType
|
|
|
+ type: 1, // Fixed: 1-Sell Book
|
|
|
+
|
|
|
+ // handleUserId is already an array of IDs
|
|
|
+ handleUsers: Array.isArray(form.handleUserId) ? form.handleUserId : (form.handleUserId ? [form.handleUserId] : []),
|
|
|
+
|
|
|
+ // fileList string -> imgInfo array
|
|
|
+ imgInfo: form.fileList ? JSON.parse(form.fileList) : []
|
|
|
+ };
|
|
|
+
|
|
|
+ const url = form.id ? '/workorder/workorderinfo/update' : '/workorder/workorderinfo/add';
|
|
|
+ proxy.$http.post(url, submitData).then(res => {
|
|
|
+ if (res.data.code === 200) {
|
|
|
+ ElMessage.success(form.id ? '修改成功' : '新增成功');
|
|
|
+ emit('success');
|
|
|
+ handleClose();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.data.msg || '操作失败');
|
|
|
+ }
|
|
|
+ }).catch(e => {
|
|
|
+ console.error(e);
|
|
|
+ }).finally(() => {
|
|
|
+ loading.value = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleFollowUp = () => {
|
|
|
+ ElMessage.info('功能待开发: 待跟进');
|
|
|
+};
|
|
|
+
|
|
|
+const handleFeedback = () => {
|
|
|
+ ElMessage.info('功能待开发: 反馈');
|
|
|
+};
|
|
|
+
|
|
|
+defineExpose({ handleOpen });
|
|
|
+</script>
|