Browse Source

feat(红包管理): 更新红包详情和分享组件

- 在红包详情组件中重构搜索表单,使用默认值函数简化重置逻辑。
- 在分享组件中添加对红色链接和ID的支持,优化表格刷新逻辑,确保在ID变化时自动更新数据。
- 修改多个按钮的文本为“一键完成”,提升用户操作的直观性。
- 在编辑对话框中新增一键理赔功能,增强用户体验。
ylong 1 month ago
parent
commit
7ab667f297

+ 17 - 14
src/views/marketing/redBag/components/red-bag-detail.vue

@@ -100,7 +100,7 @@
 </template>
 
 <script setup>
-    import { ref, reactive } from 'vue';
+    import { ref, reactive, nextTick } from 'vue';
     import { ElMessage } from 'element-plus';
     import CommonTable from '@/components/CommonPage/CommonTable.vue';
     import RedBagShare from './red-bag-share.vue';
@@ -119,21 +119,20 @@
     // 统计数据
     const stats = ref({});
 
-    // 搜索表单
-    const searchForm = ref({
-        userName: '',
-        redType: undefined,
-        drawStatus: undefined,
-        drawTimeRange: []
-    });
-
-    const resetSearch = () => {
-        searchForm.value = {
-            userName: '',
-            redType: undefined,
+    function getDefaultSearchForm() {
+        return {
+            userId: '',
+            redLink: '',
             drawStatus: undefined,
             drawTimeRange: []
         };
+    }
+
+    // 搜索表单
+    const searchForm = ref(getDefaultSearchForm());
+
+    const resetSearch = () => {
+        searchForm.value = getDefaultSearchForm();
         reload();
     };
 
@@ -272,9 +271,13 @@
         }
         activityId.value = id;
         pageConfig.params = { activityId: id };
-        resetSearch();
+        pageConfig.cacheKey = `redBagDetailTable_${id}`;
+        searchForm.value = getDefaultSearchForm();
         loadStats();
         visible.value = true;
+        nextTick(() => {
+            reload();
+        });
     };
 
     // 分享信息相关

+ 17 - 8
src/views/marketing/redBag/components/red-bag-share.vue

@@ -18,17 +18,16 @@
 </template>
 
 <script setup>
-    import { ref, reactive } from 'vue';
+    import { ref, reactive, watch, onMounted, nextTick } from 'vue';
     import CommonTable from '@/components/CommonPage/CommonTable.vue';
 
     const props = defineProps({
         redId: {
-            type: String,
+            type: [String, Number],
             required: true
         }
     });
 
-    // 表格列配置
     const columns = ref([
         {
             label: '用户 ID',
@@ -64,18 +63,29 @@
         }
     ]);
 
-    // 页面配置
     const pageRef = ref(null);
     const pageConfig = reactive({
         pageUrl: '/activity/red/bag/share/activity/redBag/share/pagelist',
         fileName: '红包码分享',
-        cacheKey: 'redBagShareTable'
+        params: {}
     });
 
-    // 刷新表格
     function reload() {
-        pageRef.value?.reload({ redId: props.redId });
+        if (!props.redId) return;
+        pageConfig.params = { redBagId: props.redId };
+        pageRef.value?.reload({ redBagId: props.redId });
     }
+
+    watch(
+        () => props.redId,
+        () => {
+            nextTick(() => reload());
+        }
+    );
+
+    onMounted(() => {
+        nextTick(() => reload());
+    });
 </script>
 
 <style lang="scss" scoped>
@@ -83,7 +93,6 @@
         padding: 0;
     }
 
