Huanyi 1 неделя назад
Родитель
Сommit
97f7caed97

+ 1 - 1
.env.production

@@ -39,4 +39,4 @@ VITE_APP_PLATFORM_CODE = 'MfJkMNMW2JKXBuPcbP2rxkD3ynXmReAZZFm4fN7cAGwGJdKCmd'
 VITE_APP_WEBSOCKET = false
 
 # sse 开关
-VITE_APP_SSE = false
+VITE_APP_SSE = true

+ 12 - 0
src/api/fulfiller/fulfiller/index.ts

@@ -0,0 +1,12 @@
+import request from '@/utils/request';
+
+/**
+ * 获取履约者详细信息
+ * @param id 履约者ID
+ */
+export function getFulfillerDetail(id: number | string) {
+  return request({
+    url: `/fulfiller/fulfiller/${id}`,
+    method: 'get'
+  });
+}

+ 0 - 53
src/api/order/index.ts

@@ -1,53 +0,0 @@
-import request from '@/utils/request';
-import { AxiosPromise } from 'axios';
-
-export interface SubOrderListParams {
-  content?: string;
-  service?: number | string;
-  status?: number | string;
-  pageNum: number;
-  pageSize: number;
-}
-
-export interface SubOrderVO {
-  id: number;
-  code: string;
-  service: number;
-  toAddress: string;
-  mode: number;
-  type: number;
-  pet: number;
-  petName: string;
-  petBreed: string;
-  customer: number;
-  customerName: string;
-  site: number;
-  store: number;
-  storeName: string;
-  placer: number;
-  placerUsername: string;
-  createTime: string;
-  status: number;
-  fulfiller: number;
-  fulfillerName: string;
-  fulfillerStatus: string;
-  price: number;
-}
-
-export interface SubOrderListResult {
-  total: number;
-  rows: SubOrderVO[];
-  code: number;
-  msg: string;
-}
-
-/**
- * 获取商户订单列表
- */
-export function listSubOrderOnMerchant(params: SubOrderListParams): AxiosPromise<SubOrderListResult> {
-  return request({
-    url: '/order/subOrder/listOnMerchant',
-    method: 'get',
-    params
-  });
-}

+ 0 - 8
src/api/order/subOrder/index.ts

@@ -39,14 +39,6 @@ export const cancelSubOrder = (data: { orderId: string | number; }) => {
     });
 };
 
