Quellcode durchsuchen

Merge remote-tracking branch 'origin/development' into uat

LuTong vor 2 Monaten
Ursprung
Commit
fdcb5bc09b

+ 30 - 0
src/api/modules/feedback.ts

@@ -0,0 +1,30 @@
+import httpApi from "@/api/indexApi";
+/** 反馈类型映射(与商家端一致) */
+export const typeMap: Record<number, string> = {
+  0: "BUG反馈",
+  1: "优化反馈",
+  2: "功能反馈"
+};
+
+export const getTypeName = (type: number): string => typeMap[type] ?? "其他";
+
+/**
+ * 获取反馈详情
+ * GET /alienStore/feedback/detail?feedbackId=xxx
+ */
+export const getFeedbackDetail = (params: { feedbackId: number | string }) => {
+  return httpApi.get(`/alienStore/feedback/detail`, params, { loading: false });
+};
+
+/**
+ * 用户回复
+ * POST /alienStore/feedback/userReply
+ */
+
+export const userReply = (params: { feedbackId: number | string; content: string }) => {
+  return httpApi.post(`/alienStore/feedback/userReply`, {
+    ...params,
+    feedbackSource: 1,
+    fileUrlList: []
+  });
+};

+ 21 - 0
src/api/modules/headerNotice.ts

@@ -46,6 +46,16 @@ export const readNoticeById = (params: { id: number | string }) => {
 };
 
 /**
+ * 删除通知(与商家端一致)
+ * GET /alienStore/notice/deleteNoticeById?id=xxx
+ */
+export const deleteNoticeById = (params: { id: number | string }) => {
+  return httpApi.get<unknown>(`/alienStore/notice/deleteNoticeById`, params, {
+    loading: false
+  });
+};
+
+/**
  * 按类型查询未读通知数量
  * GET /alienStore/notice/countUnreadByType?noticeType=0|1|2&receiverId=xxx(alien-store)
  * noticeType: 0-与我相关 1-系统通知 2-订单提醒
@@ -124,3 +134,14 @@ export const getStrangerMessageNum = (params: { receiverId: string }) => {
     { loading: false }
   );
 };
+
+/**
+ * 删除未关注人消息(与商家端 message-notFriend 一致)
+ * GET /message/deleteMessageByPhoneId
+ * 参数 receiverId: 当前商户(store_xxx) senderId: 聊天方(store_xxx)
+ */
+export const deleteMessageByPhoneId = (params: { receiverId: string; senderId: string }) => {
+  return httpApi.get<unknown>(`/alienStore/message/deleteMessageByPhoneId`, params, {
+    loading: false
+  });
+};

+ 15 - 1
src/api/modules/newLoginApi.ts

@@ -102,7 +102,10 @@ export const getStoreOcrData = params => {
 export const getFriendCouponList = (params: any) => {
   return httpLogin.get(`/alienStore/life-discount-coupon-store-friend/getReceivedSendFriendCouponList`, params);
 };
-
+// 获取好友代金券列表
+export const getCouponDetail = (params: any) => {
+  return httpLogin.get(`/alienStore/coupon/getCouponDetail`, params);
+};
 // 获取好友优惠券列表
 export const getFriendCouponDetail = (params: any) => {
   return httpLogin.get(`/alienStore/life-discount-coupon/getCounponDetailById`, params);
@@ -364,3 +367,14 @@ export const cancelFollewed = (params: {
 }) => {
   return httpLogin.post(`/alienStore/user/cancelFollewed`, params);
 };
+
+// 搜索用户和店铺(根据手机号或昵称)
+export const getStoreAndUserByName = (params: {
+  page: number;
+  size: number;
+  phoneId: string; // store_手机号
+  userId: string | number;
+  searchName: string; // 搜索关键词(手机号或昵称)
+}) => {
+  return httpLogin.post(`/alienStore/user/getStoreAndUserByName`, params);
+};

+ 12 - 2
src/components/Upload/Imgs.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="upload-box">
+  <div class="upload-box" :class="{ 'hide-upload-trigger': shouldHideUploadTrigger }">
     <el-upload
       v-model:file-list="_fileList"
       action="#"
@@ -75,6 +75,7 @@ interface UploadFileProps {
   onSuccess?: (url: string) => void;
   onVideoPreview?: (url: string) => void; // 点击视频「查看」时的回调,用于父级弹窗放大预览(不传则新标签页打开)
   showSuccessNotification?: boolean; // 是否显示上传成功通知 ==> 非必传(默认为 true)
+  hideUploadTrigger?: boolean; // 是否隐藏上传入口(达限或自定义隐藏时使用)==> 非必传(默认为 false)
 }
 
 const props = withDefaults(defineProps<UploadFileProps>(), {
@@ -87,9 +88,13 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
   height: "150px",
   width: "150px",
   borderRadius: "8px",
-  showSuccessNotification: true
+  showSuccessNotification: true,
+  hideUploadTrigger: false
 });
 
+// 上传入口是否隐藏(达限或显式传入 hideUploadTrigger 时)
+const shouldHideUploadTrigger = computed(() => props.hideUploadTrigger || (props.fileList?.length ?? 0) >= props.limit);
+
 // 获取 el-form 组件上下文
 const formContext = inject(formContextKey, void 0);
 // 获取 el-form-item 组件上下文
@@ -426,5 +431,10 @@ const handlePictureCardPreview: UploadProps["onPreview"] = file => {
     line-height: 15px;
     text-align: center;
   }
+
+  /* 隐藏上传入口(达限或 hideUploadTrigger 为 true 时),隐藏包含 Plus 图标的触发区域 */
+  &.hide-upload-trigger :deep(.el-upload:has(.upload-empty)) {
+    display: none;
+  }
 }
 </style>

+ 123 - 13
src/layouts/components/Header/components/NotificationDrawerContent.vue

@@ -83,7 +83,7 @@
                 class="message-card"
                 :class="{ unread: item.unread }"
               >
-                <div class="message-avatar">
+                <div class="message-avatar avatar-clickable" @click.stop="handleAvatarClick(item)">
                   <el-avatar :size="40" :src="item.userImage">
                     <el-icon><UserFilled /></el-icon>
                   </el-avatar>
@@ -94,12 +94,15 @@
                     <span class="message-sender">{{ item.userName || item.title || "未知" }}</span>
                     <span class="message-date">{{ item.date }}</span>
                   </div>
-                  <div class="message-content">
+                  <div class="message-content" v-if="item.title == '关注通知' || item.title == '动态通知'">
+                    {{ item.userName }} {{ item.content }}
+                  </div>
+                  <div class="message-content" v-else>
                     {{ processContent(item) }}
                   </div>
-                  <!-- <div class="message-actions">
+                  <div class="message-actions">
                     <el-button size="small" type="default" @click.stop="handleDelete(item, index)"> 删除 </el-button>
-                  </div> -->
+                  </div>
                 </div>
               </div>
             </template>
@@ -159,6 +162,10 @@
                     <span v-else-if="item.isNotDisturb === '1' && (item.notReadCount ?? 0) > 0" class="message-dot" />
                   </div>
                 </div>
+                <!-- 未关注人消息、消息列表:删除按钮(接口 deleteMessageByPhoneId) -->
+                <div class="message-actions">
+                  <el-button size="small" type="default" @click.stop="handleDeleteMessage(item, index)"> 删除 </el-button>
+                </div>
               </div>
             </div>
             <div v-if="currentMessageList.length === 0" class="empty-tip">暂无数据</div>
@@ -169,7 +176,13 @@
 
     <!-- 详情弹窗 -->
     <el-dialog v-model="detailVisible" :title="currentDetail?.title" width="500px">
-      <div class="detail-dialog-content">
+      <div class="detail-dialog-content" v-if="currentDetail?.title == '关注通知' || currentDetail?.title == '动态通知'">
+        {{ currentDetail?.userName }} {{ currentDetail?.content }}
+      </div>
+      <div class="detail-dialog-content" v-else>
+        <div style="font-size: 14px; font-weight: bold; text-align: left">
+          {{ currentDetail?.createTime }}
+        </div>
         {{ currentDetail?.content }}
       </div>
     </el-dialog>
@@ -180,13 +193,16 @@
 import { ref, computed, onMounted, watch, markRaw } from "vue";
 import { useRouter } from "vue-router";
 import { House, List, User, Loading, Message, UserFilled } from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
 import { localGet } from "@/utils";
 import {
   getNoticeListForHeader,
   readNoticeById,
+  deleteNoticeById,
   getCountUnreadByType,
   getMessageList,
-  getStrangerMessageNum
+  getStrangerMessageNum,
+  deleteMessageByPhoneId
 } from "@/api/modules/headerNotice";
 import type { NoFriendMessageItem } from "@/api/modules/headerNotice";
 
@@ -199,9 +215,16 @@ interface NoticeItem {
   content: string;
   date: string;
   unread: boolean;
-  /** 互动类可能有发送者与头像 */
+  /** 原始创建时间(接口返回 createdTime) */
+  createTime?: string;
+  /** 意见反馈回复通知:context 中的 feedbackId,用于跳转反馈详情 */
+  feedbackId?: string | number;
+  /** 互动类:发送者信息,用于跳转动态主页 */
   userName?: string;
   userImage?: string;
+  userId?: string | number;
+  phoneId?: string;
+  storeUserId?: string | number;
   /** 类型,用于 processContent 展示 */
   type?: string;
 }
@@ -450,6 +473,35 @@ function handleMessageItemClick(item: MessageItem) {
   router.push(`/storeDecorationManagement/decorationChat?${params.toString()}`);
 }
 
+// 删除消息(未关注人消息、消息列表通用,接口 deleteMessageByPhoneId)
+async function handleDeleteMessage(item: MessageItem, index: number) {
+  const receiverId = getReceiverId();
+  const senderId = item.phoneId ?? item.senderId;
+  if (!receiverId || !senderId) {
+    ElMessage.error("无法删除");
+    return;
+  }
+  try {
+    await ElMessageBox.confirm("确定要删除这条消息吗?", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+    await deleteMessageByPhoneId({
+      receiverId: "store_" + receiverId,
+      senderId: String(senderId)
+    });
+    const key = messageCategory.value;
+    messageListByCategory.value[key] = currentMessageList.value.filter((_, i) => i !== index);
+    const cat = messageCategories.value.find(c => c.key === key);
+    if (cat && cat.unread > 0) cat.unread = Math.max(0, cat.unread - (item.notReadCount ?? 1));
+    if (key === "unfollowed") await fetchUnfollowedUnreadCount();
+    ElMessage.success("删除成功");
+  } catch (e) {
+    if (e !== "cancel" && e !== "close") ElMessage.error("删除失败");
+  }
+}
+
 // 根据类型显示内容(与商家端一致)
 function getVoiceDuration(item: { voiceDuration?: number; duration?: number; [key: string]: any }): string {
   const sec = item?.voiceDuration ?? item?.duration ?? 0;
@@ -495,6 +547,18 @@ function parseContext(context: string | undefined): string {
   }
 }
 
+/** 从 context 解析 feedbackId(意见反馈回复通知) */
+function parseFeedbackIdFromContext(context: string | undefined): string | number | undefined {
+  if (!context) return undefined;
+  try {
+    const parsed = typeof context === "string" ? JSON.parse(context) : context;
+    const id = parsed?.feedbackId;
+    return id != null ? id : undefined;
+  } catch {
+    return undefined;
+  }
+}
+
 // 互动类内容:商家端 context 可能为 "类型|split|内容1|split|内容2",取第一段或解析 JSON
 function getInteractionContent(context: string | undefined): string {
   if (!context) return "";
@@ -531,16 +595,22 @@ async function fetchNoticeList(catKey: string) {
       const content = isRelated ? getInteractionContent(rawContext) : parseContext(rawContext);
       const dateRaw = item.createdTime ?? "";
       const dateStr = dateRaw.includes(" ") ? dateRaw.split(" ")[0].replace(/-/g, "/") : dateRaw.replace(/-/g, "/");
+      const feedbackId = parseFeedbackIdFromContext(rawContext);
       return {
         id: String(item.id),
         title: item.title ?? "",
         content,
         date: dateStr,
         unread: !item.isRead,
+        createTime: item.createdTime ?? "",
         type: item.type,
+        ...(feedbackId != null && { feedbackId }),
         ...(isRelated && {
           userName: item.userName ?? item.senderName ?? item.title ?? "",
-          userImage: item.userImage ?? item.storeImg ?? item.senderImg
+          userImage: item.userImage ?? item.storeImg ?? item.senderImg,
+          userId: item.userId ?? item.storeUserId,
+          phoneId: item.phoneId ?? item.senderId,
+          storeUserId: item.storeUserId ?? item.userId
         })
       };
     });
@@ -615,15 +685,52 @@ async function handleViewDetail(item: NoticeItem) {
       // 仍打开详情
     }
   }
+
+  // 系统通知 - 意见反馈回复通知:跳转反馈详情页(与商家端 s-informList 一致)
+  if (activeCategory.value === "system" && item.feedbackId != null) {
+    emit("close");
+    router.push({ path: "/feedback/detail", query: { id: String(item.feedbackId) } });
+    return;
+  }
+
   currentDetail.value = item;
   detailVisible.value = true;
 }
 
