Huanyi hai 3 semanas
pai
achega
adac22fdc8

+ 12 - 1
src/api/archieves/customer/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { UsrCustomerVO, UsrCustomerForm, UsrCustomerQuery, CustomerOnOrderVO, CustomerOnOrderQuery } from '@/api/archieves/customer/types';
+import { UsrCustomerVO, UsrCustomerForm, UsrCustomerQuery, CustomerOnOrderVO, CustomerOnOrderQuery, UsrCustomerRemarkForm } from '@/api/archieves/customer/types';
 
 /**
  * 查询用户列表
@@ -98,3 +98,14 @@ export const listCustomerOnOrder = (query?: CustomerOnOrderQuery): AxiosPromise<
     params: query
   });
 };
+
+/**
+ * 客户备注更新
+ */
+export const updateCustomerRemark = (data: UsrCustomerRemarkForm) => {
+  return request({
+    url: '/archieves/customer/remark',
+    method: 'post',
+    data: data
+  });
+};

+ 11 - 0
src/api/archieves/customer/types.ts

@@ -63,4 +63,15 @@ export interface CustomerOnOrderVO {
 
 export interface CustomerOnOrderQuery extends PageQuery {
   content?: string;
+  tenantId?: string | number;
+}
+
+/**
+ * 用户备注表单
+ */
+export interface UsrCustomerRemarkForm {
+  /** 用户ID */
+  id: string | number;
+  /** 备注内容 */
+  content: string;
 }

+ 12 - 1
src/api/archieves/pet/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { UsrPetVO, UsrPetForm, UsrPetQuery } from '@/api/archieves/pet/types';
+import { UsrPetVO, UsrPetForm, UsrPetQuery, UsrPetRemarkForm } from '@/api/archieves/pet/types';
 
 /**
  * 查询宠物列表
@@ -75,3 +75,14 @@ export const delPet = (id: string | number | Array<string | number>) => {
     method: 'delete'
   });
 };
+
+/**
+ * 宠物备注
+ */
+export const updatePetRemark = (data: UsrPetRemarkForm) => {
+  return request({
+    url: '/archieves/pet/remark',
+    method: 'post',
+    data: data
+  });
+};

+ 10 - 0
src/api/archieves/pet/types.ts

@@ -69,3 +69,13 @@ export interface UsrPetQuery extends PageQuery {
   keyword?: string;
   userId?: number;
 }
+
+/**
+ * 宠物备注表单
+ */
+export interface UsrPetRemarkForm {
+  /** 宠物ID */
+  petId: string | number;
+  /** 备注内容 */
+  content: string;
+}

+ 4 - 1
src/components/CustomerDetailDrawer/index.vue

@@ -109,7 +109,7 @@
       <el-tab-pane label="档案日志" name="logs">
         <el-timeline style="margin-top: 10px; padding-left: 5px">
           <el-timeline-item v-for="(log, index) in changeLogs" :key="index" :timestamp="log.createTime" type="primary">
-            [{{ log.logType }}] {{ log.content }}
+            [{{ changeLogType[log.logType] || log.logType }}] {{ log.content }}
             <div style="font-size: 12px; color: #999; margin-top: 4px">操作人: {{ log.operatorName }}</div>
           </el-timeline-item>
         </el-timeline>
@@ -126,6 +126,9 @@ import { listAllChangeLog } from '@/api/archieves/changeLog';
 import { listOnStore } from '@/api/system/areaStation';
 import { listSubOrderOnCustomer } from '@/api/order/subOrder/index';
 import { listAllService } from '@/api/service/list/index';
+import archievesEnum from '@/enums/archieves.json';
+
+const { changeLogType } = archievesEnum;
 
 const props = defineProps({
   visible: {

+ 7 - 4
src/components/PetDetailDrawer/index.vue

@@ -95,7 +95,7 @@
       <el-tab-pane label="备注日志" name="logs">
         <el-timeline style="margin-top: 10px; padding-left: 5px">
           <el-timeline-item v-for="(log, index) in changeLogs" :key="index" :timestamp="log.createTime" type="primary">
-            [{{ log.logType }}] {{ log.content }}
+            [{{ changeLogType[log.logType] || log.logType }}] {{ log.content }}
             <div style="font-size: 12px; color: #999; margin-top: 4px">操作人: {{ log.operatorName }}</div>
           </el-timeline-item>
         </el-timeline>
@@ -117,10 +117,13 @@
 
 <script setup name="PetDetailDrawer">
 import { ref, computed, watch, onMounted, getCurrentInstance, toRefs } from 'vue'
-import { getPet, updatePet } from '@/api/archieves/pet'
+import { getPet, updatePet, updatePetRemark } from '@/api/archieves/pet'
 import { listAllChangeLog } from '@/api/archieves/changeLog'
 import { listSubOrderOnPet } from '@/api/order/subOrder/index'
 import { ElMessage } from 'element-plus'
+import archievesEnum from '@/enums/archieves.json'
+
+const { changeLogType } = archievesEnum
 
 const props = defineProps({
   visible: {
@@ -205,8 +208,8 @@ const handleRemark = () => {
 
 const saveRemark = () => {
   if (!remarkContent.value) return ElMessage.warning('请输入内容')
-  const data = { id: props.petId, remark: remarkContent.value }
-  updatePet(data).then(() => {
+  const data = { petId: props.petId, content: remarkContent.value }
+  updatePetRemark(data).then(() => {
     ElMessage.success('备注添加成功')
     remarkDialogVisible.value = false
     loadData(props.petId)

+ 11 - 0
src/enums/archieves.json

@@ -0,0 +1,11 @@
+{
+  "changeLogType": {
+    "pet_remark": "宠物备注",
+    "customer_add_pet": "新增宠物",
+    "customer_create": "创建用户",
+    "customer_edit": "编辑用户",
+    "customer_change_satus": "状态变更",
+    "customer_remove": "删除用户",
+    "customer_remark": "用户备注"
+  }
+}

+ 8 - 10
src/views/archieves/customer/index.vue

@@ -73,7 +73,7 @@
             />
           </template>
         </el-table-column>
-        <el-table-column prop="remark" label="备注" show-overflow-tooltip />
+<!--        <el-table-column prop="remark" label="备注" show-overflow-tooltip />-->
         <el-table-column label="操作" width="200" align="center">
           <template #default="scope">
             <el-button link type="primary" size="small" @click="handleDetail(scope.row)" v-hasPermi="['archieves:customer:query']">详情</el-button>
@@ -394,9 +394,9 @@
 import { ref, reactive, computed, onMounted, getCurrentInstance, toRefs } from 'vue'
 import { globalHeaders } from '@/utils/request'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, changeCustomerStatus } from '@/api/archieves/customer'
+import { listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, changeCustomerStatus, updateCustomerRemark } from '@/api/archieves/customer'
 import { listAllTag } from '@/api/archieves/tag'
-import { listPetByUser, addPet, updatePet, delPet } from '@/api/archieves/pet'
+import { listPetByUser, addPet, updatePet, delPet, updatePetRemark } from '@/api/archieves/pet'
 import { listAllChangeLog } from '@/api/archieves/changeLog'
 import { listAreaStation } from '@/api/system/areaStation'
 import { listOnStore as listBrandOnStore } from '@/api/system/tenant'
@@ -728,11 +728,9 @@ const saveRemark = () => {
   if (!remarkForm.content) return ElMessage.warning('请输入内容')
   const data = {
     id: currentUser.value.id,
-    name: currentUser.value.name,
-    phone: currentUser.value.phone,
-    remark: remarkForm.content
+    content: remarkForm.content
   }
-  updateCustomer(data).then(() => {
+  updateCustomerRemark(data).then(() => {
     ElMessage.success('备注添加成功')
     remarkDialogVisible.value = false
     getList()
@@ -746,10 +744,10 @@ const saveRemark = () => {
 const savePetRemark = () => {
   if (!remarkForm.content) return ElMessage.warning('请输入内容')
   const data = {
-    id: currentPet.value.id,
-    remark: remarkForm.content
+    petId: currentPet.value.id,
+    content: remarkForm.content
   }
-  updatePet(data).then(() => {
+  updatePetRemark(data).then(() => {
     ElMessage.success('宠物备注添加成功')
     petRemarkDialogVisible.value = false
     if (drawerVisible.value) {

+ 75 - 51
src/views/index.vue

@@ -8,7 +8,7 @@
           <div class="title">早安,管理员</div>
           <div class="subtitle">数据监控中心 | {{ currentDate }}</div>
         </div>
-        
+
         <!-- 右侧统计 -->
         <div class="right-section">
           <div class="stat-item" @click="handleToFulfillerReview">
@@ -122,9 +122,9 @@
             <!-- 去掉本月标签 -->
           </div>
           <div class="rank-list" v-if="fulfillerRankList.length > 0">
-            <div 
-              class="rank-item" 
-              v-for="(item, index) in fulfillerRankList" 
+            <div
+              class="rank-item"
+              v-for="(item, index) in fulfillerRankList"
               :key="item.id || index"
             >
               <div class="item-left">
@@ -135,14 +135,14 @@
                 <el-avatar class="rank-avatar" :size="40" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" />
                 <div class="rank-info">
                   <div class="rank-name">{{ item.name }}</div>
-                  <div class="rank-site">{{ item.site }}</div>
+                  <div class="rank-site">{{ getStationName(item.site) }}</div>
                 </div>
               </div>
               <div class="item-right">
                 <div class="rank-count">{{ item.count }} 单</div>
                 <div class="rank-bar-bg">
-                  <div 
-                    class="rank-bar-fill" 
+                  <div
+                    class="rank-bar-fill"
                     :class="'bar-color-' + (index + 1)"
                     :style="{ width: getPercentage(item.count) + '%' }"
                   ></div>
@@ -166,9 +166,9 @@
             </div>
           </div>
           <div class="rank-list" v-if="storeRankList.length > 0">
-            <div 
-              class="rank-item" 
-              v-for="(item, index) in storeRankList" 
+            <div
+              class="rank-item"
+              v-for="(item, index) in storeRankList"
               :key="item.id || index"
             >
               <div class="item-left">
@@ -187,10 +187,10 @@
                 </div>
               </div>
               <div class="item-right store-right">
-                <div class="rank-count">{{ Number(item.count).toLocaleString() }}</div>
+                <div class="rank-count">{{ Number(item.count).toLocaleString() }}</div>
                 <div class="rank-bar-bg">
-                  <div 
-                    class="rank-bar-fill" 
+                  <div
+                    class="rank-bar-fill"
                     :class="'store-bar-color-' + (index + 1)"
                     :style="{ width: getStorePercentage(item.count) + '%' }"
                   ></div>
@@ -211,14 +211,15 @@
 import { ref, onMounted, onUnmounted, markRaw } from 'vue';
 import { useRouter } from 'vue-router';
 import { getAdminCount, listOrder, getFulfillerRank, getStoreRank } from '@/api/system/admin';
-import type { FulfillerRankVO, StoreRankVO } from '@/api/system/admin/types';
+import type { FulfillerRankVO, StoreRankVO, AdminCountVO } from '@/api/system/admin/types';
 import { listAllService } from '@/api/service/list';
+import { listAreaStation } from '@/api/system/areaStation';
 import { BellFilled, StarFilled, Shop, WalletFilled, List, Bicycle, OfficeBuilding } from '@element-plus/icons-vue';
 import * as echarts from 'echarts';
 
 import dayjs from 'dayjs';
 import 'dayjs/locale/zh-cn';
-dayjs.locale('zh-cn'); 
+dayjs.locale('zh-cn');
 
 const router = useRouter();
 
@@ -250,6 +251,8 @@ const getTrendStr = (current: number, prev: number, checkSign: boolean = true) =
 
 // 获取相关服务记录数组以便于格式化字典
 const serviceOptions = ref<any[]>([]);
+// 获取站点列表以解析履约者排名中的站点名称
+const stationOptions = ref<any[]>([]);
 
 // 数据交互及图表实例
 const listType = ref(0); // 0: 周, 1: 月
@@ -324,6 +327,23 @@ const fetchServiceList = async () => {
   }
 };
 
+/** 请求站点列表以解析排名中的站点名称 */
+const fetchStationList = async () => {
+  try {
+    const res = await listAreaStation();
+    stationOptions.value = res.data || [];
+  } catch (error) {
+    console.error('获取站点列表失败', error);
+  }
+};
+
+/** 根据站点ID获取站点名称 */
+const getStationName = (id: string | number) => {
+  if (!id || stationOptions.value.length === 0) return '未知站点';
+  const station = stationOptions.value.find(s => String(s.id) === String(id));
+  return station ? station.name : '未知站点';
+};
+
 /** 请求双图表的趋势及占比数据 */
 const fetchChartData = async () => {
   try {
@@ -346,7 +366,7 @@ const renderLineChart = (data: any[]) => {
   }
 
   const daysCount = listType.value === 0 ? 7 : 30;
-  
+
   // 自过去 N 天往前推算,初始化日期结构以防断层
   const dates: string[] = [];
   const statMap: Record<string, { count: number, price: number }> = {};
@@ -457,20 +477,23 @@ const renderPieChart = (data: any[]) => {
   }
 
   let totalOrdersCount = data.length;
-  const serviceCountMap: Record<number, number> = {};
-  
+  const serviceCountMap: Record<string, number> = {};
+
   data.forEach(item => {
-    serviceCountMap[item.service] = (serviceCountMap[item.service] || 0) + 1;
+    const sId = String(item.service || '');
+    if (sId) {
+      serviceCountMap[sId] = (serviceCountMap[sId] || 0) + 1;
+    }
   });
 
-  const getServiceNameById = (serviceId: number) => {
-    const matchedService = serviceOptions.value.find(s => s.id === serviceId);
+  const getServiceNameById = (serviceId: string) => {
+    const matchedService = serviceOptions.value.find(s => String(s.id) === serviceId);
     return matchedService ? matchedService.name : '其他服务';
   };
 
   const pieData = Object.keys(serviceCountMap).map(key => ({
-    name: getServiceNameById(Number(key)),
-    value: serviceCountMap[Number(key)]
+    name: getServiceNameById(key),
+    value: serviceCountMap[key]
   }));
 
   const option: echarts.EChartsOption = {
@@ -538,9 +561,10 @@ const resizeCharts = () => {
 onMounted(async () => {
   initDate();
   window.addEventListener('resize', resizeCharts);
-  
+
   // 按照先后依赖完成视图初始化
   await fetchServiceList();
+  fetchStationList();
   fetchAdminCount();
   fetchChartData();
   fetchFulfillerRank();
@@ -567,7 +591,7 @@ onUnmounted(() => {
   background-color: #fff;
   margin-bottom: 20px;
   box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
-  
+
   :deep(.el-card__body) {
     padding: 24px 32px;
   }
@@ -587,7 +611,7 @@ onUnmounted(() => {
     margin-bottom: 12px;
     letter-spacing: 0.5px;
   }
-  
+
   .subtitle {
     font-size: 14px;
     color: #86909c;
@@ -629,7 +653,7 @@ onUnmounted(() => {
       color: #ff7d00;
       background-color: #fff2e8;
     }
-    
+
     &.primary {
       color: #409eff;
       background-color: #e6f1fc;
@@ -673,7 +697,7 @@ onUnmounted(() => {
   justify-content: space-between;
   box-shadow: 0 4px 10px rgba(0,0,0,0.05);
   box-sizing: border-box;
-  
+
   &.bg-blue {
     background: linear-gradient(135deg, #35c8fe 0%, #20b2aa 100%);
   }
@@ -686,25 +710,25 @@ onUnmounted(() => {
   &.bg-green {
     background: linear-gradient(135deg, #79e0b1 0%, #85e89d 100%);
   }
-  
+
   .card-title {
     font-size: 14px;
     font-weight: 500;
     opacity: 0.9;
   }
-  
+
   .card-value {
     font-size: 28px;
     font-weight: bold;
     line-height: 1.2;
     z-index: 1;
-    
+
     .currency {
       font-size: 18px;
       margin-right: 2px;
     }
   }
-  
+
   .card-footer {
     display: flex;
     justify-content: space-between;
@@ -712,11 +736,11 @@ onUnmounted(() => {
     font-size: 12px;
     opacity: 0.9;
     z-index: 1;
-    
+
     .footer-text {
       white-space: nowrap;
     }
-    
+
     .trend-tag {
       background: rgba(255, 255, 255, 0.2);
       padding: 2px 8px;
@@ -725,7 +749,7 @@ onUnmounted(() => {
       display: inline-block;
     }
   }
-  
+
   .card-bg-icon {
     position: absolute;
     right: -10px;
@@ -747,7 +771,7 @@ onUnmounted(() => {
   background-color: #fff;
   height: 100%;
   box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
-  
+
   :deep(.el-card__body) {
     padding: 24px;
   }
@@ -758,14 +782,14 @@ onUnmounted(() => {
   justify-content: space-between;
   align-items: center;
   margin-bottom: 24px;
-  
+
   .chart-title {
     display: flex;
     align-items: center;
     font-size: 16px;
     font-weight: 600;
     color: #1d2129;
-    
+
     .title-icon {
       width: 4px;
       height: 14px;
@@ -805,7 +829,7 @@ onUnmounted(() => {
     align-items: center;
     padding: 10px 16px;
     border-radius: 8px;
-    
+
     &:nth-child(even) {
       background-color: #f7f8fa;
     }
@@ -815,10 +839,10 @@ onUnmounted(() => {
     display: flex;
     align-items: center;
     gap: 12px;
-    
+
     .store-avatar {
       background-color: #e8f5e9;
-      color: #67c23a; 
+      color: #67c23a;
       border-radius: 6px;
     }
   }
@@ -834,7 +858,7 @@ onUnmounted(() => {
     font-weight: bold;
     color: #fff;
     margin-right: 2px;
-    
+
     &.rank-1 { background-color: #ffc53d; }
     &.rank-2 { background-color: #c9cdd4; }
     &.rank-3 { background-color: #d28248; }
@@ -846,18 +870,18 @@ onUnmounted(() => {
     flex-direction: column;
     justify-content: center;
     gap: 2px;
-    
+
     .rank-name {
       font-size: 14px;
       font-weight: 500;
       color: #1d2129;
     }
-    
+
     .rank-site {
       font-size: 12px;
       color: #86909c;
     }
-    
+
     .rank-score {
       font-size: 12px;
       color: #e6a23c;
@@ -875,36 +899,36 @@ onUnmounted(() => {
     align-items: flex-end;
     gap: 6px;
     width: 90px;
-    
+
     .rank-count {
       font-size: 14px;
       font-weight: 600;
       color: #1d2129;
     }
-    
+
     .rank-bar-bg {
       width: 100%;
       height: 4px;
       background-color: #f2f3f5;
       border-radius: 2px;
       overflow: hidden;
-      
+
       .rank-bar-fill {
         height: 100%;
         border-radius: 2px;
         transition: width 0.5s ease;
-        
+
         &.bar-color-1 { background-color: #f56c6c; }
         &.bar-color-2 { background-color: #e6a23c; }
         &.bar-color-3 { background-color: #409eff; }
         &.bar-color-4, &.bar-color-5 { background-color: #909399; }
-        
+
         &.store-bar-color-1 { background-color: #67c23a; }
         &.store-bar-color-2 { background-color: #409eff; }
         &.store-bar-color-3, &.store-bar-color-4, &.store-bar-color-5 { background-color: #909399; }
       }
     }
-    
+
     &.store-right {
       width: 100px;
     }

+ 12 - 1
src/views/order/dispatch/components/DispatchDialog.vue

@@ -149,7 +149,7 @@
         </div>
     </el-dialog>
 
-    <CustomerDetailDrawer v-model:visible="customerDialogVisible" :customer-id="customerId" :service-list="serviceList" />
+    <CustomerDetailDrawer v-model:visible="customerDialogVisible" :customer-id="customerId" :service-list="serviceList" :area-station-list="areaStationList" />
     <PetDetailDrawer v-model:visible="petDialogVisible" :pet-id="petId" />
 </template>
 
@@ -159,6 +159,7 @@ import { ElMessage } from 'element-plus'
 import { pageFulfillerOnOrder } from '@/api/fulfiller/pool'
 import { listAllTag } from '@/api/fulfiller/tag'
 import { getSubOrderInfo } from '@/api/order/subOrder/index'
+import { listAreaStation } from '@/api/system/areaStation/index'
 import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
 import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
 
@@ -219,6 +220,15 @@ const loadAllTags = async () => {
 // 直接使用父组件传入的服务列表数据
 const serviceOptions = computed(() => props.serviceList || [])
 
+const areaStationList = ref([])
+const loadAreaStationList = async () => {
+    if (areaStationList.value.length > 0) return
+    try {
+        const res = await listAreaStation()
+        areaStationList.value = res.data || []
+    } catch { /* ignore */ }
+}
+
 const getServiceTypeText = (id) => {
     const s = serviceOptions.value.find(item => String(item.id) === String(id))
     return s ? s.name : String(id)
@@ -270,6 +280,7 @@ watch(() => props.visible, (val) => {
         }
         pageNum.value = 1
         loadAllTags()
+        loadAreaStationList()
         loadRiders()
 
         // 获取订单详细信息

+ 14 - 1
src/views/order/orderList/components/CareSummaryDrawer.vue

@@ -17,7 +17,7 @@
                                 {{ order.petAge }}
                             </el-tag>
                             <el-tag v-for="tag in (order.petTags || [])" :key="tag" type="warning" effect="plain"
-                                round>{{ tag }}</el-tag>
+                                round>{{ parseTag(tag) }}</el-tag>
                         </div>
                     </div>
                     <div class="summary-sub-row">
@@ -159,6 +159,19 @@ const drawerVisible = computed({
 const careSummaryText = ref('')
 const isEditingSummary = ref(false)
 
+const parseTag = (tag) => {
+    if (!tag) return ''
+    if (typeof tag === 'string') {
+        try {
+            const parsed = JSON.parse(tag)
+            return parsed.name || tag
+        } catch (e) {
+            return tag
+        }
+    }
+    return tag.name || tag
+}
+
 watch(() => props.visible, (val) => {
     if (val && props.order) {
         isEditingSummary.value = false

+ 12 - 1
src/views/order/orderList/components/DispatchDialog.vue

@@ -149,7 +149,7 @@
         </div>
     </el-dialog>
 
-    <CustomerDetailDrawer v-model:visible="customerDialogVisible" :customer-id="customerId" />
+    <CustomerDetailDrawer v-model:visible="customerDialogVisible" :customer-id="customerId" :area-station-list="areaStationList" />
     <PetDetailDrawer v-model:visible="petDialogVisible" :pet-id="petId" />
 </template>
 
@@ -160,6 +160,7 @@ import { pageFulfillerOnOrder } from '@/api/fulfiller/pool'
 import { listAllTag } from '@/api/fulfiller/tag'
 import { getSubOrderInfo } from '@/api/order/subOrder/index'
 import { listAllService } from '@/api/service/list/index'
+import { listAreaStation } from '@/api/system/areaStation/index'
 import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
 import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
 
@@ -221,6 +222,15 @@ const loadServiceOptions = async () => {
     } catch { /* ignore */ }
 }
 
+const areaStationList = ref([])
+const loadAreaStationList = async () => {
+    if (areaStationList.value.length > 0) return
+    try {
+        const res = await listAreaStation()
+        areaStationList.value = res.data || []
+    } catch { /* ignore */ }
+}
+
 const getServiceTypeText = (id) => {
     const s = serviceOptions.value.find(item => String(item.id) === String(id))
     return s ? s.name : String(id)
@@ -273,6 +283,7 @@ watch(() => props.visible, (val) => {
         pageNum.value = 1
         loadAllTags()
         loadServiceOptions()
+        loadAreaStationList()
         loadRiders()
 
         // 获取订单详细信息

+ 4 - 4
src/views/order/orderList/components/OrderDetailDrawer.vue

@@ -88,7 +88,7 @@
                             </div>
                         </div>
                         <el-descriptions :column="2" size="small" class="pet-desc" border>
-                            <el-descriptions-item label="绝育状态">{{ order.petSterilized ? '已绝育' : '未绝育'
+                            <el-descriptions-item label="宠物品种">{{ order.petBreed || '未知'
                                 }}</el-descriptions-item>
                             <el-descriptions-item label="疫苗状态"><span style="color:#67c23a">{{ order.petVaccine || '未知'
                                     }}</span></el-descriptions-item>
@@ -245,7 +245,7 @@
                                                 <el-image v-if="item.type === 'image'" :src="item.url"
                                                     :preview-src-list="step.media.filter(m => m.type === 'image').map(m => m.url)" fit="cover"
                                                     class="p-img" :preview-teleported="true" />
-                                                
+
                                                 <!-- 视频类型:由于后端没给第一帧,这里采用 video 标签 preload 方式尝试展示 -->
                                                 <div v-else-if="item.type === 'video'" class="p-video-box" @click="openVideoPreview(item.url)">
                                                     <video :src="item.url" preload="metadata" class="p-img p-video"></video>
@@ -287,7 +287,7 @@
                                 <el-result icon="success" title="暂无投诉" sub-title="该订单暂无投诉记录"></el-result>
                             </div>
                             <el-timeline v-else>
-                                <el-timeline-item v-for="(complaint, index) in complaintList" :key="index" 
+                                <el-timeline-item v-for="(complaint, index) in complaintList" :key="index"
                                     :timestamp="complaint.createTime" placement="top" color="#f56c6c">
                                     <div class="log-card">
                                         <div class="l-tit">履约者:{{ complaint.fulfiller }}</div>
@@ -377,7 +377,7 @@ const loadOrderLogs = async (order) => {
         orderLogs.value = []
         fulfillerLogs.value = []
     }
-    
+
     try {
         const complaintRes = await listComplaintByOrder(id)
         complaintList.value = complaintRes?.data || []

+ 7 - 33
src/views/order/purchase/components/AddUserDialog.vue

@@ -10,15 +10,6 @@
         </el-col>
 
         <el-col :span="24"><div class="form-section-header">基本资料</div></el-col>
-        <el-col :span="12">
-          <el-form-item label="所属品牌">
-            <PageSelect v-model="form.tenantId"
-              :options="brandList.map(item => ({ value: item.tenantId, label: item.name }))"
-              :total="brandTotal" :pageSize="10" placeholder="请选择所属品牌"
-              @page-change="handleBrandPageChange"
-              @visible-change="handleBrandVisibleChange" />
-          </el-form-item>
-        </el-col>
         <el-col :span="12">
           <el-form-item label="姓名" required><el-input v-model="form.name" placeholder="请输入姓名" /></el-form-item>
         </el-col>
@@ -110,7 +101,6 @@ import { globalHeaders } from '@/utils/request'
 import { addCustomerOnOrder } from '@/api/archieves/customer'
 import { listAllTag } from '@/api/archieves/tag'
 import { listAreaStation } from '@/api/system/areaStation'
-import { listOnStore as listBrandOnStore } from '@/api/system/tenant'
 import { regionData } from 'element-china-area-data'
 import PageSelect from '@/components/PageSelect/index.vue'
 import { useUserStore } from '@/store/modules/user'
@@ -118,7 +108,8 @@ import { useUserStore } from '@/store/modules/user'
 const userStore = useUserStore()
 
 const props = defineProps({
-  visible: { type: Boolean, default: false }
+  visible: { type: Boolean, default: false },
+  tenantId: { type: [String, Number], default: undefined }
 })
 
 const emit = defineEmits(['update:visible', 'success'])
@@ -131,8 +122,6 @@ const { sys_user_sex, sys_house_type, sys_entry_method } = toRefs(
 const submitLoading = ref(false)
 
 const allNodes = ref([])
-const brandList = ref([])
-const brandTotal = ref(0)
 const allUserTags = ref([])
 
 const formAreaValue = ref([])
@@ -172,6 +161,7 @@ const form = reactive({
 watch(() => props.visible, (val) => {
   if (val) {
     resetForm()
+    loadTags()
   }
 })
 
@@ -186,7 +176,7 @@ const resetForm = () => {
     areaId: undefined, stationId: undefined, regionCode: '', region: [], address: '',
     houseType: '', entryMethod: '', entryPassword: '', keyLocation: '', source: '',
     emergencyContact: '', emergencyPhone: '', memberLevel: 0, status: 0, remark: '', tagIds: [],
-    tenantId: undefined
+    tenantId: props.tenantId || undefined
   })
 }
 
@@ -221,27 +211,11 @@ const handleFormAreaChange = (value) => {
 // formStationList 已废弃
 
 
-const getBrandList = async (pageNum = 1) => {
-  const res = await listBrandOnStore({ pageNum, pageSize: 10 })
-  if (res.code === 200) {
-    brandList.value = res.rows || []
-    brandTotal.value = res.total || 0
-  }
-}
-
-const handleBrandPageChange = (page) => {
-  getBrandList(Number(page))
-}
-
-const handleBrandVisibleChange = (visible) => {
-  if (visible) {
-    getBrandList(1)
-  }
-}
-
 const loadTags = () => {
-  listAllTag({ category: 'user', status: 0 }).then((res) => {
+  listAllTag({ category: 'customer', status: 0 }).then((res) => {
     allUserTags.value = res.data || []
+  }).catch((err) => {
+    console.error('加载用户标签失败', err)
   })
 }
 

+ 31 - 10
src/views/order/purchase/index.vue

@@ -33,13 +33,14 @@
                         style="display:flex; justify-content:space-between; align-items:center; width:100%; height: 24px;">
                         <span>宠主用户</span>
                         <el-button type="primary" plain size="small" @click="openAddUser" icon="Plus"
-                          style="margin-left: 15px;" v-hasPermi="['order:purchase:addCustomer']">添加用户</el-button>
+                          style="margin-left: 15px;" v-hasPermi="['order:purchase:addCustomer']" :disabled="!form.merchantId">添加用户</el-button>
                       </div>
                     </template>
-                    <PageSelect v-model="form.userId" placeholder="搜索姓名/手机号" size="large" style="width: 100%"
+                    <PageSelect v-model="form.userId" :placeholder="form.merchantId ? '搜索姓名/手机号' : '请先选择服务门店'" size="large" style="width: 100%"
                       :options="userSelectOptions" :total="userTotal" :page-size="5" :filter-method="searchUser"
                       :loading="userLoading" :popper-class="!userQuery.content ? 'hide-search-popper' : ''"
-                      @page-change="handleUserPageChange" @update:modelValue="handleUserChange" />
+                      :disabled="!form.merchantId"
+                      @page-change="handleUserPageChange" @visible-change="handleUserVisibleChange" @update:modelValue="handleUserChange" />
                   </el-form-item>
                 </el-col>
               </el-row>
@@ -186,7 +187,7 @@
 
     <!-- Dialogs -->
     <!-- Add User Dialog -->
-    <AddUserDialog v-model:visible="userDialogVisible" :pca-options="pcaOptions" @success="handleUserSuccess" />
+    <AddUserDialog v-model:visible="userDialogVisible" :pca-options="pcaOptions" :tenant-id="currentTenantId" @success="handleUserSuccess" />
     <AddPetDialog v-model:visible="petDialogVisible" :user-id="form.userId" :user-options="userOptions"
       @success="handlePetSuccess" />
 
@@ -212,7 +213,7 @@ import { createOrder } from '@/api/order/order'
 // --- State ---
 const userOptions = ref([])
 const userTotal = ref(0)
-const userQuery = reactive({ pageNum: 1, pageSize: 5, content: '' })
+const userQuery = reactive({ pageNum: 1, pageSize: 5, content: '', tenantId: undefined })
 const userLoading = ref(false)
 
 const serviceList = [
@@ -324,6 +325,13 @@ const handleStorePageChange = (page) => {
 }
 
 const handleStoreChange = (val) => {
+  // 切换门店时强制清空已选客户和宠物
+  form.userId = ''
+  form.petId = ''
+  currentPets.value = []
+  userOptions.value = []
+  userTotal.value = 0
+
   const store = stores.value.find(s => s.id === val)
   if (store && store.services) {
     if (!store.services.includes(form.serviceId)) {
@@ -484,9 +492,20 @@ const canSubmit = computed(() => {
   return true
 })
 
+const currentTenantId = computed(() => {
+  const store = stores.value.find(s => s.id === form.merchantId)
+  return store ? store.tenantId : undefined
+})
+
 // --- Methods ---
 const fetchUsers = () => {
+  if (!form.merchantId) return
   userLoading.value = true
+  
+  // 关联当前选中门店的租户ID
+  const store = stores.value.find(s => s.id === form.merchantId)
+  userQuery.tenantId = store ? store.tenantId : undefined
+  
   listCustomerOnOrder(userQuery).then(res => {
     userOptions.value = res.rows || []
     userTotal.value = res.total || 0
@@ -501,16 +520,18 @@ const handleUserPageChange = (page) => {
 }
 
 const searchUser = (query) => {
-  if (!query) {
-    userOptions.value = []
-    userTotal.value = 0
-    return
-  }
   userQuery.content = query || ''
   userQuery.pageNum = 1
   fetchUsers()
 }
 
+const handleUserVisibleChange = (visible) => {
+  if (visible && form.merchantId) {
+    userQuery.pageNum = 1
+    fetchUsers()
+  }
+}
+
 const handleUserChange = (val) => {
   form.petId = ''
   currentPets.value = []

+ 2 - 2
vite.config.ts

@@ -25,8 +25,8 @@ export default defineConfig(({ mode, command }) => {
       // open: true,
       proxy: {
         [env.VITE_APP_BASE_API]: {
-          target: 'http://127.0.0.1:8080',
-          // target: 'http://www.hoomeng.pet/api',
+          // target: 'http://127.0.0.1:8080',
+          target: 'http://www.hoomeng.pet/api',
           changeOrigin: true,
           ws: true,
           rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')