-export function listSubOrderOnMerchant(params: SubOrderListParams): AxiosPromise<SubOrderListResult> {
-    return request({
-        url: '/order/subOrder/listOnMerchant',
-        method: 'get',
-        params
-    });
-}
-
 export const remarkSubOrder = (data: { orderId: string | number; remark: string; }) => {
     return request({
         url: '/order/subOrder/remark',

+ 53 - 31
src/views/archieves/customer/index.vue

@@ -7,7 +7,7 @@
           <span class="title">用户管理</span>
           <div class="header-actions">
             <el-cascader v-model="searchRegionValue" :options="areaCascaderOptions"
-                         :props="{ checkStrictly: true, value: 'id', label: 'name' }"
+                         :props="{ value: 'id', label: 'name' }"
                          placeholder="所属站点" style="width: 350px; margin-right: 10px" clearable @change="handleSearchRegionChange" />
             <el-input v-model="searchForm.keyword" placeholder="搜索姓名/手机号" style="width: 200px; margin-right: 10px;" clearable @keyup.enter="handleSearch" @clear="handleSearch" />
             <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['archieves:customer:add']">新增用户</el-button>
@@ -141,9 +141,9 @@
 <!--            </el-form-item>-->
 <!--          </el-col>-->
           <el-col :span="24">
-            <el-form-item label="所属站点">
+            <el-form-item label="所属站点" required>
               <el-cascader v-model="formAreaValue" :options="areaTreeOptions" placeholder="请选择站点"
-                           :props="{ checkStrictly: true, value: 'value', label: 'label' }"
+                           :props="{ value: 'value', label: 'label' }"
                            style="width: 100%" clearable @change="handleFormAreaChange" />
             </el-form-item>
           </el-col>
@@ -174,7 +174,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="24">
-            <el-form-item label="详细住址"><el-input v-model="form.address" placeholder="请输入街道/门牌号" /></el-form-item>
+            <el-form-item label="详细住址" required><el-input v-model="form.address" placeholder="请输入街道/门牌号" /></el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="房屋类型">
@@ -184,19 +184,19 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="入门方式">
+            <el-form-item label="入门方式" required>
               <el-radio-group v-model="form.entryMethod">
                 <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
           <el-col :span="12" v-if="form.entryMethod === 'password'">
-            <el-form-item label="开门密码">
+            <el-form-item label="开门密码" required>
               <el-input v-model="form.entryPassword" placeholder="请输入密码" />
             </el-form-item>
           </el-col>
           <el-col :span="12" v-if="form.entryMethod === 'key'">
-            <el-form-item label="钥匙位置">
+            <el-form-item label="钥匙位置" required>
               <el-input v-model="form.keyLocation" placeholder="如:地毯下" />
             </el-form-item>
           </el-col>
@@ -262,26 +262,24 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="品种">
+                <el-form-item label="品种" required>
                   <el-select v-model="petForm.breed" placeholder="请选择品种" style="width: 100%" filterable allow-create default-first-option>
                     <el-option v-for="dict in sys_pet_breed" :key="dict.value" :label="dict.label" :value="dict.value" />
                   </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="体型">
+                <el-form-item label="体型" required>
                   <el-select v-model="petForm.size" style="width: 100%">
-                    <el-option label="小型" value="small" />
-                    <el-option label="中型" value="medium" />
-                    <el-option label="大型" value="large" />
+                    <el-option v-for="dict in sys_pet_size" :key="dict.value" :label="dict.label" :value="dict.value" />
                   </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="体重(kg)"><el-input-number v-model="petForm.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
+                <el-form-item label="体重(kg)" required><el-input-number v-model="petForm.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="年龄(岁)"><el-input-number v-model="petForm.age" :min="0" style="width: 100%" /></el-form-item>
+                <el-form-item label="年龄(岁)" required><el-input-number v-model="petForm.age" :min="0" style="width: 100%" /></el-form-item>
               </el-col>
               <el-col :span="24">
                 <el-form-item label="性格关键词"><el-input v-model="petForm.personality" placeholder="如:活泼、粘人" /></el-form-item>
@@ -306,40 +304,38 @@
             <el-form-item label="新来家庭时间">
               <el-date-picker v-model="petForm.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
             </el-form-item>
-            <el-form-item label="家庭房屋类型">
+            <el-form-item label="家庭房屋类型" required>
               <el-radio-group v-model="petForm.houseType">
-                <el-radio label="stairs">楼梯</el-radio>
-                <el-radio label="elevator">电梯</el-radio>
+                <el-radio v-for="dict in sys_house_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="入门方式">
+            <el-form-item label="入门方式" required>
               <el-radio-group v-model="petForm.entryMethod">
-                <el-radio label="password">密码开门</el-radio>
-                <el-radio label="key">钥匙开门</el-radio>
+                <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="密码" v-if="petForm.entryMethod === 'password'">
+            <el-form-item label="密码" v-if="petForm.entryMethod === 'password'" required>
               <el-input v-model="petForm.entryPassword" placeholder="请输入门锁密码" />
             </el-form-item>
-            <el-form-item label="钥匙位置" v-if="petForm.entryMethod === 'key'">
+            <el-form-item label="钥匙位置" v-if="petForm.entryMethod === 'key'" required>
               <el-input v-model="petForm.keyLocation" placeholder="请输入钥匙存放位置" />
             </el-form-item>
           </el-form>
         </el-tab-pane>
         <el-tab-pane label="健康状况" name="health">
           <el-form :model="petForm" label-width="120px">
-            <el-form-item label="健康状态">
+            <el-form-item label="健康状态" required>
               <el-radio-group v-model="petForm.healthStatus">
                 <el-radio label="健康">健康</el-radio>
                 <el-radio label="亚健康">亚健康</el-radio>
                 <el-radio label="疾病">疾病</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="是否有攻击倾向">
+            <el-form-item label="是否有攻击倾向" required>
               <el-switch v-model="petForm.aggression" active-text="是" inactive-text="否" :active-value="1" :inactive-value="0" />
             </el-form-item>
-            <el-form-item label="疫苗情况">
-              <el-radio-group v-model="petForm.vaccine">
+            <el-form-item label="疫苗情况" required>
+              <el-radio-group v-model="petForm.vaccineStatus">
                 <el-radio label="无">无</el-radio>
                 <el-radio label="已打1次">已打1次</el-radio>
                 <el-radio label="已打2次">已打2次</el-radio>
@@ -352,10 +348,10 @@
                 <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px;"><Plus /></el-icon>
               </el-upload>
             </el-form-item>
-            <el-form-item label="既往病史">
+            <el-form-item label="既往病史" required>
               <el-input v-model="petForm.medicalHistory" type="textarea" placeholder="如有病史请记录" />
             </el-form-item>
-            <el-form-item label="过敏史">
+            <el-form-item label="过敏史" required>
               <el-input v-model="petForm.allergies" type="textarea" placeholder="如有过敏源请记录" />
             </el-form-item>
           </el-form>
@@ -407,7 +403,11 @@ const areaCascaderOptions = computed(() => {
       .map(item => {
         const children = buildTree(data, item.id)
         const node = { id: item.id, name: item.name }
-        if (children.length > 0) node.children = children
+        if (children.length > 0) {
+          node.children = children
+        } else if (String(item.type) !== '2') {
+          node.disabled = true
+        }
         return node
       })
   }
@@ -550,7 +550,11 @@ const areaTreeOptions = computed(() => {
       .map(item => {
         const children = buildTree(data, item.id)
         const node = { value: item.id, label: item.name }
-        if (children.length > 0) node.children = children
+        if (children.length > 0) {
+          node.children = children
+        } else if (String(item.type) !== '2') {
+          node.disabled = true
+        }
         return node
       })
   }
@@ -728,8 +732,13 @@ const saveRemark = () => {
 }
 
 const saveUser = () => {
+  if (!form.stationId) return ElMessage.warning('所属站点只能选择具体的站点')
   if (!form.name) return ElMessage.warning('请输入姓名')
   if (!form.phone) return ElMessage.warning('请输入电话')
+  if (!form.address) return ElMessage.warning('请输入详细住址')
+  if (!form.entryMethod) return ElMessage.warning('请选择入门方式')
+  if (form.entryMethod === 'password' && !form.entryPassword) return ElMessage.warning('请输入开门密码')
+  if (form.entryMethod === 'key' && !form.keyLocation) return ElMessage.warning('请输入钥匙位置')
   submitLoading.value = true
   form.tagIds = selectedTagIds.value
   if (regionCascaderValue.value && regionCascaderValue.value.length > 0) {
@@ -908,7 +917,20 @@ const handlePetDelete = (row) => {
 }
 
 const savePet = () => {
-  if (!petForm.name) return ElMessage.warning('请输入宠物昵称')
+  if (!petForm.name) return ElMessage.warning('请输入宠物姓名');
+  if (!petForm.breed) return ElMessage.warning('请选择品种');
+  if (!petForm.size) return ElMessage.warning('请选择体型');
+  if (petForm.weight === undefined || petForm.weight === null) return ElMessage.warning('请输入体重(kg)');
+  if (petForm.age === undefined || petForm.age === null) return ElMessage.warning('请输入年龄(岁)');
+  if (!petForm.houseType) return ElMessage.warning('请选择家庭房屋类型');
+  if (!petForm.entryMethod) return ElMessage.warning('请选择入门方式');
+  if (petForm.entryMethod === 'password' && !petForm.entryPassword) return ElMessage.warning('请输入门锁密码');
+  if (petForm.entryMethod === 'key' && !petForm.keyLocation) return ElMessage.warning('请输入钥匙存放位置');
+  if (!petForm.healthStatus) return ElMessage.warning('请选择健康状态');
+  if (petForm.aggression === undefined || petForm.aggression === null) return ElMessage.warning('请选择是否有攻击倾向');
+  if (!petForm.vaccineStatus) return ElMessage.warning('请选择疫苗情况');
+  if (!petForm.medicalHistory) return ElMessage.warning('请输入既往病史');
+  if (!petForm.allergies) return ElMessage.warning('请输入过敏史');
   submitLoading.value = true
   const data = { ...petForm, aggression: Number(petForm.aggression) || 0 }
   const api = data.id ? updatePet(data) : addPet(data)

+ 26 - 13
src/views/archieves/pet/index.vue

@@ -103,24 +103,24 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="品种">
+                <el-form-item label="品种" required>
                   <el-select v-model="form.breed" placeholder="请选择品种" style="width: 100%" filterable allow-create default-first-option>
                     <el-option v-for="dict in sys_pet_breed" :key="dict.value" :label="dict.label" :value="dict.value" />
                   </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="体型">
+                <el-form-item label="体型" required>
                   <el-select v-model="form.size" style="width: 100%">
                     <el-option v-for="dict in sys_pet_size" :key="dict.value" :label="dict.label" :value="dict.value" />
                   </el-select>
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="体重(kg)"><el-input-number v-model="form.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
+                <el-form-item label="体重(kg)" required><el-input-number v-model="form.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="年龄(岁)"><el-input-number v-model="form.age" :min="0" style="width: 100%" /></el-form-item>
+                <el-form-item label="年龄(岁)" required><el-input-number v-model="form.age" :min="0" style="width: 100%" /></el-form-item>
               </el-col>
               <el-col :span="24">
                 <el-form-item label="性格关键词"><el-input v-model="form.personality" placeholder="如:活泼、粘人" /></el-form-item>
@@ -145,37 +145,37 @@
             <el-form-item label="新来家庭时间">
               <el-date-picker v-model="form.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
             </el-form-item>
-            <el-form-item label="家庭房屋类型">
+            <el-form-item label="家庭房屋类型" required>
               <el-radio-group v-model="form.houseType">
                 <el-radio v-for="dict in sys_house_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="入门方式">
+            <el-form-item label="入门方式" required>
               <el-radio-group v-model="form.entryMethod">
                 <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="密码" v-if="form.entryMethod === 'password'">
+            <el-form-item label="密码" v-if="form.entryMethod === 'password'" required>
               <el-input v-model="form.entryPassword" placeholder="请输入门锁密码" />
             </el-form-item>
-            <el-form-item label="钥匙位置" v-if="form.entryMethod === 'key'">
+            <el-form-item label="钥匙位置" v-if="form.entryMethod === 'key'" required>
               <el-input v-model="form.keyLocation" placeholder="请输入钥匙存放位置" />
             </el-form-item>
           </el-form>
         </el-tab-pane>
         <el-tab-pane label="健康状况" name="health">
           <el-form :model="form" label-width="120px">
-            <el-form-item label="健康状态">
+            <el-form-item label="健康状态" required>
               <el-radio-group v-model="form.healthStatus">
                 <el-radio label="健康">健康</el-radio>
                 <el-radio label="亚健康">亚健康</el-radio>
                 <el-radio label="疾病">疾病</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="是否有攻击倾向">
+            <el-form-item label="是否有攻击倾向" required>
               <el-switch v-model="form.aggression" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
             </el-form-item>
-            <el-form-item label="疫苗情况">
+            <el-form-item label="疫苗情况" required>
               <el-radio-group v-model="form.vaccineStatus">
                 <el-radio label="无">无</el-radio>
                 <el-radio label="已打1次">已打1次</el-radio>
@@ -189,10 +189,10 @@
                 <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px"><Plus /></el-icon>
               </el-upload>
             </el-form-item>
-            <el-form-item label="既往病史">
+            <el-form-item label="既往病史" required>
               <el-input v-model="form.medicalHistory" type="textarea" placeholder="如有病史请记录" />
             </el-form-item>
-            <el-form-item label="过敏史">
+            <el-form-item label="过敏史" required>
               <el-input v-model="form.allergies" type="textarea" placeholder="如有过敏源请记录" />
             </el-form-item>
           </el-form>
@@ -459,6 +459,19 @@ const handleUploadVaccineCert = async (file) => {
 const saveData = () => {
   if (!form.name) return ElMessage.warning('请输入宠物姓名');
   if (!form.userId) return ElMessage.warning('请选择所属主人');
+  if (!form.breed) return ElMessage.warning('请选择品种');
+  if (!form.size) return ElMessage.warning('请选择体型');
+  if (form.weight === undefined || form.weight === null) return ElMessage.warning('请输入体重(kg)');
+  if (form.age === undefined || form.age === null) return ElMessage.warning('请输入年龄(岁)');
+  if (!form.houseType) return ElMessage.warning('请选择家庭房屋类型');
+  if (!form.entryMethod) return ElMessage.warning('请选择入门方式');
+  if (form.entryMethod === 'password' && !form.entryPassword) return ElMessage.warning('请输入门锁密码');
+  if (form.entryMethod === 'key' && !form.keyLocation) return ElMessage.warning('请输入钥匙存放位置');
+  if (!form.healthStatus) return ElMessage.warning('请选择健康状态');
+  if (form.aggression === undefined || form.aggression === null) return ElMessage.warning('请选择是否有攻击倾向');
+  if (!form.vaccineStatus) return ElMessage.warning('请选择疫苗情况');
+  if (!form.medicalHistory) return ElMessage.warning('请输入既往病史');
+  if (!form.allergies) return ElMessage.warning('请输入过敏史');
   submitLoading.value = true;
   const api = isEdit.value ? updatePet(form) : addPet(form);
   api.then(() => {

+ 10 - 4
src/views/merchant/storeManagement/index.vue

@@ -58,8 +58,11 @@
         <el-table-column label="资质认证" align="center" width="100">
           <template #default="scope">
             <div class="auth-img-wrap">
-              <el-image v-if="scope.row.businessLicenseUrl" :src="scope.row.businessLicenseUrl" class="auth-img" :preview-src-list="[scope.row.businessLicenseUrl]" preview-teleported />
-              <span v-else class="text-placeholder">-</span>
+              <el-image :src="scope.row.businessLicenseUrl" class="auth-img" :preview-src-list="scope.row.businessLicenseUrl ? [scope.row.businessLicenseUrl] : []" preview-teleported>
+                <template #error>
+                  <div class="image-slot"><el-icon><Picture /></el-icon></div>
+                </template>
+              </el-image>
             </div>
           </template>
         </el-table-column>
@@ -203,7 +206,7 @@
                        placeholder="请选择所属站点" style="width: 100%"
                        @change="handleAreaChange" />
         </el-form-item>
-        <el-form-item label="详细地址">
+        <el-form-item label="详细地址" prop="detailAddress">
           <el-row :gutter="10" style="margin-bottom: 10px">
             <el-col :span="24">
               <el-cascader v-model="addressCascaderValue" :options="regionData" placeholder="选择省市区"
@@ -538,7 +541,7 @@ const data = reactive<PageData<StoreForm, SysStorePageBo>>({
       { required: true, message: "序号不能为空", trigger: "blur" }
     ],
     businessLicense: [
-      { required: true, message: "营业执照不能为空", trigger: "blur" }
+      { required: false, message: "营业执照不能为空", trigger: "blur" }
     ],
     name: [
       { required: true, message: "门店名称不能为空", trigger: "blur" }
@@ -568,6 +571,9 @@ const data = reactive<PageData<StoreForm, SysStorePageBo>>({
     site: [
       { required: true, message: "归属站点不能为空", trigger: "change" }
     ],
+    detailAddress: [
+      { required: true, message: "详细地址不能为空", trigger: "blur" }
+    ]
   }
 });
 

+ 1 - 0
src/views/order/management/components/DispatchDialog.vue

@@ -396,6 +396,7 @@ const handleDispatchSubmit = () => {
     emit('submit', {
         riderId: rider.id,
         riderName: rider.name,
+        riderPhone: rider.phone,
         fee: dispatchFee.value
     })
     dialogVisible.value = false

+ 37 - 26
src/views/order/management/components/OrderDetailDrawer.vue

@@ -41,10 +41,7 @@
                             <el-button icon="More">更多操作</el-button>
                             <template #dropdown>
                                 <el-dropdown-menu>
-                                    <el-dropdown-item v-hasPermi="['order:management:reward']" command="reward"
-                                        icon="Trophy">奖惩操作</el-dropdown-item>
-                                    <el-dropdown-item v-hasPermi="['order:management:remark']" command="remark"
-                                        icon="EditPen">订单备注</el-dropdown-item>
+
                                     <el-dropdown-item command="delete" v-if="[5, 4].includes(order.status)" divided
                                         icon="Delete" style="color: #f56c6c;">删除订单</el-dropdown-item>
                                 </el-dropdown-menu>
@@ -85,17 +82,17 @@
                                     </el-icon>
                                 </div>
                                 <div class="b-tags">
-                                    <el-tag size="small" type="info">{{ order.petAge || '未知年龄' }}</el-tag>
-                                    <el-tag size="small" type="info">{{ order.petWeight || '未知体重' }}</el-tag>
+                                    <el-tag size="small" type="info">{{ order.petAge || '-' }}</el-tag>
+                                    <el-tag size="small" type="info">{{ order.petWeight || '-' }}</el-tag>
                                 </div>
                             </div>
                         </div>
                         <el-descriptions :column="2" size="small" class="pet-desc" border>
-                            <el-descriptions-item label="品种">{{ order.petBreed || '未知' }}</el-descriptions-item>
-                            <el-descriptions-item label="疫苗状态"><span style="color:#67c23a">{{ order.petVaccine || '未知'
+                            <el-descriptions-item label="品种">{{ order.petBreed || '-' }}</el-descriptions-item>
+                            <el-descriptions-item label="疫苗状态"><span style="color:#67c23a">{{ order.petVaccine || '-'
                                     }}</span></el-descriptions-item>
-                            <el-descriptions-item label="性格特点">{{ order.petCharacter || '温顺' }}</el-descriptions-item>
-                            <el-descriptions-item label="健康状况">{{ order.petHealth || '健康' }}</el-descriptions-item>
+                            <el-descriptions-item label="性格特点">{{ order.petCharacter || '-' }}</el-descriptions-item>
+                            <el-descriptions-item label="健康状况">{{ order.petHealth || '-' }}</el-descriptions-item>
                         </el-descriptions>
                     </div>
 
@@ -115,7 +112,7 @@
                             </div>
                             <div class="addr-box">
                                 <div class="addr-label">服务地址</div>
-                                <div class="addr-txt">{{ order.city }}{{ order.district }} {{ order.address || '' }}
+                                <div class="addr-txt">{{ order.city }}{{ order.district }} {{ order.address || '-' }}
                                 </div>
                             </div>
                         </div>
@@ -133,26 +130,24 @@
                                     <el-descriptions-item label="系统单号">{{ order.orderNo }}</el-descriptions-item>
                                     <el-descriptions-item label="服务类型">
                                         {{ getTypeName(order.type) }}
-                                        <el-tag size="small" v-if="order.type === 'transport'" style="margin-left:5px"
-                                            effect="light">{{ getTransportModeName(order.transportType) }}</el-tag>
                                     </el-descriptions-item>
 
-                                    <el-descriptions-item label="归属门店">{{ order.merchantName }}
+                                    <el-descriptions-item label="归属门店">{{ order.merchantName || '-' }}
                                         ({{ Number(order.platformId) === 1 ? '门店下单' : '平台代下单' }})</el-descriptions-item>
-                                    <el-descriptions-item label="宠主信息">{{ order.userName }} / {{ order.contactPhone
+                                    <el-descriptions-item label="宠主信息">{{ order.userName || '-' }} / {{ order.contactPhone || '-'
                                         }}</el-descriptions-item>
                                     <el-descriptions-item label="服务费用" label-class-name="money-label">
                                         <span style="color:#f56c6c; font-weight:bold;">¥ {{ order.fulfillerFee }}</span>
                                     </el-descriptions-item>
 
-                                    <el-descriptions-item label="预约时间">{{ getServiceTimeRange(order.serviceTime)
+                                    <el-descriptions-item label="预约时间">{{ getServiceTimeRange(order.serviceTime) || '-'
                                         }}</el-descriptions-item>
-                                    <el-descriptions-item label="团购套餐">{{ order.groupBuyPackage || '未使用团购套餐'
+                                    <el-descriptions-item label="团购套餐">{{ order.groupBuyPackage || '-'
                                         }}</el-descriptions-item>
-                                    <el-descriptions-item label="创建时间">{{ order.createTime }}</el-descriptions-item>
+                                    <el-descriptions-item label="创建时间">{{ order.createTime || '-' }}</el-descriptions-item>
 
                                     <el-descriptions-item label="订单备注" :span="3">
-                                        {{ order.remark || '暂无备注' }}
+                                        {{ order.remark || '-' }}
                                     </el-descriptions-item>
                                 </el-descriptions>
                             </div>
@@ -167,19 +162,19 @@
                                     </div>
                                     <div class="t-row">
                                         <span class="t-k">起点</span>
-                                        <span class="t-v">{{ order.detail?.fromAddress || order.detail?.pickAddr || '--'
+                                        <span class="t-v">{{ order.detail?.fromAddress || order.detail?.pickAddr || '-'
                                         }}</span>
                                     </div>
                                     <div class="t-row">
                                         <span class="t-k">终点</span>
                                         <span class="t-v">{{ order.detail?.toAddress || order.detail?.dropAddr ||
                                             order.toAddress ||
-                                            '--' }}</span>
+                                            '-' }}</span>
                                     </div>
                                     <div class="t-row sub">
-                                        <span class="t-v">{{ order.contact || order.userName || '--' }} {{
+                                        <span class="t-v">{{ order.contact || order.userName || '-' }} {{
                                             order.contactPhoneNumber
-                                            || order.contactPhone || '--' }}</span>
+                                            || order.contactPhone || '-' }}</span>
                                     </div>
                                 </div>
                             </div>
@@ -187,7 +182,7 @@
                             <div v-if="['feeding', 'washing'].includes(order.type)" class="section-block">
                                 <div class="sec-title-bar">服务执行要求</div>
                                 <el-descriptions :column="2" border size="default" class="custom-desc">
-                                    <el-descriptions-item label="服务地址" :span="2">{{ order.detail?.area || order.address
+                                    <el-descriptions-item label="服务地址" :span="2">{{ order.detail?.area || order.address || '-'
                                         }}</el-descriptions-item>
                                 </el-descriptions>
                             </div>
@@ -208,9 +203,9 @@
                                         <el-tag size="small" type="primary" effect="plain" round>Lv1 普通</el-tag>
                                     </div>
                                     <div class="f-row2">
-                                        <span>联系电话:{{ order.fulfillerPhone || '138****0000' }}</span>
+                                        <span>联系电话:{{ order.fulfillerPhone || '-' }}</span>
                                         <span class="sep">|</span>
-                                        <span>归属区域:{{ order.fulfillerStation || '朝阳一站' }}</span>
+                                        <span>归属区域:{{ order.fulfillerStation || '-' }}</span>
                                     </div>
                                     <div class="f-row3"
                                         style="margin-top: 8px; font-size: 13px; color: #606266; background: #f9fafe; padding: 8px; border-radius: 4px; display: flex; gap: 20px;">
@@ -328,6 +323,7 @@ import { getPet } from '@/api/archieves/pet'
 import { getCustomer } from '@/api/archieves/customer'
 import { listSubOrderLog, exportSubOrderLogUrl } from '@/api/order/subOrderLog/index'
 import { listComplaintByOrder } from '@/api/fulfiller/complaint'
+import { getFulfillerDetail } from '@/api/fulfiller/fulfiller/index'
 import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
 
 // 视频判定辅助函数
@@ -440,6 +436,21 @@ const loadPetAndCustomer = async (order) => {
         }
     }
 
+    const fulfillerId = next?.fulfiller || next?.fulfillerId
+    if (fulfillerId) {
+        try {
+            const res = await getFulfillerDetail(fulfillerId)
+            const fulfillerData = res?.data?.data || res?.data
+            if (fulfillerData) {
+                next.fulfillerAvatar = fulfillerData.avatarUrl ?? next.fulfillerAvatar
+                next.fulfillerPhone = fulfillerData.phone ?? next.fulfillerPhone
+                next.fulfillerStation = fulfillerData.stationName ?? next.fulfillerStation
+                next.fulfillerName = fulfillerData.name ?? next.fulfillerName
+            }
+        } catch {
+        }
+    }
+
     if (seq !== loadSeq.value) return
     orderDetail.value = next
 }

+ 97 - 93
src/views/order/management/index.vue

@@ -7,24 +7,14 @@
           <div class="right-panel">
             <el-radio-group v-model="filters.service" size="default" @change="handleSearch">
               <el-radio-button value="">全部类型</el-radio-button>
-              <el-radio-button v-for="item in serviceOptions" :key="item.id" :value="item.id">{{ item.name }}</el-radio-button>
+              <el-radio-button v-for="item in serviceOptions" :key="item.id" :value="item.id">{{ item.name
+              }}</el-radio-button>
             </el-radio-group>
-            <el-input
-              v-model="filters.content"
-              placeholder="订单号/商户/宠主/手机号"
-              class="search-input"
-              prefix-icon="Search"
-              clearable
-              @clear="handleSearch"
-              @keyup.enter="handleSearch"
-            />
+            <el-input v-model="filters.content" placeholder="订单号/商户/宠主/手机号" class="search-input" prefix-icon="Search"
+              clearable @clear="handleSearch" @keyup.enter="handleSearch" />
             <el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
-            <el-button
-              v-hasPermi="['order:management:export']"
-              type="success"
-              icon="Download"
-              @click="handleExport"
-            >导出Excel</el-button>
+            <el-button v-hasPermi="['order:management:export']" type="success" icon="Download"
+              @click="handleExport">导出Excel</el-button>
           </div>
         </div>
 
@@ -39,17 +29,20 @@
         </el-tabs>
       </template>
 
-      <el-table :data="tableData" style="width: 100%" v-loading="loading" :header-cell-style="{ background: '#f5f7fa' }">
+      <el-table :data="tableData" style="width: 100%" v-loading="loading"
+        :header-cell-style="{ background: '#f5f7fa' }">
         <el-table-column prop="code" label="订单号" width="170" fixed="left" />
 
         <el-table-column label="服务类型" width="190">
           <template #default="{ row }">
             <div class="service-type-cell">
               <el-tag>{{ getServiceName(row.service) }}</el-tag>
-              <el-tag v-if="getServiceModeTag(row)" class="sub-tag" type="warning" effect="plain">{{ getServiceModeTag(row) }}</el-tag>
-              <el-tag v-if="getServiceOrderTypeTag(row)" class="sub-tag" :type="getServiceOrderTypeTag(row).type" effect="dark">{{
-                getServiceOrderTypeTag(row).label
-              }}</el-tag>
+              <el-tag v-if="getServiceModeTag(row)" class="sub-tag" type="warning" effect="plain">{{
+                getServiceModeTag(row) }}</el-tag>
+              <el-tag v-if="getServiceOrderTypeTag(row)" class="sub-tag" :type="getServiceOrderTypeTag(row).type"
+                effect="dark">{{
+                  getServiceOrderTypeTag(row).label
+                }}</el-tag>
             </div>
           </template>
         </el-table-column>
@@ -111,25 +104,25 @@
           </template>
         </el-table-column>
 
-        <el-table-column label="履约信息" width="140">
+        <el-table-column label="履约者" width="120">
           <template #default="{ row }">
-            <div v-if="row.fulfillerName" class="fulfiller-info">
-              <span class="fulfiller-name">{{ row.fulfillerName }}</span>
-              <span class="fulfiller-fee" v-if="row.price !== null && row.price !== undefined">¥{{ row.price / 100.0 }}</span>
-            </div>
+            <span v-if="row.fulfillerName" style="font-weight: 500; color: #333;">{{ row.fulfillerName }}</span>
             <span v-else class="text-gray">暂未指派</span>
           </template>
         </el-table-column>
 
+
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
             <div class="op-cell">
               <el-button link type="primary" size="small" @click="handleDetail(row)">详情</el-button>
               <!--              <el-button v-if="row.status === 0" link type="success" size="small" @click="openDispatchDialog(row)">派单</el-button>-->
               <!--              <el-button v-if="![0, 4].includes(row.status)" link type="warning" size="small" @click="openDispatchDialog(row)">重新派单</el-button>-->
-              <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small" @click="handleCancel(row)">取消</el-button>
+              <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small"
+                @click="handleCancel(row)">取消</el-button>
 
-              <el-dropdown v-if="[3, 4].includes(row.status)" trigger="click" @command="(cmd) => handleCommand(cmd, row)">
+              <el-dropdown v-if="[3, 4].includes(row.status)" trigger="click"
+                @command="(cmd) => handleCommand(cmd, row)">
                 <span class="el-dropdown-link">
                   更多<el-icon class="el-icon--right">
                     <ArrowDown />
@@ -137,10 +130,10 @@
                 </span>
                 <template #dropdown>
                   <el-dropdown-menu>
-<!--                    <el-dropdown-item v-if="row.status === 3" command="complete">确认完成</el-dropdown-item>-->
-                    <el-dropdown-item v-if="row.status === 4 && getServiceMode(row.service) == 0" command="care_summary" v-hasPermi="['order:management:nursingSummary']">护理小结</el-dropdown-item>
-                    <el-dropdown-item command="reward" v-hasPermi="['order:management:reward']">奖惩</el-dropdown-item>
-                    <el-dropdown-item command="remark" v-hasPermi="['order:management:remark']">备注</el-dropdown-item>
+                    <!--                    <el-dropdown-item v-if="row.status === 3" command="complete">确认完成</el-dropdown-item>-->
+                    <el-dropdown-item v-if="row.status === 4 && getServiceMode(row.service) == 0" command="care_summary"
+                      v-hasPermi="['order:management:nursingSummary']">护理小结</el-dropdown-item>
+
                   </el-dropdown-menu>
                 </template>
               </el-dropdown>
@@ -150,48 +143,32 @@
       </el-table>
 
       <div class="pagination-container">
-        <el-pagination
-          v-model:current-page="pagination.current"
-          v-model:page-size="pagination.size"
-          :page-sizes="[10, 20, 50, 100]"
-          layout="total, sizes, prev, pager, next, jumper"
-          :total="pagination.total"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
+        <el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.size"
+          :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
+          @size-change="handleSizeChange" @current-change="handleCurrentChange" />
       </div>
     </el-card>
 
     <!-- 组件 -->
-    <OrderDetailDrawer
-      v-model:visible="detailVisible"
-      :order="currentOrder"
-      @dispatch="openDispatchDialog"
-      @cancel="handleCancel"
-      @command="handleCommand"
-      @care-summary="openCareSummary"
-    />
+    <OrderDetailDrawer v-model:visible="detailVisible" :order="currentOrder" @dispatch="openDispatchDialog"
+      @cancel="handleCancel" @command="handleCommand" @care-summary="openCareSummary" />
 
-    <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder" @submit="handleDispatchSubmit" />
+    <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder"
+      @submit="handleDispatchSubmit" />
 
     <CareSummaryDrawer v-model:visible="careSummaryVisible" :order="careSummaryOrder" @submit="saveCareSummary" />
 
-    <RewardDialog v-model:visible="rewardDialogVisible" :order="currentOperateRow" @submit="handleRewardSubmit" />
-
-    <RemarkDialog v-model:visible="remarkDialogVisible" :order="currentOperateRow" @submit="handleRemarkSubmit" />
-
     <PetDetailDrawer v-model:visible="petDetailVisible" :pet-id="currentPetId" />
   </div>
 </template>
 
 <script setup>
-import { ref, reactive, onMounted, nextTick, getCurrentInstance, toRefs } from 'vue';
+import { ref, reactive, onMounted, onUnmounted, nextTick, getCurrentInstance, toRefs } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import OrderDetailDrawer from './components/OrderDetailDrawer.vue';
 import DispatchDialog from './components/DispatchDialog.vue';
 import CareSummaryDrawer from './components/CareSummaryDrawer.vue';
-import RewardDialog from './components/RewardDialog.vue';
-import RemarkDialog from './components/RemarkDialog.vue';
+
 import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue';
 import { listAllService } from '@/api/service/list/index';
 import { listSubOrder, dispatchSubOrder, getSubOrderInfo, cancelSubOrder, remarkSubOrder, confirmSubOrder, nursingSummarySubOrder, exportSubOrder } from '@/api/order/subOrder/index';
@@ -226,10 +203,33 @@ const areaStationList = ref([]);
 const areaStationMap = ref({});
 const storeMap = ref({});
 
+let timer = null;
+
 onMounted(() => {
   getServiceList();
   getAreaStationList();
   handleSearch();
+
+  timer = setInterval(() => {
+    listSubOrder({
+      pageNum: pagination.current,
+      pageSize: pagination.size,
+      service: filters.service !== '' ? filters.service : undefined,
+      status: filters.status !== '' ? Number(filters.status) : undefined,
+      content: filters.content || undefined
+    }).then((res) => {
+      tableData.value = res.rows || [];
+      pagination.total = res.total || 0;
+      loadStoresForRows(tableData.value);
+    }).catch(() => { });
+  }, 5000);
+});
+
+onUnmounted(() => {
+  if (timer) {
+    clearInterval(timer);
+    timer = null;
+  }
 });
 
 const getServiceList = () => {
@@ -408,40 +408,35 @@ const handleDetail = async (row) => {
     orderNo: row?.code || row?.orderCode || row?.orderNo || row?.orderNumber || row?.no || '',
     type: row?.typeCode || row?.type || typeCode,
     serviceItem: getServiceName(row?.service) || row?.serviceName || row?.service || '',
-    userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
-    address: '某小区5号楼2单元101',
+    userAvatar: row?.userAvatar || '',
+    address: row?.address || '',
     groupBuyPackage: '',
-    transportType: row.splitType || row.transportType,
+    transportType: row?.splitType || row?.transportType,
     detail: {
-      ...row.detail,
-      pickTime: '2024-02-05 09:30',
-      pickAddr: row.detail?.pickAddr || '北京市朝阳区某小区5号楼2单元101',
-      pickContact: '李先生',
-      pickPhone: '13812345678',
-      dropTime: '2024-02-05 18:30',
-      dropAddr: row.detail?.dropAddr || '北京市朝阳区某小区5号楼2单元101',
-      dropContact: '李先生',
-      dropPhone: '13812345678',
-      packageName: row.detail?.packageName || '精细洗护套餐A',
-      petStatus: '胆小,需安抚',
-      area: '北京市朝阳区某小区5号楼2单元101'
+      ...row?.detail,
+      pickTime: row?.detail?.pickTime || '',
+      pickAddr: row?.detail?.pickAddr || '',
+      pickContact: row?.detail?.pickContact || '',
+      pickPhone: row?.detail?.pickPhone || '',
+      dropTime: row?.detail?.dropTime || '',
+      dropAddr: row?.detail?.dropAddr || '',
+      dropContact: row?.detail?.dropContact || '',
+      dropPhone: row?.detail?.dropPhone || '',
+      packageName: row?.detail?.packageName || '',
+      petStatus: row?.detail?.petStatus || '',
+      area: row?.detail?.area || ''
     },
-    petGender: 'male',
-    petAge: '2岁',
-    petWeight: '15kg',
-    petVaccine: '已接种',
-    petSterilized: true,
-    petCharacter: '活泼好动,喜欢球类玩具',
-    petHealth: '健康良好',
-    fulfillerAvatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
-    fulfillerPhone: '13812345678',
-    fulfillerStation: '朝阳服务站',
-    orderLogs: [
-      { time: '2024-02-04 09:30', title: '订单创建', content: '商户提交订单', icon: 'Document' },
-      { time: '2024-02-04 10:00', title: '系统派单', content: '指派给 王大力', icon: 'Bicycle' },
-      { time: '2024-02-04 10:05', title: '接单成功', content: '履约者已确认接单', icon: 'CircleCheck' },
-      { time: '2024-02-04 13:55', title: '到达服务点', content: '履约者已打卡', icon: 'Location' }
-    ]
+    petGender: '',
+    petAge: '',
+    petWeight: '',
+    petVaccine: '',
+    petSterilized: undefined,
+    petCharacter: '',
+    petHealth: '',
+    fulfillerAvatar: '',
+    fulfillerPhone: '',
+    fulfillerStation: '',
+    orderLogs: []
   };
 
   try {
@@ -473,6 +468,7 @@ const handleDetail = async (row) => {
         platformId: info.platformId ?? currentOrder.value?.platformId,
         groupBuyPackage: info.groupPurchasePackageName || currentOrder.value?.groupBuyPackage,
         fulfiller: info.fulfiller ?? currentOrder.value?.fulfiller,
+        remark: info.remark ?? currentOrder.value?.remark,
         detail: {
           ...(currentOrder.value?.detail || {}),
           pickTime: info.serviceTime || currentOrder.value?.detail?.pickTime,
@@ -493,7 +489,7 @@ const handleDetail = async (row) => {
         }
       };
     }
-  } catch {}
+  } catch { }
   detailVisible.value = true;
 };
 
@@ -505,12 +501,19 @@ const handlePetDetail = (row) => {
 
 // 取消订单
 const handleCancel = (row) => {
-  ElMessageBox.confirm('确认取消该订单吗?', '提示', { type: 'warning' }).then(() => {
-    cancelSubOrder({ orderId: row?.id }).then(() => {
+  ElMessageBox.prompt('请输入取消订单的原因', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPattern: /\S+/,
+    inputErrorMessage: '取消原因不能为空',
+    inputPlaceholder: '必填,请输入取消原因',
+    type: 'warning'
+  }).then(({ value }) => {
+    cancelSubOrder({ orderId: row?.id, reason: value }).then(() => {
       ElMessage.success('订单已取消');
       handleSearch();
     });
-  });
+  }).catch(() => { });
 };
 
 // 派单
@@ -565,7 +568,8 @@ const handleDispatchSubmit = (payload) => {
     if (row) {
       row.status = 1;
       row.fulfillerName = payload.riderName || 'Unknown';
-      row.price = payload.fee;
+      row.fulfillerPhone = payload.riderPhone;
+      row.price = Math.round(Number(payload.fee || 0) * 100);
     }
     handleSearch();
   });

+ 26 - 13
src/views/order/purchase/components/AddPetDialog.vue

@@ -28,24 +28,24 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="品种">
+              <el-form-item label="品种" required>
                 <el-select v-model="form.breed" placeholder="请选择品种" style="width: 100%" filterable allow-create default-first-option>
                   <el-option v-for="dict in sys_pet_breed" :key="dict.value" :label="dict.label" :value="dict.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="体型">
+              <el-form-item label="体型" required>
                 <el-select v-model="form.size" style="width: 100%">
                   <el-option v-for="dict in sys_pet_size" :key="dict.value" :label="dict.label" :value="dict.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="体重(kg)"><el-input-number v-model="form.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
+              <el-form-item label="体重(kg)" required><el-input-number v-model="form.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="年龄(岁)"><el-input-number v-model="form.age" :min="0" style="width: 100%" /></el-form-item>
+              <el-form-item label="年龄(岁)" required><el-input-number v-model="form.age" :min="0" style="width: 100%" /></el-form-item>
             </el-col>
             <el-col :span="24">
               <el-form-item label="性格关键词"><el-input v-model="form.personality" placeholder="如:活泼、粘人" /></el-form-item>
@@ -70,37 +70,37 @@
           <el-form-item label="新来家庭时间">
             <el-date-picker v-model="form.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
           </el-form-item>
-          <el-form-item label="家庭房屋类型">
+          <el-form-item label="家庭房屋类型" required>
             <el-radio-group v-model="form.houseType">
               <el-radio v-for="dict in sys_house_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="入门方式">
+          <el-form-item label="入门方式" required>
             <el-radio-group v-model="form.entryMethod">
               <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="密码" v-if="form.entryMethod === 'password'">
+          <el-form-item label="密码" v-if="form.entryMethod === 'password'" required>
             <el-input v-model="form.entryPassword" placeholder="请输入门锁密码" />
           </el-form-item>
-          <el-form-item label="钥匙位置" v-if="form.entryMethod === 'key'">
+          <el-form-item label="钥匙位置" v-if="form.entryMethod === 'key'" required>
             <el-input v-model="form.keyLocation" placeholder="请输入钥匙存放位置" />
           </el-form-item>
         </el-form>
       </el-tab-pane>
       <el-tab-pane label="健康状况" name="health">
         <el-form :model="form" label-width="120px">
-          <el-form-item label="健康状态">
+          <el-form-item label="健康状态" required>
             <el-radio-group v-model="form.healthStatus">
               <el-radio value="健康">健康</el-radio>
               <el-radio value="亚健康">亚健康</el-radio>
               <el-radio value="疾病">疾病</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="是否有攻击倾向">
+          <el-form-item label="是否有攻击倾向" required>
             <el-switch v-model="form.aggression" active-text="是" inactive-text="否" :active-value="1" :inactive-value="0" />
           </el-form-item>
-          <el-form-item label="疫苗情况">
+          <el-form-item label="疫苗情况" required>
             <el-radio-group v-model="form.vaccineStatus">
               <el-radio value="无">无</el-radio>
               <el-radio value="已打1次">已打1次</el-radio>
@@ -114,10 +114,10 @@
               <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px"><Plus /></el-icon>
             </el-upload>
           </el-form-item>
-          <el-form-item label="既往病史">
+          <el-form-item label="既往病史" required>
             <el-input v-model="form.medicalHistory" type="textarea" placeholder="如有病史请记录" />
           </el-form-item>
-          <el-form-item label="过敏史">
+          <el-form-item label="过敏史" required>
             <el-input v-model="form.allergies" type="textarea" placeholder="如有过敏源请记录" />
           </el-form-item>
         </el-form>
@@ -265,6 +265,19 @@ const handleUploadVaccineCert = async (file) => {
 const saveData = () => {
   if (!form.name) return ElMessage.warning('请输入宠物姓名')
   if (!form.userId) return ElMessage.warning('请先选择或新增所属主人')
+  if (!form.breed) return ElMessage.warning('请选择品种');
+  if (!form.size) return ElMessage.warning('请选择体型');
+  if (form.weight === undefined || form.weight === null) return ElMessage.warning('请输入体重(kg)');
+  if (form.age === undefined || form.age === null) return ElMessage.warning('请输入年龄(岁)');
+  if (!form.houseType) return ElMessage.warning('请选择家庭房屋类型');
+  if (!form.entryMethod) return ElMessage.warning('请选择入门方式');
+  if (form.entryMethod === 'password' && !form.entryPassword) return ElMessage.warning('请输入门锁密码');
+  if (form.entryMethod === 'key' && !form.keyLocation) return ElMessage.warning('请输入钥匙存放位置');
+  if (!form.healthStatus) return ElMessage.warning('请选择健康状态');
+  if (form.aggression === undefined || form.aggression === null) return ElMessage.warning('请选择是否有攻击倾向');
+  if (!form.vaccineStatus) return ElMessage.warning('请选择疫苗情况');
+  if (!form.medicalHistory) return ElMessage.warning('请输入既往病史');
+  if (!form.allergies) return ElMessage.warning('请输入过敏史');
 
   submitLoading.value = true
   addPetOnOrder(form).then(res => {

+ 10 - 5
src/views/order/purchase/components/AddUserDialog.vue

@@ -12,7 +12,7 @@
         <el-col :span="24"><div class="form-section-header">基本资料</div></el-col>
         <!-- 录入来源不在表单中展示,自动使用当前登录用户的 tenantId,提交时静默传递 -->
         <el-col :span="24">
-          <el-form-item label="所属站点">
+          <el-form-item label="所属站点" required>
             <el-cascader v-model="formAreaValue" :options="areaTreeOptions" placeholder="请选择站点"
               :props="{ checkStrictly: true, value: 'value', label: 'label' }"
               style="width: 100%" clearable @change="handleFormAreaChange" />
@@ -45,7 +45,7 @@
           </el-form-item>
         </el-col>
         <el-col :span="24">
-          <el-form-item label="详细住址"><el-input v-model="form.address" placeholder="请输入街道/门牌号" /></el-form-item>
+          <el-form-item label="详细住址" required><el-input v-model="form.address" placeholder="请输入街道/门牌号" /></el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="房屋类型">
@@ -55,19 +55,19 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="入门方式">
+          <el-form-item label="入门方式" required>
             <el-radio-group v-model="form.entryMethod">
               <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
             </el-radio-group>
           </el-form-item>
         </el-col>
         <el-col :span="12" v-if="form.entryMethod === 'password'">
-          <el-form-item label="开门密码">
+          <el-form-item label="开门密码" required>
             <el-input v-model="form.entryPassword" placeholder="请输入密码" />
           </el-form-item>
         </el-col>
         <el-col :span="12" v-if="form.entryMethod === 'key'">
-          <el-form-item label="钥匙位置">
+          <el-form-item label="钥匙位置" required>
             <el-input v-model="form.keyLocation" placeholder="如:地毯下" />
           </el-form-item>
         </el-col>
@@ -258,6 +258,11 @@ const handleUserUploadFile = async (file) => {
 const saveUser = () => {
   if (!form.name) return ElMessage.warning('请输入姓名')
   if (!form.phone) return ElMessage.warning('请输入电话')
+  if (!form.stationId) return ElMessage.warning('所属站点只能选择具体的站点')
+  if (!form.address) return ElMessage.warning('请输入详细住址')
+  if (!form.entryMethod) return ElMessage.warning('请选择入门方式')
+  if (form.entryMethod === 'password' && !form.entryPassword) return ElMessage.warning('请输入开门密码')
+  if (form.entryMethod === 'key' && !form.keyLocation) return ElMessage.warning('请输入钥匙存放位置')
 
   submitLoading.value = true
   form.tagIds = selectedTagIds.value

+ 7 - 5
src/views/order/purchase/index.vue

@@ -722,7 +722,7 @@ const handleSubmit = async () => {
       pet: form.petId,
       groupPurchasePackageName: form.groupBuyPackage || '',
       service: form.serviceId,
-      remark: '', // 表单目前暂无备注字段
+      remark: form[form.type] && form[form.type].other ? form[form.type].other : "",
       tenantId: storeObj.tenantId || '',
       subOrders: subOrders
     };
@@ -760,7 +760,6 @@ onMounted(() => {
 .create-layout {
   display: flex;
   gap: 20px;
-  align-items: flex-start;
   max-width: 1400px;
   margin: 0 auto;
 }
@@ -1057,9 +1056,12 @@ onMounted(() => {
 .summary-panel {
   background: white;
   border-radius: 8px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
-  position: sticky;
-  top: 20px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  position: fixed;
+  top: 150px;
+  right: 80px;
+  width: 320px;
+  z-index: 2000;
 }
 
 .summary-header {

+ 2 - 2
vite.config.ts

@@ -24,8 +24,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), '')