-function handleDelete(item: NoticeItem, index: number) {
-  const key = activeCategory.value;
-  listByCategory.value[key] = currentList.value.filter((_, i) => i !== index);
-  const cat = noticeCategories.value.find(c => c.key === key);
-  if (cat && item.unread) cat.unread = Math.max(0, cat.unread - 1);
+// 互动列表:点击头像跳转动态主页(与 dynamicManagement/index 的 handleViewUserProfile 一致)
+function handleAvatarClick(item: NoticeItem) {
+  console.log(item, "item");
+  const phoneId = item.phoneId ?? item.userId;
+  if (!phoneId) return;
+  emit("close");
+  router.push({
+    path: "/dynamicManagement/userDynamic",
+    query: {
+      userId: String(item.storeUserId ?? item.userId ?? ""),
+      phoneId: String(phoneId),
+      userName: item.userName ?? "",
+      userAvatar: item.userImage ?? ""
+    }
+  });
+}
+
+async function handleDelete(item: NoticeItem, index: number) {
+  try {
+    await ElMessageBox.confirm("确定要删除这条通知吗?", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+    await deleteNoticeById({ id: item.id });
+    const key = activeCategory.value;
+    listByCategory.value[key] = currentList.value.filter((_, i) => i !== index);
+    const cat = noticeCategories.value.find(c => c.key === key);
+    if (cat && item.unread) cat.unread = Math.max(0, cat.unread - 1);
+    paginationByCategory.value[key].total = Math.max(0, paginationByCategory.value[key].total - 1);
+    ElMessage.success("删除成功");
+  } catch (e) {
+    if (e !== "cancel" && e !== "close") ElMessage.error("删除失败");
+  }
 }
 /** 刷新全部(供 WebSocket 消息到达时实时更新,与商家端一致) */
 function refresh() {
@@ -765,6 +872,9 @@ watch(activeTab, val => {
   .message-avatar {
     position: relative;
     flex-shrink: 0;
+    &.avatar-clickable {
+      cursor: pointer;
+    }
     .message-unread-dot {
       position: absolute;
       top: 0;

+ 9 - 0
src/routers/modules/staticRouter.ts

@@ -37,6 +37,15 @@ export const staticRouter: RouteRecordRaw[] = [
           isAffix: false,
           isKeepAlive: false
         }
+      },
+      {
+        path: "/feedback/detail",
+        name: "feedbackDetail",
+        component: () => import("@/views/feedback/detail.vue"),
+        meta: {
+          title: "反馈详情",
+          isHide: true
+        }
       }
     ]
   }

+ 18 - 9
src/views/dynamicManagement/friendCoupon.vue

@@ -262,13 +262,13 @@ const columns = computed(() => {
 // 初始化请求参数 - 好友赠我传 storeUserId
 const initParam = reactive({
   storeUserId: localGet("createdId"), // 好友赠我:当前店铺ID(接收方)
-  friendStoreUserId: undefined as number | undefined, // 我赠好友:当前用户ID(赠送方)
-  type: activeName.value
+  friendStoreUserId: undefined as number | undefined // 我赠好友:当前用户ID(赠送方)
+  // type: activeName.value
 });
 
 // Tab切换处理
 const handleTabClick = () => {
-  initParam.type = activeName.value;
+  // initParam.type = activeName.value;
 
   // 根据当前 tab 设置正确的参数
   if (activeName.value === "myGift") {
@@ -295,8 +295,8 @@ const dataCallback = (data: any) => {
 // 获取表格列表
 const getTableList = (params: any) => {
   const newParams = {
-    ...params,
-    type: activeName.value === "friendMessage" ? 0 : 1 // 0-好友赠我,1-我赠好友
+    ...params
+    //type: activeName.value === "friendMessage" ? 0 : 1 // 0-好友赠我,1-我赠好友
   };
 
   return getFriendCouponList(newParams);
@@ -366,12 +366,21 @@ const handleGiftSubmit = async () => {
 
 // 查看详情
 const viewDetail = (row: any) => {
-  router.push({
-    path: "/dynamicManagement/friendCouponDetail",
-    query: {
+  let query = {};
+  if (row.voucherId) {
+    query = {
+      voucherId: row.voucherId,
+      type: activeName.value
+    };
+  } else {
+    query = {
       couponId: row.couponId,
       type: activeName.value
-    }
+    };
+  }
+  router.push({
+    path: "/dynamicManagement/friendCouponDetail",
+    query: query
   });
 };
 

+ 37 - 14
src/views/dynamicManagement/friendCouponDetail.vue

@@ -28,16 +28,22 @@
           <!-- 面值 -->
           <div class="detail-item">
             <div class="detail-label">面值(元)</div>
-            <div class="detail-value">
+            <div class="detail-value" v-if="couponId">
               {{ formatCurrency(couponModel.nominalValue, 2, "¥") }}
             </div>
+            <div class="detail-value" v-else>
+              {{ formatCurrency(couponModel.nominalValue ?? couponModel.price, 2, "¥") }}
+            </div>
           </div>
           <!-- 有效期至 -->
           <div class="detail-item">
             <div class="detail-label">有效期至</div>
-            <div class="detail-value">
+            <div class="detail-value" v-if="couponId">
               {{ couponModel.endGetDate }}
             </div>
+            <div class="detail-value" v-else>
+              {{ couponModel.endDate }}
+            </div>
           </div>
           <!-- 优惠券数量 -->
           <div class="detail-item">
@@ -117,7 +123,7 @@
 import { ref, onMounted } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { ElMessage } from "element-plus";
-import { getFriendCouponDetail } from "@/api/modules/newLoginApi";
+import { getFriendCouponDetail, getCouponDetail } from "@/api/modules/newLoginApi";
 import { formatCurrency } from "@/utils/formatCurrency";
 import { localGet } from "@/utils";
 
@@ -128,7 +134,8 @@ const router = useRouter();
 const route = useRoute();
 
 // 页面ID参数
-const id = ref<string>("");
+const couponId = ref<string>("");
+const voucherId = ref<string>("");
 
 // 优惠券类型(好友赠我 friendMessage / 我赠好友 myGift)
 const type = ref<string>("");
@@ -174,9 +181,13 @@ const couponModel = ref<any>({
  * 从路由参数中获取couponId并加载详情数据
  */
 onMounted(async () => {
-  id.value = (route.query.couponId as string) || "";
+  if (route.query.voucherId) {
+    voucherId.value = (route.query.voucherId as string) || "";
+  } else if (route.query.couponId) {
+    couponId.value = (route.query.couponId as string) || "";
+  }
   type.value = (route.query.type as string) || "";
-  if (id.value) {
+  if (voucherId.value || couponId.value) {
     await loadDetailData();
   } else {
     ElMessage.warning("缺少优惠券ID参数");
@@ -199,15 +210,27 @@ const goBack = () => {
  */
 const loadDetailData = async () => {
   try {
-    // 使用 couponId 获取详情数据
-    const res: any = await getFriendCouponDetail({
-      counponId: id.value
-    });
+    if (voucherId.value) {
+      const res: any = await getCouponDetail({
+        id: voucherId.value
+      });
+      if (res.code === 200) {
+        couponModel.value = res.data;
+        console.log(couponModel.value, "couponModel.value");
+      } else {
+        ElMessage.error(res.msg);
+      }
+    } else if (couponId.value) {
+      // 使用 couponId 获取详情数据
+      const res: any = await getFriendCouponDetail({
+        counponId: couponId.value
+      });
 
-    if (res.code === 200) {
-      couponModel.value = res.data;
-    } else {
-      ElMessage.error(res.msg);
+      if (res.code === 200) {
+        couponModel.value = res.data;
+      } else {
+        ElMessage.error(res.msg);
+      }
     }
   } catch (error) {
     ElMessage.error("加载详情数据出错");

+ 349 - 22
src/views/dynamicManagement/myDynamic.vue

@@ -22,6 +22,13 @@
               <span class="stat-item" @click="openRelationDialog('fans')">{{ userInfo.likeCount }} 粉丝</span>
             </div>
           </div>
+
+          <el-button type="primary" size="small" @click="addFriend" class="add-friend-btn">
+            <el-icon class="btn-icon">
+              <Plus />
+            </el-icon>
+            添加好友
+          </el-button>
         </div>
       </div>
 
@@ -453,7 +460,7 @@
           <div v-for="user in filteredRelationList" :key="user.id" class="relation-item">
             <div class="user-info-row">
               <div class="user-avatar-small">
-                <img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
+                <img v-if="user.image" :src="user.avatar" :alt="user.name" />
                 <el-icon v-else :size="40">
                   <Avatar />
                 </el-icon>
@@ -463,7 +470,7 @@
                   {{ user.name }}
                 </div>
                 <div class="user-desc">
-                  {{ user.description }}
+                  {{ user.blurb }}
                 </div>
               </div>
             </div>
@@ -490,6 +497,78 @@
       </div>
     </el-dialog>
 
+    <!-- 添加好友对话框 -->
+    <el-dialog
+      v-model="addFriendDialogVisible"
+      title="添加好友"
+      width="500px"
+      destroy-on-close
+      @close="handleCloseAddFriendDialog"
+    >
+      <div class="add-friend-dialog-content">
+        <!-- 搜索框 -->
+        <div class="search-box">
+          <el-input
+            v-model="addFriendSearchKeyword"
+            placeholder="请输入手机号或昵称"
+            clearable
+            @keyup.enter="handleSearchFriend"
+            @input="handleSearchInput"
+            @clear="handleClearSearch"
+          >
+            <template #prefix>
+              <el-icon>
+                <Search />
+              </el-icon>
+            </template>
+          </el-input>
+        </div>
+
+        <!-- 搜索结果 -->
+        <div v-if="addFriendSearchKeyword" class="search-results">
+          <div v-if="addFriendSearching" class="search-loading">
+            <el-icon class="is-loading" :size="20">
+              <Loading />
+            </el-icon>
+            <span>搜索中...</span>
+          </div>
+          <div v-else-if="addFriendSearchResults.length > 0" class="search-results-list">
+            <div class="search-results-count">共{{ addFriendSearchResults.length }}个搜索结果</div>
+            <div v-for="user in addFriendSearchResults" :key="user.phoneId || user.id" class="search-result-item">
+              <div class="user-info-row">
+                <div class="user-avatar-small">
+                  <img v-if="user.imgUrl" :src="user.imgUrl || user.userAvatar" :alt="user.name || user.userName" />
+                  <el-icon v-else :size="40">
+                    <Avatar />
+                  </el-icon>
+                </div>
+                <div class="user-details">
+                  <div class="user-name-text">
+                    {{ user.storeUserName || "—" }}
+                  </div>
+                  <div class="user-desc">
+                    {{ user.blurb || "—" }}
+                  </div>
+                </div>
+              </div>
+              <div class="action-button">
+                <el-button v-if="user.isFollowThis == 1" type="primary" plain size="small" @click="handleUnfollowInSearch(user)">
+                  已关注
+                </el-button>
+                <el-button v-else type="primary" plain size="small" @click="handleFollowInSearch(user)"> 关注 </el-button>
+              </div>
+            </div>
+          </div>
+          <div v-else class="search-empty">
+            <el-empty description="暂无搜索结果" />
+          </div>
+        </div>
+        <div v-else class="search-placeholder">
+          <el-empty description="请输入手机号或昵称进行搜索" />
+        </div>
+      </div>
+    </el-dialog>
+
     <!-- 举报对话框 -->
     <el-dialog v-model="reportDialogVisible" title="举报理由" width="500px" destroy-on-close @close="handleCloseReportDialog">
       <div class="report-dialog-content">
@@ -638,7 +717,8 @@ import {
   Warning,
   Plus,
   CircleCheck,
-  CircleClose
+  CircleClose,
+  Loading
 } from "@element-plus/icons-vue";
 import {
   deleteDynamicsById,
@@ -654,7 +734,10 @@ import {
   getMyUserFans,
   blockUser,
   likeDynamicNew,
-  unlikeDynamicNew
+  unlikeDynamicNew,
+  getStoreAndUserByName,
+  toggleFollowUser,
+  cancelFollewed
 } from "@/api/modules/newLoginApi";
 import {} from "@/api/modules/dynamicManagement";
 import { useUserStore } from "@/stores/modules/user";
@@ -788,6 +871,12 @@ const shareSearch = ref("");
 const shareFriendList = ref<ShareFriend[]>([]);
 const selectedFriends = ref<number[]>([]);
 
+// 添加好友对话框相关
+const addFriendDialogVisible = ref(false);
+const addFriendSearchKeyword = ref("");
+const addFriendSearchResults = ref<any[]>([]);
+const addFriendSearching = ref(false);
+
 // 过滤后的好友列表
 const filteredShareFriendList = computed(() => {
   if (!shareSearch.value) {
@@ -1638,16 +1727,9 @@ const handleFriendList = async () => {
     fansId: fansId,
     name: relationSearch.value || ""
   });
-  if (res.code === 200) {
-    const dataList = res.data?.records || res.data?.list || res.data || [];
-    relationList.value = dataList.map((item: any) => ({
-      id: item.id || item.userId,
-      name: item.userName || item.nickname || item.name || "用户",
-      avatar: item.userImage || item.avatar || item.headImg || "",
-      description: item.description || item.bio || item.signature || "欢迎来这里",
-      relationStatus: "mutual" as const, // 好友列表都是互相关注
-      phoneId: item.phoneId || item.fansId || "" // 保存 phoneId 用于后续操作
-    }));
+  if (res.code == 200) {
+    const dataList = res.data?.records || [];
+    relationList.value = dataList;
   }
 };
 //关注列表
@@ -1662,14 +1744,7 @@ const handleFollowList = async () => {
   });
   if (res.code === 200) {
     const dataList = res.data?.records || res.data?.list || res.data || [];
-    relationList.value = dataList.map((item: any) => ({
-      id: item.id || item.userId,
-      name: item.userName || item.nickname || item.name || "用户",
-      avatar: item.userImage || item.avatar || item.headImg || "",
-      description: item.description || item.bio || item.signature || "欢迎来这里",
-      relationStatus: "following" as const, // 关注列表都是已关注状态
-      phoneId: item.phoneId || item.followedId || "" // 保存 phoneId 用于后续操作
-    }));
+    relationList.value = dataList;
   }
 };
 //粉丝列表
@@ -1755,6 +1830,156 @@ const handleCloseRelationDialog = () => {
   relationList.value = [];
 };
 
+// 打开添加好友对话框
+const addFriend = () => {
+  addFriendDialogVisible.value = true;
+};
+
+// 关闭添加好友对话框
+const handleCloseAddFriendDialog = () => {
+  // 清空防抖定时器
+  if (searchDebounceTimer) {
+    clearTimeout(searchDebounceTimer);
+    searchDebounceTimer = null;
+  }
+  addFriendSearchKeyword.value = "";
+  addFriendSearchResults.value = [];
+  addFriendSearching.value = false;
+};
+
+// 搜索输入防抖定时器
+let searchDebounceTimer: NodeJS.Timeout | null = null;
+
+// 搜索输入处理(带防抖)
+const handleSearchInput = () => {
+  // 清空之前的定时器
+  if (searchDebounceTimer) {
+    clearTimeout(searchDebounceTimer);
+  }
+
+  // 如果输入框为空,清空搜索结果
+  if (!addFriendSearchKeyword.value.trim()) {
+    addFriendSearchResults.value = [];
+    return;
+  }
+
+  // 设置防抖,500ms后执行搜索
+  searchDebounceTimer = setTimeout(() => {
+    handleSearchFriend();
+  }, 200);
+};
+
+// 搜索好友
+const handleSearchFriend = async () => {
+  if (!addFriendSearchKeyword.value.trim()) {
+    addFriendSearchResults.value = [];
+    return;
+  }
+
+  addFriendSearching.value = true;
+  addFriendSearchResults.value = [];
+
+  try {
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+    const userId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
+
+    const res: any = await getStoreAndUserByName({
+      page: 1,
+      size: 100,
+      phoneId: currentUserPhoneId,
+      userId: userId,
+      searchName: addFriendSearchKeyword.value.trim()
+    });
+
+    if (res && res.code === 200) {
+      // 处理搜索结果数据
+      const dataList = res.data?.records || [];
+      addFriendSearchResults.value = dataList;
+    } else {
+      ElMessage.error(res?.msg || res?.message || "搜索失败");
+    }
+  } catch (error: any) {
+    console.error("搜索好友失败:", error);
+    ElMessage.error(error?.message || "搜索失败,请重试");
+  } finally {
+    addFriendSearching.value = false;
+  }
+};
+
+// 清空搜索
+const handleClearSearch = () => {
+  // 清空防抖定时器
+  if (searchDebounceTimer) {
+    clearTimeout(searchDebounceTimer);
+    searchDebounceTimer = null;
+  }
+  addFriendSearchResults.value = [];
+};
+
+// 在搜索结果中关注用户
+const handleFollowInSearch = async (user: any) => {
+  try {
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    if (!user.phoneId) {
+      ElMessage.error("用户信息不完整");
+      return;
+    }
+
+    await toggleFollowUser({
+      followedId: user.phoneId,
+      fansId: currentUserPhoneId,
+      fansType: 2
+    });
+
+    // 更新关注状态
+    user.isFollowed = 1;
+    user.isFollowThis = 1;
+
+    ElMessage.success("关注成功");
+  } catch (error: any) {
+    console.error("关注失败:", error);
+    ElMessage.error(error?.message || "关注失败,请重试");
+  }
+};
+
+// 在搜索结果中取消关注用户
+const handleUnfollowInSearch = async (user: any) => {
+  try {
+    await ElMessageBox.confirm("确定要取消关注吗?", "提示", {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "warning"
+    });
+
+    const phone = userStore.userInfo?.phone || "";
+    const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
+
+    if (!user.phoneId) {
+      ElMessage.error("用户信息不完整");
+      return;
+    }
+
+    await cancelFollewed({
+      followedId: user.phoneId,
+      fansId: currentUserPhoneId
+    });
+
+    // 更新关注状态
+    user.isFollowed = 0;
+    user.isFollowThis = 0;
+
+    ElMessage.success("已取消关注");
+  } catch (error: any) {
+    if (error !== "cancel") {
+      console.error("取消关注失败:", error);
+      ElMessage.error(error?.message || "取消关注失败,请重试");
+    }
+  }
+};
+
 // 初始化
 onMounted(() => {
   loadUserInfo();
@@ -2115,6 +2340,15 @@ onMounted(() => {
             }
           }
         }
+        .add-friend-btn {
+          display: flex;
+          flex-shrink: 0;
+          align-items: center;
+          margin-left: auto;
+          .btn-icon {
+            margin-right: 4px;
+          }
+        }
       }
     }
     .user-bio {
@@ -2910,6 +3144,99 @@ onMounted(() => {
       }
     }
   }
+
+  // 添加好友对话框样式
+  .add-friend-dialog-content {
+    .search-box {
+      margin-bottom: 20px;
+    }
+    .search-results {
+      max-height: 400px;
+      overflow-y: auto;
+      .search-loading {
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        justify-content: center;
+        padding: 40px 0;
+        color: #909399;
+      }
+      .search-results-count {
+        padding: 0 4px;
+        margin-bottom: 12px;
+        font-size: 14px;
+        color: #909399;
+      }
+      .search-results-list {
+        .search-result-item {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 16px;
+          border-bottom: 1px solid #f0f0f0;
+          transition: background-color 0.2s;
+          &:hover {
+            background-color: #f5f7fa;
+          }
+          &:last-child {
+            border-bottom: none;
+          }
+          .user-info-row {
+            display: flex;
+            flex: 1;
+            gap: 12px;
+            align-items: center;
+            .user-avatar-small {
+              display: flex;
+              flex-shrink: 0;
+              align-items: center;
+              justify-content: center;
+              width: 48px;
+              height: 48px;
+              overflow: hidden;
+              background-color: #f0f0f0;
+              border-radius: 50%;
+              img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .user-details {
+              flex: 1;
+              min-width: 0;
+              .user-name-text {
+                margin-bottom: 4px;
+                overflow: hidden;
+                font-size: 16px;
+                font-weight: 500;
+                color: #303133;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+              .user-desc {
+                overflow: hidden;
+                font-size: 14px;
+                color: #909399;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+            }
+          }
+          .action-button {
+            flex-shrink: 0;
+            margin-left: 12px;
+          }
+        }
+      }
+      .search-empty {
+        padding: 40px 0;
+      }
+    }
+    .search-placeholder {
+      padding: 40px 0;
+    }
+  }
 }
 </style>
 

+ 286 - 0
src/views/feedback/detail.vue

@@ -0,0 +1,286 @@
+<template>
+  <div class="feedback-detail">
+    <div class="page-header">
+      <el-button @click="goBack"> 返回 </el-button>
+    </div>
+
+    <div v-if="loading" class="loading-wrap">
+      <el-icon class="is-loading" :size="32">
+        <Loading />
+      </el-icon>
+      <span>加载中...</span>
+    </div>
+
+    <template v-else-if="feedbackInfo.id">
+      <!-- 反馈内容卡片 -->
+      <div class="detail-card">
+        <div class="card-type">
+          {{ getTypeName(feedbackInfo.feedbackType) }}
+        </div>
+        <div class="card-meta">反馈时间:{{ feedbackInfo.feedbackTime }}</div>
+        <div class="card-meta" v-if="feedbackInfo.contactWay">联系方式:{{ feedbackInfo.contactWay }}</div>
+        <div class="card-content">
+          {{ feedbackInfo.content }}
+        </div>
+        <!-- 附件 -->
+        <div v-if="allAttachments.length > 0" class="card-attach">
+          <div class="attach-title">附件</div>
+          <div class="attach-grid">
+            <el-image
+              v-for="(url, idx) in feedbackInfo.imgUrlList"
+              :key="'img-' + idx"
+              :src="url"
+              fit="cover"
+              class="attach-img"
+              :preview-src-list="feedbackInfo.imgUrlList || []"
+              :initial-index="idx"
+            />
+            <div v-for="(url, idx) in feedbackInfo.videoUrlList" :key="'vid-' + idx" class="attach-video">
+              <video :src="url" controls class="video-player" />
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 回复区域 -->
+      <div v-if="replyList.length > 0" class="reply-wrapper">
+        <div
+          v-for="(reply, index) in replyList"
+          :key="'reply-' + index"
+          class="reply-section"
+          :class="reply.isPlatform ? 'platform' : 'user'"
+        >
+          <div class="section-title">
+            {{ reply.isPlatform ? "平台回复我" : "我的回复" }}
+          </div>
+          <div class="reply-card">
+            <div class="reply-content">
+              {{ reply.content }}
+            </div>
+            <div class="reply-time">
+              {{ reply.displayTime }}
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 回复输入 -->
+      <div v-if="hasPlatformReply" class="reply-bar">
+        <el-input v-model="replyContent" type="textarea" :rows="3" placeholder="回复平台" maxlength="500" show-word-limit />
+        <el-button type="primary" :disabled="!replyContent.trim()" @click="sendReply"> 发送 </el-button>
+      </div>
+    </template>
+
+    <div v-else-if="!loading" class="empty-tip">暂无数据</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { Loading } from "@element-plus/icons-vue";
+import { ElMessage } from "element-plus";
+import { getFeedbackDetail, userReply, getTypeName } from "@/api/modules/feedback";
+// 获取缓存的geeker-user
+import { localGet } from "@/utils/index";
+const route = useRoute();
+const router = useRouter();
+
+const feedbackId = ref<string>("");
+const loading = ref(true);
+const replyContent = ref("");
+
+const feedbackInfo = ref<{
+  id?: number;
+  feedbackType?: number;
+  content?: string;
+  feedbackTime?: string;
+  contactWay?: string;
+  imgUrlList?: string[];
+  videoUrlList?: string[];
+  platformReplies?: Array<{ content?: string; feedbackTime?: string; staffId?: number }>;
+}>({});
+
+const allAttachments = computed(() => {
+  const imgs = feedbackInfo.value.imgUrlList || [];
+  const videos = feedbackInfo.value.videoUrlList || [];
+  return [...imgs, ...videos];
+});
+
+const replyList = computed(() => {
+  const replies = feedbackInfo.value.platformReplies || [];
+  return replies.map(item => ({
+    ...item,
+    isPlatform: item.staffId != null,
+    displayTime: item.feedbackTime
+  }));
+});
+
+const hasPlatformReply = computed(() => {
+  const replies = feedbackInfo.value.platformReplies || [];
+  return replies.some(item => item.staffId != null);
+});
+
+async function fetchDetail(id: string) {
+  loading.value = true;
+  try {
+    const res: any = await getFeedbackDetail({ feedbackId: id });
+    if (res?.code === 200 || res?.code === "200") {
+      feedbackInfo.value = res?.data || res || {};
+    }
+  } catch (e) {
+    ElMessage.error("获取详情失败");
+  } finally {
+    loading.value = false;
+  }
+}
+
+function goBack() {
+  router.back();
+}
+
+async function sendReply() {
+  if (!replyContent.value.trim()) return;
+  try {
+    const res: any = await userReply({
+      feedbackId: feedbackId.value,
+      content: replyContent.value.trim(),
+      userId: localGet("geeker-user")?.userInfo?.id
+    });
+    if (res?.code === 200 || res?.code === "200") {
+      ElMessage.success("回复成功");
+      replyContent.value = "";
+      await fetchDetail(feedbackId.value);
+    } else {
+      ElMessage.error(res?.msg || "回复失败");
+    }
+  } catch (e) {
+    ElMessage.error("回复失败");
+  }
+}
+
+onMounted(() => {
+  const id = route.query.id || route.query.feedbackId;
+  if (id) {
+    feedbackId.value = String(id);
+    fetchDetail(feedbackId.value);
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.feedback-detail {
+  min-height: 100vh;
+  padding: 20px;
+  background: #f4f6fb;
+}
+.page-header {
+  margin-bottom: 20px;
+}
+.loading-wrap {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  justify-content: center;
+  padding: 60px;
+  color: var(--el-text-color-secondary);
+}
+.detail-card {
+  padding: 24px;
+  margin-bottom: 16px;
+  background: #ffffff;
+  border-radius: 12px;
+  .card-type {
+    margin-bottom: 8px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #333333;
+  }
+  .card-meta {
+    margin-bottom: 4px;
+    font-size: 13px;
+    color: #999999;
+  }
+  .card-content {
+    margin-top: 12px;
+    font-size: 14px;
+    line-height: 1.8;
+    color: #333333;
+  }
+  .card-attach {
+    margin-top: 24px;
+    .attach-title {
+      margin-bottom: 12px;
+      font-size: 14px;
+      font-weight: 600;
+    }
+    .attach-grid {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 12px;
+    }
+    .attach-img {
+      width: 100px;
+      height: 100px;
+      border-radius: 8px;
+    }
+    .attach-video {
+      width: 200px;
+      .video-player {
+        width: 100%;
+        border-radius: 8px;
+      }
+    }
+  }
+}
+.reply-wrapper {
+  padding: 24px;
+  margin-bottom: 16px;
+  background: #ffffff;
+  border-radius: 12px;
+}
+.reply-section {
+  margin-bottom: 20px;
+  &:last-child {
+    margin-bottom: 0;
+  }
+  .section-title {
+    margin-bottom: 8px;
+    font-size: 14px;
+    font-weight: 600;
+  }
+  &.user .reply-card {
+    background: rgb(108 143 248 / 15%);
+  }
+  &.platform .reply-card {
+    background: #f5f6fa;
+  }
+}
+.reply-card {
+  padding: 16px;
+  border-radius: 8px;
+  .reply-content {
+    margin-bottom: 8px;
+    font-size: 14px;
+    line-height: 1.6;
+    color: #333333;
+  }
+  .reply-time {
+    font-size: 12px;
+    color: #999999;
+  }
+}
+.reply-bar {
+  padding: 16px;
+  background: #ffffff;
+  border-radius: 12px;
+  .el-textarea {
+    margin-bottom: 12px;
+  }
+}
+.empty-tip {
+  padding: 60px;
+  color: var(--el-text-color-secondary);
+  text-align: center;
+}
+</style>

+ 1 - 0
src/views/home/components/go-flow.vue

@@ -418,6 +418,7 @@ const step2Rules: FormRules = {
   storeCapacity: [{ required: true, message: "请输入容纳人数", trigger: "blur" }],
   storeArea: [{ required: true, message: "请选择门店面积", trigger: "change" }],
   storeBlurb: [{ required: true, message: "请输入门店简介", trigger: "change" }],
+  storeDetailAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
   businessSection: [{ required: true, message: "请选择经营板块", trigger: "change" }],
   storeTickets: [{ required: true, message: "请选择标签", trigger: "change" }],
   businessTypeName: [{ required: true, message: "请输入经营种类", trigger: "change" }],

+ 17 - 15
src/views/login/index.vue

@@ -564,7 +564,7 @@
           />
         </el-form-item>
       </el-form>
-      <!-- 用户协议 -->
+      <!-- 用户协议 -->login
       <div class="agreement-wrapper">
         <el-checkbox v-model="registerAgreed" />
         <span class="agreement-text">
@@ -985,20 +985,22 @@ const handleLogin = async () => {
         // 使用 nextTick 确保路由已完全添加
         await nextTick();
         const { contractManagement, foodBusinessLicense, entertainmentBusinessLicense } = await checkMenuClickPermission();
-        // 登录后根据不同证照权限跳转
-        if ((contractManagement && foodBusinessLicense && entertainmentBusinessLicense) || contractManagement) {
-          // 合同管理权限优先
-          router.replace("/licenseManagement/contractManagement");
-        } else if (foodBusinessLicense) {
-          // 食品经营许可证权限
-          router.replace("/licenseManagement/foodBusinessLicense");
-        } else if (entertainmentBusinessLicense) {
-          // 其他资质证明权限
-          router.replace("/licenseManagement/entertainmentLicense");
-        } else {
-          // 5.跳转到首页,使用 replace 避免历史记录问题
-          await router.replace(HOME_URL);
-        }
+        // // 登录后根据不同证照权限跳转
+        // if ((contractManagement && foodBusinessLicense && entertainmentBusinessLicense) || contractManagement) {
+        //   // 合同管理权限优先
+        //   router.replace("/licenseManagement/contractManagement");
+        // } else if (foodBusinessLicense) {
+        //   // 食品经营许可证权限
+        //   router.replace("/licenseManagement/foodBusinessLicense");
+        // } else if (entertainmentBusinessLicense) {
+        //   // 其他资质证明权限
+        //   router.replace("/licenseManagement/entertainmentLicense");
+        // } else {
+        //   // 5.跳转到首页,使用 replace 避免历史记录问题
+        //   await router.replace(HOME_URL);
+        // }
+        // await router.replace(HOME_URL);
+        router.push("/home/index");
         ElNotification({
           title: "登录成功",
           type: "success",

+ 402 - 5
src/views/storeDecoration/add.vue

@@ -111,7 +111,7 @@
         </el-form-item>
 
         <!-- 详细地址 -->
-        <el-form-item label="详细地址" prop="detailedAddress">
+        <el-form-item label="详细地址" prop="detailedAddress" required>
           <el-input
             v-model="formData.detailedAddress"
             type="textarea"
@@ -191,6 +191,357 @@
       :initial-index="imageViewerInitialIndex"
       @close="imageViewerVisible = false"
     />
+
+    <!-- 用户服务协议弹窗 -->
+    <el-dialog v-model="agreementDialogVisible" title="用户服务协议" width="800px" :close-on-click-modal="false">
+      <div class="agreement-content">
+        <p>
+          您在使用爱丽恩严(大连)商务科技有限公司旗下U店在这APP软件提供的服务前,应当仔细认真阅读本《服务条款》(下称"本条款")中的全部规则、《用户协议》及发布的其他服务条款、专项产品或服务规则或规范的内容,尤其是以粗体或加下划线标示的条款,包括但不限于免除或者限爱丽恩严(大连)商务科技有限公司责任的条款、对用户权利进行限制的条款以及约定争议解决方式、司法管辖的条款,上述条款请您重点阅读。您有权选择同意或者不同意本协议。
+        </p>
+
+        <p><strong>本协议所称的爱丽恩严</strong>是指爱丽恩严(大连)商务科技有限公司的简称。</p>
+
+        <p>
+          您与爱丽恩严均应当严格履行本协议及其补充协议所约定的各项义务,如发生争议或者纠纷,双方可以友好协商解决;协商不成的,任何一方均可向本协议签订地有管辖权的人民法院提起诉讼。本协议签订地为大连市中山区。
+        </p>
+
+        <p>
+          您如果通过登录U店在哪APP用户注册页面或者爱丽恩严提供的其他用户注册渠道注册用户账号,完成我们的注册流程并通过点击同意的形式在线签署本协议即视为您完全同意本协议,愿意接受本协议所有及任何条款的约束。
+        </p>
+
+        <p>所有服务规则视为本条款不可分割的一部分,与本条款具有同等法律效力。</p>
+
+        <p>
+          <strong>本条款的签约双方</strong>为爱丽恩严服务的实际运营商爱丽恩严(大连)商务科技有限公司(下称"爱丽恩严")
+          与使用爱丽恩严相关服务的使用人(以下称"用户"或"您"),本条款是您与爱丽恩严之间关于您使用爱丽恩严提供的各项服务所订立的服务条款,具有正式书面合同的效力。
+        </p>
+
+        <p>
+          本条款为爱丽恩严平台《用户协议》(包括但不限于所附的《隐私协议》)
+          的必要组成部分。《用户协议》将同时适用于爱丽恩严的各项服务。如本条款与《用户协议》文本内容存在冲突之处,则以时间上最新发布的内容为准,发布时间相同的,以本条款为准。本条款有待明确、存在歧义或未规定之处均以《用户协议》中的规定为准。
+        </p>
+
+        <p>
+          您理解并同意,爱丽恩严将根据《用户协议》的约定,对本条款或各项服务规则不时地进行修改更新。修改更新内容的发布和实施均适用《用户协议》的相关约定。
+        </p>
+
+        <h3>一、名词解释</h3>
+        <p>除您与爱丽恩严另有约定外,本协议及其补充协议当中的下列名词均采用如下解释:</p>
+
+        <p>
+          1.
+          服务条款:即本协议,指您与爱丽恩严当下订立的旨在约定您登录、使用本平台,通过本平台下达订单、购买商品/服务、支付价款以及爱丽恩严运用自己的平台系统,通过互联网络等方式为用户提供商户信息、点评信息、消费信息、优惠信息、团购等整个网络服务过程中,您与爱丽恩严之间的权利、义务的书面合同。
+        </p>
+
+        <p>
+          2. 爱丽恩严:指是爱丽恩严(大连)商务科技有限公司运营的爱丽恩严生活服务信息平台,包括但不限于
+          爱丽恩严网站、爱丽恩严客户端、爱丽恩严小程序等形式的互联网平台。
+        </p>
+
+        <p>
+          3.
+          商户:是指根据本条款及其他适用的服务规则,在爱丽恩严平台上发布信息为爱丽恩严用户提供各项线上及线下的商品或服务的第三方商家。
+        </p>
+
+        <p>
+          4.
+          团购:团购是指通过爱丽恩严平台,一定数量的用户组团或者参团,以较低折扣价格购买同一种服务或商品的行为,包括消费买单、预约及预订服务项目等。
+        </p>
+
+        <p>
+          5.
+          团购信息:是指商户通过爱丽恩严发布的团购商品或服务的信息,此类信息包括但不限于团购商品或服务的名称、种类、数量、质量、价格、配送方式、支付形式、退换货方式、退款条件、售后服务等内容。
+        </p>
+
+        <p>
+          6.
+          团购券:是指用户通过爱丽恩严购买商户的服务或商品并成功支付团购价款后,相关商户通过爱丽恩严自动向用户出具的供用户向该商户要求提供商品或服务的交易凭证。
+        </p>
+
+        <p>
+          7.
+          优惠券:是指用户通过爱丽恩严购买商户的服务或商品时,购买、免费领取或使用积分兑换用于享受减免、折扣或其他优惠条件的电子交易凭证。
+        </p>
+
+        <p>
+          8. 交互信息服务:交互信息服务是指用户在爱丽恩严应用上交互平台(下称 "点评交互平台
+          ")上发布文字、图片、视频及表演(直播)等信息。点评交互平台包括但不限于点评社区、点评头条、 点评直播、笔记功能等。
+        </p>
+
+        <h3>二、用户账号</h3>
+        <p>
+          1.在注册、管理、使用账号时,您应遵循诚实信用、合法善意的原则。您向平台提交的相关注册资料应当遵守法律法规、社会主义制度、国家利益、公民合法权益、公序良俗、信息真实等原则,不应提交任何违法或不良信息。相关资料如有变动,您应及时更新。如果因您所提供的注册资料不合法、不真实、不准确或未及时更新,从而导致相关法律责任或不利后果的,您将承担相应的法律责任及不利后果。同时,爱丽恩严有权拒绝为您提供注册服务。
+        </p>
+        <p>2.您同意并承诺以合法、合规、合理的方式使用爱丽恩严账号:</p>
+        <ul>
+          <li>
+            (1)您设置的账号昵称、头像及个人介绍等个人资料不得出现违法和不良信息,包括但不限于使用政治、色情、低俗、侮辱、诽谤等违反法律、道德及公序良俗的词语。
+          </li>
+          <li>
+            (2)未经他人许可,您不得使用他人名义(包括但不限于冒用他人姓名、名称、字号、头像、身份等或采取其他足以让人引起混淆的方式)开设爱丽恩严账号。
+          </li>
+          <li>
+            (3)不得假冒、仿冒、捏造党政军机关、企事业单位、新闻媒体等组织机构名称、标识信息或采取其他足以让人引起混淆的方式开设爱丽恩严账号。
+          </li>
+          <li>
+            (4)您不得恶意注册爱丽恩严账号(包括但不限于频繁注册、批量注册账号等行为)或将账号用于非法或不正当用途(包括但不限于流量作假等行为),不得实施任何侵害国家利益、损害其他公民合法权益,有害社会道德风尚的行为,不得采取各种技术手段恶意绕开或者对抗平台规则。
+          </li>
+        </ul>
+        <p>
+          3.您理解并同意,您的爱丽恩严账号的所有权及有关权益均归爱丽恩严所有,您仅享有该账号的使用权且仅限于您本人使用。为保证账号安全,未经爱丽恩严的书面同意,您不应将爱丽恩严账号以赠与、转让、出售、出借或其他方式许可他人使用,否则您应当承担由此产生的全部责任,爱丽恩严保留拒绝提供相应服务、冻结或收回注册账号或终止本服务协议的权利,并可要求您对爱丽恩严所承受的损失予以赔偿。
+        </p>
+        <p>
+          4.您应妥善保管账号信息、账号密码以及其他与账号相关的信息、资料。您有责任维护个人账号、密码的安全性与保密性,并对您以注册账号名义所从事的活动承担全部法律责任,包括但不限于您在爱丽恩严平台进行的任何信息发表、浏览点击等操作行为可能引起的一切法律责任。若发现他人未经许可使用您的账号或发生其他任何安全漏洞问题时,您应当立即通知爱丽恩严。
+        </p>
+        <p>
+          5.您理解并同意,如您违反上述条款,爱丽恩严有权对您采取要求限期改正、禁止注册、删除或屏蔽违法违规信息、不同时限的禁止信息发布和账号信息修改、封号、注销账号等处置措施。
+        </p>
+        <p>
+          6.您理解并同意,您可通过注册新的账号或登录您现有的账号以使用爱丽恩严的各项服务及功能,您使用爱丽恩严账号应同时遵守《用户协议》中的各项规定。
+        </p>
+
+        <h3>三、用户管理</h3>
+        <p>
+          1.您知悉、理解并同意,爱丽恩严在服务过程中,可能涉及收集、存储、使用、共享和保护用户个人信息。在您使用爱丽恩严提供的服务时,您同意爱丽恩严依据《隐私协议》的规定执行相关个人信息的收集、使用和共享。您进一步同意,就爱丽恩严平台所产生的交易或其他与个人信息使用紧密相关的交易,您授权爱丽恩严使用或允许爱丽恩严许可的第三方在必要、合理的限度内使用您的个人信息,包括但不限于身份信息、账号信息、交易信息等。
+        </p>
+        <p>
+          2.您知悉并同意,爱丽恩严将按照有权网络信息主管部门的相关规定对您的账号信息及所发布的各项信息进行必要的安全审查及评估,保留并向有关部门定期报送您的账号信息及您所发布的各项信息。
+        </p>
+        <p>
+          3.用户有权在爱丽恩严平台上发布客观、真实、亲身体验的文字点评、图片点评、视频点评或者自行添加商户、完善爱丽恩严平台中商户的商户名称、营业时间、位置等信息。用户同意并理解,为了遵守法律法规和政策,维护良好的平台秩序,爱丽恩严有权对特定用户、特定行业、特定商品或者服务的点评信息或者商户信息发布流程作出特别设定或者限制。
+        </p>
+        <p>
+          4.您可以通过正式的页面公告和/或站内信和/或电子邮件和/或客服电话和/或手机短信、常规的信件接收和查看中奖、优惠等活动或信息,并在爱丽恩严上自行浏览、下载和使用优惠券和/或团购券。
+        </p>
+        <p>
+          5.用户知晓并同意,他人可能通过"认领"等操作获得用户在爱丽恩严平台中添加的商户条目之管理权限,并可能对用户添加、完善之信息进行修改或者使用。
+        </p>
+        <p>6.用户有权根据爱丽恩严相关规定,在发布点评信息等贡献后,取得爱丽恩严给予的奖励(如贡献值、积分等)。</p>
+        <p>
+          7.用户在发布信息时,您应确保该等信息的真实性、客观性、合法性,爱丽恩严提倡用户贡献高质量点评。用户应确保在爱丽恩严上发表的各类点评信息、攻略或文章、图片及视频均不涉及侵犯第三方隐私、著作权或其他合法权益。用户需维护点评的客观性,不得利用爱丽恩严用户身份进行任何违反法律、法规、国家政策以及诚实信用原则的行为,包括但不限于:
+        </p>
+        <ul>
+          <li>(1)违反法律法规及国家政策的行为:</li>
+          <li>①反对宪法所确定的基本原则的;</li>
+          <li>②危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;</li>
+          <li>③损害国家荣誉和利益的;</li>
+          <li>④歪曲、丑化、亵渎、否定英雄烈士事迹和精神,以侮辱、诽谤或者其他方式侵害英雄烈士的姓名、肖像、名誉、荣誉的;</li>
+          <li>⑤宣扬恐怖主义、极端主义或者煽动实施恐怖活动、极端主义活动的;</li>
+          <li>⑥煽动民族仇恨、民族歧视,破坏民族团结的;</li>
+          <li>⑦破坏国家宗教政策,宣扬邪教和封建迷信的;</li>
+          <li>⑧散布谣言,扰乱经济秩序和社会秩序的;</li>
+          <li>⑨散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;</li>
+          <li>⑩侮辱或者诽谤他人,侵害他人名誉、隐私和其他合法权益的;</li>
+          <li>⑪法律、行政法规禁止的其他内容。</li>
+          <li>(2)违反诚实信用原则的行为:</li>
+          <li>①炒作并向商户收取费用或获取利益;</li>
+          <li>②为获得利益或好处,参与或组织撰写及发布虚假点评;</li>
+          <li>③以差评威胁、要求商户提供额外的利益或好处;</li>
+          <li>④以虚构事实、侮辱、诽谤等方式恶意诋毁爱丽恩严网或商家的商誉;</li>
+          <li>⑤进行其他其它影响点评客观、干扰扰乱爱丽恩严正常秩序的违规行为等。</li>
+        </ul>
+        <p>如您存在以上行为的,爱丽恩严有权采取如下行动:</p>
+        <ul>
+          <li>①屏蔽违规信息,视情节轻重情况发出警告;</li>
+          <li>②清除其所有评价、图片,屏蔽其发布的涉及商业性炒作的帖子;</li>
+          <li>③暂时限制/永久限制您使用账号在爱丽恩严平台上发布信息;</li>
+          <li>④法律、行政法规、本条款及《用户协议》规定的其他方式。</li>
+        </ul>
+        <p>
+          8.禁止用户将爱丽恩严以任何形式作为从事各种非法活动的场所、平台或媒介。未经爱丽恩严的授权或许可,用户不得借用爱丽恩严的名义从事任何商业活动,也不得以任何形式将爱丽恩严作为从事商业活动的场所、平台或媒介。
+        </p>
+        <p>
+          9.您违反本条款、《用户协议》或爱丽恩严发布的其他任何服务规则,则爱丽恩严有权在法律允许的范围内采取一切必要的措施,包括但不限于删除用户发布的内容、取消用户在爱丽恩严获得的用户积分、星级、荣誉以及虚拟财富,暂停或终止您对爱丽恩严账号的使用,给爱丽恩严造成损失的,您应负全部赔偿责任,包括且不限于财产损害赔偿、名誉损害赔偿、诉讼费、律师费、公证费、交通费等因维权而产生的合理费用。爱丽恩严有权按照本条款及《用户协议》的相关规定对您的行为进行处理。
+        </p>
+
+        <h3>四、团购服务特别约定</h3>
+        <p>1.您有权通过爱丽恩严平台浏览各项团购信息,购买团购券、优惠券并使用其参与相关商户提供的商品或服务。</p>
+        <p>
+          2.您知悉并同意,您在爱丽恩严平台上所浏览及购买的团购券、优惠券或其他消费产品均系商户所提供的商品或服务,如您因购买或使用该等商品或服务产生任何问题或争议,您应自行与相应商户协商解决。
+        </p>
+
+        <h3>五、信息评价等特别约定</h3>
+        <p>
+          1.您应确保具有相应的资格并符合在爱丽恩严开展信息评价的相应条件。
+          如果因为您不具备相应资格或者条件的情形下通过爱丽恩严平台发布信息而引发任何法律责任,您应自行承担。因此给爱丽恩严平台造成损失的,您应予以全额赔偿。
+        </p>
+        <p>
+          2.平台有可能在相关法律法规和政策的指引下,对评价、帖子等信息发布实施分级分类管理,建立发布者信用等级管理体系,建立黑名单管理制度等。
+        </p>
+        <p>3.用户在平台发布的文字、图片、视频、表演(直播)等信息均应遵守相关法律法规、规章、本条款及《用户协议》的相关规定。</p>
+        <p>4.您同意:如出现以下任一情形的,爱丽恩严将视情节轻重采取删除信息、限制平台使用功能、停止您的账号使用等处理措施:</p>
+        <ul>
+          <li>(1)发布违反相关法律法规、规章、政策之规定或者违反本条款及相关服务规则的信息;</li>
+          <li>(2)发布与主题无关的信息或评论,包含文字、图片、视频等;</li>
+          <li>(3)发布可能存在交易风险的外部网站和APP信息,如发布社交、团购等外部网站或 APP的名称、超链接、二维码等信息;</li>
+          <li>
+            (4)发布的视频违反国家法律法规的相关要求,包括但不限于《广播电视管理条例》、《电影管理条例》、《互联网视听节目服务管理规定》、《广电总局关于加强互联网视听节目内容管理的通知》等的相关要求;
+          </li>
+          <li>
+            (5)用户进行表演(直播)时违反国家法律法规的相关要求,包括但不限于《互联网文化管理暂行规定》、《互联网直播服务管理规定》等的相关要求;
+          </li>
+          <li>(6)其他违反及《用户协议》相关规定的情形。</li>
+        </ul>
+
+        <h3>六、关于服务终止的约定</h3>
+        <p>
+          1.您同意爱丽恩严有权随时修改或中断其向您提供的任何免费服务而无需事先通知您。您与爱丽恩严进行的有偿交易,您同意爱丽恩严有权在事先通知的情况下予以修改、中断,并按照公平、诚实信用、等价有偿的原则处理后续事宜。
+        </p>
+        <p>
+          2.如您的账号同时符合以下条件,则爱丽恩严有权利终止您对爱丽恩严账号的使用。这将导致您的账号不能再登录爱丽恩严,相应服务亦同时终止:
+        </p>
+        <ul>
+          <li>(1)连续六个月未登录;</li>
+          <li>(2)不存在未到期的有效业务;</li>
+          <li>(3)终止您的账号和服务的行为不违反相关法律法规的强制性规定。</li>
+        </ul>
+        <p>
+          3.如您对本条款及其不时修订有任何异议的,您有权停止使用爱丽恩严的各项服务,或通过客服等渠道告知爱丽恩严停止对您提供服务。停止服务后,除法律法规另有明确规定外,爱丽恩严有权(但无义务)保留您的账号访问爱丽恩严的相关信息和数据,或留存、转发任何账号内的任何站内信或短消息。在此情况下,爱丽恩严没有义务(但有权利)向您或代为向商家或其他第三方传送任何未处理的信息或未完成的服务或交易信息。您同意爱丽恩严不就终止爱丽恩严服务而对您或任何第三方承担任何责任。
+        </p>
+        <p>
+          4.您同意,您与爱丽恩严的合同关系终止后,爱丽恩严就您在使用爱丽恩严服务期间存在违法行为或违反本条款和/或其他服务规则的行为的,
+          仍可依据本条款向您主张权利。
+        </p>
+
+        <h3>七、知识产权及其它权利</h3>
+        <p>1.用户确认其已经仔细阅读并同意爱丽恩严关于知识产权等相关权利的一切声明。</p>
+        <p>
+          2.爱丽恩严平台及相关服务的运营系统由爱丽恩严自主开发、运营并提供技术支持,爱丽恩严对平台服务的开发和运营等过程中产生的所有数据和信息等享有全部权利。爱丽恩严提供各项服务时所依托软件的著作权、专利权,所使用的各项商标、商业形象、商业标识、技术诀窍,其著作权、商标权及其他各项相关权利均归爱丽恩严所有。
+        </p>
+        <p>
+          3,您理解并同意,您在爱丽恩严发表的各项点评信息、文章、视频、反馈意见等所有信息及其衍生品的知识产权及所有权,适用《用户协议》中的相关约定。
+        </p>
+
+        <h3>八、免责事由</h3>
+        <p>
+          1.除非爱丽恩严以书面形式明确约定,爱丽恩严对于用户以任何方式(包括但不限于包含、经由、连接或下载)从爱丽恩严所获得的任何由商户发布的内容信息,包括但不限于商户信息、点评内容等,不保证其准确性、完整性、可靠性。用户应当自行审核判断相关信息,并对于用户因爱丽恩严上的内容信息而购买、获取的任何产品、服务、信息或资料自行承担责任和风险。用户因此受损的,爱丽恩严在法律允许的最大范围内予以免责。爱丽恩严内所有用户所发表的用户点评、商户或者产品信息等,仅代表用户个人观点,并不表示爱丽恩严赞同其观点或证实其描述,爱丽恩严在法律允许的最大范围内予以免责。
+        </p>
+        <p>
+          2.您同意并理解,针对商户向您销售/提供的商品/服务,爱丽恩严并非您所购买的具体商品或者服务的生产者和销售者。您同意,针对该等商品或者服务以及相关售后服务中所产生的任何矛盾和纠纷均不应针对爱丽恩严提出。在法律允许的范围内,爱丽恩严不对上述商品/服务承担包括解释说明、赔偿在内的任何责任。
+        </p>
+        <p>
+          3.您在爱丽恩严平台上传、发布任何信息或者内容的,应当自行保留备份。爱丽恩严不对用户所发布信息的保存、修改、删除或储存失败负责,对爱丽恩严上的非因爱丽恩严故意所导致的排字错误、疏忽等不承担责任。爱丽恩严有权但无义务,改善或更正爱丽恩严任何部分之疏漏、错误。
+        </p>
+        <p>
+          4.您知悉爱丽恩严在其平台上所公布的各项排行榜单系根据用户真实评价、访问次数、浏览时长等数据自行生成,并无人工干预。该等榜单仅为您选择商品或服务提供参考,不涉及任何商户付费宣传,您应自行判断榜单中所列商品及服务的属性,爱丽恩严将不对此榜单中所列商品和服务做出任何形式的担保或保证。
+        </p>
+        <p>5.任何非经爱丽恩严正规渠道获得的中奖、优惠等活动或信息,爱丽恩严不承担法律责任。</p>
+        <p>
+          6.爱丽恩严因发现爱丽恩严上显现的团购、预订、消费买单等交易产品信息明显错误或缺货时,有权单方面作出修改,但该等修改不应视为爱丽恩严对您作出的任何承诺。
+        </p>
+        <p>
+          7.爱丽恩严有权在法律允许范围内,在不通知您的情况下删除任何用户发布的不符合本条款及各项服务规则、《用户协议》以及法律规定的信息。爱丽恩严将不对删除该等信息给您造成的不便或损失承担任何责任。
+        </p>
+        <p>
+          8.您同意,在法律许可范围内,爱丽恩严在任何情况下都不对任何个人或实体的直接、间接、偶然、特殊、惩罚性的损害或其他损害或损失承担责任,这些损害或损失包括但不限于:
+        </p>
+        <ul>
+          <li>(1)您理解并同意,由于互联网的特殊性造成的爱丽恩严显示的信息所存在的滞后性或差错,爱丽恩严对此不承担任何责任;</li>
+          <li>
+            (2)您在使用爱丽恩严服务中,因第三方原因使您遭受侮辱、诽谤、不作为、淫秽、色情或亵渎事件,爱丽恩严在法律允许的范围内不承担法律责任;
+          </li>
+          <li>(3)《用户协议》中规定其他不可抗力及免责事由。</li>
+        </ul>
+
+        <h3>九、管辖、法律适用与争议解决</h3>
+        <p>1.本协议的成立、生效、履行、解释与纠纷解决,适用中华人民共和国法律法规,并且排除一切冲突法规定的适用。</p>
+        <p>
+          2.您同意并理解,如您因通过爱丽恩严平台购买的任何商品或者服务产生任何纠纷的,爱丽恩严可在法律法规要求的范围内协助您与争议对方进行协商调解。您同意,爱丽恩严有权向争议双方了解情况,并将所了解的情况通过必要方式通知对方。但您理解并同意,爱丽恩严无任何义务对您与商家之间的任何争议承担任何责任。
+        </p>
+        <p>
+          3.您与爱丽恩严均应当严格履行本协议及其补充协议所约定的各项义务,如发生争议或者纠纷,双方可以友好协商解决;协商不成的,任何一方均可向本协议签订地有管辖权的法院提起诉讼。
+        </p>
+        <p>本协议签订地:大连市中山区。</p>
+
+        <h3>十、意见及反馈</h3>
+        <p>如您对本协议有任何问题或建议,请在工作时间联系爱丽恩严客服部门。联系方式如下:</p>
+        <ul>
+          <li>您可以通过网站(ailien.shop)/App上提供的在线联系方式/客服系统与我们联系;</li>
+        </ul>
+      </div>
+      <template #footer>
+        <div class="dialog-footer-center">
+          <el-button type="primary" @click="agreementDialogVisible = false">已阅读并同意协议内容</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 隐私政策弹窗 -->
+    <el-dialog v-model="privacyDialogVisible" title="隐私政策" width="800px" :close-on-click-modal="false">
+      <div class="agreement-content">
+        <p>生效日期:2024 年 10 月 29 日</p>
+        <p>
+          欢迎使用[U店在这]!我们深知个人信息对您的重要性,并将竭尽全力保护您的隐私。本隐私协议旨在向您说明我们如何收集、使用、存储和保护您的个人信息,以及您对个人信息享有的权利。
+        </p>
+        <p>一、定义</p>
+        <p>
+          1."个人信息"是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
+        </p>
+        <p>2."我们"、"本公司" 或 "[U店在这]" 指 [APP 所属公司名称]。</p>
+        <p>二、信息收集范围</p>
+        <p>3.您在注册、登录 [U店在这] 时提供的信息,包括但不限于用户名、密码、手机号码、电子邮箱等。</p>
+        <p>4.您在使用 [U店在这] 过程中产生的信息,如浏览记录、搜索记录、交易记录等。</p>
+        <p>5.我们从第三方获取的信息,如您通过第三方平台登录 [U店在这] 时,我们可能会获取您在该第三方平台上的部分信息。</p>
+        <p>三、信息使用目的</p>
+        <p>6.为您提供个性化的服务和内容,满足您的需求。</p>
+        <p>7.改善和优化 [U店在这] 的功能和体验。</p>
+        <p>8.进行数据分析和研究,以提升我们的服务质量和效率。</p>
+        <p>9.与您进行沟通和互动,回复您的咨询和反馈。</p>
+        <p>10.遵守法律法规的要求,履行我们的法律义务。</p>
+        <p>四、信息存储</p>
+        <p>11.我们将按照法律法规的要求,将您的个人信息存储在安全的服务器上。</p>
+        <p>12.我们会采取合理的技术和管理措施,确保您的个人信息的安全,防止其被未经授权的访问、披露、使用、修改或丢失。</p>
+        <p>
+          13.我们将根据您的使用情况和需求,确定个人信息的存储期限。在存储期限届满后,我们将对您的个人信息进行删除或匿名化处理。
+        </p>
+        <p>五、信息共享与披露</p>
+        <p>14.我们不会向第三方出售、出租或交易您的个人信息。</p>
+        <p>
+          15.我们可能会在以下情况下与第三方共享您的个人信息: 在获得您的明确同意后;
+          为了向您提供特定的服务,我们需要与第三方合作,此时我们会与该第三方签订保密协议,确保其按照本隐私协议的要求处理您的个人信息;
+          为了遵守法律法规的要求,履行我们的法律义务。
+        </p>
+        <p>
+          16.在以下情况下,我们可能会披露您的个人信息: 经您明确同意或授权; 根据法律法规的要求,向有权机关披露;
+          为了维护我们的合法权益,如在涉及诉讼、仲裁等法律程序时。
+        </p>
+        <p>六、您的权利</p>
+        <p>17.您有权访问、更正、删除您的个人信息。您可以通过 [U店在这] 的设置页面或联系我们的客服来行使这些权利。</p>
+        <p>18.您有权撤回您对我们处理您个人信息的同意。但请注意,撤回同意可能会影响您使用 [U店在这] 的部分功能。</p>
+        <p>19.您有权要求我们提供关于我们处理您个人信息的详细信息,包括处理目的、处理方式、处理的个人信息种类等。</p>
+        <p>
+          20.您有权要求我们限制对您个人信息的处理,例如在您认为我们处理您个人信息的行为违反法律法规的要求或您的合法权益受到侵害时。
+        </p>
+        <p>七、Cookie 和同类技术的使用</p>
+        <p>21.我们可能会使用 Cookie 和同类技术来收集和存储您的信息,以便为您提供更好的服务和体验。</p>
+        <p>
+          22.您可以通过设置浏览器的隐私设置来拒绝接受 Cookie 和同类技术。但请注意,拒绝接受 Cookie和同类技术可能会影响您使用
+          [U店在这] 的部分功能。
+        </p>
+        <p>八、未成年人保护</p>
+        <p>23.[U店在这] 不面向未成年人提供服务。如果您是未成年人,请不要使用 [U店在这]。</p>
+        <p>24.如果我们发现我们在未经授权的情况下收集了未成年人的个人信息,我们将立即采取措施删除该信息。</p>
+        <p>九、隐私政策的变更</p>
+        <p>25.我们可能会根据法律法规的要求、业务发展的需要或用户反馈等因素,对本隐私协议进行修订。</p>
+        <p>
+          26.修订后的隐私协议将在 [U店在这]上公布,并在公布后的特定时间生效。如果您在修订后的隐私协议生效后继续使用
+          [U店在这],则视为您同意修订后的隐私协议。
+        </p>
+        <p>十、联系方式</p>
+        <p>如果您对本隐私协议有任何疑问、意见或建议,请通过以下方式与我们联系:</p>
+        <p>[爱丽恩(大连)贸易有限公司]</p>
+        <p>[辽宁省大连市中山区普照街44号国泰港汇中心一层F109-110、112-117、FGQ103号商铺]</p>
+        <p>[0411-81820856]</p>
+        <p>[15641179898@163.com]</p>
+        <p>我们将尽快回复您的咨询和反馈。</p>
+        <p>感谢您对 [U店在这]的信任和支持!</p>
+      </div>
+      <template #footer>
+        <div class="dialog-footer-center">
+          <el-button type="primary" @click="privacyDialogVisible = false">已阅读并同意协议内容</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -231,6 +582,10 @@ const imageViewerVisible = ref(false);
 const imageViewerUrlList = ref<string[]>([]);
 const imageViewerInitialIndex = ref(0);
 
+// 协议和隐私政策弹窗
+const agreementDialogVisible = ref(false);
+const privacyDialogVisible = ref(false);
+
 // 城市选择相关
 const showCityDialog = ref(false);
 const selectedProvince = ref("");
@@ -270,6 +625,7 @@ const rules = reactive({
     { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur" }
   ],
   city: [{ required: true, message: "请选择所在城市", trigger: "change" }],
+  detailedAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
   agreementConfirmed: [
     {
       required: true,
@@ -523,14 +879,12 @@ const handleExceed = () => {
 
 // 显示服务协议
 const handleShowAgreement = () => {
-  // TODO: 打开服务协议页面
-  ElMessage.info("服务协议");
+  agreementDialogVisible.value = true;
 };
 
 // 显示隐私政策
 const handleShowPrivacy = () => {
-  // TODO: 打开隐私政策页面
-  ElMessage.info("隐私政策");
+  privacyDialogVisible.value = true;
 };
 
 // 提交表单
@@ -568,6 +922,7 @@ const handleSubmit = async () => {
         agreementConfirmed: formData.agreementConfirmed,
         storeId: formData.storeId || 0,
         auditStatus: 0,
+        auditReason: "", // 拒绝原因字段,新建时为空
         status: 0,
         hasCommunicated: false,
         inquiryCount: 0,
@@ -661,5 +1016,47 @@ onMounted(() => {
   .cursor-pointer {
     cursor: pointer;
   }
+
+  .agreement-content {
+    height: 50vh;
+    padding: 10px;
+    overflow-y: auto;
+    font-size: 14px;
+    line-height: 1.6;
+
+    h3 {
+      margin-top: 20px;
+      margin-bottom: 10px;
+      font-weight: 600;
+    }
+
+    p {
+      margin-bottom: 10px;
+    }
+
+    ul {
+      margin-left: 20px;
+      margin-bottom: 10px;
+
+      li {
+        margin-bottom: 5px;
+      }
+    }
+  }
+
+  .dialog-footer-center {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+
+    .el-button {
+      width: 406px;
+      height: 60px;
+      color: #ffffff;
+      background-color: #6c8ff8;
+      border-radius: 10px;
+    }
+  }
 }
 </style>

+ 1 - 1
src/views/storeDecoration/detail.vue

@@ -205,7 +205,7 @@ const initData = async () => {
         createdTime: data.createdTime || "",
         auditStatus: data.auditStatus ?? 0,
         updatedTime: data.updatedTime || "",
-        rejectionReason: data.rejectionReason || "",
+        rejectionReason: data.auditReason || data.rejectionReason || "", // 优先使用 auditReason 字段
         attachmentUrls: data.attachmentUrls || []
       };
 

+ 84 - 28
src/views/storeDecoration/personnelConfig/index.vue

@@ -32,6 +32,9 @@
         :init-param="initParam"
         :data-callback="dataCallback"
       >
+        <template #empty>
+          <div class="personnel-config-empty">暂无数据</div>
+        </template>
         <template #operation="scope">
           <el-button type="primary" link @click="editPersonnel(scope.row, 0)"> 编辑 </el-button>
           <el-button
@@ -205,7 +208,7 @@
     </el-dialog>
 
     <!-- 新建/编辑人员弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" @close="resetForm">
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="720px" @close="resetForm">
       <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
         <el-form-item label="头像*" prop="avatar">
           <UploadImg
@@ -225,25 +228,27 @@
           <el-input v-model="formData.position" placeholder="请输入" maxlength="50" clearable />
         </el-form-item>
         <el-form-item label="背景*" prop="backgroundImages">
-          <UploadImgs
-            :key="uploadComponentKey"
-            ref="backgroundImagesUploadRef"
-            v-model:image-url="formData.backgroundImages"
-            :limit="9"
-            :file-size="100"
-            :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4']"
-            :width="'150px'"
-            :height="'150px'"
-            :border-radius="'8px'"
-            :api="handleCustomUpload"
-            :show-success-notification="false"
-            :on-success="handleBackgroundImageSuccess"
-            :on-video-preview="handleVideoPreviewInForm"
-          >
-            <template #tip>
-              <div class="upload-tip">上传图片或视频 ({{ formData.backgroundImages.length }}/9),单个文件不超过 100M</div>
-            </template>
-          </UploadImgs>
+          <div class="background-upload-three-col">
+            <UploadImgs
+              :key="uploadComponentKey"
+              ref="backgroundImagesUploadRef"
+              v-model:file-list="formData.backgroundImages"
+              :limit="9"
+              :file-size="100"
+              :file-type="['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4']"
+              :width="'150px'"
+              :height="'150px'"
+              :border-radius="'8px'"
+              :api="handleCustomUpload"
+              :show-success-notification="false"
+              :on-success="handleBackgroundImageSuccess"
+              :on-video-preview="handleVideoPreviewInForm"
+            >
+              <template #tip>
+                <div class="upload-tip">上传图片或视频 ({{ formData.backgroundImages.length }}/9),单个文件不超过 100M</div>
+              </template>
+            </UploadImgs>
+          </div>
         </el-form-item>
         <el-form-item label="擅长" prop="specialty">
           <el-input
@@ -262,7 +267,7 @@
               <el-input v-model="formData.tags[index]" placeholder="请输入" maxlength="20" clearable />
             </div>
             <div class="tag-actions">
-              <el-button v-if="formData.tags.length < 5" :icon="Plus" circle @click="addTag" />
+              <el-button v-if="formData.tags.length < 3" :icon="Plus" circle @click="addTag" />
               <el-button v-if="formData.tags.length > 1" :icon="Delete" circle @click="removeTag" />
             </div>
           </div>
@@ -739,6 +744,9 @@ const syncFileListFromComponent = async () => {
           .sort();
         const newUrls = validFiles.map((f: UploadUserFile) => f.url!.trim()).sort();
 
+        // 未传背景时不以组件内残留数据覆盖:表单为空且组件有项,说明是上次编辑的残留,不覆盖
+        if (formData.backgroundImages.length === 0 && validFiles.length > 0) return;
+
         // 如果不一致,更新 formData(直接使用组件内部的文件列表)
         if (JSON.stringify(currentUrls) !== JSON.stringify(newUrls)) {
           // 直接使用组件内部的文件列表,确保数据一致
@@ -975,6 +983,9 @@ watch(
                 const urlsChanged = JSON.stringify(currentUrls) !== JSON.stringify(lastFileListUrls);
                 const lengthChanged = currentLength !== lastFileListLength;
 
+                // 未传背景时不以组件内残留数据覆盖:表单为空且组件有项时不同步
+                if (formData.backgroundImages.length === 0 && currentLength > 0) return;
+
                 if (urlsChanged || lengthChanged) {
                   lastFileListUrls = currentUrls;
                   lastFileListLength = currentLength;
@@ -1444,6 +1455,7 @@ const openCreateDialog = async () => {
 
     editId.value = null;
     resetForm();
+    uploadComponentKey.value += 1;
     dialogVisible.value = true;
   } catch (error: any) {
     // 如果检查标题时出错,记录错误但允许继续创建人员(避免阻塞用户)
@@ -1461,6 +1473,7 @@ const openCreateDialog = async () => {
     // 其他错误,允许继续创建(避免因接口问题阻塞用户)
     editId.value = null;
     resetForm();
+    uploadComponentKey.value += 1;
     dialogVisible.value = true;
   }
 };
@@ -1476,7 +1489,7 @@ const editPersonnel = async (person: Personnel, index: number) => {
     // 调用获取人员详情接口(使用和详情页面相同的接口,确保数据格式一致)
     const res: any = await getStaffConfigDetail({ id: person.id });
     if (res && (res.code === 200 || res.code === "200")) {
-      const personDetail = res.data || res;
+      const personDetail = res.data ?? res;
       console.log("编辑时获取的完整人员详情数据:", personDetail);
 
       editId.value = personDetail.id;
@@ -1484,8 +1497,9 @@ const editPersonnel = async (person: Personnel, index: number) => {
       formData.position = personDetail.staffPosition || "";
       formData.avatar = personDetail.staffImage || "";
 
-      // 处理背景图片:将字符串转换为 UploadUserFile[] 格式
-      const backgroundUrlValue = personDetail.backgroundUrl || personDetail.backgroundImages || personDetail.background;
+      // 处理背景图片:接口返回 backgroundUrl 逗号分隔,转为 UploadUserFile[](兼容 background_url 等字段名)
+      const backgroundUrlValue =
+        personDetail.backgroundUrl ?? personDetail.background_url ?? personDetail.backgroundImages ?? personDetail.background;
 
       let imageFiles: UploadUserFile[] = [];
 
@@ -1522,6 +1536,8 @@ const editPersonnel = async (person: Personnel, index: number) => {
       formData.description = personDetail.personalIntroduction || "";
       formData.backgroundImages = imageFiles;
 
+      // 强制背景上传组件重新挂载,避免沿用上次的 _fileList 导致数量显示错误
+      uploadComponentKey.value += 1;
       // 打开对话框
       dialogVisible.value = true;
     } else {
@@ -2362,6 +2378,32 @@ onMounted(async () => {
         }
       }
     }
+
+    /* 列表为空时去掉空状态残留图形/背景,仅显示「暂无数据」文字 */
+    :deep(.el-table__empty-block) {
+      background: transparent !important;
+      background-image: none !important;
+      .table-empty {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        align-items: center;
+        justify-content: center;
+        padding: 24px 0;
+        img {
+          display: none !important;
+        }
+      }
+
+      /* 隐藏可能存在的默认空状态插图(如 Element Plus 自带的 SVG) */
+      svg {
+        display: none !important;
+      }
+    }
+    .personnel-config-empty {
+      font-size: 14px;
+      color: var(--el-text-color-secondary);
+    }
   }
   .dialog-footer {
     display: flex;
@@ -2413,12 +2455,13 @@ onMounted(async () => {
           border-radius: 8px;
         }
         .background-images {
-          display: flex;
-          flex-wrap: wrap;
+          display: grid;
+          grid-template-columns: repeat(3, 1fr);
           gap: 10px;
           margin-top: 8px;
           .background-image {
-            width: 120px;
+            width: 100%;
+            max-width: 120px;
             height: 120px;
             cursor: pointer;
             object-fit: cover;
@@ -2431,7 +2474,8 @@ onMounted(async () => {
           }
           .background-media-item {
             position: relative;
-            width: 120px;
+            width: 100%;
+            max-width: 120px;
             height: 120px;
             overflow: hidden;
             cursor: pointer;
@@ -2468,6 +2512,18 @@ onMounted(async () => {
     }
   }
 }
+
+/* 编辑弹窗:背景图片/视频上传区域一行显示三个 */
+.background-upload-three-col {
+  :deep(.upload-box .upload) {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 10px;
+  }
+  :deep(.el-upload-list--picture-card) {
+    display: contents;
+  }
+}
 .video-preview-wrap {
   display: flex;
   align-items: center;