Просмотр исходного кода

feat(mallOrder): 添加商城订单备注功能

- 新增订单备注弹窗组件,支持选择标签颜色和填写备注信息
- 新增备注历史记录时间线展示组件
- 在订单列表项中添加备注图标点击事件,触发备注弹窗
- 在订单列表页面集成备注功能,支持备注后刷新数据
- 修复专题列表序号列配置,将 type: 'index' 改为 prop: 'id'
ylong 1 месяц назад
Родитель
Сommit
a41ecfe3ca

+ 2 - 2
src/views/mallOrder/all/components/order-item.vue

@@ -19,7 +19,7 @@
                     v-if="order.orderFrom == 1 || order.orderFrom == 2"
                     v-if="order.orderFrom == 1 || order.orderFrom == 2"
                     style="width: 16px; height: 16px; margin-left: 4px; vertical-align: text-bottom;" />
                     style="width: 16px; height: 16px; margin-left: 4px; vertical-align: text-bottom;" />
             </span>
             </span>
-            <span class="flag-icon"><el-icon size="23">
+            <span class="flag-icon cursor-pointer" @click="$emit('add-remark', order)"><el-icon size="23" :color="order.remarkLabel">
                     <Flag />
                     <Flag />
             </el-icon></span>
             </el-icon></span>
             <span class="urge-tag" v-if="order.isUrge">催发货</span>
             <span class="urge-tag" v-if="order.isUrge">催发货</span>
@@ -107,7 +107,7 @@
         }
         }
     });
     });
 
 
-    defineEmits(['view-detail', 'push-sms', 'refund', 'view-log']);
+    defineEmits(['view-detail', 'push-sms', 'refund', 'view-log', 'add-remark']);
 
 
     const { copy } = useClipboard();
     const { copy } = useClipboard();
 
 

+ 125 - 0
src/views/mallOrder/all/components/order-remarks.vue

@@ -0,0 +1,125 @@
+<!-- 编辑弹窗 -->
+<template>
+    <ele-modal
+        form
+        :width="760"
+        v-model="visible"
+        title="商城订单备注"
+        @open="handleOpen"
+    >
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+            <el-form-item label="选择标签" prop="label">
+                <el-radio-group v-model="form.label">
+                    <el-radio v-for="item in tags" :value="item" :key="item"
+                        ><el-icon :color="item" :size="18"> <Flag /> </el-icon
+                    ></el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <el-form-item label="备注信息" prop="remark">
+                <el-input
+                    :rows="4"
+                    type="textarea"
+                    v-model="form.remark"
+                    placeholder="请输入备注"
+                />
+            </el-form-item>
+        </el-form>
+
+        <orderTimeline title="备注历史记录" :orderId="form.orderId" ref="timelineRef" />
+
+        <template #footer>
+            <el-button @click="handleCancel">关闭</el-button>
+            <el-button type="primary" @click="handleSubmit">确定</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref, reactive, nextTick } from 'vue';
+    import { Flag } from '@element-plus/icons-vue';
+    import { EleMessage } from 'ele-admin-plus';
+    import orderTimeline from './order-timeline.vue';
+    import request from '@/utils/request';
+
+    const tags = reactive(['green', 'blue', 'yellow', 'red', 'purple']);
+
+    /** 弹窗是否打开 */
+    const visible = defineModel({ type: Boolean });
+
+    /** 关闭弹窗 */
+    const handleCancel = () => {
+        visible.value = false;
+    };
+
+    const form = ref({
+        orderId: '',
+        label: '',
+        remark: ''
+    });
+
+    const timelineRef = ref(null);
+
+    /** 弹窗打开事件 */
+    const handleOpen = () => {
+        nextTick(() => {
+            if (form.value.orderId && timelineRef.value) {
+                timelineRef.value.getRemarks(form.value.orderId);
+            }
+        });
+    };
+    
+    const open = (orderId) => {
+        form.value = {
+            orderId: orderId,
+            label: '',
+            remark: ''
+        };
+        visible.value = true;
+    };
+
+    /** 刷新事件 */
+    const emit = defineEmits(['refresh']);
+
+    /** 确定 /shop/shopOrderRemark/addRemark */
+    const formRef = ref(null);
+    const handleSubmit = () => {
+        formRef.value.validate((valid) => {
+            if (valid) {
+                const url = '/shop/shopOrderRemark/addRemark';
+                request.post(url, form.value).then((res) => {
+                    if (res.data.code === 200) {
+                        EleMessage.success('操作成功');
+                        handleCancel();
+                        emit('refresh');
+                    } else {
+                        EleMessage.error(res.data.msg);
+                    }
+                });
+            }
+        });
+    };
+
+    /** 表单验证规则 */
+    const rules = reactive({
+        label: [
+            {
+                required: true,
+                message: '请选择标签',
+                type: 'string',
+                trigger: 'change'
+            }
+        ],
+        remark: [
+            {
+                required: true,
+                message: '请输入备注信息',
+                type: 'string',
+                trigger: 'blur'
+            }
+        ]
+    });
+
+    defineExpose({
+        open
+    });
+</script>

+ 82 - 0
src/views/mallOrder/all/components/order-timeline.vue

