zhuli 1 semana atrás
pai
commit
3e48a375b0

+ 19 - 5
src/api/modules/dynamicManagement.ts

@@ -168,14 +168,28 @@ export const cancelFollowed = (params: {
   return httpStore.post(PORT_NONE + `/user/cancelFollewed`, params);
 };
 
-// 点赞动态(新接口)
+// 点赞动态(新接口)- 表单方式提交
 export const likeDynamicNew = (params: {
-  // 动态ID
-  userId: string;
-  huifuId: number; // 当前用户phoneId
+  userId: string; // 当前用户phoneId
+  huifuId: number; // 动态ID
   type: number; // 类型:2表示点赞
 }) => {
-  return httpStore.post(PORT_NONE + `/comment/like`, params);
+  const formData = new FormData();
+  formData.append("userId", params.userId);
+  formData.append("huifuId", String(params.huifuId));
+  formData.append("type", String(params.type));
+  return httpStore.post(PORT_NONE + `/comment/like`, formData);
+};
+
+// 列表点赞动态 - 表单方式提交
+export const likeDynamicList = (params: {
+  dynamicId: number; // 动态ID
+  phoneId: string; // 当前用户phoneId
+}) => {
+  const formData = new FormData();
+  formData.append("dynamicId", String(params.dynamicId));
+  formData.append("phoneId", params.phoneId);
+  return httpStore.post(PORT_NONE + `/userDynamics/like`, formData);
 };
 
 // 举报动态

+ 69 - 0
src/api/modules/newLoginApi.ts

@@ -73,3 +73,72 @@ export const getAiapprovestoreInfo = params => {
 export const getStoreOcrData = params => {
   return httpLogin.get(`/alienStore/store/info/getStoreOcrData`, params);
 };
+// 获取好友优惠券列表
+export const getFriendCouponList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getReceivedSendFriendCouponList`, params);
+};
+
+// 获取好友优惠券列表
+export const getFriendCouponDetail = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon/getCounponDetailById`, params);
+};
+
+// 好友列表
+export const getMutualAttention = (params: any) => {
+  return httpLogin.get(`/alienStore/stores/getMutualAttention`, params);
+};
+
+// 优惠券列表
+export const getCouponList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon/getStoreAllCouponListPaginateNot`, params);
+};
+
+// 赠送好友优惠券
+export const setFriendCoupon = (params: any) => {
+  return httpLogin.post(`/alienStore/life-discount-coupon-store-friend/setFriendCoupon`, params);
+};
+
+//好友关系管理列表
+export const getRuleList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getRuleList`, params);
+};
+
+//添加活动-选择商家
+export const getReceivedFriendCouponList = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getReceivedFriendCouponList`, params);
+};
+
+//保存活动
+export const saveFriendCouponRule = (params: any) => {
+  return httpLogin.post(`/alienStore/life-discount-coupon-store-friend/saveFriendCouponRule`, params);
+};
+//删除活动
+export const delFriendCouponRule = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/delFriendCouponRule`, params);
+};
+
+//编辑回显
+export const getRuleById = (params: any) => {
+  return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getRuleById`, params);
+};
+
+//查询评价列表
+export const getList = (params: any) => {
+  return httpLogin.get(`/alienStore/storeComment/getList`, params);
+};
+
+//申诉历史
+export const getAppealHistory = (params: any) => {
+  return httpLogin.get(`/alienStore/storeCommentAppeal/getAppealHistory`, params);
+};
+
+//申诉详情
+export const getAppealDetail = (params: any) => {
+  return httpLogin.get(`/alienStore/storeCommentAppeal/getAppealDetail`, params);
+};
+
+//申诉删除
+
+export const addAppealNew = (params: any) => {
+  return httpLogin.post(`/alienStore/storeCommentAppeal/addAppealNew`, params);
+};

+ 1 - 0
src/utils/permission.ts

@@ -186,6 +186,7 @@ export async function checkMenuClickPermission(path?: string): Promise<{
         messages.push("合同已到期,请上传最新合同。");
       }
       if (foodBusinessLicense) {
+        console.log(foodBusinessLicense);
         messages.push("食品经营许可证已到期,请上传最新许可证。");
       }
       if (entertainmentBusinessLicense) {

+ 136 - 31
src/views/dynamicManagement/friendCoupon.vue

@@ -32,13 +32,20 @@
     <el-dialog v-model="giftDialogVisible" title="赠送好友优惠券" width="600px" @close="closeGiftDialog">
       <el-form ref="giftFormRef" :model="giftFormData" :rules="giftRules" label-width="120px">
         <el-form-item label="选择好友" prop="friendId">
-          <el-select v-model="giftFormData.friendId" placeholder="请选择好友" style="width: 100%">
+          <el-select v-model="giftFormData.friendId" placeholder="请选择好友" style="width: 100%" :loading="friendListLoading">
             <el-option v-for="friend in friendList" :key="friend.id" :label="friend.name" :value="friend.id" />
           </el-select>
         </el-form-item>
 
         <el-form-item label="选择优惠券" prop="couponId">
-          <el-select v-model="giftFormData.couponId" placeholder="请选择优惠券" style="width: 100%" @change="handleCouponChange">
+          <el-select
+            v-model="giftFormData.couponId"
+            placeholder="请选择优惠券"
+            style="width: 100%"
+            :loading="couponListLoading"
+            @focus="loadCouponList"
+            @change="handleCouponChange"
+          >
             <el-option v-for="coupon in couponList" :key="coupon.id" :label="coupon.name" :value="coupon.id" />
           </el-select>
           <div v-if="giftFormData.couponId" class="coupon-info">
@@ -69,7 +76,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
 import { localGet } from "@/utils";
-import { getFriendCouponList, sendCouponToFriend, deleteFriendCoupon } from "@/api/modules/friendCoupon";
+import { getFriendCouponList, getMutualAttention, getCouponList, setFriendCoupon } from "@/api/modules/newLoginApi";
 
 const router = useRouter();
 
@@ -83,19 +90,88 @@ const proTable = ref<ProTableInstance>();
 const giftDialogVisible = ref(false);
 const giftFormRef = ref<FormInstance>();
 
-// 好友列表(模拟数据)
-const friendList = ref([
-  { id: 1, name: "张三" },
-  { id: 2, name: "李四" },
-  { id: 3, name: "王五" }
-]);
+// 好友列表
+const friendList = ref<any[]>([]);
+const friendListLoading = ref(false);
+const friendListLoaded = ref(false); // 标记是否已加载
+
+// 优惠券列表
+const couponList = ref<any[]>([]);
+const couponListLoading = ref(false);
+const couponListLoaded = ref(false); // 标记是否已加载
+
+// 加载好友列表
+const loadFriendList = async () => {
+  // 如果已经加载过,则不重复加载
+  if (friendListLoaded.value || friendListLoading.value) {
+    return;
+  }
 
-// 优惠券列表(模拟数据)
-const couponList = ref([
-  { id: 1, name: "满100减20代金券", stock: 50 },
-  { id: 2, name: "8折优惠券", stock: 30 },
-  { id: 3, name: "满200减50代金券", stock: 20 }
-]);
+  friendListLoading.value = true;
+
+  try {
+    const res: any = await getMutualAttention({
+      page: 1,
+      size: 999, // 获取所有好友
+      fansId: "store_" + localGet("geeker-user")?.userInfo?.phone
+    });
+
+    if (res.code === 200) {
+      friendList.value = res.data.records.map((item: any) => ({
+        id: item.id,
+        name: item.username
+      }));
+      friendListLoaded.value = true;
+    } else {
+      // 如果接口失败,使用模拟数据
+      friendList.value = [];
+      friendListLoaded.value = true;
+    }
+  } catch (error) {
+    console.error("加载好友列表失败:", error);
+    // 使用模拟数据作为备用
+    friendList.value = [];
+    friendListLoaded.value = true;
+  } finally {
+    friendListLoading.value = false;
+  }
+};
+
+// 加载优惠券列表
+const loadCouponList = async () => {
+  // 如果已经加载过,则不重复加载
+  if (couponListLoaded.value || couponListLoading.value) {
+    return;
+  }
+
+  couponListLoading.value = true;
+
+  try {
+    const res: any = await getCouponList({
+      storeId: localGet("createdId"),
+      status: 0
+    });
+
+    if (res.code === 200) {
+      couponList.value = res.data.map((item: any) => ({
+        id: item.id,
+        name: item.name
+      }));
+      couponListLoaded.value = true;
+    } else {
+      // 如果接口失败,使用模拟数据
+      couponList.value = [];
+      couponListLoaded.value = true;
+    }
+  } catch (error) {
+    console.error("加载优惠券列表失败:", error);
+    // 使用模拟数据作为备用
+    couponList.value = [];
+    couponListLoaded.value = true;
+  } finally {
+    couponListLoading.value = false;
+  }
+};
 
 // 赠送表单数据
 const giftFormData = reactive({
@@ -203,16 +279,28 @@ const myGiftColumns = reactive<ColumnProps<any>[]>([
 const columns = computed(() => {
   return activeName.value === "friendMessage" ? friendMessageColumns : myGiftColumns;
 });
-
-// 初始化请求参数
+// 初始化请求参数 - 好友赠我传 storeUserId
 const initParam = reactive({
-  storeId: localGet("createdId") || "",
+  storeUserId: localGet("createdId"), // 好友赠我:当前店铺ID(接收方)
+  friendStoreUserId: undefined as number | undefined, // 我赠好友:当前用户ID(赠送方)
   type: activeName.value
 });
 
 // Tab切换处理
 const handleTabClick = () => {
   initParam.type = activeName.value;
+
+  // 根据当前 tab 设置正确的参数
+  if (activeName.value === "myGift") {
+    // 好友赠我:传 storeUserId
+    initParam.storeUserId = localGet("createdId");
+    initParam.friendStoreUserId = undefined;
+  } else {
+    // 我赠好友:传 friendStoreUserId
+    initParam.storeUserId = undefined;
+    initParam.friendStoreUserId = localGet("geeker-user").userInfo.id;
+  }
+
   proTable.value?.getTableList();
 };
 
@@ -237,6 +325,9 @@ const getTableList = (params: any) => {
 // 打开赠送对话框
 const openGiftDialog = () => {
   giftDialogVisible.value = true;
+  loadFriendList();
+  loadCouponList();
+  // 点击下拉框时才会加载数据(通过 @focus 事件触发)
 };
 
 // 关闭赠送对话框
@@ -248,6 +339,9 @@ const closeGiftDialog = () => {
     couponId: "",
     quantity: 1
   });
+  // 重置加载状态,下次打开时重新加载
+  friendListLoaded.value = false;
+  couponListLoaded.value = false;
 };
 
 // 优惠券改变时
@@ -262,18 +356,29 @@ const handleGiftSubmit = async () => {
   await giftFormRef.value.validate(async (valid: boolean) => {
     if (valid) {
       try {
-        // TODO: 集成真实接口时,取消下面的注释
-        // await sendCouponToFriend({
-        //   friendId: giftFormData.friendId,
-        //   couponId: giftFormData.couponId,
-        //   quantity: giftFormData.quantity
-        // });
-
-        ElMessage.success("赠送成功");
-        closeGiftDialog();
-        proTable.value?.getTableList();
-      } catch (error) {
-        ElMessage.error("赠送失败");
+        // 调用赠送接口
+        const params = {
+          couponIds: [
+            {
+              couponId: giftFormData.couponId,
+              singleQty: giftFormData.quantity
+            }
+          ],
+          friendStoreUserId: String(giftFormData.friendId)
+        };
+
+        const res: any = await setFriendCoupon(params);
+
+        if (res && res.code === 200) {
+          ElMessage.success("赠送成功");
+          closeGiftDialog();
+          proTable.value?.getTableList();
+        } else {
+          ElMessage.error(res?.msg || "赠送失败");
+        }
+      } catch (error: any) {
+        console.error("赠送失败:", error);
+        ElMessage.error(error?.message || "赠送失败");
       }
     }
   });
@@ -284,7 +389,7 @@ const viewDetail = (row: any) => {
   router.push({
     path: "/dynamicManagement/friendCouponDetail",
     query: {
-      id: row.id,
+      couponId: row.couponId,
       type: activeName.value
     }
   });

+ 13 - 34
src/views/dynamicManagement/friendCouponDetail.vue

@@ -15,14 +15,14 @@
           <div class="detail-item">
             <div class="detail-label">店铺名称</div>
             <div class="detail-value">
-              {{ couponModel.storeName || "--" }}
+              {{ couponModel.name || "--" }}
             </div>
           </div>
           <!-- 优惠券名称 -->
           <div class="detail-item">
             <div class="detail-label">优惠券名称</div>
             <div class="detail-value">
-              {{ couponModel.couponName || "--" }}
+              {{ couponModel.name || "--" }}
             </div>
           </div>
           <!-- 面值 -->
@@ -36,14 +36,14 @@
           <div class="detail-item">
             <div class="detail-label">有效期至</div>
             <div class="detail-value">
-              {{ couponModel.endDate ? formatDate(couponModel.endDate) : "--" }}
+              {{ couponModel.endGetDate }}
             </div>
           </div>
           <!-- 优惠券数量 -->
           <div class="detail-item">
             <div class="detail-label">优惠券数量</div>
             <div class="detail-value">
-              {{ couponModel.couponNum || "--" }}
+              {{ couponModel.singleQty || "--" }}
             </div>
           </div>
           <!-- 最低消费金额 -->
@@ -117,7 +117,7 @@
 import { ref, onMounted } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { ElMessage } from "element-plus";
-import { getFriendCouponList } from "@/api/modules/friendCoupon";
+import { getFriendCouponDetail } from "@/api/modules/newLoginApi";
 import { formatCurrency } from "@/utils/formatCurrency";
 import { localGet } from "@/utils";
 
@@ -171,10 +171,10 @@ const couponModel = ref<any>({
 
 /**
  * 组件挂载时初始化
- * 从路由参数中获取ID并加载详情数据
+ * 从路由参数中获取couponId并加载详情数据
  */
 onMounted(async () => {
-  id.value = (route.query.id as string) || "";
+  id.value = (route.query.couponId as string) || "";
   type.value = (route.query.type as string) || "";
   if (id.value) {
     await loadDetailData();
@@ -199,42 +199,21 @@ const goBack = () => {
  */
 const loadDetailData = async () => {
   try {
-    // 临时方案:使用列表接口获取数据后根据ID筛选
-    // TODO: 如果后端提供了单独的详情接口,可以替换这里的实现
-    const res: any = await getFriendCouponList({
-      type: type.value === "friendMessage" ? 0 : 1,
-      storeId: localGet("createdId") || ""
+    // 使用 couponId 获取详情数据
+    const res: any = await getFriendCouponDetail({
+      counponId: id.value
     });
 
-    if (res && res.code === 0 && res.success) {
-      // 从列表中找到对应的数据
-      const detail = res.data?.find((item: any) => item.id === Number(id.value));
-      if (detail) {
-        couponModel.value = { ...couponModel.value, ...detail };
-      } else {
-        ElMessage.error("未找到对应的优惠券详情");
-      }
+    if (res.code === 200) {
+      couponModel.value = res.data;
     } else {
-      ElMessage.error("加载详情数据失败");
+      ElMessage.error(res.msg);
     }
   } 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, "/");
-};
-
 /**
  * 获取状态文本
  */

+ 338 - 181
src/views/dynamicManagement/friendRelation.vue

@@ -4,7 +4,7 @@
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
         <div class="header-button">
-          <el-button type="primary" @click="openAddDialog"> 添加好友 </el-button>
+          <el-button type="primary" @click="openAddDialog"> 添加活动 </el-button>
         </div>
       </template>
 
@@ -23,52 +23,66 @@
       </template>
     </ProTable>
 
-    <!-- 添加/编辑好友对话框 -->
+    <!-- 添加/编辑活动对话框 -->
     <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" @close="closeDialog">
       <el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px">
-        <el-form-item label="好友名称" prop="friendName">
-          <el-input v-model="formData.friendName" placeholder="请输入好友名称" clearable />
+        <el-form-item label="活动名称" prop="acName">
+          <el-input v-model="formData.acName" placeholder="请输入" clearable />
         </el-form-item>
 
-        <el-form-item label="好友手机号" prop="friendPhone">
-          <el-input v-model="formData.friendPhone" placeholder="请输入手机号" maxlength="11" clearable />
-        </el-form-item>
-
-        <el-form-item label="好友备注" prop="remark">
-          <el-input
-            v-model="formData.remark"
-            type="textarea"
-            :rows="3"
-            placeholder="请输入备注信息"
-            maxlength="200"
-            show-word-limit
-          />
-        </el-form-item>
-
-        <el-form-item label="关系类型" prop="relationType">
-          <el-select v-model="formData.relationType" placeholder="请选择关系类型" style="width: 100%">
-            <el-option v-for="item in relationTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
-          </el-select>
+        <el-form-item label="消费门槛金额(¥)" required>
+          <div style="display: flex; gap: 10px; align-items: center; width: 100%">
+            <el-form-item prop="moneyLow" style=" flex: 1;margin-bottom: 0">
+              <el-input v-model.number="formData.moneyLow" placeholder="0.00" clearable>
+                <template #prefix> ¥ </template>
+              </el-input>
+            </el-form-item>
+            <span>~</span>
+            <el-form-item prop="moneyHigh" style=" flex: 1;margin-bottom: 0">
+              <el-input v-model.number="formData.moneyHigh" placeholder="0.00" clearable>
+                <template #prefix> ¥ </template>
+              </el-input>
+            </el-form-item>
+          </div>
         </el-form-item>
 
-        <el-form-item label="赠送优惠券" prop="couponList">
-          <div class="coupon-list">
-            <div v-for="(item, index) in formData.couponList" :key="index" class="coupon-item">
-              <el-select v-model="item.couponId" placeholder="请选择优惠券" style="width: 300px; margin-right: 10px">
-                <el-option v-for="coupon in availableCoupons" :key="coupon.id" :label="coupon.name" :value="coupon.id" />
+        <el-form-item label="来源商家及优惠券">
+          <div class="merchant-coupon-list">
+            <div v-for="(item, index) in formData.details" :key="index" class="merchant-coupon-item">
+              <el-select
+                v-model="item.friendStoreUserId"
+                placeholder="选择商家"
+                style="width: 200px; margin-right: 10px"
+                @change="handleMerchantChange(index)"
+              >
+                <el-option
+                  v-for="merchant in merchantList"
+                  :key="merchant.couponId"
+                  :label="merchant.storeName"
+                  :value="merchant.couponId"
+                />
               </el-select>
-              <el-input-number
-                v-model="item.quantity"
-                :min="1"
-                :max="100"
-                placeholder="数量"
-                style="width: 120px; margin-right: 10px"
-              />
-              <el-button type="danger" link @click="removeCoupon(index)"> 删除 </el-button>
+              <el-select
+                v-model="item.couponId"
+                placeholder="选择优惠券"
+                style="width: 200px; margin-right: 10px"
+                @change="handleCouponChange(index)"
+              >
+                <el-option
+                  v-for="coupon in item.couponList"
+                  :key="coupon.couponId || coupon.id"
+                  :label="coupon.couponName"
+                  :value="coupon.couponId || coupon.id"
+                />
+              </el-select>
+              <el-input v-model="item.remainingCount" placeholder="剩余张数1000张" style="width: 180px; margin-right: 10px" />
+              <el-button type="danger" link @click="removeMerchantCoupon(index)" v-if="formData.details.length > 1">
+                删除
+              </el-button>
             </div>
-            <el-button type="primary" link @click="addCoupon" style="margin-top: 10px">
+            <el-button type="primary" link @click="addMerchantCoupon" style="margin-top: 10px">
               <el-icon><Plus /></el-icon>
-              添加优惠券
+              添加商家优惠券
             </el-button>
           </div>
         </el-form-item>
@@ -92,100 +106,77 @@ import { Plus } from "@element-plus/icons-vue";
 import ProTable from "@/components/ProTable/index.vue";
 import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
 import { localGet } from "@/utils";
-// import { getFriendRelationList, addFriendRelation, updateFriendRelation, deleteFriendRelation, approveFriend } from "@/api/modules/friendRelation";
+
+import {
+  getRuleList,
+  saveFriendCouponRule,
+  getReceivedFriendCouponList,
+  delFriendCouponRule,
+  getRuleById
+} from "@/api/modules/newLoginApi";
 
 // ProTable 实例
 const proTable = ref<ProTableInstance>();
 
 // 对话框相关
 const dialogVisible = ref(false);
-const dialogTitle = computed(() => (isEdit.value ? "编辑好友" : "添加好友"));
+const dialogTitle = computed(() => (isEdit.value ? "编辑活动" : "添加活动"));
 const isEdit = ref(false);
 const currentEditId = ref("");
 
 // 表单引用
 const formRef = ref<FormInstance>();
 
-// 关系类型选项
-const relationTypeOptions = [
-  { label: "普通好友", value: 1 },
-  { label: "亲密好友", value: 2 },
-  { label: "商业伙伴", value: 3 }
-];
-
-// 可用优惠券列表(模拟数据)
-const availableCoupons = ref([
-  { id: 1, name: "满100减20代金券" },
-  { id: 2, name: "8折优惠券" },
-  { id: 3, name: "满200减50代金券" }
-]);
+// 商家列表
+const merchantList = ref<any[]>([]);
 
 // 表单数据
 const formData = reactive({
-  friendName: "",
-  friendPhone: "",
-  remark: "",
-  relationType: "",
-  couponList: [] as Array<{ couponId: string | number; quantity: number }>
+  acName: "",
+  moneyLow: undefined as number | undefined,
+  moneyHigh: undefined as number | undefined,
+  details: [] as Array<{
+    friendStoreUserId: string | number;
+    couponId: string | number;
+    couponList: any[]; // 当前商家的优惠券列表
+    remainingCount: number; // 剩余张数
+  }>
 });
 
 // 表单验证规则
 const formRules = reactive<FormRules>({
-  friendName: [{ required: true, message: "请输入好友名称", trigger: "blur" }],
-  friendPhone: [
-    { required: true, message: "请输入手机号", trigger: "blur" },
-    {
-      pattern: /^1[3-9]\d{9}$/,
-      message: "请输入正确的手机号",
-      trigger: "blur"
-    }
-  ],
-  relationType: [{ required: true, message: "请选择关系类型", trigger: "change" }]
+  acName: [{ required: true, message: "请输入活动名称", trigger: "blur" }],
+  moneyLow: [{ required: true, message: "请输入最低消费金额", trigger: "blur" }],
+  moneyHigh: [{ required: true, message: "请输入最高消费金额", trigger: "blur" }]
 });
 
 // 表格列配置
 const columns = reactive<ColumnProps<any>[]>([
   {
-    prop: "friendName",
-    label: "好友名称",
-    search: {
-      el: "input"
-    }
+    prop: "acName",
+    label: "活动名称"
   },
   {
-    prop: "friendPhone",
-    label: "手机号"
+    prop: "endDate",
+    label: "有效期至"
   },
   {
     prop: "relationType",
-    label: "关系类型",
-    search: {
-      el: "select",
-      props: { placeholder: "请选择" }
-    },
-    enum: relationTypeOptions,
-    fieldNames: { label: "label", value: "value" },
+    label: "消费门槛",
     render: (scope: any) => {
-      const type = relationTypeOptions.find(item => item.value === scope.row.relationType);
-      return type?.label || "--";
-    }
-  },
-  {
-    prop: "createTime",
-    label: "添加时间",
-    render: (scope: any) => {
-      return scope.row.createTime?.replace(/-/g, "/") || "--";
+      return scope.row.moneyLow + "元" + "~" + scope.row.moneyHigh + "元";
     }
   },
   {
     prop: "status",
-    label: "状态"
-  },
-  {
-    prop: "remark",
-    label: "备注",
+    label: "状态",
+    enum: [
+      { label: "启用中", value: "0" },
+      { label: "已停用", value: "1" }
+    ],
+    fieldNames: { label: "label", value: "value" },
     render: (scope: any) => {
-      return scope.row.remark || "--";
+      return scope.row.status == "0" ? "启用中" : "已停用";
     }
   },
   { prop: "operation", label: "操作", fixed: "right", width: 250 }
@@ -199,42 +190,14 @@ const initParam = reactive({
 // 数据回调处理
 const dataCallback = (data: any) => {
   return {
-    list: data?.records || [],
+    list: data || [],
     total: data?.total || 0
   };
 };
 
 // 获取表格列表
 const getTableList = (params: any) => {
-  // TODO: 集成真实接口时,取消下面的注释
-  // return getFriendRelationList(params);
-
-  // 临时方案:返回模拟数据
-  return new Promise(resolve => {
-    setTimeout(() => {
-      const mockData = generateMockData();
-      resolve({
-        code: 200,
-        data: {
-          records: mockData,
-          total: mockData.length
-        }
-      });
-    }, 500);
-  });
-};
-
-// 生成模拟数据
-const generateMockData = () => {
-  return Array.from({ length: 5 }, (_, i) => ({
-    id: i + 1,
-    friendName: `好友${i + 1}`,
-    friendPhone: `138${String(i).padStart(8, "0")}`,
-    relationType: (i % 3) + 1,
-    createTime: "2025-03-01",
-    status: i % 3, // 0-待同意, 1-已同意, 2-已拒绝
-    remark: i % 2 === 0 ? `备注信息${i + 1}` : ""
-  }));
+  return getRuleList(params);
 };
 
 // 获取状态文本
@@ -248,18 +211,43 @@ const getStatusText = (status: number) => {
 };
 
 // 获取状态类型
-const getStatusType = (status: number) => {
-  const typeMap: Record<number, string> = {
+const getStatusType = (status: number): "success" | "warning" | "info" | "danger" => {
+  const typeMap: Record<number, "success" | "warning" | "info" | "danger"> = {
     0: "warning",
     1: "success",
     2: "info"
   };
-  return typeMap[status] || "";
+  return typeMap[status] || "info";
+};
+
+// 获取商家列表(选择商家)
+const getMerchantList = async () => {
+  try {
+    const res: any = await getReceivedFriendCouponList({
+      storeId: localGet("createdId") || ""
+    });
+    if (res.code == 200) {
+      merchantList.value = res.data;
+      console.log(merchantList.value);
+    }
+  } catch (error) {
+    console.error("获取商家列表失败", error);
+  }
 };
 
 // 打开添加对话框
-const openAddDialog = () => {
+const openAddDialog = async () => {
   isEdit.value = false;
+  await getMerchantList();
+  // 初始化一个空的商家优惠券项
+  formData.details = [
+    {
+      friendStoreUserId: "",
+      couponId: "",
+      couponList: [],
+      remainingCount: 0
+    }
+  ];
   dialogVisible.value = true;
 };
 
@@ -268,58 +256,199 @@ const closeDialog = () => {
   dialogVisible.value = false;
   formRef.value?.resetFields();
   Object.assign(formData, {
-    friendName: "",
-    friendPhone: "",
-    remark: "",
-    relationType: "",
-    couponList: []
+    acName: "",
+    moneyLow: undefined,
+    moneyHigh: undefined,
+    details: []
   });
   currentEditId.value = "";
 };
 
-// 添加优惠券
-const addCoupon = () => {
-  formData.couponList.push({
+// 添加商家优惠券
+const addMerchantCoupon = () => {
+  formData.details.push({
+    friendStoreUserId: "",
     couponId: "",
-    quantity: 1
+    couponList: [],
+    remainingCount: 0
   });
 };
 
-// 移除优惠券
-const removeCoupon = (index: number) => {
-  formData.couponList.splice(index, 1);
+// 移除商家优惠券
+const removeMerchantCoupon = (index: number) => {
+  formData.details.splice(index, 1);
+};
+
+// 商家选择改变时,获取该商家的优惠券列表(选择优惠券)
+const handleMerchantChange = async (index: number) => {
+  const item = formData.details[index];
+  item.couponId = ""; // 重置优惠券选择
+  item.remainingCount = 0; // 重置剩余张数
+
+  if (!item.friendStoreUserId) {
+    item.couponList = [];
+    return;
+  }
+
+  // item.friendStoreUserId 存储的是商家的 couponId,需要找到对应商家的 friendStoreUserId
+  const merchant = merchantList.value.find((m: any) => m.couponId === item.friendStoreUserId);
+  if (!merchant) {
+    item.couponList = [];
+    return;
+  }
+
+  try {
+    const res: any = await getReceivedFriendCouponList({
+      storeId: localGet("createdId") || "",
+      friendStoreUserId: merchant.friendStoreUserId
+    });
+    if (res.code == 200) {
+      item.couponList = res.data;
+    } else {
+      item.couponList = [];
+    }
+  } catch (error) {
+    console.error("获取优惠券列表失败", error);
+    item.couponList = [];
+  }
+};
+
+// 优惠券选择改变时,更新剩余张数
+const handleCouponChange = (index: number) => {
+  const item = formData.details[index];
+  const coupon = item.couponList.find((c: any) => (c.couponId || c.id) === item.couponId);
+  if (coupon) {
+    item.remainingCount = coupon.couponNum || 0; // 使用 getReceivedFriendCouponList 返回的 couponNum
+  }
 };
 
 // 编辑行数据
-const editRow = (row: any) => {
+const editRow = async (row: any) => {
   isEdit.value = true;
   currentEditId.value = row.id;
-  Object.assign(formData, {
-    friendName: row.friendName,
-    friendPhone: row.friendPhone,
-    remark: row.remark,
-    relationType: row.relationType,
-    couponList: []
-  });
-  dialogVisible.value = true;
+
+  try {
+    // 1. 获取商家列表
+    await getMerchantList();
+
+    // 2. 调用 getRuleById 获取活动详情
+    const res: any = await getRuleById({ id: row.id });
+
+    if (res.code == 200 && res.data) {
+      const detailData = res.data;
+      console.log("getRuleById 返回的数据:", detailData);
+
+      // 3. 处理 lifeDiscountCouponFriendRuleDetailVos 数据
+      let details: Array<{
+        friendStoreUserId: string | number;
+        couponId: string | number;
+        couponList: any[];
+        remainingCount: number;
+      }> = [];
+
+      const detailVos = detailData.lifeDiscountCouponFriendRuleDetailVos || detailData.details || [];
+
+      if (detailVos && Array.isArray(detailVos) && detailVos.length > 0) {
+        details = await Promise.all(
+          detailVos.map(async (item: any) => {
+            console.log("detail item:", item);
+
+            // 根据 couponId 在商家列表中找到对应的商家
+            const matchedMerchant = merchantList.value.find((m: any) => m.couponId === item.couponId);
+            console.log("匹配到的商家:", matchedMerchant);
+
+            // 如果没有匹配到商家,返回默认值
+            if (!matchedMerchant) {
+              return {
+                friendStoreUserId: item.couponId, // 商家选择框绑定的是 couponId
+                couponId: "",
+                couponList: [],
+                remainingCount: 0
+              };
+            }
+
+            // 获取该商家的优惠券列表
+            let couponList: any[] = [];
+            try {
+              const couponRes: any = await getReceivedFriendCouponList({
+                storeId: localGet("createdId") || "",
+                friendStoreUserId: matchedMerchant.friendStoreUserId
+              });
+              if (couponRes.code == 200) {
+                couponList = couponRes.data || [];
+              }
+            } catch (error) {
+              console.error("获取优惠券列表失败", error);
+            }
+
+            // 在优惠券列表中找到匹配的优惠券
+            let matchedCoupon = couponList.find((c: any) => c.couponId === item.couponId);
+            console.log("匹配到的优惠券:", matchedCoupon);
+
+            // 如果没有在列表中找到,使用 lifeDiscountCouponFriendRuleDetailVos 中的数据创建一个临时对象
+            if (!matchedCoupon && item.couponId) {
+              matchedCoupon = {
+                couponId: item.couponId,
+                couponName: item.couponName, // 使用 getRuleById 返回的 couponName
+                couponNum: item.couponNum || 0 // 使用 getRuleById 返回的 couponNum
+              };
+              // 将这个临时对象添加到列表中,以便下拉框可以显示
+              couponList = [matchedCoupon, ...couponList];
+            }
+
+            return {
+              friendStoreUserId: item.couponId, // 商家选择框的值是商家的 couponId
+              couponId: matchedCoupon?.couponId || item.couponId || "", // 优惠券选择框的值使用 couponId
+              couponList: couponList,
+              remainingCount: matchedCoupon?.couponNum || 0 // 使用 getReceivedFriendCouponList 返回的 couponNum
+            };
+          })
+        );
+      } else {
+        details = [
+          {
+            friendStoreUserId: "",
+            couponId: "",
+            couponList: [],
+            remainingCount: 0
+          }
+        ];
+      }
+
+      Object.assign(formData, {
+        acName: detailData.acName,
+        moneyLow: detailData.moneyLow,
+        moneyHigh: detailData.moneyHigh,
+        details: details
+      });
+
+      dialogVisible.value = true;
+    } else {
+      ElMessage.error(res.msg || "获取活动详情失败");
+    }
+  } catch (error: any) {
+    ElMessage.error(error?.msg || "获取活动详情失败");
+  }
 };
 
 // 删除行数据
 const deleteRow = (row: any) => {
-  ElMessageBox.confirm("确定要删除这个好友吗?", "提示", {
+  ElMessageBox.confirm("确定要删除这个活动吗?", "提示", {
     confirmButtonText: "确定",
     cancelButtonText: "取消",
     type: "warning"
   })
     .then(async () => {
       try {
-        // TODO: 集成真实接口时,取消下面的注释
-        // await deleteFriendRelation({ id: row.id });
-
-        ElMessage.success("删除成功");
-        proTable.value?.getTableList();
-      } catch (error) {
-        ElMessage.error("删除失败");
+        const res: any = await delFriendCouponRule({ id: row.id });
+        if (res.code == 200) {
+          ElMessage.success("删除成功");
+          proTable.value?.getTableList();
+        } else {
+          ElMessage.error(res.msg || "删除失败");
+        }
+      } catch (error: any) {
+        ElMessage.error(error?.msg || "删除失败");
       }
     })
     .catch(() => {
@@ -327,13 +456,10 @@ const deleteRow = (row: any) => {
     });
 };
 
-// 同意好友申请
+// 同意好友申请(此页面可能不需要)
 const handleApprove = async (row: any) => {
   try {
-    // TODO: 集成真实接口时,取消下面的注释
-    // await approveFriend({ id: row.id });
-
-    ElMessage.success("已同意好友申请");
+    ElMessage.success("操作成功");
     proTable.value?.getTableList();
   } catch (error) {
     ElMessage.error("操作失败");
@@ -346,24 +472,54 @@ const handleSubmit = async () => {
 
   await formRef.value.validate(async (valid: boolean) => {
     if (valid) {
+      // 验证是否至少添加了一个商家优惠券
+      if (!formData.details.length) {
+        ElMessage.warning("请至少添加一个商家优惠券");
+        return;
+      }
+
+      // 验证所有商家优惠券项是否都已选择
+      const hasEmpty = formData.details.some(item => !item.friendStoreUserId || !item.couponId);
+      if (hasEmpty) {
+        ElMessage.warning("请完善所有商家优惠券信息");
+        return;
+      }
+
       try {
+        // 转换数据:item.friendStoreUserId 存储的是商家的 couponId,item.couponId 存储的是优惠券的 couponId
+        const details = formData.details.map(item => {
+          // 找到对应的商家
+          const merchant = merchantList.value.find((m: any) => m.couponId === item.friendStoreUserId);
+          // 找到对应的优惠券
+          const coupon = item.couponList.find((c: any) => (c.couponId || c.id) === item.couponId);
+
+          return {
+            couponId: item.couponId, // 直接使用选中的 couponId
+            friendStoreUserId: merchant?.friendStoreUserId || "" // 使用商家的真实 friendStoreUserId
+          };
+        });
+
         const params = {
-          ...formData,
-          id: isEdit.value ? currentEditId.value : undefined
+          storeId: localGet("createdId") || "",
+          acName: formData.acName,
+          moneyHigh: formData.moneyHigh,
+          moneyLow: formData.moneyLow,
+          details: details
         };
 
-        // TODO: 集成真实接口时,取消下面的注释
-        // if (isEdit.value) {
-        //   await updateFriendRelation(params);
-        // } else {
-        //   await addFriendRelation(params);
-        // }
-
-        ElMessage.success(isEdit.value ? "编辑成功" : "添加成功");
-        closeDialog();
-        proTable.value?.getTableList();
-      } catch (error) {
-        ElMessage.error(isEdit.value ? "编辑失败" : "添加失败");
+        console.log("提交参数:", params);
+
+        const res: any = await saveFriendCouponRule(params);
+
+        if (res.code == 200) {
+          ElMessage.success(isEdit.value ? "编辑成功" : "添加成功");
+          closeDialog();
+          proTable.value?.getTableList();
+        } else {
+          ElMessage.error(res.msg || "操作失败");
+        }
+      } catch (error: any) {
+        ElMessage.error(error?.msg || (isEdit.value ? "编辑失败" : "添加失败"));
       }
     }
   });
@@ -380,8 +536,9 @@ onMounted(() => {
   .header-button {
     margin-bottom: 16px;
   }
-  .coupon-list {
-    .coupon-item {
+  .merchant-coupon-list {
+    width: 100%;
+    .merchant-coupon-item {
       display: flex;
       align-items: center;
       margin-bottom: 10px;

+ 190 - 38
src/views/dynamicManagement/index.vue

@@ -100,35 +100,55 @@
       <div v-if="currentDetail" class="detail-content">
         <!-- 主内容区域 -->
         <div class="detail-main">
-          <!-- 图片/视频展示 -->
+          <!-- 图片/视频轮播展示 -->
           <div class="media-container">
-            <!-- 视频 -->
-            <video
-              v-if="currentDetail.isVideo && currentDetail.imageUrl"
-              :src="currentDetail.imageUrl"
-              class="detail-media"
-              controls
-            />
-            <!-- 图片 -->
-            <img
-              v-else-if="currentDetail.imageUrl"
-              :src="currentDetail.imageUrl"
-              :alt="currentDetail.title"
-              class="detail-media"
-            />
+            <!-- 多媒体轮播 -->
+            <el-carousel
+              v-if="currentDetail.mediaList && currentDetail.mediaList.length > 0"
+              :autoplay="false"
+              :loop="false"
+              indicator-position="outside"
+              arrow="always"
+              height="100%"
+              class="media-carousel"
+              @change="handleCarouselChange"
+            >
+              <el-carousel-item v-for="(media, index) in currentDetail.mediaList" :key="index">
+                <!-- 视频 -->
+                <video
+                  v-if="media.type === 'video'"
+                  :ref="el => setVideoRef(el, index)"
+                  :src="media.url"
+                  class="detail-media detail-video"
+                  controls
+                  preload="metadata"
+                  @play="handleVideoPlay(index)"
+                />
+                <!-- 图片 -->
+                <img v-else :src="media.url" :alt="currentDetail.title" class="detail-media detail-image" />
+              </el-carousel-item>
+            </el-carousel>
             <!-- 占位符 -->
             <div v-else class="media-placeholder">
               <el-icon :size="80" color="#dcdfe6">
                 <Picture />
               </el-icon>
             </div>
+            <!-- 媒体计数指示器 -->
+            <div v-if="currentDetail.mediaList && currentDetail.mediaList.length > 1" class="media-counter">
+              {{ currentCarouselIndex + 1 }} / {{ currentDetail.mediaList.length }}
+            </div>
           </div>
 
           <!-- 底部信息 -->
           <div class="detail-info">
             <div class="author-info">
               <div class="author-avatar">
-                <img v-if="currentDetail.author?.avatar" :src="currentDetail.author.avatar" :alt="currentDetail.author.name" />
+                <img
+                  v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
+                  :src="currentDetail.author?.avatar || currentDetail.userAvatar"
+                  :alt="currentDetail.author?.name || currentDetail.userName"
+                />
                 <el-icon v-else :size="32">
                   <Avatar />
                 </el-icon>
@@ -152,7 +172,11 @@
           <!-- 作者头像 -->
           <div class="action-item author-action">
             <div class="action-avatar" @click="handleViewUserProfile">
-              <img v-if="currentDetail.author?.avatar" :src="currentDetail.author.avatar" :alt="currentDetail.author.name" />
+              <img
+                v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
+                :src="currentDetail.author?.avatar || currentDetail.userAvatar"
+                :alt="currentDetail.author?.name || currentDetail.userName"
+              />
               <el-icon v-else :size="40" color="#fff">
                 <Avatar />
               </el-icon>
@@ -348,7 +372,8 @@ import {
   getUserByPhone,
   toggleFollowUser,
   cancelFollowed,
-  likeDynamicNew
+  likeDynamicNew,
+  likeDynamicList
 } from "@/api/modules/dynamicManagement";
 import { uploadImg } from "@/api/modules/upload";
 import { useUserStore } from "@/stores/modules/user";
@@ -374,6 +399,12 @@ const violationTypeMap: Record<string, number> = {
 };
 
 // 接口定义
+// 媒体项类型
+interface MediaItem {
+  url: string;
+  type: "image" | "video";
+}
+
 interface DynamicItem {
   id: number;
   title: string;
@@ -392,6 +423,7 @@ interface DynamicItem {
   isFollowed?: number; // 是否已关注:0未关注,1已关注
   isVideo?: boolean; // 是否为视频
   mediaType?: string; // 媒体类型:image 或 video
+  mediaList?: MediaItem[]; // 媒体列表(支持多张图片和视频)
 }
 
 interface DetailItem extends DynamicItem {
@@ -415,6 +447,37 @@ const isfollowed = ref(0); // 0: 推荐, 1: 关注
 const detailDrawerVisible = ref(false);
 const currentDetail = ref<DetailItem | null>(null);
 
+// 轮播相关
+const currentCarouselIndex = ref(0);
+const videoRefs = ref<Map<number, HTMLVideoElement>>(new Map());
+
+// 设置视频引用
+const setVideoRef = (el: any, index: number) => {
+  if (el) {
+    videoRefs.value.set(index, el as HTMLVideoElement);
+  }
+};
+
+// 轮播切换时暂停所有视频
+const handleCarouselChange = (newIndex: number) => {
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
+  currentCarouselIndex.value = newIndex;
+};
+
+// 视频播放时暂停其他视频
+const handleVideoPlay = (currentIndex: number) => {
+  videoRefs.value.forEach((video, index) => {
+    if (index !== currentIndex && video && !video.paused) {
+      video.pause();
+    }
+  });
+};
+
 // 举报对话框相关
 const reportDialogVisible = ref(false);
 const reportSubmitting = ref(false);
@@ -532,19 +595,32 @@ const loadDynamicList = async () => {
           console.log("接口返回的isFollowThis:", item.isFollowThis, "(0=未关注, 1=已关注)");
         }
 
-        // 判断是否为视频(只检查逗号之前的第一个字符串,判断是否以.mp4结尾
+        // 解析媒体列表(支持多张图片和视频
         const mediaUrl = item.imagePath || "";
-        const firstUrl = mediaUrl.split(",")[0].trim(); // 截取逗号之前的第一个字符串
-        const isVideo = firstUrl.toLowerCase().endsWith(".mp4"); // 判断是否以.mp4结尾
+        const mediaUrls = mediaUrl
+          .split(",")
+          .map((url: string) => url.trim())
+          .filter((url: string) => url);
+        const mediaList: MediaItem[] = mediaUrls.map((url: string) => ({
+          url,
+          type: url.toLowerCase().endsWith(".mp4") ? ("video" as const) : ("image" as const)
+        }));
+
+        const firstUrl = mediaUrls[0] || "";
+        const isVideo = firstUrl.toLowerCase().endsWith(".mp4");
         const mediaType = isVideo ? "video" : "image";
 
+        // 获取用户头像(优先使用 userImage 字段)
+        const userAvatar = item.userImage || item.userAvatar || item.avatar || item.headImg || "";
+
         return {
           id: item.id || item.dynamicId,
           title: item.title || item.content || item.dynamicContent || "这家店超好吃....",
           content: item.content || item.dynamicContent || "",
-          imageUrl: firstUrl, // 使用第一个URL
+          imageUrl: firstUrl, // 使用第一个URL(兼容旧逻辑)
+          mediaList, // 完整媒体列表
           userName: item.userName || item.nickname || item.storeName || "用户",
-          userAvatar: item.userAvatar || item.avatar || item.headImg || "",
+          userAvatar: userAvatar,
           likeCount: item.likeCount || item.praiseCount || 0,
           isLiked: item.isLiked || item.isPraise || false,
           createTime: item.createTime || item.createDate || new Date().toISOString(),
@@ -595,22 +671,26 @@ const handleCardClick = (item: DynamicItem) => {
   detailDrawerVisible.value = true;
 };
 
-// 点赞/取消点赞
+// 列表点赞/取消点赞
 const handleLike = async (item: DynamicItem) => {
   try {
-    // TODO: 集成真实接口时,取消下面的注释
-    // if (item.isLiked) {
-    //   await unlikeDynamic({ id: item.id });
-    // } else {
-    //   await likeDynamic({ id: item.id });
-    // }
-
-    // 临时方案:直接修改状态
+    // 获取当前用户的手机号,并在前面拼接 "store_"
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    // 调用列表点赞接口(表单方式提交)
+    await likeDynamicList({
+      dynamicId: item.id,
+      phoneId: currentUserPhoneId
+    });
+
+    // 切换点赞状态
     item.isLiked = !item.isLiked;
     item.likeCount += item.isLiked ? 1 : -1;
 
     ElMessage.success(item.isLiked ? "点赞成功" : "取消点赞");
   } catch (error) {
+    console.error("列表点赞操作失败:", error);
     ElMessage.error("操作失败");
   }
 };
@@ -618,12 +698,20 @@ const handleLike = async (item: DynamicItem) => {
 // 关闭详情
 const handleCloseDetail = () => {
   detailDrawerVisible.value = false;
+  // 暂停所有视频
+  videoRefs.value.forEach(video => {
+    if (video && !video.paused) {
+      video.pause();
+    }
+  });
   setTimeout(() => {
     currentDetail.value = null;
+    currentCarouselIndex.value = 0;
+    videoRefs.value.clear();
   }, 300);
 };
 
-// 详情页点赞
+// 详情页点赞(表单方式提交)
 const handleDetailLike = async () => {
   if (!currentDetail.value) return;
 
@@ -632,11 +720,10 @@ const handleDetailLike = async () => {
     const phone = userStore.userInfo?.phone || "";
     const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
 
-    // 调用点赞接口
+    // 调用详情点赞接口(表单方式提交)
     await likeDynamicNew({
-      userId: currentUserPhoneId,
-      huifuId: currentDetail.value.id, // ✅ 动态ID(不是phoneId)
-      // 当前用户phoneId (格式: store_xxx)
+      userId: currentUserPhoneId, // 当前用户phoneId
+      huifuId: currentDetail.value.id, // 动态ID
       type: 2 // 2表示点赞
     });
 
@@ -1202,18 +1289,73 @@ onMounted(() => {
         justify-content: center;
         padding: 80px 120px 40px 40px;
         .media-container {
+          position: relative;
           display: flex;
           flex: 1;
           align-items: center;
           justify-content: center;
           width: 100%;
           max-width: 800px;
+          min-height: 400px;
+          .media-carousel {
+            width: 100%;
+            height: 100%;
+            min-height: 400px;
+            max-height: 70vh;
+            :deep(.el-carousel__container) {
+              height: 100% !important;
+              min-height: 400px;
+            }
+            :deep(.el-carousel__item) {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              background: transparent;
+            }
+            :deep(.el-carousel__arrow) {
+              width: 48px;
+              height: 48px;
+              font-size: 24px;
+              color: #ffffff;
+              background-color: rgb(0 0 0 / 40%);
+              border: none;
+              &:hover {
+                background-color: rgb(0 0 0 / 60%);
+              }
+            }
+            :deep(.el-carousel__indicators) {
+              bottom: -30px;
+              .el-carousel__indicator {
+                .el-carousel__button {
+                  width: 8px;
+                  height: 8px;
+                  background-color: rgb(255 255 255 / 40%);
+                  border-radius: 50%;
+                }
+                &.is-active .el-carousel__button {
+                  background-color: #409eff;
+                }
+              }
+            }
+          }
           .detail-media {
             max-width: 100%;
-            max-height: 100%;
+            max-height: 65vh;
+            object-fit: contain;
+            border-radius: 8px;
+          }
+          .detail-image {
+            max-width: 100%;
+            max-height: 65vh;
             object-fit: contain;
             border-radius: 8px;
           }
+          .detail-video {
+            width: 100%;
+            max-height: 65vh;
+            background: #000000;
+            border-radius: 8px;
+          }
           .media-placeholder {
             display: flex;
             align-items: center;
@@ -1223,6 +1365,16 @@ onMounted(() => {
             background: rgb(255 255 255 / 5%);
             border-radius: 8px;
           }
+          .media-counter {
+            position: absolute;
+            right: 16px;
+            bottom: -24px;
+            padding: 4px 12px;
+            font-size: 14px;
+            color: #ffffff;
+            background: rgb(0 0 0 / 50%);
+            border-radius: 12px;
+          }
         }
         .detail-info {
           width: 100%;

+ 3 - 4
src/views/dynamicManagement/publishDynamic.vue

@@ -122,7 +122,7 @@ import { ArrowLeft, Plus, Location, Search, ZoomIn, Delete } from "@element-plus
 import type { FormInstance, FormRules, UploadUserFile, UploadFile } from "element-plus";
 // import { publishDynamic, saveDraft, uploadDynamicImage } from "@/api/modules/dynamicManagement";
 import { addOrUpdateDynamic } from "@/api/modules/dynamicManagement";
-import { uploadImg, uploadVideo } from "@/api/modules/upload";
+import { uploadImg } from "@/api/modules/upload";
 import { useUserStore } from "@/stores/modules/user";
 
 const router = useRouter();
@@ -278,9 +278,8 @@ const uploadSingleFile = async (file: UploadFile) => {
   try {
     console.log("开始上传文件:", rawFile.name, "类型:", rawFile.type);
 
-    // 根据文件类型选择上传接口
-    const isVideo = rawFile.type.startsWith("video/");
-    const result: any = isVideo ? await uploadVideo(uploadFormData) : await uploadImg(uploadFormData);
+    // 图片和视频都使用同一个上传接口
+    const result: any = await uploadImg(uploadFormData);
 
     console.log("上传接口返回:", result);
 

+ 333 - 72
src/views/dynamicManagement/reviewAppeal.vue

@@ -2,54 +2,59 @@
   <div class="review-appeal-container">
     <!-- 顶部统计数据 -->
     <div class="statistics-section">
-      <div class="store-name">
-        {{ storeName }}
-      </div>
       <div class="statistics-cards">
         <div class="stat-card">
-          <div class="stat-label">评数</div>
+          <div class="stat-label">评价总数</div>
           <div class="stat-value">
-            {{ statistics.totalReviews }}
+            {{ total }}
           </div>
         </div>
         <div class="stat-card">
-          <div class="stat-label">差评文字数</div>
+          <div class="stat-label">新增差评数</div>
           <div class="stat-value">
             {{ statistics.badTextReviews }}
           </div>
         </div>
         <div class="stat-card">
-          <div class="stat-label">差评图片</div>
+          <div class="stat-label">新增好评数</div>
           <div class="stat-value">
             {{ statistics.badImageReviews }}
           </div>
         </div>
         <div class="stat-card">
-          <div class="stat-label">差评中数</div>
+          <div class="stat-label">差评中数</div>
           <div class="stat-value">
             {{ statistics.neutralReviews }}
           </div>
         </div>
         <div class="stat-card">
-          <div class="stat-label">异常评论率</div>
+          <div class="stat-label">评论回复率</div>
           <div class="stat-value highlight">
             {{ statistics.abnormalRate }}
           </div>
         </div>
       </div>
-      <el-button type="primary" @click="refreshData"> 数据刷新 </el-button>
     </div>
 
     <!-- 评价列表区域 -->
     <div class="review-list-section">
       <div class="section-header">
-        <div class="section-title">评价列表</div>
+        <div class="section-title">
+          评价列表 <el-button type="primary" style="float: right" @click="goToAppealHistory"> 申诉历史 </el-button>
+        </div>
+        <div class="time-filter">
+          <span>评论时间:</span>
+          <el-select v-model="timeFilter" placeholder="请选择" class="time-filter-select" @change="handleTimeFilterChange">
+            <el-option label="全部" value="" />
+            <el-option label="30天" value="30" />
+          </el-select>
+        </div>
         <el-tabs v-model="activeTab" @tab-click="handleTabClick">
-          <el-tab-pane :label="`全部 (${tabCounts.all})`" name="all" />
+          <el-tab-pane :label="`全部 (${tabCounts.all})`" name="0" />
           <el-tab-pane :label="`待回复差评 (${tabCounts.pending})`" name="pending" />
-          <el-tab-pane :label="`差评 (${tabCounts.bad})`" name="bad" />
-          <el-tab-pane :label="`好评 (${tabCounts.good})`" name="good" />
-          <el-tab-pane :label="`中评 (${tabCounts.neutral})`" name="neutral" />
+          <el-tab-pane :label="`差评 (${tabCounts.bad})`" name="3" />
+          <el-tab-pane :label="`好评 (${tabCounts.good})`" name="1" />
+          <el-tab-pane :label="`中评 (${tabCounts.neutral})`" name="2" />
         </el-tabs>
       </div>
 
@@ -63,18 +68,22 @@
               </el-avatar>
               <div class="user-details">
                 <div class="user-name">
-                  {{ review.userName }}
+                  {{ review.isAnonymous == 1 || !review.userName ? "匿名用户" : review.userName }}
                 </div>
-                <el-rate v-model="review.rating" disabled show-score />
+                <el-rate v-model="review.score" disabled />
               </div>
             </div>
             <div class="review-time">
-              {{ review.createTime }}
+              {{ review.createdTime.replace(/-/g, "/") }}
             </div>
           </div>
 
           <div class="review-content">
-            {{ review.content }}
+            {{ review.commentContent }}
+          </div>
+
+          <div v-for="(itm, idx) in review.storeComment" :key="idx">
+            <div class="sjhf">我的回复: {{ itm.commentContent }}</div>
           </div>
 
           <div v-if="review.images && review.images.length > 0" class="review-images">
@@ -89,18 +98,33 @@
           </div>
 
           <div class="review-footer">
-            <el-button type="primary" link @click="openAppealDialog(review)"> 申诉 </el-button>
-            <el-button v-if="review.appealStatus" type="info" link @click="goToAppealHistory"> 查看申诉 </el-button>
+            <el-button type="primary" link :disabled="review.isAppealed" @click="delReviewAppeal(review)"> 申诉删除 </el-button>
+
+            <el-button type="primary" link @click="openReplayDialog(review)"> 回复 </el-button>
           </div>
         </div>
       </div>
 
       <!-- 空状态 -->
       <el-empty v-else description="暂无评论数据" />
+
+      <!-- 分页 -->
+      <div v-if="total > 0" class="pagination-wrapper">
+        <el-pagination
+          v-model:current-page="pageNum"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 30, 50]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          background
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
     </div>
 
     <!-- 申诉提交对话框 -->
-    <el-dialog v-model="appealDialogVisible" title="提交申诉" width="600px" @close="closeAppealDialog">
+    <el-dialog v-model="appealDialogVisible" title="申诉删除" width="600px" @close="closeAppealDialog">
       <el-form ref="appealFormRef" :model="appealFormData" :rules="appealFormRules" label-width="100px">
         <el-form-item label="申诉原因" prop="reason">
           <el-input
@@ -118,8 +142,10 @@
             v-model:file-list="appealFormData.fileList"
             list-type="picture-card"
             :limit="6"
+            :http-request="handleUpload"
             :on-preview="handlePreview"
             :on-remove="handleRemove"
+            :on-success="handleUploadSuccess"
             accept="image/*"
           >
             <el-icon><Plus /></el-icon>
@@ -129,7 +155,28 @@
 
       <template #footer>
         <el-button @click="closeAppealDialog"> 取消 </el-button>
-        <el-button type="primary" @click="submitAppeal"> 提交申诉 </el-button>
+        <el-button type="primary" @click="submitAppeal"> 确定 </el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 回复对话框 -->
+    <el-dialog v-model="replyDialogVisible" title="回复评价" width="600px" @close="closeReplyDialog">
+      <el-form ref="replyFormRef" :model="replyFormData" :rules="replyFormRules" label-width="100px">
+        <el-form-item label="回复内容" prop="content">
+          <el-input
+            v-model="replyFormData.content"
+            type="textarea"
+            :rows="6"
+            placeholder="请输入回复内容"
+            maxlength="500"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="closeReplyDialog"> 取消 </el-button>
+        <el-button type="primary" @click="submitReply"> 提交回复 </el-button>
       </template>
     </el-dialog>
   </div>
@@ -141,7 +188,8 @@ import { useRouter } from "vue-router";
 import { ElMessage } from "element-plus";
 import { User, Plus } from "@element-plus/icons-vue";
 import type { FormInstance, FormRules, UploadUserFile } from "element-plus";
-// import { getReviewList, submitReviewAppeal } from "@/api/modules/reviewAppeal";
+import { getList, addAppealNew, uploadImg } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils";
 
 const router = useRouter();
 
@@ -150,11 +198,11 @@ const storeName = ref("重庆老火锅");
 
 // 统计数据
 const statistics = reactive({
-  totalReviews: 22,
-  badTextReviews: 2,
-  badImageReviews: 2,
-  neutralReviews: 2,
-  abnormalRate: "16.67%"
+  totalReviews: 0,
+  badTextReviews: 0,
+  badImageReviews: 0,
+  neutralReviews: 0,
+  abnormalRate: "0%"
 });
 
 // 标签页计数
@@ -167,11 +215,19 @@ const tabCounts = reactive({
 });
 
 // 当前激活的标签
-const activeTab = ref("all");
+const activeTab = ref("0");
+
+// 评论时间筛选
+const timeFilter = ref("");
 
 // 评论列表
 const reviewList = ref<any[]>([]);
 
+// 分页参数
+const pageNum = ref(1);
+const pageSize = ref(10);
+const total = ref(0);
+
 // 申诉提交对话框
 const appealDialogVisible = ref(false);
 const appealFormRef = ref<FormInstance>();
@@ -179,7 +235,8 @@ const currentReviewId = ref("");
 
 const appealFormData = reactive({
   reason: "",
-  images: [],
+  images: [] as string[],
+  files: [] as File[], // 保存原始的 File 对象
   fileList: [] as UploadUserFile[]
 });
 
@@ -187,15 +244,24 @@ const appealFormRules = reactive<FormRules>({
   reason: [{ required: true, message: "请输入申诉原因", trigger: "blur" }]
 });
 
-// 刷新数据
-const refreshData = () => {
-  loadReviewList();
-  ElMessage.success("数据刷新成功");
-};
+// 回复对话框
+const replyDialogVisible = ref(false);
+const replyFormRef = ref<FormInstance>();
+const currentReplyReview = ref<any>(null);
+
+const replyFormData = reactive({
+  content: ""
+});
+
+const replyFormRules = reactive<FormRules>({
+  content: [{ required: true, message: "请输入回复内容", trigger: "blur" }]
+});
 
 // 标签页切换
-const handleTabClick = () => {
-  loadReviewList();
+const handleTabClick = (tab: any) => {
+  // 获取点击的标签页的 name 值
+  const tabName = tab.paneName || tab.props?.name || activeTab.value;
+  loadReviewList(tabName, true);
 };
 
 // 跳转到申诉历史
@@ -203,30 +269,163 @@ const goToAppealHistory = () => {
   router.push("/dynamicManagement/reviewAppealHistory");
 };
 
+// 评论时间筛选变化
+const handleTimeFilterChange = () => {
+  loadReviewList(activeTab.value, true);
+};
+
+// 加载统计数据(只在初始化时调用一次)
+const loadStatistics = async () => {
+  try {
+    const baseParams = {
+      pageNum: 1,
+      pageSize: 1,
+      phoneId: `store_${localGet("geeker-user").userInfo.phone}`,
+      businessType: 5,
+      days: timeFilter.value,
+      replyStatus: 0,
+      storeId: localGet("createdId"),
+      userType: 0
+    };
+
+    // 并行请求各个评论等级的数量
+    const [allRes, goodRes, neutralRes, badRes, pendingRes]: any[] = await Promise.all([
+      getList({ ...baseParams, commentLevel: 0 }), // 全部
+      getList({ ...baseParams, commentLevel: 1 }), // 好评
+      getList({ ...baseParams, commentLevel: 2 }), // 中评
+      getList({ ...baseParams, commentLevel: 3 }), // 差评
+      getList({ ...baseParams, commentLevel: 3, replyStatus: 2 }) // 待回复差评
+    ]);
+
+    // 更新标签页计数
+    tabCounts.all = allRes?.code === 200 ? allRes.data?.total || 0 : 0;
+    tabCounts.good = goodRes?.code === 200 ? goodRes.data?.total || 0 : 0;
+    tabCounts.neutral = neutralRes?.code === 200 ? neutralRes.data?.total || 0 : 0;
+    tabCounts.bad = badRes?.code === 200 ? badRes.data?.total || 0 : 0;
+    tabCounts.pending = pendingRes?.code === 200 ? pendingRes.data?.total || 0 : 0;
+
+    // 更新统计数据
+    statistics.totalReviews = tabCounts.all;
+    statistics.badTextReviews = tabCounts.bad; // 新增差评数
+    statistics.badImageReviews = tabCounts.good; // 新增好评数
+    statistics.neutralReviews = tabCounts.neutral; // 中评数
+
+    // 计算评论回复率 = (已回复评论数 ÷ 总评论数) × 100%
+    const totalCount = tabCounts.all;
+    const repliedCount = totalCount - tabCounts.pending; // 已回复 = 总数 - 未回复
+    if (totalCount > 0) {
+      const rate = (repliedCount / totalCount) * 100;
+      statistics.abnormalRate = rate.toFixed(2) + "%";
+    } else {
+      statistics.abnormalRate = "0%";
+    }
+  } catch (error) {
+    console.error("获取统计数据失败", error);
+  }
+};
+
 // 加载评论列表
-const loadReviewList = () => {
-  // TODO: 集成真实接口
-  // const res = await getReviewList({ type: activeTab.value });
-
-  // 模拟数据
-  reviewList.value = Array.from({ length: 5 }, (_, i) => ({
-    id: i + 1,
-    userName: "不长寿爱",
-    userAvatar: "",
-    rating: 4,
-    createTime: "2025/06/30 12:00:00",
-    content:
-      "这里是评论内容这里是评论内容这里是评论内容这里是评论内容,商家定义交易问题,非常好吃很好吃,商家定义交易问题。环境很优雅很好。正文定义交易问题,正文定义交易问题。环境很优雅很好。工作不错太不错了!",
-    images: i % 2 === 0 ? [] : ["", "", ""],
-    appealStatus: i % 3 === 0 ? 1 : 0
-  }));
+const loadReviewList = async (commentLevel?: string | number, resetPage = false) => {
+  try {
+    // 如果需要重置页码
+    if (resetPage) {
+      pageNum.value = 1;
+    }
+
+    // 如果没有传入参数,使用当前激活的标签页
+    const level = commentLevel !== undefined ? commentLevel : activeTab.value;
+
+    // 判断是否是待回复差评
+    const isPending = level === "pending";
+
+    const params: any = {
+      pageNum: pageNum.value,
+      pageSize: pageSize.value,
+      phoneId: `store_${localGet("geeker-user").userInfo.phone}`,
+      businessType: 5, //业务类型(1:订单评论, 2:动态社区评论, 3:活动评论, 4:店铺打卡评论, 5:订单评价, 6:订单评论的评论)
+      commentLevel: isPending ? 3 : level, // 待回复差评传3,其他按原值
+      days: timeFilter.value, // 评论时间筛选
+      replyStatus: isPending ? 2 : 0, // 待回复差评传2,其他传0
+      storeId: localGet("createdId"),
+      userType: 0
+    };
+
+    const res: any = await getList(params);
+
+    if (res.code === 200) {
+      reviewList.value = res.data.records || [];
+      total.value = res.data.total || 0;
+    } else {
+      reviewList.value = [];
+      total.value = 0;
+      ElMessage.error(res.msg || "获取评论列表失败");
+    }
+  } catch (error: any) {
+    console.error("获取评论列表失败", error);
+    reviewList.value = [];
+    total.value = 0;
+    ElMessage.error(error?.msg || "获取评论列表失败");
+  }
 };
 
-// 打开申诉对话框
-const openAppealDialog = (review: any) => {
+// 分页大小变化
+const handleSizeChange = (val: number) => {
+  pageSize.value = val;
+  pageNum.value = 1;
+  loadReviewList();
+};
+
+// 页码变化
+const handleCurrentChange = (val: number) => {
+  pageNum.value = val;
+  loadReviewList();
+};
+
+// 申诉删除
+const delReviewAppeal = (review: any) => {
   currentReviewId.value = review.id;
   appealDialogVisible.value = true;
 };
+//回复评论
+// 打开回复对话框
+const openReplayDialog = (review: any) => {
+  console.log("打开回复对话框:", review);
+  currentReplyReview.value = review;
+  replyDialogVisible.value = true;
+};
+
+// 关闭回复对话框
+const closeReplyDialog = () => {
+  replyDialogVisible.value = false;
+  replyFormRef.value?.resetFields();
+  Object.assign(replyFormData, {
+    content: ""
+  });
+  currentReplyReview.value = null;
+};
+
+// 提交回复
+const submitReply = async () => {
+  if (!replyFormRef.value) return;
+
+  await replyFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        // TODO: 集成真实接口
+        // await submitReviewReply({
+        //   reviewId: currentReplyReview.value.id,
+        //   content: replyFormData.content
+        // });
+
+        ElMessage.success("回复提交成功");
+        closeReplyDialog();
+        loadReviewList();
+      } catch (error) {
+        ElMessage.error("回复提交失败");
+      }
+    }
+  });
+};
 
 // 关闭申诉对话框
 const closeAppealDialog = () => {
@@ -235,6 +434,7 @@ const closeAppealDialog = () => {
   Object.assign(appealFormData, {
     reason: "",
     images: [],
+    files: [],
     fileList: []
   });
 };
@@ -246,16 +446,28 @@ const submitAppeal = async () => {
   await appealFormRef.value.validate(async (valid: boolean) => {
     if (valid) {
       try {
-        // TODO: 集成真实接口
-        // await submitReviewAppeal({
-        //   reviewId: currentReviewId.value,
-        //   reason: appealFormData.reason,
-        //   images: appealFormData.images
-        // });
+        // 使用 FormData 发送图片
+        const formData = new FormData();
+        formData.append("appealReason", appealFormData.reason);
+        formData.append("storeId", localGet("geeker-user").userInfo.storeId);
+        formData.append("commentId", currentReviewId.value);
+
+        // 添加图片文件,使用 file_0, file_1, file_2 等格式
+        appealFormData.files.forEach((file, index) => {
+          formData.append(`file_${index}`, file);
+        });
+
+        const res: any = await addAppealNew(formData);
+        if (res.code === 200) {
+          ElMessage.success("申诉提交成功");
+          // 更新评价的申诉状态,使按钮置灰
+          const review = reviewList.value.find((r: any) => r.id === currentReviewId.value);
+          if (review) {
+            review.isAppealed = true;
+          }
+        }
 
-        ElMessage.success("申诉提交成功");
         closeAppealDialog();
-        loadReviewList();
       } catch (error) {
         ElMessage.error("申诉提交失败");
       }
@@ -269,13 +481,47 @@ const handlePreview = (file: UploadUserFile) => {
 };
 
 // 移除图片
-const handleRemove = (file: UploadUserFile) => {
+const handleRemove = (file: UploadUserFile, fileList: UploadUserFile[]) => {
   console.log("remove", file);
+  // 找到对应的索引并删除
+  const index = appealFormData.fileList.findIndex(f => f.uid === file.uid);
+  if (index !== -1) {
+    appealFormData.images.splice(index, 1);
+    appealFormData.files.splice(index, 1);
+  }
+  appealFormData.fileList = fileList;
+};
+
+// 自定义上传方法
+const handleUpload = async (options: any) => {
+  const { file, onSuccess, onError } = options;
+
+  try {
+    // 保存原始的 File 对象用于提交时上传
+    appealFormData.files.push(file);
+
+    // 创建本地预览URL
+    const previewUrl = URL.createObjectURL(file);
+    onSuccess({ url: previewUrl });
+
+    // 将预览URL添加到images数组
+    appealFormData.images.push(previewUrl);
+    console.log("图片已添加:", file.name);
+  } catch (error: any) {
+    onError(error);
+    ElMessage.error(error?.msg || "添加图片失败");
+  }
+};
+
+// 上传成功回调
+const handleUploadSuccess = (response: any, file: any) => {
+  console.log("上传成功回调:", response, file);
 };
 
 // 初始化
 onMounted(() => {
-  loadReviewList();
+  loadStatistics(); // 加载统计数据
+  loadReviewList(); // 加载评论列表
 });
 </script>
 
@@ -291,12 +537,6 @@ onMounted(() => {
     margin-bottom: 20px;
     background: #ffffff;
     border-radius: 8px;
-    .store-name {
-      margin-bottom: 16px;
-      font-size: 18px;
-      font-weight: 600;
-      color: #303133;
-    }
     .statistics-cards {
       display: flex;
       gap: 20px;
@@ -337,6 +577,13 @@ onMounted(() => {
         font-weight: 600;
         color: #303133;
       }
+      .time-filter {
+        margin-bottom: 10px;
+        font-size: 14px;
+        .time-filter-select {
+          width: 120px;
+        }
+      }
     }
 
     // 评论卡片
@@ -376,6 +623,11 @@ onMounted(() => {
           line-height: 1.6;
           color: #606266;
         }
+        .sjhf {
+          padding-bottom: 10px;
+          font-size: 14px;
+          color: #606266;
+        }
         .review-images {
           display: flex;
           gap: 8px;
@@ -394,6 +646,15 @@ onMounted(() => {
         }
       }
     }
+
+    // 分页
+    .pagination-wrapper {
+      display: flex;
+      justify-content: flex-end;
+      padding-top: 20px;
+      margin-top: 20px;
+      border-top: 1px solid #e4e7ed;
+    }
   }
 }
 </style>

+ 262 - 309
src/views/dynamicManagement/reviewAppealDetail.vue

@@ -2,222 +2,203 @@
   <div class="review-appeal-detail-container">
     <!-- 返回按钮 -->
     <div class="page-header">
-      <el-button type="primary" link @click="goBack">
-        <el-icon><ArrowLeft /></el-icon>
-        返回
-      </el-button>
-      <h2 class="page-title">申诉详情</h2>
+      <el-button type="primary" @click="goBack"> 返回 </el-button>
     </div>
 
-    <div v-if="appealDetail" class="appeal-detail-content">
-      <!-- 处理状态 -->
+    <!-- 标题和状态 -->
+    <div class="detail-header">
+      <h2 class="page-title">申诉详情</h2>
       <div class="status-section">
-        <el-result
-          :icon="getResultIcon(appealDetail.status)"
-          :title="getResultTitle(appealDetail.status)"
-          :sub-title="getResultSubTitle(appealDetail.status)"
-        />
-      </div>
-
-      <!-- 处理原因(仅已驳回时显示) -->
-      <div v-if="appealDetail.status === 2" class="section-card reason-section">
-        <div class="section-title">处理原因</div>
-        <el-radio-group v-model="rejectReason" disabled class="reason-radio-group">
-          <el-radio :label="1"> 商家定义交易问题 </el-radio>
-          <el-radio :label="2"> 违反主流价值观,已为客等可不为疏 </el-radio>
-          <el-radio :label="3"> 恶意差评 </el-radio>
-        </el-radio-group>
+        <el-icon class="status-icon" :size="48">
+          <Clock />
+        </el-icon>
+        <div class="status-text">
+          {{ getStatusText(detailData.appealStatus) }}
+        </div>
+        <div class="status-desc">您反馈的评价内容及账号行为正处于审核阶段,请您耐心等待</div>
       </div>
+    </div>
 
-      <!-- 审批详情 -->
-      <div v-if="appealDetail.status !== 0" class="section-card approval-section">
-        <div class="section-title">审批详情</div>
-        <div class="approval-info">
-          <div class="info-row">
-            <span class="info-label">申诉人:</span>
-            <span class="info-value">{{ appealDetail.userName }}</span>
-          </div>
-          <div class="info-row">
-            <span class="info-label">申诉编号:</span>
-            <span class="info-value">{{ appealDetail.appealNo }}</span>
-          </div>
-          <div class="info-row">
-            <span class="info-label">申诉时间:</span>
-            <span class="info-value">{{ appealDetail.appealTime }}</span>
-          </div>
-          <div class="info-row">
-            <span class="info-label">处理时间:</span>
-            <span class="info-value">{{ appealDetail.processTime }}</span>
-          </div>
-        </div>
+    <!-- 处理进度 -->
+    <div class="progress-section">
+      <h3 class="section-title">处理进度</h3>
+      <el-timeline>
+        <el-timeline-item v-for="(step, index) in progressSteps" :key="index" :timestamp="step.timestamp" placement="top">
+          {{ step.content }}
+        </el-timeline-item>
+      </el-timeline>
+    </div>
 
-        <div v-if="appealDetail.approvalComment" class="approval-comment">
-          <div class="comment-title">审批评语</div>
-          <div class="comment-text">
-            {{ appealDetail.approvalComment }}
-          </div>
-        </div>
-      </div>
+    <!-- 申诉详情 -->
+    <div class="appeal-detail-section">
+      <h3 class="section-title">申诉详情</h3>
 
-      <!-- 评价详情 -->
-      <div class="section-card review-detail-section">
-        <div class="section-title">评价详情</div>
-        <div class="review-detail-card">
+      <!-- 顾客评价 -->
+      <div class="review-card">
+        <div class="card-label">顾客评价</div>
+        <div class="review-header">
           <div class="user-info">
-            <el-avatar :src="appealDetail.userAvatar" :size="48">
+            <el-avatar :src="detailData.userAvatar" :size="40">
               <el-icon><User /></el-icon>
             </el-avatar>
             <div class="user-details">
               <div class="user-name">
-                {{ appealDetail.userName }}
-              </div>
-              <div class="review-time">
-                {{ appealDetail.reviewTime }}
+                {{ detailData.isAnonymous == 1 || !detailData.userName ? "匿名用户" : detailData.userName }}
               </div>
             </div>
           </div>
-
-          <div class="review-content">
-            {{ appealDetail.reviewContent }}
+          <div class="review-time">
+            {{ detailData.commentTime }}
           </div>
+        </div>
 
-          <div class="review-rating">
-            <div class="rating-item">
-              <span class="rating-label">口味评分:</span>
-              <el-rate v-model="appealDetail.tasteRating" disabled show-score />
-            </div>
-            <div class="rating-item">
-              <span class="rating-label">环境评分:</span>
-              <el-rate v-model="appealDetail.envRating" disabled show-score />
-            </div>
-            <div class="rating-item">
-              <span class="rating-label">服务评分:</span>
-              <el-rate v-model="appealDetail.serviceRating" disabled show-score />
-            </div>
-          </div>
+        <div class="review-content">
+          {{ detailData.commentContent }}
+        </div>
 
-          <div class="appeal-reason">
-            <div class="reason-label">申诉原因:</div>
-            <div class="reason-text">
-              {{ appealDetail.appealReason }}
-            </div>
-          </div>
+        <div v-if="detailData.commentImages && detailData.commentImages.length > 0" class="review-images">
+          <el-image
+            v-for="(img, index) in detailData.commentImages"
+            :key="index"
+            :src="img"
+            :preview-src-list="detailData.commentImages"
+            fit="cover"
+            class="review-image"
+          />
+        </div>
+      </div>
 
-          <div v-if="appealDetail.appealImages && appealDetail.appealImages.length > 0" class="appeal-images">
-            <div class="images-label">申诉凭证:</div>
-            <div class="images-list">
-              <el-image
-                v-for="(img, index) in appealDetail.appealImages"
-                :key="index"
-                :src="img"
-                :preview-src-list="appealDetail.appealImages"
-                fit="cover"
-                class="appeal-image"
-              />
-            </div>
+      <!-- 申诉信息 -->
+      <div class="appeal-info-card">
+        <div class="card-label">申诉信息</div>
+        <div class="info-item">
+          <span class="info-label">申诉账号</span>
+          <span class="info-value">{{ detailData.storePhone }}</span>
+        </div>
+        <div class="info-item">
+          <span class="info-label">申诉时间</span>
+          <span class="info-value">{{ detailData.appealTime }}</span>
+        </div>
+        <div class="info-item">
+          <span class="info-label">申诉原因</span>
+          <span class="info-value">{{ detailData.appealReason }}</span>
+        </div>
+        <div class="info-item">
+          <span class="info-label">申诉图片</span>
+          <div v-if="detailData.imgList && detailData.imgList.length > 0" class="appeal-images">
+            <el-image
+              v-for="(img, index) in detailData.imgList"
+              :key="index"
+              :src="img"
+              :preview-src-list="detailData.imgList"
+              fit="cover"
+              class="appeal-image"
+            />
           </div>
+          <span v-else class="info-value">--</span>
         </div>
       </div>
     </div>
-
-    <!-- 加载状态 -->
-    <div v-else class="loading-section">
-      <el-skeleton :rows="10" animated />
-    </div>
   </div>
 </template>
 
 <script setup lang="ts" name="reviewAppealDetail">
-import { ref, onMounted } from "vue";
+import { ref, reactive, onMounted } from "vue";
 import { useRouter, useRoute } from "vue-router";
-import { ArrowLeft, User } from "@element-plus/icons-vue";
+import { Clock, User } from "@element-plus/icons-vue";
 import { ElMessage } from "element-plus";
-// import { getAppealDetail } from "@/api/modules/reviewAppeal";
+import { getAppealDetail } from "@/api/modules/newLoginApi";
+import { de } from "element-plus/es/locale";
 
 const router = useRouter();
 const route = useRoute();
 
-// 申诉详情
-const appealDetail = ref<any>(null);
-const rejectReason = ref(1);
+// 详情数据
+const detailData = reactive({
+  appealStatus: 0, // 0-待审核, 1-已驳回, 2-已通过
+  userName: "",
+  userAvatar: "",
+  isAnonymous: 0,
+  commentTime: "",
+  commentContent: "",
+  commentImages: [] as string[],
+  appealAccount: "",
+  appealTime: "",
+  appealReason: "",
+  storePhone: "",
+  imgList: [] as string[]
+});
+
+// 处理进度步骤
+const progressSteps = ref<Array<{ content: string; timestamp: string }>>([
+  {
+    content: "商家提交申诉",
+    timestamp: "2025/06/10 12:00:00"
+  },
+  {
+    content: "通过系统初审,已为您安排专人审核",
+    timestamp: "2025/06/10 12:00:00"
+  },
+  {
+    content: "申诉成功",
+    timestamp: "2025/06/10 12:00:00"
+  }
+]);
 
 // 返回
 const goBack = () => {
   router.back();
 };
 
-// 获取结果图标
-const getResultIcon = (status: number) => {
-  const iconMap: Record<number, string> = {
-    0: "info",
-    1: "success",
-    2: "error"
-  };
-  return iconMap[status] || "info";
-};
-
-// 获取结果标题
-const getResultTitle = (status: number) => {
-  const titleMap: Record<number, string> = {
-    0: "审核中",
-    1: "审核通过",
-    2: "已驳回"
-  };
-  return titleMap[status] || "";
-};
-
-// 获取结果副标题
-const getResultSubTitle = (status: number) => {
-  const subTitleMap: Record<number, string> = {
-    0: "您的申诉正在审核中,请耐心等待。",
-    1: "恭喜您的申诉已经通过审核,评论将被删除或修改。",
-    2: "很抱歉,您的申诉被驳回,如有疑问请联系客服。"
+// 获取状态文本
+const getStatusText = (status: number) => {
+  const statusMap: Record<number, string> = {
+    0: "待审核",
+    1: "已驳回",
+    2: "已通过"
   };
-  return subTitleMap[status] || "";
+  return statusMap[status] || "待审核";
 };
 
 // 加载申诉详情
 const loadAppealDetail = async () => {
-  const id = route.query.id;
-  if (!id) {
-    ElMessage.error("缺少申诉ID");
-    router.back();
-    return;
-  }
-
   try {
-    // TODO: 集成真实接口
-    // const res = await getAppealDetail({ id });
-    // appealDetail.value = res.data;
+    const appealId = route.query.id;
+    if (!appealId) {
+      ElMessage.error("缺少申诉ID");
+      return;
+    }
+
+    const res: any = await getAppealDetail({ id: appealId });
+    console.log("申诉详情:", res);
 
-    // 模拟数据
-    setTimeout(() => {
-      appealDetail.value = {
-        id: id,
-        userName: "不长寿爱",
-        userAvatar: "",
-        status: Number(id) % 3, // 0-审核中, 1-已通过, 2-已驳回
-        appealNo: `APL${String(1380000000 + Number(id)).padStart(10, "0")}`,
-        appealTime: "2025/06/30 12:00:00",
-        processTime: "2025/06/30 15:00:00",
-        reviewTime: "2025/06/30 12:00:00",
-        reviewContent:
-          "这是评论内容这是评论内容这是评论内容这是评论内容,商家定义交易问题,非常好吃很好吃,商家定义交易问题。环境很优雅很好。",
-        tasteRating: 4,
-        envRating: 5,
-        serviceRating: 4,
-        appealReason: "商家定义交易问题,恶意差评。该评论内容与实际消费体验不符,存在明显的恶意诋毁行为。",
-        appealImages: ["", "", ""],
-        approvalComment:
-          Number(id) % 3 === 2
-            ? "您的申诉不符合平台规则,该评论内容真实反映了用户的消费体验。"
-            : Number(id) % 3 === 1
-              ? "经核实,该评论确实存在恶意差评行为,申诉通过。"
-              : ""
-      };
-    }, 500);
-  } catch (error) {
-    ElMessage.error("加载申诉详情失败");
+    if (res.code === 200 || res.code == 200) {
+      const data = res.data;
+      Object.assign(detailData, {
+        appealStatus: data.appealStatus ?? 0,
+        userName: data.userName || "",
+        userAvatar: data.userAvatar || "",
+        isAnonymous: data.isAnonymous ?? 0,
+        commentTime: data.commentTime || data.createdTime || "",
+        commentContent: data.commentContent || "",
+        commentImages: data.commentImages || [],
+        appealAccount: data.appealAccount || data.phone || "",
+        appealTime: data.appealTime || data.createdTime || "",
+        appealReason: data.appealReason || "",
+        storePhone: data.storePhone || "",
+        imgList: data.imgList || []
+      });
+
+      // 更新处理进度
+      if (data.progressSteps && Array.isArray(data.progressSteps)) {
+        progressSteps.value = data.progressSteps;
+      }
+    } else {
+      ElMessage.error(res.msg || "获取申诉详情失败");
+    }
+  } catch (error: any) {
+    console.error("获取申诉详情失败", error);
+    ElMessage.error(error?.msg || "获取申诉详情失败");
   }
 };
 
@@ -233,176 +214,148 @@ onMounted(() => {
   padding: 20px;
   background: #f5f7fa;
   .page-header {
-    display: flex;
-    gap: 16px;
-    align-items: center;
-    margin-bottom: 24px;
+    margin-bottom: 20px;
+  }
+
+  // 标题和状态
+  .detail-header {
+    padding: 24px;
+    margin-bottom: 20px;
+    text-align: center;
+    background: #ffffff;
+    border-radius: 8px;
     .page-title {
-      margin: 0;
+      margin: 0 0 24px;
       font-size: 20px;
       font-weight: 600;
       color: #303133;
     }
-  }
-  .appeal-detail-content {
-    max-width: 1200px;
-    margin: 0 auto;
     .status-section {
-      margin-bottom: 24px;
-      background: #ffffff;
-      border-radius: 8px;
-    }
-    .section-card {
-      padding: 24px;
-      margin-bottom: 20px;
-      background: #ffffff;
-      border-radius: 8px;
-      .section-title {
-        margin-bottom: 20px;
-        font-size: 16px;
+      .status-icon {
+        margin-bottom: 12px;
+        color: #e6a23c;
+      }
+      .status-text {
+        margin-bottom: 8px;
+        font-size: 18px;
         font-weight: 600;
         color: #303133;
       }
-    }
-    .reason-section {
-      .reason-radio-group {
-        display: flex;
-        flex-direction: column;
-        gap: 16px;
-        :deep(.el-radio) {
-          margin-right: 0;
-        }
+      .status-desc {
+        font-size: 14px;
+        color: #909399;
       }
     }
-    .approval-section {
-      .approval-info {
-        padding: 20px;
-        background: #f5f7fa;
-        border-radius: 4px;
-        .info-row {
-          display: flex;
-          margin-bottom: 12px;
-          font-size: 14px;
-          &:last-child {
-            margin-bottom: 0;
-          }
-          .info-label {
-            flex-shrink: 0;
-            min-width: 100px;
-            color: #909399;
-          }
-          .info-value {
-            flex: 1;
-            color: #303133;
-          }
-        }
-      }
-      .approval-comment {
-        margin-top: 20px;
-        .comment-title {
-          margin-bottom: 12px;
-          font-size: 14px;
-          font-weight: 600;
-          color: #303133;
-        }
-        .comment-text {
-          padding: 16px;
-          font-size: 14px;
-          line-height: 1.8;
-          color: #606266;
-          background: #f5f7fa;
-          border-radius: 4px;
-        }
-      }
+  }
+
+  // 处理进度
+  .progress-section {
+    padding: 24px;
+    margin-bottom: 20px;
+    background: #ffffff;
+    border-radius: 8px;
+    .section-title {
+      margin: 0 0 20px;
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+
+  // 申诉详情
+  .appeal-detail-section {
+    padding: 24px;
+    background: #ffffff;
+    border-radius: 8px;
+    .section-title {
+      margin: 0 0 20px;
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
     }
-    .review-detail-section {
-      .review-detail-card {
+    .card-label {
+      margin-bottom: 16px;
+      font-size: 14px;
+      font-weight: 600;
+      color: #606266;
+    }
+
+    // 顾客评价卡片
+    .review-card {
+      padding: 16px;
+      margin-bottom: 24px;
+      background: #f5f7fa;
+      border-radius: 8px;
+      .review-header {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 12px;
         .user-info {
           display: flex;
-          gap: 16px;
+          gap: 12px;
           align-items: center;
-          margin-bottom: 20px;
           .user-details {
             .user-name {
-              margin-bottom: 6px;
-              font-size: 16px;
+              font-size: 14px;
               font-weight: 600;
               color: #303133;
             }
-            .review-time {
-              font-size: 14px;
-              color: #909399;
-            }
           }
         }
-        .review-content {
-          margin-bottom: 20px;
-          font-size: 15px;
-          line-height: 1.8;
-          color: #606266;
+        .review-time {
+          font-size: 13px;
+          color: #909399;
         }
-        .review-rating {
-          padding: 20px;
-          margin-bottom: 20px;
-          background: #f5f7fa;
+      }
+      .review-content {
+        margin-bottom: 12px;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #606266;
+      }
+      .review-images {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        .review-image {
+          width: 100px;
+          height: 100px;
           border-radius: 4px;
-          .rating-item {
-            display: flex;
-            gap: 12px;
-            align-items: center;
-            margin-bottom: 12px;
-            &:last-child {
-              margin-bottom: 0;
-            }
-            .rating-label {
-              flex-shrink: 0;
-              min-width: 80px;
-              font-size: 14px;
-              color: #606266;
-            }
-          }
         }
-        .appeal-reason {
-          margin-bottom: 20px;
-          .reason-label {
-            margin-bottom: 12px;
-            font-size: 15px;
-            font-weight: 600;
-            color: #303133;
-          }
-          .reason-text {
-            font-size: 14px;
-            line-height: 1.8;
-            color: #606266;
-          }
+      }
+    }
+
+    // 申诉信息卡片
+    .appeal-info-card {
+      .info-item {
+        display: flex;
+        margin-bottom: 16px;
+        &:last-child {
+          margin-bottom: 0;
+        }
+        .info-label {
+          flex-shrink: 0;
+          width: 100px;
+          font-size: 14px;
+          color: #909399;
+        }
+        .info-value {
+          flex: 1;
+          font-size: 14px;
+          color: #606266;
         }
         .appeal-images {
-          .images-label {
-            margin-bottom: 12px;
-            font-size: 15px;
-            font-weight: 600;
-            color: #303133;
-          }
-          .images-list {
-            display: grid;
-            grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
-            gap: 12px;
-            .appeal-image {
-              width: 100%;
-              height: 150px;
-              border-radius: 8px;
-            }
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+          .appeal-image {
+            width: 100px;
+            height: 100px;
+            border-radius: 4px;
           }
         }
       }
     }
   }
-  .loading-section {
-    max-width: 1200px;
-    padding: 24px;
-    margin: 0 auto;
-    background: #ffffff;
-    border-radius: 8px;
-  }
 }
 </style>

+ 56 - 37
src/views/dynamicManagement/reviewAppealHistory.vue

@@ -2,10 +2,7 @@
   <div class="review-appeal-history-container">
     <!-- 返回按钮 -->
     <div class="page-header">
-      <el-button type="primary" link @click="goBack">
-        <el-icon><ArrowLeft /></el-icon>
-        返回
-      </el-button>
+      <el-button @click="goBack"> 返回 </el-button>
       <h2 class="page-title">申诉历史</h2>
     </div>
 
@@ -29,15 +26,15 @@
             </el-avatar>
             <div class="user-details">
               <div class="user-name">
-                {{ item.userName }}
+                {{ item.isAnonymous == 1 || !item.userName ? "匿名用户" : item.userName }}
               </div>
               <div class="appeal-time">
-                {{ item.appealTime }}
+                {{ item.createdTime }}
               </div>
             </div>
           </div>
           <el-tag :type="getAppealStatusType(item.status)" size="large">
-            {{ getAppealStatusText(item.status) }}
+            {{ getAppealStatusText(item.appealStatus) }}
           </el-tag>
         </div>
 
@@ -69,7 +66,7 @@
       <el-pagination
         v-model:current-page="pagination.page"
         v-model:page-size="pagination.pageSize"
-        :page-sizes="[10, 20, 30, 50]"
+        :page-sizes="[10, 20, 30, 50, 100]"
         :total="pagination.total"
         layout="total, sizes, prev, pager, next, jumper"
         @size-change="handleSizeChange"
@@ -83,7 +80,9 @@
 import { ref, reactive, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import { ArrowLeft, User } from "@element-plus/icons-vue";
-// import { getAppealHistory } from "@/api/modules/reviewAppeal";
+import { ElMessage } from "element-plus";
+import { getAppealHistory } from "@/api/modules/newLoginApi";
+import { localGet } from "@/utils";
 
 const router = useRouter();
 
@@ -96,7 +95,7 @@ const appealHistoryList = ref<any[]>([]);
 // 分页
 const pagination = reactive({
   page: 1,
-  pageSize: 10,
+  pageSize: 50,
   total: 0
 });
 
@@ -125,27 +124,45 @@ const handleCurrentChange = (val: number) => {
 };
 
 // 加载申诉历史
-const loadAppealHistory = () => {
-  // TODO: 集成真实接口
-  // const res = await getAppealHistory({
-  //   type: activeTab.value,
-  //   page: pagination.page,
-  //   pageSize: pagination.pageSize
-  // });
+const loadAppealHistory = async () => {
+  try {
+    // 根据标签页设置 appealStatus
+    let appealStatus: string | number = "";
+    if (activeTab.value === "pending") {
+      appealStatus = 0; // 审核中
+    } else if (activeTab.value === "rejected") {
+      appealStatus = 1; // 已驳回
+    } else if (activeTab.value === "approved") {
+      appealStatus = 2; // 已通过
+    } else {
+      appealStatus = ""; // 全部:空字符串
+    }
+
+    const params: any = {
+      storeId: localGet("createdId") || "",
+      appealStatus: appealStatus,
+      pageNum: pagination.page,
+      pageSize: pagination.pageSize
+    };
 
-  // 模拟数据
-  appealHistoryList.value = Array.from({ length: 10 }, (_, i) => ({
-    id: i + 1,
-    userName: "不长寿爱",
-    userAvatar: "",
-    appealTime: "2025/06/30 12:00:00",
-    reviewContent: "这是评论内容,商家定义交易问题,非常好吃很好吃。环境很优雅很好。",
-    status: i % 3, // 0-审核中, 1-已通过, 2-已驳回
-    appealReason: i % 3 === 0 ? "等待审核" : i % 3 === 1 ? "审核通过" : "已驳回",
-    appealNo: `APL${String(1380000000 + i).padStart(10, "0")}`
-  }));
+    console.log("请求参数:", params);
+    const res: any = await getAppealHistory(params);
+    console.log("接口返回:", res);
 
-  pagination.total = 30;
+    if (res.code === 200 || res.code == 200) {
+      appealHistoryList.value = res.data?.records || res.data || [];
+      pagination.total = res.data?.total || 0;
+    } else {
+      appealHistoryList.value = [];
+      pagination.total = 0;
+      ElMessage.error(res.msg || "获取申诉历史失败");
+    }
+  } catch (error: any) {
+    console.error("获取申诉历史失败", error);
+    appealHistoryList.value = [];
+    pagination.total = 0;
+    ElMessage.error(error?.msg || "获取申诉历史失败");
+  }
 };
 
 // 查看详情
@@ -157,21 +174,21 @@ const viewDetail = (item: any) => {
 };
 
 // 获取申诉状态类型
-const getAppealStatusType = (status: number) => {
-  const typeMap: Record<number, string> = {
-    0: "warning",
-    1: "success",
-    2: "danger"
+const getAppealStatusType = (status: number): "success" | "warning" | "info" | "danger" => {
+  const typeMap: Record<number, "success" | "warning" | "info" | "danger"> = {
+    0: "warning", // 审核中
+    1: "danger", // 已驳回
+    2: "success" // 已通过
   };
-  return typeMap[status] || "";
+  return typeMap[status] || "info";
 };
 
 // 获取申诉状态文本
 const getAppealStatusText = (status: number) => {
   const textMap: Record<number, string> = {
     0: "审核中",
-    1: "已通过",
-    2: "已驳回"
+    1: "已驳回",
+    2: "已通过"
   };
   return textMap[status] || "";
 };
@@ -193,10 +210,12 @@ onMounted(() => {
     align-items: center;
     margin-bottom: 24px;
     .page-title {
+      width: 100%;
       margin: 0;
       font-size: 20px;
       font-weight: 600;
       color: #303133;
+      text-align: center;
     }
   }
   .tabs-section {