Ver código fonte

代码提交优化

yjwang 1 dia atrás
pai
commit
4ac071cc04

+ 44 - 0
sql/course_complaint_channel.sql

@@ -0,0 +1,44 @@
+-- ----------------------------
+-- 看课投诉通道主表
+-- ----------------------------
+DROP TABLE IF EXISTS `course_complaint_channel`;
+CREATE TABLE `course_complaint_channel` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
+  `contact` varchar(200) DEFAULT '' COMMENT '联系方式',
+  `contact_type` varchar(20) DEFAULT '' COMMENT '联系方式类型: wechat-微信, email-邮箱, phone-电话, other-其他',
+  `title` varchar(500) DEFAULT '' COMMENT '投诉标题',
+  `content` text COMMENT '投诉内容',
+  `fs_user_id` varchar(100) DEFAULT '' COMMENT '用户关联id',
+  `proof_images` text COMMENT '凭证图片URL, 逗号分隔',
+  `platform_reply_status` tinyint DEFAULT 0 COMMENT '平台回复状态: 0-未回复, 1-已回复',
+  `user_reply_status` tinyint DEFAULT 0 COMMENT '用户回复状态: 0-未回复, 1-已回复',
+  `is_completed` tinyint DEFAULT 0 COMMENT '是否已完成: 0-未完成, 1-已完成',
+  `remarks` varchar(500) DEFAULT '' COMMENT '备注',
+  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `del_flag` tinyint DEFAULT 0 COMMENT '删除标志: 0-存在, 2-删除',
+  PRIMARY KEY (`id`),
+  KEY `idx_fs_user_id` (`fs_user_id`),
+  KEY `idx_contact` (`contact`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='看课投诉通道表';
+
+
+-- ----------------------------
+-- 看课投诉消息记录表
+-- ----------------------------
+DROP TABLE IF EXISTS `course_complaint_msg`;
+CREATE TABLE `course_complaint_msg` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
+  `complaint_id` bigint NOT NULL COMMENT '关联投诉id',
+  `send_type` tinyint DEFAULT 1 COMMENT '发送类型: 1-用户, 2-平台/商家',
+  `content` text COMMENT '消息内容',
+  `images` text COMMENT '图片URL, 逗号分隔',
+  `create_by` varchar(64) DEFAULT '' COMMENT '发送人',
+  `create_time` datetime DEFAULT NULL COMMENT '发送时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_complaint_id` (`complaint_id`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='看课投诉消息记录表';

+ 99 - 0
src/api/course/complaintChannel.js

@@ -0,0 +1,99 @@
+import request from '@/utils/request'
+
+// 查询看课投诉通道列表
+export function listComplaintChannel(query) {
+  return request({
+    url: '/course/complaintChannel/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询看课投诉通道详细
+export function getComplaintChannel(id) {
+  return request({
+    url: '/course/complaintChannel/' + id,
+    method: 'get'
+  })
+}
+
+// 新增看课投诉通道
+export function addComplaintChannel(data) {
+  return request({
+    url: '/course/complaintChannel',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改看课投诉通道
+export function updateComplaintChannel(data) {
+  return request({
+    url: '/course/complaintChannel',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除看课投诉通道
+export function delComplaintChannel(id) {
+  return request({
+    url: '/course/complaintChannel/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出看课投诉通道
+export function exportComplaintChannel(query) {
+  return request({
+    url: '/course/complaintChannel/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 完成投诉
+export function completeComplaint(data) {
+  return request({
+    url: '/course/complaintChannel/complete',
+    method: 'post',
+    data: data
+  })
+}
+
+// ==================== 消息记录 ====================
+
+// 查询投诉消息记录列表
+export function listComplaintMsg(query) {
+  return request({
+    url: '/course/complaintMsg/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 新增投诉消息记录(平台回复)
+export function addComplaintMsg(data) {
+  return request({
+    url: '/course/complaintMsg',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改投诉消息记录
+export function updateComplaintMsg(data) {
+  return request({
+    url: '/course/complaintMsg',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除投诉消息记录
+export function delComplaintMsg(id) {
+  return request({
+    url: '/course/complaintMsg/' + id,
+    method: 'delete'
+  })
+}

+ 8 - 0
src/api/hisStore/store.js

@@ -126,3 +126,11 @@ export function saveStoreErpInfo(data) {
   })
 }
 
+// 一键启用店铺(含资质证书有效期验证)
+export function enableStore(storeId) {
+  return request({
+    url: '/store/his/store/enable/' + storeId,
+    method: 'put'
+  })
+}
+

BIN
src/assets/logo/bjyjb.jpg


+ 621 - 0
src/views/course/complaintChannel/index.vue

@@ -0,0 +1,621 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
+      <el-form-item label="用户ID" prop="fsUserId">
+        <el-input
+          v-model="queryParams.fsUserId"
+          placeholder="请输入用户ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="联系方式" prop="contact">
+        <el-input
+          v-model="queryParams.contact"
+          placeholder="请输入联系方式"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="联系方式类型" prop="contactType">
+        <el-select v-model="queryParams.contactType" placeholder="请选择" clearable size="small">
+          <el-option label="微信" value="wechat" />
+          <el-option label="邮箱" value="email" />
+          <el-option label="电话" value="phone" />
+          <el-option label="其他" value="other" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="平台回复" prop="platformReplyStatus">
+        <el-select v-model="queryParams.platformReplyStatus" placeholder="请选择" clearable size="small">
+          <el-option label="未回复" :value="0" />
+          <el-option label="已回复" :value="1" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="是否完成" prop="isCompleted">
+        <el-select v-model="queryParams.isCompleted" placeholder="请选择" clearable size="small">
+          <el-option label="未完成" :value="0" />
+          <el-option label="已完成" :value="1" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="投诉时间" prop="dateRange">
+        <el-date-picker
+          v-model="dateRange"
+          size="small"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleDateRangeChange"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
+    </el-row>
+
+    <el-table border v-loading="loading" :data="complaintList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="id" width="80" />
+      <el-table-column label="用户ID" align="center" prop="fsUserId" width="120" />
+      <el-table-column label="标题" align="center" prop="title" width="200" show-overflow-tooltip />
+      <el-table-column label="联系方式" align="center" prop="contact" width="160" />
+      <el-table-column label="联系方式类型" align="center" prop="contactType" width="120">
+        <template slot-scope="scope">
+          <el-tag size="small" v-if="scope.row.contactType">
+            {{ contactTypeMap[scope.row.contactType] || scope.row.contactType }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="投诉时间" align="center" prop="createTime" width="160" />
+      <el-table-column label="平台回复状态" align="center" width="120">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.platformReplyStatus === 1 ? 'success' : 'warning'" disable-transitions>
+            {{ scope.row.platformReplyStatus === 1 ? '已回复' : '未回复' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="用户回复状态" align="center" width="120">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.userReplyStatus === 1 ? 'success' : 'info'" disable-transitions>
+            {{ scope.row.userReplyStatus === 1 ? '已回复' : '未回复' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="是否完成" align="center" width="100">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.isCompleted === 1 ? 'success' : 'danger'" disable-transitions>
+            {{ scope.row.isCompleted === 1 ? '已完成' : '未完成' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remarks" width="200" show-overflow-tooltip />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="handleDetail(scope.row)"
+          >详情</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-check"
+            @click="handleComplete(scope.row)"
+            v-if="scope.row.isCompleted !== 1"
+          >完成</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 投诉详情对话框 -->
+    <el-dialog :title="detailTitle" :visible.sync="open" width="900px" append-to-body @closed="getList">
+      <el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
+        <!-- 投诉详情 -->
+        <el-tab-pane label="投诉详情" name="detail">
+          <el-form ref="detailForm" :model="detailForm" label-width="100px">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="用户ID">
+                  <el-input v-model="detailForm.fsUserId" readonly />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="联系方式类型">
+                  <el-tag>{{ contactTypeMap[detailForm.contactType] || detailForm.contactType }}</el-tag>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="联系方式">
+              <el-input v-model="detailForm.contact" readonly />
+            </el-form-item>
+            <el-form-item label="投诉标题">
+              <el-input v-model="detailForm.title" readonly />
+            </el-form-item>
+            <el-form-item label="投诉内容">
+              <el-input type="textarea" :rows="4" v-model="detailForm.content" readonly />
+            </el-form-item>
+            <el-form-item label="凭证图片" v-if="proofImageList.length > 0">
+              <div class="image-preview-container">
+                <div v-for="(url, index) in proofImageList" :key="index" class="image-item">
+                  <el-image
+                    :src="url"
+                    :preview-src-list="proofImageList"
+                    fit="cover"
+                    style="width: 100px; height: 100px;"
+                  />
+                </div>
+              </div>
+            </el-form-item>
+            <el-row :gutter="20">
+              <el-col :span="8">
+                <el-form-item label="平台回复状态">
+                  <el-tag :type="detailForm.platformReplyStatus === 1 ? 'success' : 'warning'">
+                    {{ detailForm.platformReplyStatus === 1 ? '已回复' : '未回复' }}
+                  </el-tag>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="用户回复状态">
+                  <el-tag :type="detailForm.userReplyStatus === 1 ? 'success' : 'info'">
+                    {{ detailForm.userReplyStatus === 1 ? '已回复' : '未回复' }}
+                  </el-tag>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="是否完成">
+                  <el-tag :type="detailForm.isCompleted === 1 ? 'success' : 'danger'">
+                    {{ detailForm.isCompleted === 1 ? '已完成' : '未完成' }}
+                  </el-tag>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="创建时间">
+              <el-input v-model="detailForm.createTime" readonly />
+            </el-form-item>
+            <el-form-item label="备注" v-if="detailForm.isCompleted === 1 || !isCompleted">
+              <el-input type="textarea" :rows="2" v-model="detailForm.remarks" placeholder="请输入备注" :disabled="detailForm.isCompleted === 1" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+
+        <!-- 消息记录 -->
+        <el-tab-pane label="消息记录" name="msg">
+          <div class="msg-container" @scroll="handleMsgScroll">
+            <!-- 消息列表 -->
+            <div class="msg-list-scroll">
+              <el-card class="msg-list-card" shadow="never">
+                <div slot="header">
+                  <span>沟通记录</span>
+                </div>
+                <div v-loading="msgLoading">
+                  <div v-if="msgList.length === 0 && !msgLoading" class="empty-msg">
+                    <el-empty description="暂无沟通记录" :image-size="80" />
+                  </div>
+                  <div v-else>
+                    <div v-for="msg in msgList" :key="msg.id" class="msg-item">
+                      <div class="msg-header">
+                        <span class="msg-sender">
+                          <el-tag size="mini" :type="msg.sendType === 1 ? 'info' : 'success'">
+                            {{ msg.sendType === 1 ? '用户' : '平台' }}
+                          </el-tag>
+                        </span>
+                        <span class="msg-time">{{ msg.createTime }}</span>
+                        <el-button
+                          v-if="msg.sendType === 2"
+                          type="text"
+                          size="mini"
+                          icon="el-icon-delete"
+                          style="color: #f56c6c; margin-left: 10px;"
+                          @click="handleDelMsg(msg)"
+                        >删除</el-button>
+                      </div>
+                      <div class="msg-content">{{ msg.content }}</div>
+                      <div class="msg-images" v-if="msg.images">
+                        <div v-for="(url, idx) in splitImages(msg.images)" :key="idx" class="msg-image-item">
+                          <el-image
+                            :src="url"
+                            :preview-src-list="splitImages(msg.images)"
+                            fit="cover"
+                            style="width: 60px; height: 60px;"
+                          />
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="msg-load-more" v-if="msgLoadingMore">
+                    <i class="el-icon-loading"></i>
+                    <span>加载中...</span>
+                  </div>
+                  <div class="msg-load-more" v-if="msgNoMore && msgList.length > 0">
+                    <span style="color: #999;">没有更多了</span>
+                  </div>
+                </div>
+              </el-card>
+            </div>
+
+            <!-- 平台回复区 -->
+            <div class="reply-fixed" v-if="detailForm.isCompleted !== 1">
+              <el-card class="reply-card" shadow="never">
+                <div slot="header">
+                  <span>平台回复</span>
+                </div>
+                <el-form :model="replyForm" label-width="80px">
+                  <el-form-item label="回复内容" prop="content">
+                    <el-input
+                      type="textarea"
+                      :rows="3"
+                      v-model="replyForm.content"
+                      placeholder="请输入回复内容"
+                      maxlength="500"
+                      show-word-limit
+                    />
+                  </el-form-item>
+                  <el-form-item label="上传图片">
+                    <ImageUpload v-model="replyForm.images" type="image" :num="4" :width="100" :height="100" />
+                  </el-form-item>
+                  <el-form-item>
+                    <el-button type="primary" size="small" @click="submitReply" :loading="replySubmitting">
+                      发送回复
+                    </el-button>
+                  </el-form-item>
+                </el-form>
+              </el-card>
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitDetail" v-if="detailForm.isCompleted !== 1">保存备注</el-button>
+        <el-button @click="open = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listComplaintChannel,
+  getComplaintChannel,
+  delComplaintChannel,
+  updateComplaintChannel,
+  exportComplaintChannel,
+  completeComplaint
+} from '@/api/course/complaintChannel'
+import {
+  listComplaintMsg,
+  addComplaintMsg,
+  delComplaintMsg
+} from '@/api/course/complaintChannel'
+import ImageUpload from '@/components/ImageUpload/index'
+
+export default {
+  name: 'ComplaintChannel',
+  components: { ImageUpload },
+  data() {
+    return {
+      loading: true,
+      exportLoading: false,
+      showSearch: true,
+      total: 0,
+      complaintList: [],
+      ids: [],
+      dateRange: [],
+      open: false,
+      detailTitle: '',
+      activeTab: 'detail',
+      isCompleted: 0,
+      contactTypeMap: {
+        wechat: '微信',
+        email: '邮箱',
+        phone: '电话',
+        other: '其他'
+      },
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        fsUserId: null,
+        contact: null,
+        contactType: null,
+        platformReplyStatus: null,
+        isCompleted: null,
+        beginTime: null,
+        endTime: null
+      },
+      detailForm: {},
+      proofImageList: [],
+      replyForm: {
+        content: '',
+        images: ''
+      },
+      replySubmitting: false,
+      msgList: [],
+      msgLoading: false,
+      msgLoadingMore: false,
+      msgPageNum: 1,
+      msgPageSize: 10,
+      msgNoMore: false
+    }
+  },
+  created() {
+    this.getList()
+  },
+  mounted() {
+    this._blockViewerWheel = (e) => {
+      if (this.activeTab === 'msg' && document.querySelector('.el-image-viewer__wrapper')) {
+        e.preventDefault()
+        e.stopPropagation()
+      }
+    }
+    document.addEventListener('wheel', this._blockViewerWheel, { capture: true, passive: false })
+  },
+  beforeDestroy() {
+    document.removeEventListener('wheel', this._blockViewerWheel, { capture: true, passive: false })
+  },
+  methods: {
+    getList() {
+      this.loading = true
+      listComplaintChannel(this.queryParams).then(res => {
+        this.complaintList = res.rows
+        this.total = res.total
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    resetQuery() {
+      this.dateRange = []
+      this.resetForm('queryForm')
+      this.queryParams.beginTime = null
+      this.queryParams.endTime = null
+      this.handleQuery()
+    },
+    handleDateRangeChange(val) {
+      if (val && val.length === 2) {
+        this.queryParams.beginTime = val[0]
+        this.queryParams.endTime = val[1]
+      } else {
+        this.queryParams.beginTime = null
+        this.queryParams.endTime = null
+      }
+    },
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+    },
+    handleDetail(row) {
+      this.resetMsgList()
+      getComplaintChannel(row.id).then(res => {
+        this.detailForm = res.data
+        this.isCompleted = res.data.isCompleted
+        this.proofImageList = res.data.proofImages ? res.data.proofImages.split(',').filter(Boolean) : []
+        this.detailTitle = '投诉详情 - ' + (res.data.title || res.data.id)
+        this.activeTab = 'detail'
+        this.open = true
+      })
+    },
+    submitDetail() {
+      updateComplaintChannel(this.detailForm).then(res => {
+        if (res.code === 200) {
+          this.$message.success('保存成功')
+          this.open = false
+          this.getList()
+        }
+      })
+    },
+    handleComplete(row) {
+      this.$confirm('确认将该投诉标记为已完成?', '提示', {
+        type: 'warning'
+      }).then(() => {
+        completeComplaint({ id: row.id }).then(res => {
+          if (res.code === 200) {
+            this.$message.success('操作成功')
+            this.getList()
+          }
+        })
+      }).catch(() => {})
+    },
+    handleDelete(row) {
+      this.$confirm('确认删除该投诉记录?', '提示', { type: 'warning' }).then(() => {
+        delComplaintChannel(row.id).then(() => {
+          this.$message.success('删除成功')
+          this.getList()
+        })
+      }).catch(() => {})
+    },
+    handleExport() {
+      this.$confirm('确认导出所有投诉数据?', '提示', { type: 'warning' }).then(() => {
+        this.exportLoading = true
+        exportComplaintChannel(this.queryParams).then(res => {
+          this.download(res.msg)
+          this.exportLoading = false
+        }).catch(() => {
+          this.exportLoading = false
+        })
+      }).catch(() => {})
+    },
+    handleTabClick(tab) {
+      if (tab.name === 'msg') {
+        this.resetMsgList()
+        this.getMsgList()
+      }
+    },
+    resetMsgList() {
+      this.msgList = []
+      this.msgPageNum = 1
+      this.msgNoMore = false
+    },
+    getMsgList() {
+      if (this.msgLoadingMore || this.msgNoMore) return
+      this.msgLoadingMore = true
+      if (this.msgPageNum === 1) this.msgLoading = true
+      listComplaintMsg({
+        complaintId: this.detailForm.id,
+        pageNum: this.msgPageNum,
+        pageSize: this.msgPageSize
+      }).then(res => {
+        const list = res.rows.list || []
+        const total = res.rows.total || 0
+        this.msgList = this.msgList.concat(list)
+        this.msgNoMore = this.msgList.length >= total
+        this.msgLoading = false
+        this.msgLoadingMore = false
+      }).catch(() => {
+        this.msgLoading = false
+        this.msgLoadingMore = false
+      })
+    },
+    handleMsgScroll(e) {
+      const { scrollTop, scrollHeight, clientHeight } = e.target
+      if (scrollHeight - scrollTop - clientHeight < 20) {
+        if (!this.msgLoadingMore && !this.msgNoMore) {
+          this.msgPageNum++
+          this.getMsgList()
+        }
+      }
+    },
+    submitReply() {
+      if (!this.replyForm.content.trim()) {
+        this.$message.warning('请输入回复内容')
+        return
+      }
+      this.replySubmitting = true
+      const data = {
+        complaintId: this.detailForm.id,
+        content: this.replyForm.content,
+        images: this.replyForm.images,
+        sendType: 2
+      }
+      addComplaintMsg(data).then(res => {
+        if (res.code === 200) {
+          this.$message.success('回复成功')
+          this.replyForm.content = ''
+          this.replyForm.images = ''
+          this.resetMsgList()
+          this.getMsgList()
+        }
+        this.replySubmitting = false
+      }).catch(() => {
+        this.replySubmitting = false
+      })
+    },
+    handleDelMsg(row) {
+      this.$confirm('确认删除该消息?', '提示', { type: 'warning' }).then(() => {
+        delComplaintMsg(row.id).then(() => {
+          this.$message.success('删除成功')
+          this.resetMsgList()
+          this.getMsgList()
+        })
+      }).catch(() => {})
+    },
+    splitImages(images) {
+      return images ? images.split(',').filter(Boolean) : []
+    }
+  }
+}
+</script>
+
+<style scoped>
+.image-preview-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+.image-item {
+  border: 1px solid #eee;
+  border-radius: 4px;
+  overflow: hidden;
+}
+.msg-container {
+  max-height: 500px;
+  overflow-y: scroll;
+}
+.reply-card {
+  margin-top: 12px;
+}
+.msg-item {
+  padding: 10px 0;
+  border-bottom: 1px solid #f0f0f0;
+}
+.msg-item:last-child {
+  border-bottom: none;
+}
+.msg-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 6px;
+}
+.msg-sender {
+  margin-right: 10px;
+}
+.msg-time {
+  color: #999;
+  font-size: 12px;
+}
+.msg-content {
+  color: #333;
+  line-height: 1.6;
+  padding: 4px 0;
+}
+.msg-images {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+  margin-top: 6px;
+}
+.msg-image-item {
+  border: 1px solid #eee;
+  border-radius: 4px;
+  overflow: hidden;
+}
+.empty-msg {
+  padding: 20px 0;
+}
+.msg-pagination {
+  margin-top: 10px;
+  text-align: right;
+}
+.msg-load-more {
+  text-align: center;
+  padding: 12px 0;
+  font-size: 13px;
+  color: #666;
+}
+</style>

+ 164 - 41
src/views/hisStore/store/audit.vue

@@ -238,7 +238,7 @@
           <el-row>
             <el-col :span="12">
               <el-form-item label="营业执照">
-                <el-image v-if="dialogForm.businessLicense" style="width: 200px" :src="dialogForm.businessLicense" :preview-src-list="[dialogForm.businessLicense]"></el-image>
+                <img v-if="dialogForm.businessLicense" style="width: 200px; cursor: pointer" :src="dialogForm.businessLicense" @click="previewImage(dialogForm.businessLicense)" />
               </el-form-item>
               <el-form-item label="营业执照编号" style="margin-left: 5px">
                 <el-input v-model="dialogForm.businessCode" />
@@ -257,7 +257,7 @@
             <el-row>
               <el-col :span="12">
                 <el-form-item label="药品经营许可证">
-                  <el-image v-if="dialogForm.drugLicense" style="width: 200px" :src="dialogForm.drugLicense" :preview-src-list="[dialogForm.drugLicense]"></el-image>
+                  <img v-if="dialogForm.drugLicense" style="width: 200px; cursor: pointer" :src="dialogForm.drugLicense" @click="previewImage(dialogForm.drugLicense)" />
                   <!-- 未上传图片时显示的默认图标 -->
                   <div v-else class="no-image-placeholder">
                     <span>用户未上传</span>
@@ -299,7 +299,7 @@
             <el-row>
               <el-col :span="12">
                 <el-form-item label="2类医疗器械备案证书">
-                  <el-image v-if="dialogForm.medicalDevice2" style="width: 200px" :src="dialogForm.medicalDevice2" :preview-src-list="[dialogForm.medicalDevice2]"></el-image>
+                  <img v-if="dialogForm.medicalDevice2" style="width: 200px; cursor: pointer" :src="dialogForm.medicalDevice2" @click="previewImage(dialogForm.medicalDevice2)" />
                   <!-- 未上传图片时显示的默认图标 -->
                   <div v-else class="no-image-placeholder">
                     <span>用户未上传</span>
@@ -338,7 +338,7 @@
             <el-row>
               <el-col :span="12">
                 <el-form-item label="3类器械经营许可证">
-                  <el-image v-if="dialogForm.medicalDevice3" style="width: 200px" :src="dialogForm.medicalDevice3" :preview-src-list="[dialogForm.medicalDevice3]"></el-image>
+                  <img v-if="dialogForm.medicalDevice3" style="width: 200px; cursor: pointer" :src="dialogForm.medicalDevice3" @click="previewImage(dialogForm.medicalDevice3)" />
                   <!-- 未上传图片时显示的默认图标 -->
                   <div v-else class="no-image-placeholder">
                     <span>用户未上传</span>
@@ -381,7 +381,7 @@
                   <div v-if="dialogForm.foodLicense && dialogForm.foodLicense.length > 0" class="food-license-container">
                     <div v-for="(url, index) in dialogForm.foodLicense" :key="index" class="food-license-item">
                       <div class="avatar-wrapper">
-                        <img :src="url" class="avatar small-avatar" />
+                        <img :src="url" class="avatar small-avatar" style="cursor: pointer" @click="previewImage(url)" />
                         <div class="button-group">
                           <div class="preview-btn" @click.stop="previewImage(url)">
                             <i class="el-icon-zoom-in"></i>
@@ -429,38 +429,58 @@
           <el-divider content-position="left">店铺配置信息</el-divider>
           <el-col :span="12">
             <el-form-item label="网销报告" prop="reportUrl">
-              <el-upload
-                class="upload-demo"
-                :action="uploadUrl"
-                :on-preview="handleFilePreview"
-                :on-remove="handleRemove"
-                :before-remove="beforeRemove"
-                multiple
-                :limit="1"
-                :on-exceed="handleExceed"
-                :file-list="reportFileList"
-                :show-file-list="true"
-                disabled>
-                <el-button size="small" type="primary" disabled>点击上传</el-button>
-              </el-upload>
+              <div>
+                <el-upload
+                  class="upload-demo"
+                  :action="uploadUrl"
+                  :on-preview="handleFilePreview"
+                  :on-remove="handleRemove"
+                  :before-remove="beforeRemove"
+                  :limit="1"
+                  :on-exceed="handleExceed"
+                  :file-list="reportFileList"
+                  :show-file-list="true"
+                  accept=".jpg,.jpeg,.png,.gif,.bmp,.pdf"
+                  disabled>
+                  <el-button size="small" type="primary" disabled>点击上传</el-button>
+                  <div slot="tip" class="el-upload__tip">支持 jpg/png/pdf 格式</div>
+                </el-upload>
+                <el-button
+                  v-if="dialogForm.reportUrl"
+                  type="text"
+                  size="small"
+                  icon="el-icon-view"
+                  style="margin-top: 4px;"
+                  @click="viewReportFile('reportUrl')">查看已上传文件</el-button>
+              </div>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="备案" prop="filingUrl">
-              <el-upload
-                class="upload-demo"
-                :action="uploadUrl"
-                :on-preview="handleFilePreview"
-                :on-remove="handleRemove"
-                :before-remove="beforeRemove"
-                multiple
-                :limit="1"
-                :on-exceed="handleExceed"
-                :file-list="fileList"
-                :show-file-list="true"
-                disabled>
-                <el-button size="small" type="primary" disabled>点击上传</el-button>
-              </el-upload>
+              <div>
+                <el-upload
+                  class="upload-demo"
+                  :action="uploadUrl"
+                  :on-preview="handleFilePreview"
+                  :on-remove="handleRemove"
+                  :before-remove="beforeRemove"
+                  :limit="1"
+                  :on-exceed="handleExceed"
+                  :file-list="fileList"
+                  :show-file-list="true"
+                  accept=".jpg,.jpeg,.png,.gif,.bmp,.pdf"
+                  disabled>
+                  <el-button size="small" type="primary" disabled>点击上传</el-button>
+                  <div slot="tip" class="el-upload__tip">支持 jpg/png/pdf 格式</div>
+                </el-upload>
+                <el-button
+                  v-if="dialogForm.filingUrl"
+                  type="text"
+                  size="small"
+                  icon="el-icon-view"
+                  style="margin-top: 4px;"
+                  @click="viewReportFile('filingUrl')">查看已上传文件</el-button>
+              </div>
             </el-form-item>
           </el-col>
           <el-form-item label="退货地址">
@@ -499,7 +519,7 @@
                 <span>{{dialogForm.totalMoney || 0}}</span>
               </el-form-item>
             </el-col>
-            <el-col :span="12">
+            <el-col :span="12" v-if="false">
               <el-form-item label="分佣方式">
                 <span v-if="dialogForm.brokerageType == 1">每盒</span>
                 <span v-else-if="dialogForm.brokerageType == 2">总价</span>
@@ -507,7 +527,7 @@
             </el-col>
           </el-row>
           <el-row>
-            <el-col :span="12">
+            <el-col :span="12" v-if="false">
               <el-form-item label="状态">
                 <dict-tag :options="statusOptions" :value="dialogForm.status"/>
               </el-form-item>
@@ -518,7 +538,7 @@
               </el-form-item>
             </el-col>
           </el-row>
-          <el-form-item label="配送方式">
+          <el-form-item label="配送方式" v-if="false">
             <span v-if="dialogForm.shippingType">
               <span v-if="dialogForm.shippingType.includes('1') || dialogForm.shippingType === '1'">配送</span>
               <span v-if="dialogForm.shippingType.includes('2') || dialogForm.shippingType === '2'">,自提</span>
@@ -532,8 +552,39 @@
       </template>
     </StoreDialog>
 
-    <el-dialog :visible.sync="imagePreviewVisible" width="800px" append-to-body>
-      <img :src="previewImageUrl" style="width: 100%; height: auto; max-height: 600px; object-fit: contain;" />
+    <el-dialog :visible.sync="imagePreviewVisible" width="1100px" append-to-body :modal-append-to-body="false" @close="onImagePreviewClose">
+      <div slot="title">
+        <span>图片预览</span>
+        <span style="margin-left: 16px; font-size: 13px; color: #909399;">{{ previewScale }}%</span>
+      </div>
+      <div
+        class="img-preview-body"
+        @wheel.prevent="onPreviewWheel"
+        @mousedown="onImgMouseDown"
+        @mousemove="onImgMouseMove"
+        @mouseup="onImgMouseUp"
+        @mouseleave="onImgMouseUp"
+      >
+        <img
+          :src="previewImageUrl"
+          :style="{
+            transform: `translate(${dragOffset.x}px, ${dragOffset.y}px) scale(${previewScale / 100})`,
+            transition: isDragging ? 'none' : 'transform .15s',
+            cursor: isDragging ? 'grabbing' : (previewScale > 100 ? 'grab' : 'default')
+          }"
+          style="max-width: 100%; max-height: 65vh; user-select: none;"
+          ref="previewImg"
+          @dragstart.prevent
+        />
+      </div>
+      <div slot="footer" class="dialog-footer" style="text-align: left;">
+        <el-button-group>
+          <el-button size="small" icon="el-icon-zoom-out" :disabled="previewScale <= 25" @click="zoomOut">缩小</el-button>
+          <el-button size="small" icon="el-icon-refresh" @click="resetZoom">复位</el-button>
+          <el-button size="small" icon="el-icon-zoom-in" :disabled="previewScale >= 300" @click="zoomIn">放大</el-button>
+        </el-button-group>
+        <el-button size="small" style="float: right;" @click="imagePreviewVisible = false">关 闭</el-button>
+      </div>
     </el-dialog>
   </div>
 
@@ -554,6 +605,10 @@ export default {
     return {
       previewImageUrl: '',
       imagePreviewVisible: false,
+      previewScale: 100,
+      dragOffset: { x: 0, y: 0 },
+      isDragging: false,
+      dragStart: { x: 0, y: 0 },
       reportFileList:[],
       fileList:[],
       // 弹出层标题
@@ -635,9 +690,6 @@ export default {
         licenseImages: [
           { required: true, message: "资质证书不能为空", trigger: "blur" }
         ],
-        shippingType: [
-          { required: true, message: "配送方式不能为空", trigger: "blur" }
-        ],
         phone: [
           { required: true, message: "店铺电话不能为空", trigger: "blur" }
         ],
@@ -824,6 +876,8 @@ export default {
     },
     // 表单重置
     reset() {
+      this.reportFileList = [];
+      this.fileList = [];
       this.form = {
         storeId: null,
         cityIds: null,
@@ -916,14 +970,75 @@ export default {
       const { url, name } = file;
       if (this.isImageFile(name)) {
         this.previewImageUrl = url;
+        this.previewScale = 100;
         this.imagePreviewVisible = true;
       } else {
         window.open(url, '_blank');
       }
     },
+
+    viewReportFile(field) {
+      const url = this.form[field]
+      if (!url) {
+        this.$message.warning('暂无文件')
+        return
+      }
+      if (this.isImageFile(url.split('?')[0])) {
+        this.previewImageUrl = url
+        this.previewScale = 100
+        this.imagePreviewVisible = true
+      } else {
+        window.open(url, '_blank')
+      }
+    },
+
+    onImagePreviewClose() {
+      this.previewScale = 100
+      this.dragOffset = { x: 0, y: 0 }
+      this.isDragging = false
+      this.imagePreviewVisible = false
+    },
+    zoomOut() {
+      if (this.previewScale > 25) {
+        this.previewScale = Math.max(25, this.previewScale - 25)
+        this.dragOffset = { x: 0, y: 0 }
+      }
+    },
+    zoomIn() {
+      if (this.previewScale < 300) {
+        this.previewScale = Math.min(300, this.previewScale + 25)
+      }
+    },
+    resetZoom() {
+      this.previewScale = 100
+      this.dragOffset = { x: 0, y: 0 }
+    },
+    onPreviewWheel(e) {
+      if (e.deltaY < 0) {
+        this.zoomIn()
+      } else {
+        this.zoomOut()
+      }
+    },
+    onImgMouseDown(e) {
+      this.isDragging = true
+      this.dragStart = { x: e.clientX - this.dragOffset.x, y: e.clientY - this.dragOffset.y }
+    },
+    onImgMouseMove(e) {
+      if (!this.isDragging) return
+      this.dragOffset = {
+        x: e.clientX - this.dragStart.x,
+        y: e.clientY - this.dragStart.y
+      }
+    },
+    onImgMouseUp() {
+      this.isDragging = false
+    },
+
     // 添加图片预览方法
     previewImage(url) {
       this.previewImageUrl = url;
+      this.previewScale = 100;
       this.imagePreviewVisible = true;
     },
   }
@@ -1048,4 +1163,12 @@ export default {
 .preview-btn:hover {
   background-color: rgba(0, 0, 0, 0.7);
 }
+
+.img-preview-body {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 300px;
+  overflow: hidden;
+}
 </style>

+ 390 - 31
src/views/hisStore/store/index.vue

@@ -161,6 +161,17 @@
           v-hasPermi="['his:store:export']"
         >导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          icon="el-icon-video-play"
+          size="mini"
+          :loading="enableLoading"
+          :disabled="multiple"
+          @click="handleEnable"
+          v-hasPermi="['his:store:edit']"
+        >一键启用</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -201,6 +212,17 @@
       <el-table-column label="登录帐号" align="center" prop="account" width="150px" />
 
       <el-table-column label="上次审核日期" align="center" prop="qualificationUpdateTime" width="150px" />
+      <el-table-column label="网销报告" align="center" width="100px">
+        <template slot-scope="scope">
+          <el-button
+            v-if="scope.row.reportUrl"
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="viewReportFileByUrl(scope.row.reportUrl)">查看</el-button>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
       <el-table-column label="下次到期日" align="center" prop="nextQualificationUpdateTime" width="150px" />
       <el-table-column label="剩余天数" align="center" prop="daysDiff" width="150px" />
       <el-table-column label="提醒状态(标签)" align="center" prop="daysDiff" width="150px">
@@ -754,8 +776,9 @@
                         :src="form.settlementAgreement"
                         class="uploaded-img"
                         width="100px"
+                        @click="viewAgreementFile('settlementAgreement')"
                       />
-                      <div v-else class="document-icon">
+                      <div v-else class="document-icon" @click="viewAgreementFile('settlementAgreement')">
                         <i :class="getDocumentIconClass(form.settlementAgreement, 'settlementAgreement')" class="icon"></i>
                         <span class="file-name">{{ form.settlementAgreementFileName || getFileName(form.settlementAgreement) }}</span>
                       </div>
@@ -827,8 +850,9 @@
                         :src="form.qualityAssuranceAgreement"
                         class="uploaded-img"
                         width="100px"
+                        @click="viewAgreementFile('qualityAssuranceAgreement')"
                       />
-                      <div v-else class="document-icon">
+                      <div v-else class="document-icon" @click="viewAgreementFile('qualityAssuranceAgreement')">
                         <i :class="getDocumentIconClass(form.qualityAssuranceAgreement, 'qualityAssuranceAgreement')" class="icon"></i>
                         <span class="file-name">{{ form.qualityAssuranceAgreementFileName || getFileName(form.qualityAssuranceAgreement) }}</span>
                       </div>
@@ -900,8 +924,9 @@
                         :src="form.otherSpecialQualification"
                         class="uploaded-img"
                         width="100px"
+                        @click="viewAgreementFile('otherSpecialQualification')"
                       />
-                      <div v-else class="document-icon">
+                      <div v-else class="document-icon" @click="viewAgreementFile('otherSpecialQualification')">
                         <i :class="getDocumentIconClass(form.otherSpecialQualification, 'otherSpecialQualification')" class="icon"></i>
                         <span class="file-name">{{ form.otherSpecialQualificationFileName || getFileName(form.otherSpecialQualification) }}</span>
                       </div>
@@ -956,15 +981,35 @@
             <el-upload
               class="upload-demo"
               :action="uploadUrl"
-              :on-preview="handlePreview"
               :on-remove="handleRemove"
               :before-remove="beforeRemove"
-              multiple
               :limit="1"
               :on-exceed="handleExceed"
-              :on-success="(response, file) => handleFileSuccess(response, file, 'reportUrl')"
-              :file-list="reportFileList">
-              <el-button size="small" type="primary">点击上传</el-button>
+              :on-success="(response, file) => handleReportUploadSuccess(response, file, 'reportUrl')"
+              :before-upload="beforeReportUpload"
+              :disabled="!!form.reportUrl"
+              accept=".jpg,.jpeg,.png,.gif,.bmp,.pdf"
+            >
+              <div class="report-file-wrapper" v-if="form.reportUrl">
+                <img
+                  v-if="isImageFile(form.reportUrl)"
+                  :src="form.reportUrl"
+                  class="uploaded-file-thumb"
+                  width="100px"
+                  @click="viewReportFile('reportUrl')"
+                />
+                <div v-else class="document-file-icon" @click="viewReportFile('reportUrl')">
+                  <i class="el-icon-document"></i>
+                  <span class="file-name">{{ getFileName(form.reportUrl) }}</span>
+                </div>
+              </div>
+              <div v-else class="upload-btn-wrapper">
+                <el-button size="small" type="primary">点击上传</el-button>
+              </div>
+              <div v-if="form.reportUrl" class="report-preview-btn">
+                <el-button size="mini" type="success" @click.stop="viewReportFile('reportUrl')">查看已上传文件</el-button>
+                <el-button size="mini" type="danger" @click.stop="handleReportRemove('reportUrl')">删除</el-button>
+              </div>
             </el-upload>
           </el-form-item>
         </el-col>
@@ -973,15 +1018,35 @@
             <el-upload
               class="upload-demo"
               :action="uploadUrl"
-              :on-preview="handlePreview"
               :on-remove="handleRemove"
               :before-remove="beforeRemove"
-              :on-success="(response, file) => handleFileSuccess(response, file, 'filingUrl')"
-              multiple
               :limit="1"
               :on-exceed="handleExceed"
-              :file-list="fileList">
-              <el-button size="small" type="primary">点击上传</el-button>
+              :on-success="(response, file) => handleReportUploadSuccess(response, file, 'filingUrl')"
+              :before-upload="beforeReportUpload"
+              :disabled="!!form.filingUrl"
+              accept=".jpg,.jpeg,.png,.gif,.bmp,.pdf"
+            >
+              <div class="report-file-wrapper" v-if="form.filingUrl">
+                <img
+                  v-if="isImageFile(form.filingUrl)"
+                  :src="form.filingUrl"
+                  class="uploaded-file-thumb"
+                  width="100px"
+                  @click="viewReportFile('filingUrl')"
+                />
+                <div v-else class="document-file-icon" @click="viewReportFile('filingUrl')">
+                  <i class="el-icon-document"></i>
+                  <span class="file-name">{{ getFileName(form.filingUrl) }}</span>
+                </div>
+              </div>
+              <div v-else class="upload-btn-wrapper">
+                <el-button size="small" type="primary">点击上传</el-button>
+              </div>
+              <div v-if="form.filingUrl" class="report-preview-btn">
+                <el-button size="mini" type="success" @click.stop="viewReportFile('filingUrl')">查看已上传文件</el-button>
+                <el-button size="mini" type="danger" @click.stop="handleReportRemove('filingUrl')">删除</el-button>
+              </div>
             </el-upload>
           </el-form-item>
         </el-col>
@@ -1025,7 +1090,7 @@
           </el-col>
         </el-row>
         <el-row>
-          <el-col :span="12">
+          <el-col :span="12" v-if="false">
             <el-form-item label="状态">
               <el-radio-group v-model="form.status">
                 <el-radio
@@ -1037,7 +1102,7 @@
             </el-form-item>
           </el-col>
 
-          <el-col :span="12">
+          <el-col :span="12" v-if="false">
             <el-form-item label="分佣方式" prop="brokerageType">
               <el-radio-group v-model="form.brokerageType" disabled>
                 <el-radio label="1" >每盒</el-radio>
@@ -1047,7 +1112,7 @@
           </el-col>
 
         </el-row>
-        <el-form-item label="配送方式" prop="shippingType">
+        <el-form-item label="配送方式" prop="shippingType" v-if="false">
           <el-checkbox-group v-model="form.shippingType" size="medium">
             <el-checkbox v-for="(item, index) in shippingTypeOptions" :key="index" :label="item.value"
                          :disabled="item.disabled">{{item.label}}</el-checkbox>
@@ -1067,9 +1132,40 @@
       </div>
     </el-dialog>
 
-    <!-- 修改图片预览对话框,增加更大的预览尺寸 -->
-    <el-dialog :visible.sync="imagePreviewVisible" width="1100px" append-to-body :modal-append-to-body="false" @close="imagePreviewVisible = false">
-      <img :src="previewImageUrl" style="width: 100%; height: auto; max-height: 700px; object-fit: contain;" />
+    <!-- 图片预览对话框,支持放大缩小 -->
+    <el-dialog :visible.sync="imagePreviewVisible" width="1100px" append-to-body :modal-append-to-body="false" @close="onImagePreviewClose">
+      <div slot="title">
+        <span>图片预览</span>
+        <span style="margin-left: 16px; font-size: 13px; color: #909399;">{{ previewScale }}%</span>
+      </div>
+      <div
+        class="img-preview-body"
+        @wheel.prevent="onPreviewWheel"
+        @mousedown="onImgMouseDown"
+        @mousemove="onImgMouseMove"
+        @mouseup="onImgMouseUp"
+        @mouseleave="onImgMouseUp"
+      >
+        <img
+          :src="previewImageUrl"
+          :style="{
+            transform: `translate(${dragOffset.x}px, ${dragOffset.y}px) scale(${previewScale / 100})`,
+            transition: isDragging ? 'none' : 'transform .15s',
+            cursor: isDragging ? 'grabbing' : (previewScale > 100 ? 'grab' : 'default')
+          }"
+          style="max-width: 100%; max-height: 65vh; user-select: none;"
+          ref="previewImg"
+          @dragstart.prevent
+        />
+      </div>
+      <div slot="footer" class="dialog-footer" style="text-align: left;">
+        <el-button-group>
+          <el-button size="small" icon="el-icon-zoom-out" :disabled="previewScale <= 25" @click="zoomOut">缩小</el-button>
+          <el-button size="small" icon="el-icon-refresh" @click="resetZoom">复位</el-button>
+          <el-button size="small" icon="el-icon-zoom-in" :disabled="previewScale >= 300" @click="zoomIn">放大</el-button>
+        </el-button-group>
+        <el-button size="small" style="float: right;" @click="imagePreviewVisible = false">关 闭</el-button>
+      </div>
     </el-dialog>
 
     <!-- 设置ERP店铺信息对话框 -->
@@ -1116,7 +1212,7 @@
 </template>
 
 <script>
-import { listStore, getStore, delStore, addStore, updateStore, exportStore, refreshPasWod,exportFsStream, businessLicenseCheck, getStoreErpInfo, saveStoreErpInfo} from '@/api/hisStore/store'
+import { listStore, getStore, delStore, addStore, updateStore, exportStore, refreshPasWod,exportFsStream, businessLicenseCheck, getStoreErpInfo, saveStoreErpInfo, enableStore} from '@/api/hisStore/store'
 import storeDetails from '../components/storeDetails.vue';
 import StoreDialog from '../components/StoreDialog.vue';
 import {getCitys} from "@/api/store/city";
@@ -1131,6 +1227,10 @@ export default {
       fileList:[],
       previewImageUrl: '', // 添加预览图片URL
       imagePreviewVisible: false,// 添加图片预览对话框可见性控制
+      previewScale: 100, // 图片预览缩放比例
+      dragOffset: { x: 0, y: 0 }, // 拖动偏移
+      isDragging: false, // 是否正在拖动
+      dragStart: { x: 0, y: 0 }, // 拖动起始坐标
       // 新增:控制LOGO上传组件禁用状态
       isDeleting: false,
       promptList:[],
@@ -1196,6 +1296,27 @@ export default {
       loading: true,
       // 导出遮罩层
       exportLoading: false,
+      enableLoading: false,
+      originalForm: null,
+      excludedFields: [
+        'storeId', 'createTime', 'updateTime',
+        'balance', 'totalMoney', 'salesCount', 'productCount',
+        'qualificationUpdateTime', 'nextQualificationUpdateTime', 'daysDiff', 'remainDays',
+        'permStatus', 'storeSeq', 'merchantId', 'selectableProductTypes',
+        'doctorList', 'queryStoreId', 'hasErpInfo',
+        'shippingType', 'brokerageType', 'brokerageRate', 'status',
+        'password', 'licenseImages'
+      ],
+      agreementFields: [
+        'settlementAgreement', 'settlementAgreementFileName', 'settlementAgreementCode',
+        'settlementAgreementStart', 'settlementAgreementEnd', 'settlementAgreementExpiry', 'settlementAgreementFileType',
+        'qualityAssuranceAgreement', 'qualityAssuranceAgreementFileName', 'qualityAssuranceAgreementCode',
+        'qualityAssuranceAgreementStart', 'qualityAssuranceAgreementEnd', 'qualityAssuranceAgreementExpiry', 'qualityAssuranceAgreementFileType',
+        'otherSpecialQualification', 'otherSpecialQualificationFileName', 'otherSpecialQualificationCode',
+        'otherSpecialQualificationStart', 'otherSpecialQualificationEnd', 'otherSpecialQualificationExpiry', 'otherSpecialQualificationFileType',
+        'titleNameOne', 'titleNameTwo', 'titleNameThree',
+        'isEffectivePermanent1', 'isEffectivePermanent2', 'isEffectivePermanent3'
+      ],
       // 选中数组
       ids: [],
       // 非单个禁用
@@ -1319,15 +1440,9 @@ export default {
         licenseImages: [
           { required: true, message: "资质证书不能为空", trigger: "blur" }
         ],
-        shippingType: [
-          { required: true, message: "配送方式不能为空", trigger: "blur" }
-        ],
         account: [
           { required: true, message: "登录账号不能为空", trigger: "blur" }
         ],
-        brokerageType: [
-          { required: true, message: "分佣方式不能为空", trigger: "blur" }
-        ],
         refundPhone: [
           { required: true, message: "退货电话不能为空", trigger: "blur" }
         ],
@@ -1457,8 +1572,9 @@ export default {
     },
     // 添加图片预览方法
     previewImage(url) {
-      this.previewImageUrl = url;
-      this.imagePreviewVisible = true;
+      this.previewImageUrl = url
+      this.previewScale = 100
+      this.imagePreviewVisible = true
     },
     handleNoticeInfo() {
       qualifications()
@@ -1582,8 +1698,10 @@ export default {
     reset() {
       this.switchValue = false;
       this.switchMedicalValue=false;
-      // 重置时确保isDeleting为false
       this.isDeleting = false;
+      this.reportFileList = [];
+      this.fileList = [];
+      this.originalForm = null;
       this.form = {
         storeId: null,
         cityIds: null,
@@ -1824,6 +1942,7 @@ export default {
           // 如果为空,则初始化为空数组
           this.form.foodLicense = [];
         }
+        this.originalForm = JSON.parse(JSON.stringify(this.form));
       });
     },
     // 验证所有执照有效性验证方法
@@ -1892,6 +2011,17 @@ export default {
           if (!this.validateLicenses()) {
             return; // 验证失败,停止提交
           }
+          const isEdit = this.form.storeId != null;
+          let isAgreementOnlySave = false;
+          if (isEdit && this.originalForm) {
+            const changedFields = this.getChangedFields(this.originalForm, this.form);
+            if (changedFields.length === 0) {
+              this.$message.warning("未检测到任何修改,无需提交");
+              return;
+            }
+            const agreementFieldSet = this.getAgreementFieldSet();
+            isAgreementOnlySave = changedFields.every(f => agreementFieldSet.has(f));
+          }
           // 处理表单数据
           const formData = Object.assign({}, this.form);
           //清空非必填日期控件
@@ -1938,23 +2068,27 @@ export default {
               expiryField: 'settlementAgreementExpiry',
               startField: 'settlementAgreementStart',
               endField: 'settlementAgreementEnd',
+              permanentFlag: this.medicalLicenseExpiryValue1,
               message: '其它资质-入驻协议,有效期开始日期或结束日期不能为空!'
             },
             {
               expiryField: 'qualityAssuranceAgreementExpiry',
               startField: 'qualityAssuranceAgreementStart',
               endField: 'qualityAssuranceAgreementEnd',
+              permanentFlag: this.medicalLicenseExpiryValue2,
               message: '质量保证协议,有效期开始日期或结束日期不能为空!'
             },
             {
               expiryField: 'otherSpecialQualificationExpiry',
               startField: 'otherSpecialQualificationStart',
               endField: 'otherSpecialQualificationEnd',
+              permanentFlag: this.medicalLicenseExpiryValue3,
               message: '其它特殊资质,有效期开始日期或结束日期不能为空!'
             }
           ];
 
           for (const field of agreementFields) {
+            if (field.permanentFlag) continue;
             if (formData[field.expiryField] && formData[field.expiryField].length === 2) {
               formData[field.startField] = formData[field.expiryField][0];
               formData[field.endField] = formData[field.expiryField][1];
@@ -1980,9 +2114,16 @@ export default {
             formData.foodLicense = formData.foodLicense.join(',');
           }
 
-          if (formData.storeId != null) {
+          if (isAgreementOnlySave) {
+            delete formData.isAudit;
             updateStore(formData).then(response => {
-              this.msgSuccess("修改成功");
+              this.msgSuccess("直接保存成功(未触发审核)");
+              this.open = false;
+              this.getList();
+            });
+          } else if (isEdit) {
+            updateStore(formData).then(response => {
+              this.msgSuccess("提交审核成功");
               this.open = false;
               this.getList();
             });
@@ -2052,6 +2193,40 @@ export default {
       });
 
     },
+    handleEnable() {
+      const storeIds = this.ids
+      if (storeIds.length === 0) {
+        this.$message.warning('请选择要启用的店铺')
+        return
+      }
+      const storeNames = this.storeList
+        .filter(s => storeIds.includes(s.storeId))
+        .map(s => s.storeName)
+        .join('、')
+      this.$confirm(`确认一键启用以下店铺吗?<br/>${storeNames}`, '一键启用', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+        dangerouslyUseHTMLString: true
+      }).then(() => {
+        this.enableLoading = true
+        const promises = storeIds.map(id => enableStore(id))
+        Promise.all(promises).then(responses => {
+          this.enableLoading = false
+          const failures = responses.filter(r => r.code !== 200)
+          if (failures.length > 0) {
+            const msgs = failures.map(f => f.msg).join('<br/>')
+            this.$message({ dangerouslyUseHTMLString: true, message: msgs, type: 'error', duration: 5000 })
+          } else {
+            this.$message.success('全部启用成功')
+          }
+          this.getList()
+        }).catch(() => {
+          this.enableLoading = false
+          this.getList()
+        })
+      }).catch(() => {})
+    },
     /** 设置ERP店铺信息按钮 */
     handleErpInfo(row) {
       this.erpForm = {
@@ -2198,6 +2373,130 @@ export default {
     handlePreview(file) {
       console.log(file);
     },
+    handleReportRemove(field) {
+      this.$set(this.form, field, '');
+      this.$message.info('文件已移除');
+    },
+    handleReportUploadSuccess(response, file, field) {
+      if (response.code === 200) {
+        this.$set(this.form, field, response.url);
+        this.$message.success(`文件:${file.name}上传成功`);
+        this.$forceUpdate();
+      } else {
+        this.msgError(response.msg);
+      }
+    },
+    beforeReportUpload(file) {
+      const isValid = file.type.startsWith('image/') || file.type === 'application/pdf'
+      if (!isValid) {
+        this.$message.error('仅支持 jpg/png/gif/bmp 图片或 PDF 文件')
+        return false
+      }
+      const isLt10M = file.size / 1024 / 1024 < 10
+      if (!isLt10M) {
+        this.$message.error('文件大小不能超过 10MB')
+        return false
+      }
+      return true
+    },
+    isImageFile(url) {
+      if (!url) return false
+      return /\.(jpg|jpeg|png|gif|bmp|webp)(\?|$)/i.test(url)
+    },
+    viewReportFile(field) {
+      const url = this.form[field]
+      if (!url) {
+        this.$message.warning('暂无文件')
+        return
+      }
+      this.viewReportFileByUrl(url)
+    },
+    viewReportFileByUrl(url) {
+      if (this.isImageFile(url)) {
+        this.previewImageUrl = url
+        this.previewScale = 100
+        this.imagePreviewVisible = true
+      } else {
+        window.open(url, '_blank')
+      }
+    },
+    viewAgreementFile(field) {
+      const url = this.form[field]
+      if (!url) return
+      if (this.isImageFile(url, field)) {
+        this.previewImageUrl = url
+        this.previewScale = 100
+        this.imagePreviewVisible = true
+      } else {
+        window.open(url, '_blank')
+      }
+    },
+    onImagePreviewClose() {
+      this.previewScale = 100
+      this.dragOffset = { x: 0, y: 0 }
+      this.isDragging = false
+      this.imagePreviewVisible = false
+    },
+    zoomOut() {
+      if (this.previewScale > 25) {
+        this.previewScale = Math.max(25, this.previewScale - 25)
+        this.dragOffset = { x: 0, y: 0 }
+      }
+    },
+    zoomIn() {
+      if (this.previewScale < 300) {
+        this.previewScale = Math.min(300, this.previewScale + 25)
+      }
+    },
+    resetZoom() {
+      this.previewScale = 100
+      this.dragOffset = { x: 0, y: 0 }
+    },
+    onPreviewWheel(e) {
+      if (e.deltaY < 0) {
+        this.zoomIn()
+      } else {
+        this.zoomOut()
+      }
+    },
+    onImgMouseDown(e) {
+      this.isDragging = true
+      this.dragStart = { x: e.clientX - this.dragOffset.x, y: e.clientY - this.dragOffset.y }
+    },
+    onImgMouseMove(e) {
+      if (!this.isDragging) return
+      this.dragOffset = {
+        x: e.clientX - this.dragStart.x,
+        y: e.clientY - this.dragStart.y
+      }
+    },
+    onImgMouseUp() {
+      this.isDragging = false
+    },
+    fieldsEqual(a, b) {
+      if (a === b) return true;
+      if (a == null && b == null) return true;
+      if (a == null || b == null) return false;
+      if (Array.isArray(a) && Array.isArray(b)) {
+        if (a.length !== b.length) return false;
+        return a.every((v, i) => this.fieldsEqual(v, b[i]));
+      }
+      if ((a === '' && b === null) || (a === null && b === '')) return true;
+      return String(a) === String(b);
+    },
+    getChangedFields(original, current) {
+      const changed = [];
+      for (const key of Object.keys(original)) {
+        if (this.excludedFields.indexOf(key) !== -1) continue;
+        if (!this.fieldsEqual(original[key], current[key])) {
+          changed.push(key);
+        }
+      }
+      return changed;
+    },
+    getAgreementFieldSet() {
+      return new Set(this.agreementFields);
+    },
     handleExceed(files, fileList) {
       this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
     },
@@ -2238,6 +2537,7 @@ export default {
   border-radius: 4px;
   border: 1px solid #e4e7ed;
   vertical-align: middle;
+  cursor: pointer;
 }
 
 /* 文档图标容器 */
@@ -2253,6 +2553,7 @@ export default {
   justify-content: center;
   background-color: #f5f7fa;
   vertical-align: middle;
+  cursor: pointer;
 }
 
 .document-icon .icon {
@@ -2531,5 +2832,63 @@ export default {
   background-color: #f0f7ff;
 }
 
+.img-preview-body {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 300px;
+  overflow: hidden;
+}
+
+.report-file-wrapper {
+  display: inline-block;
+}
+
+.uploaded-file-thumb {
+  width: 100px;
+  height: 100px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  cursor: pointer;
+  vertical-align: middle;
+}
+
+.document-file-icon {
+  width: 150px;
+  height: 100px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f7fa;
+  cursor: pointer;
+  vertical-align: middle;
+}
+
+.document-file-icon .el-icon-document {
+  font-size: 32px;
+  color: #409eff;
+  margin-bottom: 4px;
+}
+
+.document-file-icon .file-name {
+  font-size: 12px;
+  color: #606266;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 120px;
+  text-align: center;
+}
+
+.upload-btn-wrapper {
+  display: inline-block;
+}
+
+.report-preview-btn {
+  margin-top: 8px;
+}
 
 </style>