瀏覽代碼

消息回复

yjwang 4 天之前
父節點
當前提交
420b2a4fbb
共有 3 個文件被更改,包括 412 次插入1 次删除
  1. 21 0
      src/api/user/complaintMsg.js
  2. 94 1
      src/views/user/complaint/index.vue
  3. 297 0
      src/views/user/complaint/reply/index.vue

+ 21 - 0
src/api/user/complaintMsg.js

@@ -0,0 +1,21 @@
+import request from '@/utils/request'
+
+// 查询投诉消息回复列表
+export function listComplaintMsg(query) {
+  return request({
+    url: '/user/msg/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 新增用户投诉
+export function addComplaintMsg(data) {
+  return request({
+    url: '/user/msg',
+    method: 'post',
+    data: data
+  })
+}
+
+

+ 94 - 1
src/views/user/complaint/index.vue

@@ -106,6 +106,12 @@
       <el-table-column label="备注" align="center" prop="remark" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-chat-dot-round"
+            @click="reply(scope.row)"
+          >回复</el-button>
           <el-button
             size="mini"
             type="text"
@@ -168,16 +174,75 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+
+    <el-dialog
+      title="投诉消息回复"
+      :visible.sync="replyVisible"
+      width="50%"
+      :before-close="handleClose"
+      center>
+      <el-form label-width="80px">
+        <el-row>
+          <el-col>
+            <el-form-item label="投诉标题">
+              <el-input v-model="complaint.title" disabled></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col>
+            <el-form-item label="投诉内容">
+              <el-input type="textarea" v-model="complaint.content" :rows="2" disabled></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="凭证图片">
+              <el-image
+                style="width: 100px; height: 100px"
+                :src="replyInfo.url"
+                :preview-src-list="replyInfo.urlList">
+              </el-image>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="投诉类型">
+              <span v-for="item in typeList" :key="item.type">
+                 <template v-if="item.type === complaint.type">
+                    {{ item.value }}
+                </template>
+              </span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <replyIndex v-if="isReplyOpen" ref="replyRef" :complaint-id="complaintId"></replyIndex>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="replyClose">关 闭</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import { listComplaint, getComplaint, delComplaint, addComplaint, updateComplaint, exportComplaint } from "@/api/user/complaint";
-
+import Index from '@/views/user/complaint/reply/index.vue'
 export default {
   name: "Complaint",
   data() {
     return {
+      isReplyOpen:false,
+      complaint:{
+        title:"",
+        content:"",
+        type:'1',
+      },
+      replyInfo:{
+        url:null,
+        urlList:[],
+      },
+      complaintId:null,
       srcList:[],
       typeList:[
         {
@@ -191,6 +256,7 @@ export default {
           value:'其它'
         },
       ],
+      replyVisible:false,
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -230,6 +296,9 @@ export default {
       }
     };
   },
+  components:{
+    replyIndex: Index
+  },
   created() {
     this.getList();
   },
@@ -352,6 +421,30 @@ export default {
     imageClick(url){
       this.srcList=[];
       this.srcList.push(url)
+    },
+    handleClose(done) {
+      this.$confirm('确认关闭?')
+        .then(_ => {
+          this.isReplyOpen=false;
+          done();
+        })
+        .catch(_ => {});
+    },
+    //消息回复页面
+    reply(row){
+      this.isReplyOpen=true;
+      this.complaintId=row.id;
+      this.replyInfo.url = row.images;
+      this.replyInfo.urlList=[];
+      this.replyInfo.urlList.push(row.images);
+      this.complaint.title = row.title;
+      this.complaint.content = row.content;
+      this.complaint.type = row.type;
+      this.replyVisible = true;
+    },
+    replyClose(){
+      this.isReplyOpen=false;
+      this.replyVisible = false;
     }
   }
 };

+ 297 - 0
src/views/user/complaint/reply/index.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="reply-container">
+    <el-divider content-position="left">沟通记录</el-divider>
+    <div
+      class="message-container"
+      @scroll="handleScroll"
+    >
+      <div v-if="loadingHistory" class="loading-history">加载历史消息中...</div>
+
+      <div v-if="noMoreHistory" class="no-more-history">已加载全部历史消息</div>
+
+      <div
+        v-for="msg in messageList"
+        :key="msg.id"
+        :class="['message-item',
+  msg.sender === 'user' ? 'message-user-left' :
+  msg.sender === 'store' ? 'message-store-left' :
+  'message-system-right'
+]"
+      >
+        <div class="sender">
+          <span style="font-size: 13px;font-weight: bold;">{{ msg.sender === 'user' ? '用户' : msg.sender === 'store' ? '店铺' : '平台' }}</span>
+          <span class="time">{{ msg.time }}</span>
+        </div>
+        <div class="content">{{ msg.content }}</div>
+      </div>
+    </div>
+
+    <el-divider content-position="left">消息回复</el-divider>
+    <div class="reply-area">
+      <el-input
+        v-model="replyContent"
+        :rows="3"
+        class="reply-input"
+        placeholder="请输入回复内容..."
+        type="textarea"
+        @keyup.enter.native="sendReply"
+      ></el-input>
+      <el-button
+        class="send-btn"
+        type="primary"
+        @click="sendReply"
+      >
+        发 送
+      </el-button>
+    </div>
+  </div>
+</template>
+
+
+<script>
+import { listComplaintMsg,addComplaintMsg } from '@/api/user/complaintMsg'
+
+export default ({
+  name: 'Reply',
+  props: {
+    complaintId: {
+      type: [String, Number],
+      required: true
+    }
+  },
+  data() {
+    return {
+      messageList: [],
+      replyContent: '',
+      nextId: 1,
+
+      // 分页参数
+      pageNum: 1,
+      pageSize: 5,
+      total: 0,
+      loadingHistory: false,
+      noMoreHistory: false,
+      initialLoaded: false
+    }
+  },
+  methods: {
+    sendReply() {
+      const content = this.replyContent.trim()
+      if (!content) return
+      //请求消息发送接口
+      addComplaintMsg({
+        complaintId: this.complaintId,
+        content: content,
+      }).then(response => {
+        this.messageList.push({
+          id: this.nextId++,
+          sender: 'system',
+          content,
+          time: this.getFormattedTime()
+        })
+        this.replyContent = ''
+        this.scrollToBottom()
+      })
+    },
+    getFormattedTime() {
+      const now = new Date()
+      const year = now.getFullYear()
+      const month = (now.getMonth() + 1).toString().padStart(2, '0')
+      const day = now.getDate().toString().padStart(2, '0')
+      const hours = now.getHours().toString().padStart(2, '0')
+      const minutes = now.getMinutes().toString().padStart(2, '0')
+      return `${year}-${month}-${day} ${hours}:${minutes}`
+    },
+
+    handleScroll(e) {
+      const scrollTop = e.target.scrollTop
+      if (scrollTop <= 10 && !this.loadingHistory && !this.noMoreHistory) {
+        this.loadHistoryMessages()
+      }
+    },
+
+    loadHistoryMessages() {
+      if (this.loadingHistory || this.noMoreHistory) {
+        return Promise.resolve()
+      }
+
+      this.loadingHistory = true
+      return new Promise((resolve, reject) => {
+        listComplaintMsg({
+          pageNum: this.pageNum,
+          pageSize: this.pageSize,
+          complaintId: this.complaintId
+        }).then(response => {
+          if (!response) {
+            this.$message.error('接口响应异常,请重试')
+            resolve()
+            return
+          }
+
+          if (response.code === 200) {
+            const { rows = [], total = 0 } = response
+            this.total = total
+
+            const formattedList = rows.map(msg => ({
+              id: msg.id || this.nextId++,
+              sender: msg.sendType === 1 ? 'user' : msg.sendType === 2 ? 'system' : 'store',
+              content: msg.content || '(无内容)',
+              time: msg.createTime || this.getFormattedTime()
+            })).reverse()
+
+            if (this.pageNum === 1) {
+              this.messageList = formattedList
+            } else {
+              this.messageList.unshift(...formattedList)
+            }
+
+            if (total === 0 || this.pageNum * this.pageSize >= total) {
+              this.noMoreHistory = true
+            } else {
+              this.pageNum++
+            }
+          } else {
+            this.$message.error(`加载失败:${response.msg || '未知错误'}`)
+          }
+          resolve()
+        }).catch(error => {
+          console.error('请求历史消息失败:', error)
+          this.$message.error('网络异常或接口请求失败,请检查网络后重试')
+          reject(error)
+        }).finally(() => {
+          this.loadingHistory = false
+          this.initialLoaded = true
+        })
+      })
+    },
+    scrollToBottom() {
+      this.$nextTick(() => {
+        const messageContainer = document.querySelector('.message-container')
+        if (messageContainer) {
+          messageContainer.scrollTop = messageContainer.scrollHeight
+        }
+      })
+    }
+  },
+  mounted() {
+    this.loadHistoryMessages().then(() => {
+      this.scrollToBottom()
+    })
+  }
+})
+</script>
+<style scoped>
+.reply-container {
+  padding: 20px;
+}
+
+.message-container {
+  border: 1px solid #e6e6e6;
+  width: 100%;
+  height: 300px;
+  overflow-y: auto;
+  padding: 15px;
+  border-radius: 4px;
+  margin-bottom: 15px;
+  box-sizing: border-box;
+  background: #fff;
+  transform: translateZ(0);
+}
+
+.no-message {
+  text-align: center;
+  color: #999;
+  font-size: 14px;
+  padding: 50px 0;
+}
+
+.loading-history {
+  text-align: center;
+  color: #666;
+  font-size: 12px;
+  padding: 8px 0;
+}
+
+.no-more-history {
+  text-align: center;
+  color: #999;
+  font-size: 12px;
+  padding: 8px 0;
+}
+
+.message-item {
+  margin: 0 0 15px 0;
+  max-width: 80%;
+}
+
+.message-user-left .content {
+  background-color: #e4e4e4;
+  color: #333;
+  border-radius: 6px;
+}
+
+.message-store-left .content {
+  background-color: #448e17;
+  color: white;
+  border-radius: 6px;
+}
+
+.message-system-right .content {
+  background-color: #0f64b5;
+  color: white;
+  border-radius: 6px;
+}
+
+.sender {
+  font-size: 12px;
+  margin-bottom: 5px;
+  color: #888;
+  display: flex;
+  justify-content: space-between;
+}
+
+.time {
+  font-size: 11px;
+}
+
+.content {
+  padding: 8px 12px;
+  border-radius: 6px;
+  line-height: 1.5;
+  word-break: break-all;
+}
+
+.reply-area {
+  border: 1px solid #e6e6e6;
+  padding: 15px;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.reply-input {
+  margin-bottom: 10px;
+}
+
+.send-btn {
+  margin-top: 20px;
+  float: right;
+}
+
+.message-container::-webkit-scrollbar {
+  width: 5px;
+  height: 5px;
+}
+
+.message-container::-webkit-scrollbar-track {
+  background: transparent;
+}
+
+.message-container::-webkit-scrollbar-thumb {
+  background: #e0e0e0;
+  border-radius: 3px;
+}
+
+.message-container::-webkit-scrollbar-thumb:hover {
+  background: #ccc;
+}
+</style>