-    // 表格样式优化
     :deep(.el-table) {
         .el-table__header th {
             font-size: 13px;

+ 38 - 32
src/views/workOrder/recycle/index.vue

@@ -28,7 +28,7 @@
                         @click="handleBatchComplete"
                         v-permission="'workOrder:sell:complete'"
                         :icon="Check"
-                        >批量完成</el-button
+                        >一键完成</el-button
                     >
                 </div>
             </template>
@@ -68,10 +68,11 @@
             <!-- Action Column -->
             <template #action="{ row }">
                 <div class="flex justify-center space-x-2">
-                    <!-- Logic: Creator -->
-                    <template v-if="isCreator(row)">
-                        <template v-if="[1, 2, 3].includes(row.status)">
-                            <!-- Pending, Processing -->
+                    <template v-if="row.status === 3">
+                        <el-button link type="primary" @click="handleView(row)">查看</el-button>
+                    </template>
+                    <template v-else-if="canOperate(row)">
+                        <template v-if="[1, 2].includes(row.status)">
                             <el-button
                                 link
                                 type="danger"
@@ -92,7 +93,6 @@
                             >
                         </template>
                         <template v-else-if="[4, 5].includes(row.status)">
-                            <!-- Completed, Void -->
                             <el-button
                                 link
                                 type="warning"
@@ -101,29 +101,12 @@
                             >
                         </template>
                     </template>
-                    <!-- Logic: Assignee -->
-                    <template v-else-if="isAssignee(row)">
-                        <template v-if="[1, 2, 3].includes(row.status)">
-                            <el-button
-                                link
-                                type="success"
-                                @click="handleComplete(row)"
-                                >完成</el-button
-                            >
-                            <el-button
-                                link
-                                type="primary"
-                                @click="handleEdit(row)"
-                                >编辑</el-button
-                            >
-                        </template>
-                    </template>
                 </div>
             </template>
         </common-table>
 
         <!-- Edit Dialog -->
-        <EditDialog ref="editDialogRef" @success="refreshPage" />
+        <EditDialog ref="editDialogRef" orderType="recycle" @success="refreshPage" />
     </ele-page>
 </template>
 
@@ -132,18 +115,18 @@
         ref,
         reactive,
         computed,
-        onMounted,
         getCurrentInstance
     } from 'vue';
-    import { ElMessage, ElMessageBox } from 'element-plus';
     import CommonTable from '@/components/CommonPage/CommonTable.vue';
     import PageSearch from '../sell/components/page-search.vue';
     import EditDialog from '@/views/workOrder/sell/components/edit-dialog.vue';
     import { useUserStore } from '@/store/modules/user';
+    import { usePermission } from '@/utils/use-permission';
     import { Plus, Check } from '@element-plus/icons-vue';
     const { proxy } = getCurrentInstance();
 
     const userStore = useUserStore();
+    const { hasAnyPermission } = usePermission();
     const currentUserId = computed(
         () => userStore.info?.userId || userStore.info?.id
     );
@@ -179,6 +162,12 @@
             width: 120,
             align: 'center'
         },
+        {
+            prop: 'inspectionStatusName',
+            label: '验货状态',
+            width: 100,
+            align: 'center'
+        },
         { prop: 'statusName', label: '状态', width: 100, align: 'center' },
         {
             prop: 'handleUsers',
@@ -203,13 +192,25 @@
         {
             columnKey: 'action',
             label: '操作',
-            width: 150,
+            width: 180,
             fixed: 'right',
             align: 'center',
             slot: 'action'
         }
     ]);
 
+
+    const operatePermissions = [
+        'workOrder:sell:edit',
+        'workOrder:sell:add',
+        'workOrder:sell:complete'
+    ];
+
+    const canOperate = (row) =>
+        hasAnyPermission(operatePermissions) ||
+        isCreator(row) ||
+        isAssignee(row);
+
     // Helpers
     const getTaskTypeName = (type) => {
         const map = { 1: '仓库缺货', 2: '部分发货', 3: '书单不符' };
@@ -233,13 +234,13 @@
 
     const isCreator = (row) => {
         if (!currentUserId.value) return false;
-        return row.createUserId === currentUserId.value;
+        return String(row.createUserId) === String(currentUserId.value);
     };
 
     const isAssignee = (row) => {
         if (!currentUserId.value) return false;
         if (!row.handleUsers) return false;
-        return row.handleUsers.some((u) => u.userId === currentUserId.value);
+        return row.handleUsers.some((u) => String(u.userId) === String(currentUserId.value));
     };
 
     // Event Handlers
@@ -248,7 +249,12 @@
     };
 
     const handleAdd = () => {
-        editDialogRef.value?.handleOpen();
+        editDialogRef.value?.handleOpen({ type: 2 });
+    };
+
+
+    const handleView = (row) => {
+        editDialogRef.value?.handleOpen(row, { readonly: true });
     };
 
     const handleBatchComplete = () => {
@@ -257,7 +263,7 @@
         tableRef.value?.operatBatch({
             method: 'post',
             url: '/workorder/workorderinfo/finish',
-            title: `确认批量完成这 ${selectedRows.length} 个工单吗?`,
+            title: `确认一键完成这 ${selectedRows.length} 个工单吗?`,
             data: { ids: selectedRows.map((row) => row.id) }
         });
     };
