Browse Source

feat(营销活动): 添加活动详情查看功能及附件管理

yz 2 months ago
parent
commit
466f2bd17d
2 changed files with 642 additions and 25 deletions
  1. 60 0
      src/api/order/marketing-activity.js
  2. 582 25
      src/views/order/marketing-activity/index.vue

+ 60 - 0
src/api/order/marketing-activity.js

@@ -375,3 +375,63 @@ export const saveApprovalRecord = async (
         return await addApprovalRecord(approvalData);
     }
 };
+
+/**
+ * 活动附件查询参数类型定义
+ * @typedef {Object} ActivityAttachmentQueryParams
+ * @property {number} size - 每页数量
+ * @property {number} current - 当前页码
+ * @property {string|number} activityId - 活动ID
+ */
+
+/**
+ * 活动附件项类型定义
+ * @typedef {Object} ActivityAttachmentItem
+ * @property {string} id - 附件ID
+ * @property {string} createUser - 创建用户ID
+ * @property {string} createDept - 创建部门ID
+ * @property {string} createTime - 创建时间
+ * @property {string} updateUser - 更新用户ID
+ * @property {string} updateTime - 更新时间
+ * @property {number} status - 状态
+ * @property {number} isDeleted - 是否删除
+ * @property {string|number} activityId - 活动ID
+ * @property {string} fileName - 文件名称
+ * @property {string} fileUrl - 文件URL
+ * @property {string} fileType - 文件类型
+ * @property {number} fileSize - 文件大小(字节)
+ * @property {string} attachType - 附件类型
+ */
+
+/**
+ * 活动附件列表响应类型定义
+ * @typedef {Object} ActivityAttachmentListResponse
+ * @property {number} code - 响应码
+ * @property {boolean} success - 是否成功
+ * @property {Object} data - 响应数据
+ * @property {ActivityAttachmentItem[]} data.records - 附件列表
+ * @property {number} data.total - 总数
+ * @property {number} data.size - 每页数量
+ * @property {number} data.current - 当前页码
+ * @property {number} data.pages - 总页数
+ * @property {string} msg - 响应消息
+ */
+
+/**
+ * 获取营销活动附件列表
+ * @param {number} current - 当前页码
+ * @param {number} size - 每页数量
+ * @param {string|number} activityId - 活动ID
+ * @returns {Promise<ActivityAttachmentListResponse>} 附件列表响应
+ */
+export const getActivityAttachments = (current, size, activityId) => {
+  return request({
+    url: '/api/blade-factory/api/factory/activity-attachment',
+    method: 'get',
+    params: {
+      current,
+      size,
+      activityId
+    }
+  })
+}

+ 582 - 25
src/views/order/marketing-activity/index.vue

@@ -224,6 +224,240 @@
       </div>
 
     </el-dialog>