@@ -0,0 +1,82 @@
+<template>
+    <div class="demo-timeline">
+        <div v-if="loading" v-loading="loading" style="height: 180px"></div>
+        <template v-else>
+            <div v-if="records.length" style="margin-bottom: 10px">{{
+                title
+            }}</div>
+            <el-timeline v-if="records.length">
+                <el-timeline-item
+                    v-for="(item, index) in records"
+                    :key="index"
+                    :timestamp="item.createTime"
+                    :color="item.label"
+                >
+                    <div
+                        style="
+                            display: flex;
+                            align-items: center;
+                            flex-wrap: wrap;
+                        "
+                    >
+                        <el-icon :size="14" :color="item.label">
+                            <Flag />
+                        </el-icon>
+                        <span>{{ item.createName }}:</span>
+                        <span>{{ item.remark }}</span>
+                    </div>
+                </el-timeline-item>
+            </el-timeline>
+            <el-empty v-else description="暂无数据" :image-size="80" />
+        </template>
+    </div>
+</template>
+
+<script setup>
+    import { watch, ref, onMounted } from 'vue';
+    import request from '@/utils/request';
+    import { Flag } from '@element-plus/icons-vue';
+
+    const props = defineProps({
+        orderId: {
+            type: [String, Number],
+            default: ''
+        },
+        title: {
+            type: String,
+            default: ''
+        }
+    });
+
+    const records = ref([]);
+    const loading = ref(false);
+    const dataCache = ref(new Map());
+
+    //获取备注
+    const getRemarks = async (orderId) => {
+        if (!orderId || loading.value) return;
+
+        loading.value = true;
+        try {
+            const url = '/shop/shopOrderRemark/list';
+            const res = await request.get(url, { params: { orderId } });
+            records.value = res.data.data;
+            // 缓存结果
+            dataCache.value.set(orderId, res.data.data);
+        } catch (error) {
+            console.error('Failed to fetch remarks:', error);
+        } finally {
+            loading.value = false;
+        }
+    };
+
+    //删除指定缓存
+    const deleteCache = (orderId) => {
+        dataCache.value.delete(orderId);
+    };
+
+    defineExpose({
+        getRemarks,
+        deleteCache
+    });
+</script>

+ 9 - 1
src/views/mallOrder/all/index.vue

@@ -16,7 +16,7 @@
             <div class="order-list" v-loading="loading">
             <div class="order-list" v-loading="loading">
                 <div v-if="list.length === 0" class="empty-text">暂无数据</div>
                 <div v-if="list.length === 0" class="empty-text">暂无数据</div>
                 <order-item v-for="order in list" :key="order.orderId" :order="order" @view-detail="openDetail"
                 <order-item v-for="order in list" :key="order.orderId" :order="order" @view-detail="openDetail"
-                    @push-sms="openSms" @refund="openRefund" @view-log="openLog" />
+                    @push-sms="openSms" @refund="openRefund" @view-log="openLog" @add-remark="openRemark" />
             </div>
             </div>
 
 
             <!-- Pagination -->
             <!-- Pagination -->
@@ -33,6 +33,7 @@
         <refund-dialog ref="refundRef" />
         <refund-dialog ref="refundRef" />
         <add-package-dialog ref="packageRef" />
         <add-package-dialog ref="packageRef" />
         <order-log ref="logRef" />
         <order-log ref="logRef" />
+        <order-remarks ref="remarkRef" @refresh="fetchData" />
     </ele-page>
     </ele-page>
 </template>
 </template>
 
 
@@ -46,6 +47,7 @@
     import OrderLog from './components/order-log.vue';
     import OrderLog from './components/order-log.vue';
     import OrderTableHeader from './components/order-table-header.vue';
     import OrderTableHeader from './components/order-table-header.vue';
     import OrderItem from './components/order-item.vue';
     import OrderItem from './components/order-item.vue';
+    import OrderRemarks from './components/order-remarks.vue';
     import request from '@/utils/request';
     import request from '@/utils/request';
     import { useDictData } from '@/utils/use-dict-data';
     import { useDictData } from '@/utils/use-dict-data';
 
 
@@ -66,7 +68,9 @@
     const detailRef = ref(null);
     const detailRef = ref(null);
     const smsRef = ref(null);
     const smsRef = ref(null);
     const refundRef = ref(null);
     const refundRef = ref(null);
+    const packageRef = ref(null);
     const logRef = ref(null);
     const logRef = ref(null);
+    const remarkRef = ref(null);
 
 
     const fetchData = () => {
     const fetchData = () => {
         loading.value = true;
         loading.value = true;
@@ -129,6 +133,10 @@
         logRef.value?.handleOpen(row.orderId);
         logRef.value?.handleOpen(row.orderId);
     };
     };
 
 
+    const openRemark = (row) => {
+        remarkRef.value?.open(row.orderId);
+    };
+
     onMounted(() => {
     onMounted(() => {
         fetchData();
         fetchData();
     });
     });

+ 1 - 1
src/views/salesOps/topics/index.vue

@@ -73,7 +73,7 @@
 
 
     const columns = ref([
     const columns = ref([
         { type: 'selection', width: 50, align: 'center' },
         { type: 'selection', width: 50, align: 'center' },
-        { label: '序号', type: 'index', width: 80, align: 'center' },
+        { label: '序号', prop: 'id', width: 80, align: 'center' },
         { label: '专题名称', prop: 'showName', align: 'center', minWidth: 150 },
         { label: '专题名称', prop: 'showName', align: 'center', minWidth: 150 },
         { label: '封面', prop: 'imgUrl', slot: 'imgUrl', align: 'center', width: 120 },
         { label: '封面', prop: 'imgUrl', slot: 'imgUrl', align: 'center', width: 120 },
         {
         {