@@ -282,7 +288,7 @@
 
     const handleComplete = (row) => {
         tableRef.value?.messageBoxConfirm({
-            message: '确认完成该工单吗?',
+            message: '确认一键完成该工单吗?',
             fetch: () =>
                 proxy.$http.post('/workorder/workorderinfo/finish', {
                     ids: [row.id]

+ 206 - 341
src/views/workOrder/sell/components/edit-dialog.vue

@@ -9,7 +9,6 @@
         :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"
@@ -24,421 +23,287 @@
                         <ImageUpload
                             v-model="proForm.model.fileList"
                             :limit="5"
+                            :disabled="isReadonly"
                         />
                     </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
-                >
+                <template v-if="form.id && !isReadonly">
+                    <el-button type="warning" plain @click="handleFollowUp">待跟进</el-button>
+                    <el-button type="success" plain @click="handleFinish">完成</el-button>
+                    <el-button type="danger" plain @click="openClaimsDialog">一键理赔</el-button>
+                </template>
                 <el-button @click="handleClose">取消</el-button>
-                <el-button
-                    type="primary"
-                    @click="handleSubmit"
-                    :loading="loading"
-                    >保存</el-button
-                >
+                <el-button v-if="!isReadonly" type="primary" @click="handleSubmit" :loading="loading">保存</el-button>
             </div>
         </template>
     </ele-modal>
+
+    <ele-modal title="一键理赔" v-model="claimsVisible" width="480px" :close-on-click-modal="false" destroy-on-close>
+        <el-form ref="claimsFormRef" :model="claimsForm" :rules="claimsRules" label-width="80px">
+            <el-form-item label="理赔金额" prop="money">
+                <el-input-number v-model="claimsForm.money" :min="0" :precision="2" class="w-full" controls-position="right" />
+            </el-form-item>
+            <el-form-item label="理赔原因" prop="reason">
+                <el-input v-model="claimsForm.reason" type="textarea" :rows="4" placeholder="请输入理赔原因" />
+            </el-form-item>
+        </el-form>
+        <template #footer>
+            <el-button @click="claimsVisible = false">取消</el-button>
+            <el-button type="primary" :loading="claimsLoading" @click="submitClaims">确认理赔</el-button>
+        </template>
+    </ele-modal>
 </template>
 
 <script setup>
-    import { ref, reactive, computed, watch, getCurrentInstance } from 'vue';
+    import { ref, reactive, computed, 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 props = defineProps({
+        orderType: {
+            type: String,
+            default: 'sell',
+            validator: (value) => ['sell', 'recycle'].includes(value)
+        }
+    });
 
-    const emit = defineEmits(['submit', 'success']);
+    const defaultOrderType = computed(() => (props.orderType === 'recycle' ? 2 : 1));
+    const { proxy } = getCurrentInstance();
+    const emit = defineEmits(['success']);
 
     const visible = ref(false);
     const title = ref('工单信息');
     const loading = ref(false);
-    // Use proFormRef instead of formRef
+    const isReadonly = ref(false);
     const proFormRef = ref(null);
+    const claimsVisible = ref(false);
+    const claimsLoading = ref(false);
+    const claimsFormRef = ref(null);
+    const claimsForm = reactive({ money: undefined, reason: '' });
+    const claimsRules = {
+        money: [{ required: true, message: '请输入理赔金额', trigger: 'blur' }],
+        reason: [{ required: true, message: '请输入理赔原因', trigger: 'blur' }]
+    };
 
     const form = reactive({
         id: undefined,
         taskType: undefined,
-        handleUserId: [], // Changed from undefined to []
+        handleUserId: [],
         detail: '',
+        remark: '',
         fileList: '',
         carrierId: undefined,
         waybillCode: '',
         inspectionStatus: undefined,
         orderNo: '',
-        type: 1 // 1-卖书 2-收书
+        type: 1,
+        createUserName: '',
+        createTime: ''
     });
 
     const logs = ref([]);
     const userList = ref([]);
     const userLoading = ref(false);
+    const isEditMode = computed(() => !!form.id);
+    const fieldDisabled = computed(() => isEditMode.value || isReadonly.value);
 
-    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' }
-        ]
-    };
+    const rules = computed(() => {
+        const base = {
+            detail: [{ required: !isEditMode.value, message: '请输入详情', trigger: 'blur' }]
+        };
+        if (!isEditMode.value) {
+            base.taskType = [{ required: true, message: '请选择任务类型', trigger: 'change' }];
+            base.handleUserId = [{ required: true, message: '请选择指派人', trigger: 'change' }];
+        }
+        // 新增工单时验货状态必填(卖书/回收都传 inspectionStatus)
+        if (!isEditMode.value) {
+            base.inspectionStatus = [{ required: true, message: '请选择验货状态', trigger: 'change' }];
+        }
+        return base;
+    });
 
-    // Form Items Configuration
-    const formItems = computed(() => [
-        {
-            label: '任务类型',
-            prop: 'taskType',
-            type: 'dictSelect',
-            props: {
-                class: 'w-full',
-                placeholder: '请选择任务类型',
-                code: form.type === 1 ? 'sell_task_type' : '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: 'shop_order_express_name'
-            }
-        },
-        {
-            label: '发出快递单号',
-            prop: 'waybillCode',
-            type: 'input',
-            props: { placeholder: '请输入' }
-        },
-        {
-            label: '验货状态',
-            prop: 'inspectionStatus',
-            type: 'dictSelect',
-            props: {
-                class: 'w-full',
-                placeholder: '请选择验货状态',
-                code: 'inspection_status'
+    const parseImgInfoToFileList = (imgInfo) => {
+        if (!imgInfo) return '';
+        if (imgInfo.imgUrlList && Array.isArray(imgInfo.imgUrlList)) {
+            return JSON.stringify(imgInfo.imgUrlList);
+        }
+        if (imgInfo.imgUrl) {
+            const raw = imgInfo.imgUrl;
+            if (typeof raw === 'string') {
+                try {
+                    const parsed = JSON.parse(raw);
+                    if (Array.isArray(parsed)) return JSON.stringify(parsed);
+                } catch (e) {}
+                return JSON.stringify([raw]);
             }
-        },
-        {
-            label: '订单号',
-            prop: 'orderNo',
-            type: 'input',
-            props: { placeholder: '请输入' }
+            if (Array.isArray(raw)) return JSON.stringify(raw);
+        }
+        return '';
+    };
+
+    const formItems = computed(() => {
+        const items = [];
+        if (isEditMode.value) {
+            items.push(
+                { label: '创建人', prop: 'createUserName', type: 'input', props: { disabled: true } },
+                { label: '创建时间', prop: 'createTime', type: 'input', props: { disabled: true } }
+            );
+        }
+        items.push({
+            label: '任务类型', prop: 'taskType', type: 'dictSelect',
+            props: { class: 'w-full', placeholder: '请选择任务类型', code: form.type === 1 ? 'sell_task_type' : 'task_type', disabled: fieldDisabled.value }
+        });
+        if (!isEditMode.value) {
+            items.push({
+                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, remoteMethod: searchUsers, loading: userLoading.value, disabled: isReadonly.value }
+            });
+        }
+        items.push({ label: '任务详情', prop: 'detail', type: 'textarea', props: { rows: 4, placeholder: '请输入', disabled: fieldDisabled.value } });
+        if (isEditMode.value) {
+            items.push({ label: '备注', prop: 'remark', type: 'textarea', props: { rows: 3, placeholder: '请输入备注', disabled: isReadonly.value } });
         }
-    ]);
+        items.push({ label: '图片/附件', prop: 'fileList', type: 'imageUpload', itemType: 'view' });
+        items.push({ label: '承运商', prop: 'carrierId', type: 'dictSelect', props: { class: 'w-full', placeholder: '请选择发货快递', filterable: true, code: 'shop_order_express_name', disabled: isReadonly.value } });
+        items.push({ label: '发出快递单号', prop: 'waybillCode', type: 'input', props: { placeholder: '请输入', disabled: fieldDisabled.value } });
+        // 新增时始终展示验货状态;编辑时卖书工单展示
+        if (!isEditMode.value || form.type === 1) {
+            items.push({ label: '验货状态', prop: 'inspectionStatus', type: 'dictSelect', props: { class: 'w-full', placeholder: '请选择验货状态', code: 'inspection_status', disabled: isReadonly.value } });
+        }
+        items.push({ label: '订单号', prop: 'orderNo', type: 'input', props: { placeholder: '请输入', disabled: isReadonly.value } });
+        return items;
+    });
 
-    // 搜索用户
     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 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-收书
-        });
+    const resetForm = (type) => {
+        Object.assign(form, { id: undefined, taskType: undefined, handleUserId: [], detail: '', remark: '', fileList: '', carrierId: undefined, waybillCode: '', inspectionStatus: undefined, orderNo: '', type, createUserName: '', createTime: '' });
         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);
+    const mapDetailToForm = (detail) => {
+        form.id = detail.id;
+        form.type = detail.type ?? defaultOrderType.value;
+        form.taskType = detail.taskType !== undefined && detail.taskType !== null ? String(detail.taskType) : undefined;
+        form.inspectionStatus = detail.inspectionStatus !== undefined && detail.inspectionStatus !== null ? String(detail.inspectionStatus) : undefined;
+        form.waybillCode = detail.waybillCode || '';
+        form.detail = detail.deatil || '';
+        form.remark = detail.remark || '';
+        form.orderNo = detail.orderId || '';
+        form.carrierId = detail.expressType ? String(detail.expressType) : undefined;
+        form.createUserName = detail.createUserName || '';
+        form.createTime = detail.createTime || '';
+        form.fileList = parseImgInfoToFileList(detail.imgInfo);
+        if (detail.handleUsers && detail.handleUsers.length > 0) {
+            form.handleUserId = detail.handleUsers.map((user) => {
+                if (typeof user === 'object' && user.userId) {
+                    if (!userList.value.find((u) => u.userId === user.userId)) userList.value.push(user);
+                    return user.userId;
                 }
-            } catch (e) {
-                console.error(e);
-                Object.assign(form, data);
-            } finally {
-                loading.value = false;
-            }
-        }
+                return user;
+            });
+        } else { form.handleUserId = []; }
+        if (detail.changeRecords) {
+            logs.value = detail.changeRecords.map((item) => ({ createTime: item.createTime, createName: item.createUserName, oldContent: item.oldContent, newContent: item.newContent, remark: item.remark }));
+        } else { logs.value = []; }
     };
 
-    const handleClose = () => {
-        visible.value = false;
-        // Use proFormRef to reset
-        proFormRef.value?.resetFields();
+    const handleOpen = async (data = {}, options = {}) => {
+        isReadonly.value = !!options.readonly;
+        visible.value = true;
+        const orderTypeValue = data.type !== undefined && data.type !== null ? Number(data.type) : defaultOrderType.value;
+        title.value = isReadonly.value ? '查看工单' : data.id ? '编辑工单' : '新增工单';
+        if (userList.value.length === 0) searchUsers();
+        resetForm(orderTypeValue);
+        if (!data.id) return;
+        loading.value = true;
+        try {
+            const res = await proxy.$http.get('/workorder/workorderinfo/getInfo/' + data.id);
+            if (res.data && res.data.code === 200) mapDetailToForm(res.data.data);
+            else { Object.assign(form, data); form.type = orderTypeValue; }
+        } catch (e) { console.error(e); Object.assign(form, data); form.type = orderTypeValue; }
+        finally { loading.value = false; }
     };
 
+    const handleClose = () => { visible.value = false; claimsVisible.value = false; proFormRef.value?.resetFields(); };
+    const buildHandleUsers = () => Array.isArray(form.handleUserId) ? form.handleUserId : form.handleUserId ? [form.handleUserId] : [];
+    const parseFileList = () => { if (!form.fileList) return []; try { return JSON.parse(form.fileList); } catch (e) { return []; } };
+
     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: form.type, // 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;
-                    });
+            if (!valid) return;
+            loading.value = true;
+            const inspectionStatus =
+                form.inspectionStatus === undefined || form.inspectionStatus === null || form.inspectionStatus === ''
+                    ? undefined
+                    : Number(form.inspectionStatus);
+            const submitData = {
+                id: form.id,
+                taskType: form.taskType,
+                inspectionStatus,
+                waybillCode: form.waybillCode,
+                deatil: isEditMode.value ? form.remark || form.detail : form.detail,
+                remark: form.remark,
+                orderId: form.orderNo,
+                expressType: form.carrierId,
+                type: form.type,
+                handleUsers: buildHandleUsers(),
+                imgInfo: parseFileList()
+            };
+            // 新增时确保带上验货状态入参
+            if (!form.id && inspectionStatus !== undefined) {
+                submitData.inspectionStatus = inspectionStatus;
             }