+  <!-- 查看详情弹窗 -->
+  <el-dialog
+    title="营销活动详情"
+    :visible.sync="viewDialogVisible"
+    width="80%"
+    append-to-body
+    @close="handleCloseViewDialog"
+    class="activity-detail-dialog"
+  >
+    <div v-loading="detailLoading" class="activity-detail-container">
+      <div v-if="currentViewActivity" class="activity-detail-content">
+        <!-- 基本信息 -->
+        <div class="detail-section">
+          <h3 class="section-title">基本信息</h3>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>活动编码:</label>
+                <span>{{ currentViewActivity.activityCode }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>活动标题:</label>
+                <span>{{ currentViewActivity.title }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>活动类型:</label>
+                <span>{{ currentViewActivity.activityType }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>审批状态:</label>
+                <el-tag :type="getApprovalStatusType(currentViewActivity.approvalStatus)" size="small">
+                  {{ getApprovalStatusText(currentViewActivity.approvalStatus) }}
+                </el-tag>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>开始时间:</label>
+                <span>{{ currentViewActivity.startTime }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>结束时间:</label>
+                <span>{{ currentViewActivity.endTime }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>促销价格:</label>
+                <span>{{ currentViewActivity.promotionPrice }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>创建时间:</label>
+                <span>{{ currentViewActivity.createTime }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 客户信息 -->
+        <div class="detail-section">
+          <h3 class="section-title">客户信息</h3>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>客户编码:</label>
+                <span>{{ currentViewActivity.customerCode }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>客户名称:</label>
+                <span>{{ currentViewActivity.customerName }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>联系人:</label>
+                <span>{{ currentViewActivity.contactName }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>联系电话:</label>
+                <span>{{ currentViewActivity.contactPhone }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 活动描述 -->
+        <div class="detail-section" v-if="currentViewActivity.description">
+          <h3 class="section-title">活动描述</h3>
+          <div class="description-content">
+            {{ currentViewActivity.description }}
+          </div>
+        </div>
+
+        <!-- 审批信息 -->
+        <div class="detail-section" v-if="currentViewActivity.approverName">
+          <h3 class="section-title">审批信息</h3>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>审批人:</label>
+                <span>{{ currentViewActivity.approverName }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <label>审批时间:</label>
+                <span>{{ currentViewActivity.approvalTime }}</span>
+              </div>
+            </el-col>
+            <el-col :span="24" v-if="currentViewActivity.approvalRemark">
+              <div class="detail-item">
+                <label>审批备注:</label>
+                <span>{{ currentViewActivity.approvalRemark }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 相关附件 -->
+        <div class="detail-section">
+          <h3 class="section-title">相关附件</h3>
+          <el-table
+            :data="attachmentsList"
+            v-loading="attachmentsLoading"
+            stripe
+            border
+            style="width: 100%"
+            empty-text="暂无相关附件"
+            size="small"
+          >
+            <el-table-column
+              label="文件类型"
+              width="80"
+              align="center"
+            >
+              <template slot-scope="scope">
+                <i :class="getFileTypeIcon(scope.row.fileType)" style="font-size: 20px; color: #666;"></i>
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              prop="fileName"
+              label="文件名称"
+              min-width="200"
+              show-overflow-tooltip
+            >
+              <template slot-scope="scope">
+                <span :title="scope.row.fileName">{{ scope.row.fileName }}</span>
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              prop="attachType"
+              label="附件类型"
+              width="120"
+              align="center"
+            >
+              <template slot-scope="scope">
+                <el-tag size="mini" type="info">{{ scope.row.attachType }}</el-tag>
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              prop="fileSize"
+              label="文件大小"
+              width="100"
+              align="center"
+            >
+              <template slot-scope="scope">
+                {{ formatFileSize(scope.row.fileSize) }}
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              prop="createTime"
+              label="上传时间"
+              width="160"
+              align="center"
+            >
+              <template slot-scope="scope">
+                {{ scope.row.createTime }}
+              </template>
+            </el-table-column>
+
+            <el-table-column
+              label="操作"
+              width="120"
+              align="center"
+              fixed="right"
+            >
+              <template slot-scope="scope">
+                <el-button
+                  type="text"
+                  size="mini"
+                  icon="el-icon-view"
+                  @click="handleViewAttachment(scope.row)"
+                  title="预览"
+                >
+                  预览
+                </el-button>
+                <el-button
+                  type="text"
+                  size="mini"
+                  icon="el-icon-download"
+                  @click="handleDownloadAttachment(scope.row)"
+                  title="下载"
+                >
+                  下载
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="handleCloseViewDialog">关闭</el-button>
+    </div>
+  </el-dialog>
   </basic-container>
 </template>
 
@@ -234,9 +468,11 @@ import {
   update,
   approveWithSmartRecord,
   getApprovalRecords,
+  getApprovalRecordDetail,
   addApprovalRecord,
   updateApprovalRecord,
-  saveApprovalRecord
+  saveApprovalRecord,
+  getActivityAttachments
 } from '@/api/order/marketing-activity'
 import { mapGetters } from 'vuex'
 
@@ -279,6 +515,37 @@ import { mapGetters } from 'vuex'
 export default {
   data() {
     return {
+
+    /**
+     * 查看弹窗显示状态
+     * @type {boolean}
+     */
+    viewDialogVisible: false,
+
+    /**
+     * 当前查看的活动详情
+     * @type {MarketingActivityItem|null}
+     */
+    currentViewActivity: null,
+
+    /**
+     * 附件列表数据
+     * @type {ActivityAttachmentItem[]}
+     */
+    attachmentsList: [],
+
+    /**
+     * 附件加载状态
+     * @type {boolean}
+     */
+    attachmentsLoading: false,
+
+    /**
+     * 详情加载状态
+     * @type {boolean}
+     */
+    detailLoading: false,
+
       /**
        * 审批记录对话框配置
        * @type {Object}
@@ -554,6 +821,148 @@ export default {
 
   methods: {
 
+  /**
+   * 关闭查看弹窗
+   * @returns {void}
+   */
+  handleCloseViewDialog() {
+    this.viewDialogVisible = false
+    this.currentViewActivity = null
+    this.attachmentsList = []
+  },
+
+  /**
+   * 加载活动附件列表
+   * @param {string|number} activityId - 活动ID
+   * @returns {Promise<void>}
+   */
+  async loadActivityAttachments(activityId) {
+    if (!activityId) return
+
+    this.attachmentsLoading = true
+    try {
+      const response = await getActivityAttachments(1, 100, activityId)
+      if (response.data.success) {
+        this.attachmentsList = response.data.data.records || []
+      } else {
+        this.$message.error('获取附件列表失败')
+        this.attachmentsList = []
+      }
+    } catch (error) {
+      console.error('获取附件列表失败:', error)
+      this.$message.error('获取附件列表失败')
+      this.attachmentsList = []
+    } finally {
+      this.attachmentsLoading = false
+    }
+  },
+
+  /**
+   * 预览附件
+   * @param {ActivityAttachmentItem} attachment - 附件信息
+   * @returns {void}
+   */
+  handleViewAttachment(attachment) {
+    if (attachment.fileUrl) {
+      window.open(attachment.fileUrl.trim(), '_blank')
+    } else {
+      this.$message.warning('文件链接不存在')
+    }
+  },
+
+  /**
+   * 下载附件
+   * @param {ActivityAttachmentItem} attachment - 附件信息
+   * @returns {void}
+   */
+  handleDownloadAttachment(attachment) {
+    if (attachment.fileUrl) {
+      const link = document.createElement('a')
+      link.href = attachment.fileUrl.trim()
+      link.download = attachment.fileName
+      link.target = '_blank'
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
+    } else {
+      this.$message.warning('文件链接不存在')
+    }
+  },
+
+  /**
+   * 格式化文件大小
+   * @param {number} bytes - 文件大小(字节)
+   * @returns {string} 格式化后的文件大小
+   */
+  formatFileSize(bytes) {
+    if (!bytes || bytes === 0) return '0 B'
+
+    const k = 1024
+    const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
+    const i = Math.floor(Math.log(bytes) / Math.log(k))
+
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+  },
+
+  /**
+   * 获取文件类型图标
+   * @param {string} fileType - 文件类型
+   * @returns {string} 图标类名
+   */
+  getFileTypeIcon(fileType) {
+    if (!fileType) return 'el-icon-document'
+
+    const type = fileType.toLowerCase()
+    const iconMap = {
+      pdf: 'el-icon-document',
+      doc: 'el-icon-document',
+      docx: 'el-icon-document',
+      xls: 'el-icon-s-grid',
+      xlsx: 'el-icon-s-grid',
+      ppt: 'el-icon-present',
+      pptx: 'el-icon-present',
+      jpg: 'el-icon-picture',
+      jpeg: 'el-icon-picture',
+      png: 'el-icon-picture',
+      gif: 'el-icon-picture',
+      bmp: 'el-icon-picture',
+      txt: 'el-icon-document',
+      zip: 'el-icon-folder-opened',
+      rar: 'el-icon-folder-opened',
+      '7z': 'el-icon-folder-opened'
+    }
+
+    return iconMap[type] || 'el-icon-document'
+  },
+
+  /**
+   * 获取审批状态文本
+   * @param {number} status - 审批状态
+   * @returns {string} 状态文本
+   */
+  getApprovalStatusText(status) {
+    const statusMap = {
+      1: '待审批',
+      2: '审批通过',
+      3: '审批拒绝'
+    }
+    return statusMap[status] || '未知状态'
+  },
+
+  /**
+   * 获取审批状态类型
+   * @param {number} status - 审批状态
+   * @returns {string} 状态类型
+   */
+  getApprovalStatusType(status) {
+    const typeMap = {
+      1: 'warning',
+      2: 'success',
+      3: 'danger'
+    }
+    return typeMap[status] || 'info'
+  },
+
     /**
      * 查看审批记录
      * @param {MarketingActivityRecord} row - 营销活动记录
@@ -644,37 +1053,58 @@ export default {
      * @param {number} status - 审批状态
      * @returns {string} 状态文本
      */
-    getApprovalStatusText(status) {
-      const statusMap = {
-        1: '待审批',
-        2: '审批通过',
-        3: '审批拒绝'
-      }
-      return statusMap[status] || '未知状态'
-    },
+    // getApprovalStatusText(status) {
+    //   const statusMap = {
+    //     1: '待审批',
+    //     2: '审批通过',
+    //     3: '审批拒绝'
+    //   }
+    //   return statusMap[status] || '未知状态'
+    // },
 
     /**
      * 获取审批状态标签类型
      * @param {number} status - 审批状态
      * @returns {string} 标签类型
      */
-    getApprovalStatusType(status) {
-      const typeMap = {
-        1: 'warning',
-        2: 'success',
-        3: 'danger'
-      }
-      return typeMap[status] || 'info'
-    },
+    // getApprovalStatusType(status) {
+    //   const typeMap = {
+    //     1: 'warning',
+    //     2: 'success',
+    //     3: 'danger'
+    //   }
+    //   return typeMap[status] || 'info'
+    // },
 
-    /**
-     * 查看详情
-     * @param {MarketingActivityRecord} row - 行数据
-     * @returns {void}
-     */
-    handleView(row) {
-      this.$refs.crud.rowView(row)
-    },
+  /**
+   * 查看详情
+   * @param {MarketingActivityRecord} row - 行数据
+   * @returns {Promise<void>}
+   */
+  async handleView(row) {
+    this.viewDialogVisible = true
+    this.currentViewActivity = null
+    this.attachmentsList = []
+    this.detailLoading = true
+
+    try {
+      // 获取活动详情
+      const detailRes = await getDetail(row.id)
+      if (detailRes.data.success) {
+        this.currentViewActivity = detailRes.data.data
+
+        // 同时加载附件数据
+        await this.loadActivityAttachments(row.id)
+      } else {
+        this.$message.error('获取活动详情失败')
+      }
+    } catch (error) {
+      console.error('获取活动详情失败:', error)
+      this.$message.error('获取活动详情失败')
+    } finally {
+      this.detailLoading = false
+    }
+  },
 
     /**
      * 处理审批操作
@@ -1031,4 +1461,131 @@ export default {
 .dialog-footer {
   text-align: right;
 }
+/* 查看详情弹窗样式 */
+.activity-detail-dialog .el-dialog__body {
+  padding: 20px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+.activity-detail-container {
+  min-height: 200px;
+}
+
+.activity-detail-content {
+  padding: 0;
+}
+
+.detail-section {
+  margin-bottom: 30px;
+  padding: 20px;
+  background: #fafafa;
+  border-radius: 6px;
+  border: 1px solid #e8e8e8;
+}
+
+.detail-section:last-child {
+  margin-bottom: 0;
+}
+
+.section-title {
+  margin: 0 0 20px 0;
+  padding-bottom: 10px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  border-bottom: 2px solid #409eff;
+}
+
+.detail-item {
+  margin-bottom: 15px;
+  display: flex;
+  align-items: center;
+}
+
+.detail-item:last-child {
+  margin-bottom: 0;
+}
+
+.detail-item label {
+  min-width: 100px;
+  font-weight: 500;
+  color: #666;
+  margin-right: 10px;
+}
+
+.detail-item span {
+  color: #333;
+  word-break: break-all;
+}
+
+.description-content {
+  padding: 15px;
+  background: #fff;
+  border-radius: 4px;
+  border: 1px solid #ddd;
+  line-height: 1.6;
+  color: #333;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+
+/* 附件表格样式 */
+.detail-section .el-table {
+  margin-top: 10px;
+  font-size: 13px;
+}
+
+.detail-section .el-table th {
+  background-color: #f5f7fa;
+  color: #333;
+  font-weight: 600;
+}
+
+.detail-section .el-table td {
+  padding: 8px 0;
+}
+
+.detail-section .el-button--text {
+  padding: 0;
+  margin: 0 4px;
+  font-size: 12px;
+}
+
+.detail-section .el-tag {
+  border-radius: 2px;
+}
+
+/* 弹窗底部按钮 */
+.dialog-footer {
+  text-align: center;
+  padding: 20px 0 10px 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .activity-detail-dialog {
+    width: 95% !important;
+  }
+
+  .detail-item {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  .detail-item label {
+    min-width: auto;
+    margin-bottom: 5px;
+  }
+
+  .detail-section .el-table {
+    font-size: 12px;
+  }
+}
+
+@media (max-width: 1200px) {
+  .activity-detail-dialog {
+    width: 90% !important;
+  }
+}
 </style>