فهرست منبع

feat(operationManagement): 新增优惠券模板详情与发放记录功能

- 新增优惠券模板详情页面,展示基础信息、领取规则和使用规则
- 新增优惠券发放记录页面,支持活动名称筛选和分页查询
- 在优惠券模板列表中添加查看详情按钮,跳转至详情页
- 隐藏菜单项"优惠券发放记录",调整其在导航中的可见性
- 优化金额展示格式,统一使用货币符号和小数点处理
- 添加日期格式化工具函数,提升时间展示一致性
- 完善接口调用逻辑,确保数据正确加载和错误提示
- 引入权限控制,在页面加载时校验用户查看权限
- 更新表格组件配置,增强列渲染和搜索功能
- 修复最低消费金额判断逻辑,自动设置是否有低消状态
- 添加补充说明字段展示,支持HTML内容渲染
- 调整页面样式布局,实现左右双栏信息展示结构
- 增强代码健壮性,添加参数校验和异常捕获处理
- 规范命名和注释,提高代码可读性和维护性
- 优化响应式数据结构,合理组织优惠券详情信息模型
- 统一空值显示处理,缺失数据统一展示为"--"
- 提升用户体验,增加返回上一页功能和友好提示信息
congxuesong 2 هفته پیش
والد
کامیت
d297a50b2c

+ 1 - 1
src/assets/json/authMenuList.json

@@ -609,7 +609,7 @@
             "icon": "FolderOpened",
             "title": "优惠券发放记录",
             "isLink": "",
-            "isHide": false,
+            "isHide": true,
             "isFull": false,
             "isAffix": false,
             "isKeepAlive": false

+ 314 - 0
src/views/operationManagement/couponDetail.vue