+            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 = () => {
         if (!form.id) return;
-
         loading.value = true;
-        const submitData = {
-            id: form.id,
-            updateType: 1, // 待跟进
-            deatil: form.detail || '',
-            handleUsers: Array.isArray(form.handleUserId)
-                ? form.handleUserId
-                : form.handleUserId
-                  ? [form.handleUserId]
-                  : [],
-            imgInfo: form.fileList ? JSON.parse(form.fileList) : []
-        };
-
-        proxy.$http
-            .post('/workorder/workorderinfo/update', submitData)
-            .then((res) => {
-                if (res.data.code === 200) {
-                    ElMessage.success('已设置为待跟进');
-                    emit('success');
-                    handleClose();
-                } else {
-                    ElMessage.error(res.data.msg || '操作失败');
-                }
-            })
-            .catch((e) => {
-                console.error(e);
-                ElMessage.error('操作失败');
-            })
-            .finally(() => {
-                loading.value = false;
-            });
+        proxy.$http.post('/workorder/workorderinfo/update', { id: form.id, updateType: 1, deatil: form.remark || form.detail || '', remark: form.remark, handleUsers: buildHandleUsers(), imgInfo: parseFileList() })
+            .then((res) => { if (res.data.code === 200) { ElMessage.success('已设置为待跟进'); emit('success'); handleClose(); } else ElMessage.error(res.data.msg || '操作失败'); })
+            .catch((e) => { console.error(e); ElMessage.error('操作失败'); }).finally(() => { loading.value = false; });
     };
 
