Ver código fonte

投诉管理

Alex 9 meses atrás
pai
commit
abea046628

+ 30 - 6
src/views/optimization/complain/components/complain-item.vue

@@ -7,18 +7,42 @@
       src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
     />
 
-    <div class="flex-1 flex flex-col  ml-2">
+    <div class="flex-1 flex flex-col ml-2">
       <div class="flex justify-between">
-        <ele-text size="small" type="info">用户</ele-text>
-        <ele-text size="small" type="info">2024-10-23 10:20:30</ele-text>
+        <ele-text size="small" type="info">{{ item.userType || '用户' }}</ele-text>
+        <ele-text size="small" type="info">{{ item.createTime || '' }}</ele-text>
       </div>
       <div class="mt-2">
-        <ele-text>买家发起投诉,投诉原因:快递不取件,投诉说明:快递说发不了,不取件</ele-text>
+        <ele-text>{{ item.description || '' }}</ele-text>
+      </div>
+      <!-- 显示图片列表 -->
+      <div v-if="item.fileUrls && item.fileUrls.length" class="mt-2 flex flex-wrap">
+        <el-image
+          v-for="(img, imgIndex) in item.fileUrls"
+          :key="imgIndex"
+          style="width: 70px; height: 70px; border-radius: 5px; margin-right: 8px; margin-bottom: 8px;"
+          :src="img"
+          fit="cover"
+          :preview-src-list="item.fileUrls"
+          :initial-index="imgIndex"
+          preview-teleported
+        />
       </div>
     </div>
   </div>
   <el-divider />
-
 </template>
 
-<script setup></script>
+<script setup>
+defineProps({
+  item: {
+    type: Object,
+    default: () => ({
+      userType: '用户',
+      createTime: '',
+      description: '',
+      fileUrls: []
+    })
+  }
+});
+</script>

+ 149 - 0
src/views/optimization/complain/components/negotiate-dialog.vue

@@ -0,0 +1,149 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="协商(留言)"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <div>
+      <div class="mb-2">说明:</div>
+      <el-input
+        v-model="form.description"
+        type="textarea"
+        :rows="5"
+        placeholder="请输入"
+      ></el-input>
+
+      <div class="mt-4 mb-2">附件:</div>
+      <ImageUpload
+        v-model="fileUrls"
+        multiple
+        :limit="3"
+        @change="handleFileUrlsChange"
+      />
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">确定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+  import {
+    ref,
+    reactive,
+    defineEmits,
+    defineProps,
+    defineExpose,
+    watch
+  } from 'vue';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import ImageUpload from '@/components/ImageUpload/index.vue';
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    id: {
+      type: [Number, String],
+      default: ''
+    }
+  });
+
+  const emit = defineEmits(['update:visible', 'submit', 'cancel']);
+
+  // 对话框可见性
+  const dialogVisible = ref(false);
+
+  // 监听visible属性变化
+  watch(
+    () => props.visible,
+    (val) => {
+      dialogVisible.value = val;
+      if (val) {
+        // 重置表单
+        resetForm();
+      }
+    }
+  );
+
+  // 监听dialogVisible变化
+  watch(dialogVisible, (val) => {
+    emit('update:visible', val);
+  });
+
+  // 表单数据
+  const form = reactive({
+    id: '',
+    description: '',
+    fileUrls: []
+  });
+
+  // 文件URL字符串,用于ImageUpload组件
+  const fileUrls = ref('');
+
+  // 重置表单
+  const resetForm = () => {
+    form.id = props.id;
+    form.description = '';
+    form.fileUrls = [];
+    fileUrls.value = '';
+  };
+
+  // 处理文件URL变更
+  const handleFileUrlsChange = (value) => {
+    if (value) {
+      try {
+        // ImageUpload组件返回的是JSON字符串,需要解析
+        form.fileUrls = JSON.parse(value);
+      } catch (e) {
+        // 单个文件的情况,直接作为字符串处理
+        form.fileUrls = [value];
+      }
+    } else {
+      form.fileUrls = [];
+    }
+  };
+
+  // 取消
+  const handleCancel = () => {
+    dialogVisible.value = false;
+    emit('cancel');
+  };
+
+  // 提交
+  const handleSubmit = () => {
+    if (!form.description.trim()) {
+      EleMessage.warning('请输入协商内容');
+      return;
+    }
+
+    // 准备提交数据
+    const submitData = {
+      id: form.id,
+      description: form.description,
+      fileUrls: form.fileUrls
+    };
+
+    // 发送提交事件
+    emit('submit', submitData);
+  };
+
+  // 对外暴露方法
+  defineExpose({
+    resetForm
+  });
+</script>
+
+<style lang="scss" scoped>
+  .upload-demo {
+    .el-upload {
+      margin-right: 10px;
+    }
+  }
+</style>