@@ -0,0 +1,314 @@
+<template>
+  <!-- 优惠券管理 - 详情页面 -->
+  <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
+    <div class="header">
+      <el-button @click="goBack"> 返回 </el-button>
+      <h2 class="title">优惠券模板详情</h2>
+    </div>
+    <div class="content">
+      <!-- 左侧内容区域 -->
+      <div class="contentLeft">
+        <!-- 基础信息模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">基础信息:</h3>
+          <!-- 优惠券名称 -->
+          <div class="detail-item">
+            <div class="detail-label">优惠券名称</div>
+            <div class="detail-value">
+              {{ couponModel.name || "--" }}
+            </div>
+          </div>
+          <!-- 面值 -->
+          <div class="detail-item">
+            <div class="detail-label">面值(元)</div>
+            <div class="detail-value">
+              {{ formatCurrency(couponModel.nominalValue, 2, "¥") }}
+            </div>
+          </div>
+          <!-- 开始领取时间 -->
+          <div class="detail-item">
+            <div class="detail-label">开始领取时间</div>
+            <div class="detail-value">
+              {{ couponModel.beginGetDate ? formatDate(couponModel.beginGetDate) : "--" }}
+            </div>
+          </div>
+          <!-- 结束领取时间 -->
+          <div class="detail-item">
+            <div class="detail-label">结束领取时间</div>
+            <div class="detail-value">
+              {{ couponModel.endGetDate ? formatDate(couponModel.endGetDate) : "--" }}
+            </div>
+          </div>
+          <!-- 有效期 -->
+          <div class="detail-item">
+            <div class="detail-label">有效期</div>
+            <div class="detail-value">
+              {{ couponModel.specifiedDay ? `购买后${couponModel.specifiedDay}天` : "--" }}
+            </div>
+          </div>
+          <!-- 库存 -->
+          <div class="detail-item">
+            <div class="detail-label">库存</div>
+            <div class="detail-value">
+              {{ couponModel.singleQty || "--" }}
+            </div>
+          </div>
+        </div>
+        <!-- 领取规则模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">领取规则:</h3>
+          <!-- 用户领取规则 -->
+          <div class="detail-item">
+            <div class="detail-label">用户领取规则</div>
+            <div class="detail-value">
+              {{ getClaimRuleText() }}
+            </div>
+          </div>
+          <!-- 用户是否需要收藏店铺领取 -->
+          <div class="detail-item">
+            <div class="detail-label">用户是否需要收藏店铺领取</div>
+            <div class="detail-value">
+              {{ couponModel.attentionCanReceived === 1 ? "是" : couponModel.attentionCanReceived === 0 ? "否" : "--" }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧内容区域 -->
+      <div class="contentRight">
+        <!-- 使用规则模块 -->
+        <div class="model">
+          <h3 style="font-weight: bold">使用规则:</h3>
+          <!-- 是否有低消 -->
+          <div class="detail-item">
+            <div class="detail-label">是否有低消</div>
+            <div class="detail-value">
+              {{ couponModel.hasMinimumSpend === 1 ? "是" : couponModel.hasMinimumSpend === 0 ? "否" : "--" }}
+            </div>
+          </div>
+          <!-- 最低消费金额 -->
+          <div class="detail-item" v-if="couponModel.hasMinimumSpend === 1">
+            <div class="detail-label">最低消费金额</div>
+            <div class="detail-value">
+              {{ couponModel.minimumSpendingAmount ? `¥${couponModel.minimumSpendingAmount}` : "--" }}
+            </div>
+          </div>
+          <!-- 补充说明 -->
+          <div class="detail-item">
+            <div class="detail-label">补充说明</div>
+            <div class="detail-value" v-html="couponModel.supplementaryInstruction || '--'" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="couponTemplateDetail">
+/**
+ * 优惠券模板 - 详情页面
+ * 功能:显示优惠券的详细信息
+ */
+import { ref, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import { getCouponDetail } from "@/api/modules/couponManagement";
+import { formatCurrency } from "@/utils/formatCurrency";
+
+// ==================== 响应式数据定义 ====================
+
+// 路由相关
+const router = useRouter();
+const route = useRoute();
+
+// 页面ID参数
+const id = ref<string>("");
+
+// ==================== 优惠券信息数据模型 ====================
+const couponModel = ref<any>({
+  // 优惠券名称
+  name: "",
+  // 面值(元)
+  nominalValue: "",
+  // 开始领取时间
+  beginGetDate: "",
+  // 结束领取时间
+  endGetDate: "",
+  // 有效期
+  specifiedDay: "",
+  // 库存
+  singleQty: "",
+  // 用户领取规则:day-每日一领,week-每周一领,month-每月一领
+  claimRule: "day",
+  // 用户是否需要收藏店铺领取:1-是,0-否
+  attentionCanReceived: 0,
+  // 是否有低消:1-是,0-否
+  hasMinimumSpend: 0,
+  // 最低消费金额
+  minimumSpendingAmount: "",
+  // 补充说明
+  supplementaryInstruction: ""
+});
+
+// ==================== 生命周期钩子 ====================
+
+/**
+ * 组件挂载时初始化
+ * 从路由参数中获取ID并加载详情数据
+ */
+onMounted(async () => {
+  id.value = (route.query.id as string) || "";
+  if (id.value) {
+    await loadDetailData();
+  } else {
+    ElMessage.warning("缺少优惠券ID参数");
+  }
+});
+
+// ==================== 事件处理函数 ====================
+
+/**
+ * 返回上一页
+ */
+const goBack = () => {
+  router.go(-1);
+};
+
+// ==================== 数据加载函数 ====================
+
+/**
+ * 加载详情数据
+ */
+const loadDetailData = async () => {
+  try {
+    const res: any = await getCouponDetail({ counponId: id.value });
+    if (res && res.code == 200) {
+      // 合并主数据
+      couponModel.value = { ...couponModel.value, ...res.data };
+      // 根据最低消费金额设置是否有低消
+      const amount = Number(couponModel.value.minimumSpendingAmount);
+      if (!isNaN(amount) && amount > 0) {
+        couponModel.value.hasMinimumSpend = 1;
+      } else {
+        couponModel.value.hasMinimumSpend = 0;
+      }
+    } else {
+      ElMessage.error("加载详情数据失败");
+    }
+  } catch (error) {
+    console.error("加载详情数据出错:", error);
+    ElMessage.error("加载详情数据出错");
+  }
+};
+
+// ==================== 工具函数 ====================
+
+/**
+ * 格式化日期
+ * @param date 日期字符串 (YYYY-MM-DD)
+ * @returns 格式化后的日期字符串 (YYYY.MM.DD)
+ */
+const formatDate = (date: string) => {
+  if (!date) return "--";
+  return date.replace(/-/g, "/");
+};
+
+/**
+ * 获取用户领取规则文本
+ */
+const getClaimRuleText = () => {
+  const ruleMap: Record<string, string> = {
+    day: "每日一领",
+    week: "每周一领",
+    month: "每月一领"
+  };
+  return ruleMap[couponModel.value.claimRule] || "--";
+};
+</script>
+
+<style scoped lang="scss">
+/* 页面容器 */
+.table-box {
+  display: flex;
+  flex-direction: column;
+  height: auto !important;
+  min-height: 100%;
+}
+
+/* 头部区域 */
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20px 24px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  box-shadow: 0 2px 4px rgb(0 0 0 / 2%);
+}
+.title {
+  flex: 1;
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+  text-align: center;
+}
+
+/* 内容区域布局 */
+.content {
+  display: flex;
+  flex: 1;
+  column-gap: 24px;
+  width: 98%;
+  padding: 0 12px;
+  margin: 24px auto;
+
+  /* 左侧内容区域 */
+  .contentLeft {
+    width: 50%;
+    padding-right: 12px;
+  }
+
+  /* 右侧内容区域 */
+  .contentRight {
+    width: 50%;
+    padding-left: 12px;
+  }
+}
+
+/* 模块容器 */
+.model {
+  margin-bottom: 50px;
+  h3 {
+    padding-bottom: 12px;
+    margin: 0 0 20px;
+    font-size: 16px;
+    color: #303133;
+    border-bottom: 2px solid #e4e7ed;
+  }
+}
+
+/* 详情项样式 */
+.detail-item {
+  display: flex;
+  align-items: flex-start;
+  min-height: 32px;
+  margin-bottom: 24px;
+}
+.detail-label {
+  flex-shrink: 0;
+  min-width: 200px;
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 32px;
+  color: #606266;
+}
+.detail-value {
+  flex: 1;
+  font-size: 14px;
+  line-height: 32px;
+  color: #303133;
+  word-break: break-word;
+}
+.empty-text {
+  color: #909399;
+}
+</style>

+ 120 - 0
src/views/operationManagement/couponIssuanceRecords.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="table-box button-table">
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :data-callback="dataCallback"
+      :init-param="initParam"
+      :request-api="getTableList"
+    />
+  </div>
+</template>
+
+<script lang="tsx" name="couponIssuanceRecords" setup>
+import { onMounted, reactive, ref } from "vue";
+import ProTable from "@/components/ProTable/index.vue";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { getActivityList, getCouponTemplateList } from "@/api/modules/operationManagement";
+import { localGet } from "@/utils";
+
+const proTable = ref<ProTableInstance>();
+
+// 表格列配置
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "couponName",
+    label: "优惠券名称"
+  },
+  {
+    prop: "couponId",
+    label: "优惠券ID"
+  },
+  {
+    prop: "issuanceTime",
+    label: "发放时间",
+    render: (scope: any) => {
+      return scope.row.issuanceTime?.replace(/-/g, "/") || "--";
+    }
+  },
+  {
+    prop: "activityName",
+    label: "活动名称",
+    search: {
+      el: "select",
+      props: { placeholder: "请选择", filterable: true }
+    },
+    enum: async () => {
+      const params = {
+        storeId: localGet("createdId"),
+        status: "5",
+        pageNum: 1,
+        pageSize: 99999
+      };
+      const res: any = await getActivityList(params);
+      if (res && res.code == 200 && res.data) {
+        const data = res.data as any;
+        if (data.records && Array.isArray(data.records)) {
+          return {
+            data: data.records.map((item: any) => ({
+              label: item.activityName,
+              value: item.activityName
+            }))
+          };
+        }
+        return { data: [] };
+      }
+      return { data: [] };
+    },
+    fieldNames: { label: "label", value: "value" }
+  },
+  {
+    prop: "activityId",
+    label: "活动ID"
+  },
+  {
+    prop: "userId",
+    label: "用户ID"
+  },
+  {
+    prop: "isUsed",
+    label: "是否使用",
+    render: (scope: any) => {
+      return scope.row.isUsed === 1 || scope.row.isUsed === true ? "是" : "否";
+    },
+    // search: {
+    //   el: "select",
+    //   props: { placeholder: "请选择" }
+    // },
+    // enum: [
+    //   { label: "是", value: 1 },
+    //   { label: "否", value: 0 }
+    // ],
+    fieldNames: { label: "label", value: "value" }
+  }
+]);
+
+// 初始化请求参数
+const initParam = reactive({
+  storeId: localGet("createdId")
+});
+
+// 数据回调处理
+const dataCallback = (data: any) => {
+  return {
+    list: data?.records || [],
+    total: data?.total || 0
+  };
+};
+
+// 获取表格列表
+const getTableList = (params: any) => {
+  return getCouponTemplateList(params);
+};
+
+// 页面加载时触发查询
+onMounted(() => {
+  proTable.value?.getTableList();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 126 - 0
src/views/operationManagement/couponTemplate.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="table-box button-table">
+    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button link type="primary" @click="toDetail(scope.row)"> 查看详情 </el-button>
+      </template>
+    </ProTable>
+  </div>
+</template>
+
+<script setup lang="tsx" name="couponTemplate">
+import { reactive, ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import ProTable from "@/components/ProTable/index.vue";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { getCouponList } from "@/api/modules/operationManagement";
+import { localGet, usePermission } from "@/utils";
+import { formatCurrency } from "@/utils/formatCurrency";
+
+const router = useRouter();
+const proTable = ref<ProTableInstance>();
+
+// 优惠券状态枚举
+const statusEnumY = [
+  { label: "草稿", value: "3" },
+  { label: "进行中", value: "1" },
+  { label: "已结束", value: "2" }
+];
+
+// 表格列配置
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "name",
+    label: "优惠券名称",
+    search: {
+      el: "input",
+      props: { placeholder: "请输入" }
+    }
+  },
+  {
+    prop: "id",
+    label: "优惠券ID"
+  },
+  {
+    prop: "createdTime",
+    label: "创建时间",
+    render: (scope: any) => {
+      if (scope.row.createdTime) {
+        return scope.row.createdTime.replace(/-/g, "/");
+      }
+      return "--";
+    }
+  },
+  {
+    prop: "nominalValue",
+    label: "面值",
+    render: (scope: any) => {
+      return formatCurrency(scope.row.nominalValue, 2, "¥") || "--";
+    }
+  },
+  {
+    prop: "minimumSpendingAmount",
+    label: "消费金额门槛",
+    render: (scope: any) => {
+      if (scope.row.minimumSpendingAmount) {
+        return formatCurrency(scope.row.minimumSpendingAmount, 2, "¥");
+      }
+      return "--";
+    }
+  },
+  {
+    prop: "couponStatus",
+    label: "优惠券状态",
+    isShow: false,
+    search: {
+      el: "select",
+      props: { placeholder: "请选择" }
+    },
+    enum: statusEnumY,
+    fieldNames: { label: "label", value: "value" }
+  },
+  { prop: "operation", label: "操作", fixed: "right" }
+]);
+
+// 初始化请求参数
+const initParam = reactive({
+  storeId: localGet("createdId"),
+  groupType: localGet("businessSection"),
+  couponType: "2", // 优惠券类型
+  couponsFromType: 1
+});
+
+// 数据回调处理
+const dataCallback = (data: any) => {
+  // 优惠券从 discountList.records 取值
+  return {
+    list: data.discountList?.records || [],
+    total: data.discountList?.total || 0
+  };
+};
+
+// 获取表格列表
+const getTableList = (params: any) => {
+  let newParams = JSON.parse(JSON.stringify(params));
+  // 优惠券相关参数
+  delete newParams.dataType;
+  newParams.couponType = "2";
+  newParams.couponsFromType = 1;
+  return getCouponList(newParams);
+};
+
+// 跳转详情页
+const toDetail = (row: any) => {
+  router.push(`/operationManagement/couponTemplate/detail?id=${row.id}`);
+};
+
+const type = ref(false);
+// 页面加载时触发查询
+onMounted(async () => {
+  type.value = await usePermission("查看优惠券模板");
+  proTable.value?.getTableList();
+});
+</script>
+
+<style lang="scss" scoped></style>