-    const handleFeedback = () => {
+    const handleFinish = () => {
         if (!form.id) return;
-
         loading.value = true;
-        const submitData = {
-            id: form.id,
-            updateType: 2, // 反馈
-            deatil: form.detail || '',
-            handleUsers: Array.isArray(form.handleUserId)
-                ? form.handleUserId
-                : form.handleUserId
-                  ? [form.handleUserId]
-                  : [],
-            imgInfo: form.fileList ? JSON.parse(form.fileList) : []
-        };
+        proxy.$http.post('/workorder/workorderinfo/finish', { ids: [form.id] })
+            .then((res) => { if (res.data.code === 200) { ElMessage.success('工单已完成'); emit('success'); handleClose(); } else ElMessage.error(res.data.msg || '操作失败'); })
+            .catch((e) => { console.error(e); ElMessage.error('操作失败'); }).finally(() => { loading.value = false; });
+    };
 
-        proxy.$http
-            .post('/workorder/workorderinfo/update', submitData)
-            .then((res) => {
-                if (res.data.code === 200) {
-                    ElMessage.success('已提交反馈');
-                    emit('success');
-                    handleClose();
-                } else {
-                    ElMessage.error(res.data.msg || '操作失败');
-                }
-            })
-            .catch((e) => {
-                console.error(e);
-                ElMessage.error('操作失败');
-            })
-            .finally(() => {
-                loading.value = false;
-            });
+    const openClaimsDialog = () => { claimsForm.money = undefined; claimsForm.reason = ''; claimsVisible.value = true; };
+    const submitClaims = () => {
+        claimsFormRef.value?.validate((valid) => {
+            if (!valid || !form.id) return;
+            claimsLoading.value = true;
+            proxy.$http.post('/workorder/workorderinfo/claims', { id: form.id, money: claimsForm.money, reason: claimsForm.reason })
+                .then((res) => { if (res.data.code === 200) { ElMessage.success('理赔提交成功'); claimsVisible.value = false; emit('success'); handleClose(); } else ElMessage.error(res.data.msg || '操作失败'); })
+                .catch((e) => { console.error(e); ElMessage.error('操作失败'); }).finally(() => { claimsLoading.value = false; });
+        });
     };
 
     defineExpose({ handleOpen });

+ 6 - 13
src/views/workOrder/sell/components/page-search.vue

@@ -32,7 +32,7 @@
 </template>
 
 <script setup>
-    import { reactive, defineEmits, defineProps, ref, computed } from 'vue';
+    import { defineEmits, defineProps, ref, computed } from 'vue';
     import ProSearch from '@/components/CommonPage/ProSearch2.vue';
 
     const props = defineProps({
@@ -71,19 +71,15 @@
                             : 'task_type'
                 },
                 colProps: { span: 3 }
-            }
-        ];
-
-        // 验货状态只在卖书工单显示
-        if (props.orderType === 'sell') {
-            items.push({
+            },
+            {
                 type: 'dictSelect',
                 label: '验货状态',
                 prop: 'inspectionStatus',
                 props: { code: 'inspection_status' },
                 colProps: { span: 3 }
-            });
-        }
+            }
+        ];
 
         // 订单编号只在回收工单显示
         if (props.orderType === 'recycle') {
@@ -107,7 +103,7 @@
                 type: 'datetimerange',
                 label: '创建时间',
                 prop: 'createTime',
-                colProps: { span: 6 } // Make date range wider
+                colProps: { span: 6 }
             }
         );
 