+ 143 - 20
src/views/optimization/complain/components/page-edit.vue

@@ -9,19 +9,19 @@
   >
     <div class="flex w-full">
       <div style="flex: 1.5">
-        <el-tag size="large">协商中</el-tag>
+        <el-tag size="large">{{ getStatusText(form.status) }}</el-tag>
 
         <ele-text style="margin-top: 15px"
           >1.请点击协商(留言)与对方协商。</ele-text
         >
-        <ele-text>2.也可点击赔付优惠券,投诉完结。</ele-text>
+        <ele-text>2.也可点击完结,投诉完结。</ele-text>
         <el-divider />
-        <el-button>协商(留言)</el-button>
-        <el-button type="danger">赔付优惠券</el-button>
+        <el-button @click="handleNegotiate">协商(留言)</el-button>
+        <el-button type="danger" @click="handleComplete">完结</el-button>
         <el-divider />
 
         <div class="common-title">协商历史</div>
-        <complain-item></complain-item>
+        <complain-item v-for="(item, index) in disposeLogList" :key="index" :item="item"></complain-item>
       </div>
       <div class="flex-1 complain-detail">
         <div class="common-title">投诉详情</div>
@@ -33,12 +33,34 @@
       <el-button @click="handleCancel">关闭</el-button>
     </template>
   </ele-modal>
+
+  <!-- 协商弹窗 -->
+  <negotiate-dialog
+    v-model:visible="negotiateVisible"
+    :id="currentId"
+    @submit="submitNegotiate"
+  />
 </template>
 
 <script setup>
-  import { ref, reactive, nextTick } from 'vue';
+  import { ref, reactive, nextTick, getCurrentInstance } from 'vue';
   import ProForm from '@/components/ProForm/index.vue';
-  import complainItem from '@/views/optimization/complain/components/complain-item.vue'
+  import complainItem from '@/views/optimization/complain/components/complain-item.vue';
+  import negotiateDialog from '@/views/optimization/complain/components/negotiate-dialog.vue';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { ElMessageBox } from 'element-plus';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  const { proxy } = getCurrentInstance();
+  const emit = defineEmits(['refresh']);
+
+  // 获取投诉状态字典
+  const [statusDicts] = useDictData(['complain_status']);
+
+  // 获取状态文本
+  const getStatusText = (status) => {
+    return statusDicts.value.find((d) => d.dictValue == status)?.dictLabel || '协商中';
+  };
 
   /** 弹窗是否打开 */
   const visible = defineModel({ type: Boolean });
@@ -48,27 +70,128 @@
     visible.value = false;
   };
 
+  // 当前投诉ID
+  const currentId = ref(null);
+  // 处理记录列表
+  const disposeLogList = ref([]);
+
   /** 弹窗打开事件 */