@@ -116,7 +112,6 @@
 
     const toggleMyPending = () => {
         myHandleWorkOrder.value = myHandleWorkOrder.value === 0 ? 1 : 0;
-        // Trigger search in ProSearch to get current form data
         if (proSearchRef.value) {
             proSearchRef.value.search();
         }
@@ -125,7 +120,6 @@
     const search = (data) => {
         const params = { ...data };
 
-        // Handle date range
         if (
             params.createTime &&
             Array.isArray(params.createTime) &&
@@ -136,7 +130,6 @@
             delete params.createTime;
         }
 
-        // Add quick action state
         params.myHandleWorkOrder = myHandleWorkOrder.value;
 
         emit('search', params);

+ 20 - 4
src/views/workOrder/sell/components/tracking-timeline.vue

@@ -13,11 +13,27 @@
                     :timestamp="log.createTime"
                     placement="top"
                 >
-                    <div class="text-sm flex">
-                        <div class="font-medium text-gray-700">
-                            {{ log.createName }}:
+                    <div class="text-sm">
+                        <div class="font-medium text-gray-700 mb-1">
+                            {{ log.createName }}
                         </div>
-                        <div class="text-gray-500">
+                        <div
+                            v-if="log.oldContent || log.newContent"
+                            class="text-gray-600"
+                        >
+                            <span v-if="log.oldContent">{{ log.oldContent }}</span>
+                            <span v-if="log.oldContent && log.newContent">
+                                →
+                            </span>
+                            <span v-if="log.newContent">{{ log.newContent }}</span>
+                        </div>
+                        <div v-if="log.remark" class="text-gray-500 mt-1">
+                            {{ log.remark }}
+                        </div>
+                        <div
+                            v-if="!log.oldContent && !log.newContent && !log.remark && log.content"
+                            class="text-gray-500"
+                        >
                             {{ log.content }}
                         </div>
                     </div>

+ 32 - 32
src/views/workOrder/sell/index.vue

@@ -28,7 +28,7 @@
                         @click="handleBatchComplete"
                         v-permission="'workOrder:sell:complete'"
                         :icon="Check"
-                        >批量完成</el-button
+                        >一键完成</el-button
                     >
                 </div>
             </template>
@@ -68,10 +68,11 @@
             <!-- Action Column -->
             <template #action="{ row }">
                 <div class="flex justify-center space-x-2">
-                    <!-- Logic: Creator -->
-                    <template v-if="isCreator(row)">
-                        <template v-if="[1, 2, 3].includes(row.status)">
-                            <!-- Pending, Processing -->
+                    <template v-if="row.status === 3">
+                        <el-button link type="primary" @click="handleView(row)">查看</el-button>
+                    </template>
+                    <template v-else-if="canOperate(row)">
+                        <template v-if="[1, 2].includes(row.status)">
                             <el-button
                                 link
                                 type="danger"
@@ -92,7 +93,6 @@
                             >
                         </template>
                         <template v-else-if="[4, 5].includes(row.status)">
-                            <!-- Completed, Void -->
                             <el-button
                                 link
                                 type="warning"
@@ -101,29 +101,12 @@
                             >
                         </template>
                     </template>
-                    <!-- Logic: Assignee -->
-                    <template v-else-if="isAssignee(row)">
-                        <template v-if="[1, 2, 3].includes(row.status)">
-                            <el-button
-                                link
-                                type="success"
-                                @click="handleComplete(row)"
-                                >完成</el-button
-                            >
-                            <el-button
-                                link
-                                type="primary"
-                                @click="handleEdit(row)"
-                                >编辑</el-button
-                            >
-                        </template>
-                    </template>
                 </div>
             </template>
         </common-table>
 
         <!-- Edit Dialog -->
-        <EditDialog ref="editDialogRef" @success="refreshPage" />
+        <EditDialog ref="editDialogRef" orderType="sell" @success="refreshPage" />
     </ele-page>
 </template>
 
@@ -132,18 +115,18 @@
         ref,
         reactive,
         computed,
-        onMounted,
         getCurrentInstance
     } from 'vue';
-    import { ElMessage, ElMessageBox } from 'element-plus';
     import CommonTable from '@/components/CommonPage/CommonTable.vue';
     import PageSearch from './components/page-search.vue';
     import EditDialog from './components/edit-dialog.vue';
     import { useUserStore } from '@/store/modules/user';
+    import { usePermission } from '@/utils/use-permission';
     import { Plus, Check } from '@element-plus/icons-vue';
 
     const { proxy } = getCurrentInstance();
     const userStore = useUserStore();
+    const { hasAnyPermission } = usePermission();
     const currentUserId = computed(
         () => userStore.info?.userId || userStore.info?.id
     );
@@ -208,13 +191,25 @@
         {
             columnKey: 'action',
             label: '操作',
-            width: 150,
+            width: 180,
             fixed: 'right',
             align: 'center',
             slot: 'action'
         }
     ]);
 