-  const handleOpen = () => {
-    visible.value = true;
-    nextTick(() => console.log('打开'));
+  const handleOpen = (row) => {
+    if (row && row.id) {
+      currentId.value = row.id;
+      visible.value = true;
+      getComplaintDetail(row.id);
+    } else {
+      visible.value = true;
+      nextTick(() => console.log('打开'));
+    }
+  };
+
+  // 获取投诉详情
+  const getComplaintDetail = (id) => {
+    proxy.$http.get(`/order/orderComplaintsLog/getInfo/${id}`).then(res => {
+      if (res.data.code === 200) {
+        const data = res.data.data || {};
+        // 映射API返回的数据到表单
+        form.id = data.id || '';
+        form.userName = data.userName || '-';
+        form.orderId = data.orderId || '';
+        form.reason = data.reason || '';
+        form.description = data.description || '';
+        form.createTime = data.createTime || '';
+        form.status = data.status || '';
+
+        // 处理协商历史
+        disposeLogList.value = data.disposeLogList || [];
+      } else {
+        EleMessage.error(res.data.msg || '获取投诉详情失败');
+      }
+    }).catch(err => {
+      console.error('获取投诉详情失败', err);
+      EleMessage.error('获取投诉详情失败');
+    });
+  };
+
+  // 协商弹窗相关
+  const negotiateVisible = ref(false);
+
+  // 处理协商
+  const handleNegotiate = () => {
+    if (!currentId.value) {
+      EleMessage.warning('请先选择投诉记录');
+      return;
+    }
+
+    // 打开协商弹窗
+    negotiateVisible.value = true;
+  };
+
+  // 提交协商
+  const submitNegotiate = (formData) => {
+    // 调用协商API
+    proxy.$http.post('/order/orderComplaintsLog/dispose', formData).then(res => {
+      if (res.data.code === 200) {
+        EleMessage.success('协商成功');
+        negotiateVisible.value = false;
+
+        // 刷新详情,更新协商历史
+        getComplaintDetail(currentId.value);
+      } else {
+        EleMessage.error(res.data.msg || '协商失败');
+      }
+    }).catch(err => {
+      console.error('协商失败', err);
+      EleMessage.error('协商失败');
+    });
+  };
+
+  // 处理完结
+  const handleComplete = () => {
+    if (!currentId.value) {
+      EleMessage.warning('请先选择投诉记录');
+      return;
+    }
+
+    ElMessageBox.confirm('确认完结此投诉?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }).then(() => {
+      // 调用完结API
+      proxy.$http.post(`/order/orderComplaintsLog/over/${currentId.value}`).then(res => {
+        if (res.data.code === 200) {
+          EleMessage.success('投诉已完结');
+          emit('refresh'); // 通知父组件刷新列表
+          handleCancel(); // 关闭弹窗
+        } else {
+          EleMessage.error(res.data.msg || '操作失败');
+        }
+      }).catch(err => {
+        console.error('完结投诉失败', err);
+        EleMessage.error('完结投诉失败');
+      });
+    }).catch(() => {
+      // 用户取消操作
+    });
   };
 
   const formItems = reactive([
-    { type: 'text', label: '投诉编号:', prop: 'code' },
+    { type: 'text', label: '投诉编号:', prop: 'id' },
+    { type: 'text', label: '投诉人:', prop: 'userName' },
+    { type: 'text', label: '订单编号:', prop: 'orderId' },
     { type: 'text', label: '投诉原因:', prop: 'reason' },
-    { type: 'text', label: '投诉说明:', prop: 'desc' },
+    { type: 'text', label: '投诉说明:', prop: 'description' },
     { type: 'text', label: '投诉时间:', prop: 'createTime' },
-    { type: 'text', label: '发起人:', prop: 'createName' },
-    { type: 'text', label: '订单编号:', prop: 'orderCode' },
   ]);