+
+    const operatePermissions = [
+        'workOrder:sell:edit',
+        'workOrder:sell:add',
+        'workOrder:sell:complete'
+    ];
+
+    const canOperate = (row) =>
+        hasAnyPermission(operatePermissions) ||
+        isCreator(row) ||
+        isAssignee(row);
+
     const getCompletedCount = (handleUsers) => {
         if (!handleUsers) return 0;
         return handleUsers.filter((u) => u.status === 2).length;
@@ -222,13 +217,13 @@
 
     const isCreator = (row) => {
         if (!currentUserId.value) return false;
-        return row.createUserId === currentUserId.value;
+        return String(row.createUserId) === String(currentUserId.value);
     };
 
     const isAssignee = (row) => {
         if (!currentUserId.value) return false;
         if (!row.handleUsers) return false;
-        return row.handleUsers.some((u) => u.userId === currentUserId.value);
+        return row.handleUsers.some((u) => String(u.userId) === String(currentUserId.value));
     };
 
     // Event Handlers
@@ -237,7 +232,12 @@
     };
 
     const handleAdd = () => {
-        editDialogRef.value?.handleOpen();
+        editDialogRef.value?.handleOpen({ type: 1 });
+    };
+
+
+    const handleView = (row) => {
+        editDialogRef.value?.handleOpen(row, { readonly: true });
     };
 
     const handleBatchComplete = () => {
@@ -246,7 +246,7 @@
         tableRef.value?.operatBatch({
             method: 'post',
             url: '/workorder/workorderinfo/finish',
-            title: `确认批量完成这 ${selectedRows.length} 个工单吗?`,
+            title: `确认一键完成这 ${selectedRows.length} 个工单吗?`,
             data: { ids: selectedRows.map((row) => row.id) }
         });
     };
@@ -271,7 +271,7 @@
 
     const handleComplete = (row) => {
         tableRef.value?.messageBoxConfirm({
-            message: '确认完成该工单吗?',
+            message: '确认一键完成该工单吗?',
             fetch: () =>
                 proxy.$http.post('/workorder/workorderinfo/finish', {
                     ids: [row.id]