+
   const form = reactive({
-    code: '1234567890',
-    reason: '商品质量差',
-    desc: '商品包装破损严重,质量很差',
-    createTime: '2022-01-01 12:00:00',
-    createName: '张三',
-    orderCode: '1234567890'
+    id: '',
+    userName: '',
+    orderId: '',
+    reason: '',
+    description: '',
+    createTime: '',
+    status: ''
   });
 
   defineExpose({

+ 16 - 8
src/views/optimization/complain/components/page-search.vue

@@ -14,29 +14,32 @@
   import { reactive, ref, defineEmits } from 'vue';
   import ProSearch from '@/components/CommonPage/ProSearch2.vue';
 
-  let { proxy } = getCurrentInstance();
   const emit = defineEmits(['search']);
 
   const formItems = reactive([
-    { type: 'input', label: '订单编号', prop: 'orderCode' },
-    { type: 'input', label: '投诉编号', prop: 'code' },
+    { type: 'input', label: '订单编号', prop: 'orderId' },
+    { type: 'input', label: '投诉编号', prop: 'id' },
     {
       type: 'datetimerange',
       label: '投诉时间',
       prop: 'time',
       props: {
         format: 'YYYY-MM-DD HH:mm:ss',
-        valueFormat: 'YYYY-MM-DD HH:mm:ss'
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        onChange: (value) => {
+          initKeys.createTimeStart = value ? value[0] : '';
+          initKeys.createTimeEnd = value ? value[1] : '';
+          searchRef.value?.setData(initKeys);
+        }
       },
       colProps: { span: 6 }
     },
     {
       type: 'dictSelect',
       label: '投诉原因',
-      prop: 'content',
+      prop: 'reason',
       props: { code: 'optimization_content' },
       colProps: { span: 3 }
-
     },
     {
       type: 'dictSelect',
@@ -48,8 +51,13 @@
   ]);
 
   const initKeys = reactive({
-    userName: '',
-    restrictType: ''
+    id: '',
+    orderId: '',
+    reason: '',
+    time: [],
+    createTimeStart: '',
+    createTimeEnd: '',
+    status: ''
   });
 
   const searchRef = ref(null);

+ 21 - 18
src/views/optimization/complain/index.vue

@@ -10,14 +10,23 @@
       <template #baseinfo="{ row }">
         <div class="flex flex-col items-start">
           <el-tag size="large" class="w-full">
-            <el-text class="flex-1">投诉编号:32939021321</el-text>
-            <el-text class="flex-1">订单编号:32939021321</el-text>
+            <el-text class="flex-1">投诉编号:{{ row.id }}</el-text>
+            <el-text class="flex-1">订单编号:{{ row.orderId }}</el-text>
           </el-tag>
           <div class="demo-image__preview mt-2">
             <el-image
-              style="width: 70px; height: 70px;border-radius: 5px"
-              src="https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg"
+              v-for="item in row.imgList"
+              style="
+                width: 70px;
+                height: 70px;
+                border-radius: 5px;
+                margin-right: 8px;
+              "
+              :src="item"
               fit="cover"
+              :preview-src-list="row.imgList"
+              :initial-index="row.imgList.indexOf(item)"
+              preview-teleported
             />
           </div>
         </div>
@@ -35,7 +44,7 @@
         </div>
       </template>
     </common-table>
-    <page-edit ref="pageEditRef" @refresh="reload()" />
+    <page-edit ref="pageEditRef" @refresh="reload" />
   </ele-page>
 </template>
 
@@ -47,10 +56,7 @@
   import { useDictData } from '@/utils/use-dict-data';
 
   defineOptions({ name: 'complainList' });
-  const [contentDicts, statusDicts] = useDictData([
-    'optimization_content',
-    'complain_status'
-  ]);
+  const [statusDicts] = useDictData(['complain_status']);
 
   /** 表格列配置 */
   const columns = ref([
@@ -63,7 +69,7 @@
     },
     {
       label: '投诉人',
-      prop: 'createName',
+      prop: 'userName',
       align: 'center'
     },
     {
@@ -74,17 +80,15 @@
     },
     {
       label: '投诉原因',
-      prop: 'useStatus',
-      align: 'center',
-      formatter: (row) =>
-        contentDicts.value.find((d) => d.dictValue == row.useStatus)?.dictLabel
+      prop: 'reason',
+      align: 'center'
     },
     {
       label: '投诉状态',
-      prop: 'complainStatus',
+      prop: 'status',
       align: 'center',
       formatter: (row) =>
-        statusDicts.value.find((d) => d.dictValue == row.useStatus)?.dictLabel
+        statusDicts.value.find((d) => d.dictValue == row.status)?.dictLabel
     },
     {
       columnKey: 'action',
@@ -99,8 +103,7 @@
   const pageRef = ref(null);
 
   const pageConfig = reactive({
-    pageUrl: '/baseinfo/godown/pagelist',
-    exportUrl: '/baseinfo/godown/export',
+    pageUrl: '/order/orderComplaintsLog/pagelist',
     fileName: '投诉管理',
     cacheKey: 'complainTable'
   });