Browse Source

统一整改基本完成

Huanyi 1 ngày trước cách đây
mục cha
commit
d3f1bb64b9

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package",
   "name": "pet-system-admin",
-  "version": "0.0.1",
+  "version": "1.0.0",
   "description": "宠物系统管理后台.",
   "author": "Huanyi",
   "license": "MIT",

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

@@ -142,3 +142,11 @@ export const activateSubOrder = (data: { id: string | number; service: string |
         data
     });
 };
+
+export const closeSubOrder = (data: { orderId: string | number; reason?: string; }) => {
+    return request({
+        url: '/order/subOrder/close',
+        method: 'put',
+        data
+    });
+};

+ 11 - 0
src/api/order/subOrderAppeal/index.ts

@@ -72,3 +72,14 @@ export function auditSubOrderAppeal(data: SubOrderAppealAudit) {
   });
 }
 
+/**
+ * 根据订单ID查询服务变更记录列表
+ * @param orderId 订单ID
+ */
+export function listSubOrderAppealByOrderId(orderId: string | number): AxiosPromise<SubOrderAppealVO[]> {
+  return request({
+    url: '/order/subOrderAppeal/listByOrderId/' + orderId,
+    method: 'get'
+  });
+}
+

+ 39 - 26
src/components/DispatchDialog/index.vue

@@ -34,12 +34,16 @@
                         </div>
                     </div>
                     <!-- 新增右侧按钮组 -->
-                    <div class="card-right" style="display: flex; flex-direction: column; align-items: flex-end; justify-content: center; gap: 8px; padding-left: 20px;">
+                    <div class="card-right"
+                        style="display: flex; flex-direction: column; align-items: flex-end; justify-content: center; gap: 8px; padding-left: 20px;">
                         <div style="display: flex; gap: 10px;">
-                            <el-button type="primary" size="small" plain round icon="User" @click="openCustomerDetail" :loading="orderInfoLoading">用户档案</el-button>
-                            <el-button type="success" size="small" plain round @click="openPetDetail" :loading="orderInfoLoading" style="margin-left: 0;">宠物档案</el-button>
+                            <el-button type="primary" size="small" plain round icon="User" @click="openCustomerDetail"
+                                :loading="orderInfoLoading">用户档案</el-button>
+                            <el-button type="success" size="small" plain round @click="openPetDetail"
+                                :loading="orderInfoLoading" style="margin-left: 0;">宠物档案</el-button>
                         </div>
-                        <el-button type="warning" size="small" plain disabled style="width: 100%; border-color: #f3d19e; color: #e6a23c; opacity: 1; cursor: default; justify-content: center;">
+                        <el-button type="warning" size="small" plain disabled
+                            style="width: 100%; border-color: #f3d19e; color: #e6a23c; opacity: 1; cursor: default; justify-content: center;">
                             团购套餐: {{ localOrder.groupPurchasePackageName || '-' }}
                         </el-button>
                     </div>
@@ -54,7 +58,7 @@
                 <div class="list-card rider-card"
                     style="margin-bottom: 20px; border: 1px solid #e4e7ed; background:#fafafa; cursor:default;">
                     <div class="card-left relative">
-                        <el-avatar :src="currentRider.avatar" :size="40" />
+                        <el-avatar :src="currentRider.avatarUrl || currentRider.avatar" :size="40" />
                     </div>
                     <div class="card-main">
                         <div class="row-1"
@@ -63,7 +67,8 @@
                                 <span class="r-name">{{ currentRider.name || '--' }}</span>
                                 <span class="r-phone">{{ currentRider.phone || '--' }}</span>
                                 <dict-tag :options="sys_user_sex" :value="currentRider.gender" />
-                                <el-tag v-if="currentRider.status" size="small" :type="getStatusType(currentRider.status)" effect="plain">
+                                <el-tag v-if="currentRider.status" size="small"
+                                    :type="getStatusType(currentRider.status)" effect="plain">
                                     {{ getStatusText(currentRider.status) }}
                                 </el-tag>
                             </div>
@@ -71,8 +76,10 @@
 
                         <div class="row-2 categories-row"
                             style="margin-top: 6px; display:flex; gap:4px; flex-wrap:wrap;">
-                            <el-tag v-for="typeId in (currentRider.serviceTypes ? String(currentRider.serviceTypes).split(',') : [])" :key="typeId" size="small"
-                                type="primary" effect="plain">{{ getServiceTypeText(typeId) }}</el-tag>
+                            <el-tag
+                                v-for="typeId in (currentRider.serviceTypes ? String(currentRider.serviceTypes).split(',') : [])"
+                                :key="typeId" size="small" type="primary" effect="plain">{{ getServiceTypeText(typeId)
+                                }}</el-tag>
                         </div>
                         <div class="row-3 time-row" style="margin-top: 4px;">
                             <span class="last-time">下一单: {{ currentRider.nextOrderTime || '-' }}</span>
@@ -97,24 +104,29 @@
                                 :class="{ active: selectedRiderId === rider.id }" @click="selectedRiderId = rider.id">
                                 <!-- Reusing Rider Card Layout -->
                                 <div class="card-left relative">
-                                    <el-avatar :src="rider.avatar" :size="40" />
+                                    <el-avatar :src="rider.avatarUrl || rider.avatar" :size="40" />
                                 </div>
                                 <div class="card-main">
-                                    <div class="row-1" style="justify-content: space-between; align-items: flex-start; display: flex;">
+                                    <div class="row-1"
+                                        style="justify-content: space-between; align-items: flex-start; display: flex;">
                                         <div style="display:flex; align-items:baseline; gap:8px;">
                                             <span class="r-name">{{ rider.name || '--' }}</span>
                                             <span class="r-phone">{{ rider.phone || '--' }}</span>
                                             <dict-tag :options="sys_user_sex" :value="rider.gender" />
                                         </div>
-                                        <el-tag v-if="rider.status" size="small" :type="getStatusType(rider.status)" effect="plain">
+                                        <el-tag v-if="rider.status" size="small" :type="getStatusType(rider.status)"
+                                            effect="plain">
                                             {{ getStatusText(rider.status) }}
                                         </el-tag>
                                     </div>
 
                                     <div class="row-2 categories-row"
                                         style="margin-top: 6px; display:flex; gap:4px; flex-wrap:wrap;">
-                                        <el-tag v-for="typeId in (rider.serviceTypes ? String(rider.serviceTypes).split(',') : [])" :key="typeId" size="small"
-                                            type="primary" effect="plain">{{ getServiceTypeText(typeId) }}</el-tag>
+                                        <el-tag
+                                            v-for="typeId in (rider.serviceTypes ? String(rider.serviceTypes).split(',') : [])"
+                                            :key="typeId" size="small" type="primary" effect="plain">{{
+                                            getServiceTypeText(typeId)
+                                            }}</el-tag>
                                     </div>
                                     <div class="row-3 time-row" style="margin-top: 4px">
                                         <span class="last-time">下一单: {{ rider.nextOrderTime || '-' }}</span>
@@ -164,7 +176,8 @@
         </div>
     </el-dialog>
 
-    <CustomerDetailDrawer v-model:visible="customerDialogVisible" :customer-id="customerId" :area-station-list="areaStationList" />
+    <CustomerDetailDrawer v-model:visible="customerDialogVisible" :customer-id="customerId"
+        :area-station-list="areaStationList" />
     <PetDetailDrawer v-model:visible="petDialogVisible" :pet-id="petId" />
 </template>
 
@@ -294,11 +307,11 @@ watch(() => props.visible, async (val) => {
     if (val && props.order) {
         // 1. 初始化 localOrder,处理模型转换逻辑 @Author: Antigravity
         const raw = { ...props.order }
-        
+
         // 确保基本属性存在,如果不完整则从 props.order 推导 (统一两个页面的逻辑)
         const isTransport = raw.mode === 1 || raw.mode === '1' || raw.type === 'transport' || raw.typeCode === 'transport'
         if (!raw.typeCode) raw.typeCode = isTransport ? 'transport' : 'feeding'
-        
+
         const toAddr = raw.toAddress || raw.address || ''
         if (isTransport) {
             if (!raw.pickAddr) raw.pickAddr = raw.fromAddress || toAddr
@@ -306,11 +319,11 @@ watch(() => props.visible, async (val) => {
         } else {
             if (!raw.address) raw.address = toAddr
         }
-        
+
         if (!raw.time) raw.time = raw.serviceTime || raw.appointTime || raw.createTime
         if (!raw.service && raw.serviceId) raw.service = raw.serviceId
         if (!raw.riderId && raw.fulfiller) raw.riderId = raw.fulfiller
-        
+
         localOrder.value = raw
 
         currentRider.value = null
@@ -320,7 +333,7 @@ watch(() => props.visible, async (val) => {
         // 金额单位转换
         dispatchFee.value = localOrder.value?.fulfillmentCommission ? Number((localOrder.value.fulfillmentCommission / 100).toFixed(2)) : 0
         orderCommission.value = localOrder.value?.orderCommission ? Number((localOrder.value.orderCommission / 100).toFixed(2)) : 0
-        
+
         if (localOrder.value?.riderId) {
             currentRider.value = {
                 id: localOrder.value.riderId,
@@ -339,11 +352,11 @@ watch(() => props.visible, async (val) => {
         petId.value = null
         orderInfoLoading.value = true
         getSubOrderInfo(localOrder.value.id).then((res) => {
-            if(res.data) {
+            if (res.data) {
                 const info = res.data
                 customerId.value = info.usrCustomer?.id || info.usrCustomer
                 petId.value = info.usrPet?.id || info.usrPet
-                
+
                 // 详情覆盖
                 if (info.fulfillmentCommission !== undefined) {
                     dispatchFee.value = Number((info.fulfillmentCommission / 100).toFixed(2))
@@ -351,12 +364,12 @@ watch(() => props.visible, async (val) => {
                 if (info.orderCommission !== undefined) {
                     orderCommission.value = Number((info.orderCommission / 100).toFixed(2))
                 }
-                
+
                 // 再次修正 typeCode
                 const detailIsTransport = info.mode === 1 || info.mode === '1'
                 const serviceName = getServiceTypeText(info.service)
                 const typeCode = detailIsTransport ? 'transport' : (serviceName?.includes('洗') ? 'washing' : 'feeding')
-                
+
                 localOrder.value = {
                     ...localOrder.value,
                     ...info,
@@ -377,7 +390,7 @@ watch(() => props.visible, async (val) => {
                                 id: fId,
                                 name: r.name || info.fulfillerName,
                                 phone: r.phone || info.fulfillerPhone,
-                                avatar: r.avatar || info.fulfillerAvatar,
+                                avatar: r.avatarUrl || r.avatar || info.fulfillerAvatar,
                                 gender: r.gender ?? r.sex ?? (info.fulfillerGender ?? info.fulfillerSex),
                                 status: r.status || info.fulfillerStatus,
                                 serviceTypes: r.serviceTypes || info.fulfillerServiceTypes,
@@ -468,7 +481,7 @@ const handleDispatchSubmit = async () => {
         ElMessage.warning('请输入服务费用')
         return
     }
-    
+
     try {
         const payload = {
             orderId: localOrder.value.id,
@@ -480,7 +493,7 @@ const handleDispatchSubmit = async () => {
         ElMessage.success('派单成功')
         emit('success')
         dialogVisible.value = false
-    } catch(e) {
+    } catch (e) {
         // 请求拦截器已处理异常,如需特殊处理可在此增加
     }
 }

+ 12 - 2
src/layout/components/notice/index.vue

@@ -64,7 +64,9 @@ const emit = defineEmits(['read']);
 const NOTICE_TYPE = {
   DISPATCH: 0,
   REJECT: 1,
-  ANOMALY: 2
+  ANOMALY: 2,
+  APPEAL: 3,
+  COMPLAINT: 4
 };
 
 const noticeList = ref<NoticeVO[]>([]);
@@ -87,6 +89,8 @@ const getIconConfig = (item: any) => {
   if (type === NOTICE_TYPE.ANOMALY) return { icon: 'bug', bgColor: '#f56c6c' };
   if (type === NOTICE_TYPE.REJECT) return { icon: 'message-center', bgColor: '#f56c6c' };
   if (type === NOTICE_TYPE.DISPATCH) return { icon: 'guide', bgColor: '#409eff' };
+  if (type === NOTICE_TYPE.APPEAL) return { icon: 'edit', bgColor: '#e6a23c' };
+  if (type === NOTICE_TYPE.COMPLAINT) return { icon: 'peoples', bgColor: '#f56c6c' };
   return { icon: 'message-center', bgColor: '#909399' };
 };
 
@@ -123,6 +127,12 @@ const handleDetail = (item: NoticeVO) => {
   } else if (type === NOTICE_TYPE.DISPATCH) {
     detailDialog.tagLabel = '新订单';
     detailDialog.tagType = 'primary';
+  } else if (type === NOTICE_TYPE.APPEAL) {
+    detailDialog.tagLabel = '服务变更';
+    detailDialog.tagType = 'warning';
+  } else if (type === NOTICE_TYPE.COMPLAINT) {
+    detailDialog.tagLabel = '商户投诉';
+    detailDialog.tagType = 'danger';
   } else {
     detailDialog.tagLabel = '系统消息';
     detailDialog.tagType = 'info';
@@ -146,7 +156,7 @@ const handleProcess = () => {
   const type = detailDialog.type;
   if (type === NOTICE_TYPE.ANOMALY) {
     router.push({ path: '/fulfiller/anamaly', query: { id: detailDialog.businessId } });
-  } else if (type === NOTICE_TYPE.REJECT || type === NOTICE_TYPE.DISPATCH) {
+  } else if (type === NOTICE_TYPE.REJECT || type === NOTICE_TYPE.DISPATCH || type === NOTICE_TYPE.APPEAL || type === NOTICE_TYPE.COMPLAINT) {
     router.push({ path: '/order/orderList', query: { id: detailDialog.businessId } });
   }
 };

+ 52 - 37
src/views/fulfiller/pool/index.vue

@@ -8,19 +8,15 @@
             <el-tag type="info" effect="plain" style="margin-left: 10px;">共 {{ total }} 人</el-tag>
           </div>
           <div class="right-panel">
-            <el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['fulfiller:pool:exportExcel']">导出Excel</el-button>
-            <el-button type="primary" icon="Plus" style="margin-right: 16px" @click="handleCreate" v-hasPermi="['fulfiller:pool:add']">新增履约者</el-button>
+            <el-button type="success" icon="Download" @click="handleExport"
+              v-hasPermi="['fulfiller:pool:exportExcel']">导出Excel</el-button>
+            <el-button type="primary" icon="Plus" style="margin-right: 16px" @click="handleCreate"
+              v-hasPermi="['fulfiller:pool:add']">新增履约者</el-button>
             <el-input v-model="searchKey" placeholder="搜索姓名/手机号/身份证" class="search-input" prefix-icon="Search" clearable
               @keyup.enter="handleSearch" @clear="handleSearch" />
-            <el-cascader
-              v-model="filterCascaderValue"
-              :options="areaTreeOptions"
-              :props="{ checkStrictly: true, value: 'id', label: 'name' }"
-              placeholder="所属站点"
-              clearable
-              style="width: 350px; margin-left: 10px;"
-              @change="handleFilterCascaderChange"
-            />
+            <el-cascader v-model="filterCascaderValue" :options="areaTreeOptions"
+              :props="{ checkStrictly: true, value: 'id', label: 'name' }" placeholder="所属站点" clearable
+              style="width: 350px; margin-left: 10px;" @change="handleFilterCascaderChange" />
           </div>
         </div>
 
@@ -93,7 +89,8 @@
         <el-table-column label="服务区域" width="180">
           <template #default="scope">
             <div class="text-col">
-              <span style="font-size: 13px; color: #333;">{{ getStationPathText(scope.row.stationId).cityAndRegion }}</span>
+              <span style="font-size: 13px; color: #333;">{{ getStationPathText(scope.row.stationId).cityAndRegion
+              }}</span>
               <span style="font-size: 12px; color: #999;">{{ getStationPathText(scope.row.stationId).station }}</span>
             </div>
           </template>
@@ -134,20 +131,28 @@
         <el-table-column label="操作" width="240" fixed="right">
           <template #default="scope">
             <div class="op-cell">
-              <el-button link type="primary" size="small" @click="handleDetail(scope.row)" v-hasPermi="['fulfiller:pool:query']">详情</el-button>
-              <el-button link type="primary" size="small" @click="handleEdit(scope.row)" v-hasPermi="['fulfiller:pool:edit']">编辑</el-button>
-              <el-button link type="warning" size="small" @click="handleReward(scope.row)" v-hasPermi="['fulfiller:pool:reward']">奖惩</el-button>
+              <el-button link type="primary" size="small" @click="handleDetail(scope.row)"
+                v-hasPermi="['fulfiller:pool:query']">详情</el-button>
+              <el-button link type="primary" size="small" @click="handleEdit(scope.row)"
+                v-hasPermi="['fulfiller:pool:edit']">编辑</el-button>
+              <el-button link type="warning" size="small" @click="handleReward(scope.row)"
+                v-hasPermi="['fulfiller:pool:reward']">奖惩</el-button>
               <el-dropdown trigger="click" @command="(cmd) => handleCommand(cmd, scope.row)">
                 <el-button link type="primary">更多<el-icon class="el-icon--right"><arrow-down /></el-icon></el-button>
                 <template #dropdown>
                   <el-dropdown-menu>
-                    <el-dropdown-item command="adjustPoints" v-hasPermi="['fulfiller:pool:editScore']">修改积分</el-dropdown-item>
-                    <el-dropdown-item command="adjustBalance" v-hasPermi="['fulfiller:pool:editBalance']">余额增减</el-dropdown-item>
+                    <el-dropdown-item command="adjustPoints"
+                      v-hasPermi="['fulfiller:pool:editScore']">修改积分</el-dropdown-item>
+                    <el-dropdown-item command="adjustBalance"
+                      v-hasPermi="['fulfiller:pool:editBalance']">余额增减</el-dropdown-item>
                     <el-dropdown-item v-if="scope.row.status !== 'disabled'" command="disable" divided
                       style="color: #f56c6c" v-hasPermi="['fulfiller:pool:disable']">禁用账号</el-dropdown-item>
-                    <el-dropdown-item v-else command="enable" divided style="color: #67c23a" v-hasPermi="['fulfiller:pool:enable']">启用账号</el-dropdown-item>
-                    <el-dropdown-item command="violation" v-hasPermi="['fulfiller:pool:violationLog']">违规记录</el-dropdown-item>
-                    <el-dropdown-item command="resetPwd" v-hasPermi="['fulfiller:pool:resetPassword']">重置密码</el-dropdown-item>
+                    <el-dropdown-item v-else command="enable" divided style="color: #67c23a"
+                      v-hasPermi="['fulfiller:pool:enable']">启用账号</el-dropdown-item>
+                    <el-dropdown-item command="violation"
+                      v-hasPermi="['fulfiller:pool:violationLog']">违规记录</el-dropdown-item>
+                    <el-dropdown-item command="resetPwd"
+                      v-hasPermi="['fulfiller:pool:resetPassword']">重置密码</el-dropdown-item>
                   </el-dropdown-menu>
                 </template>
               </el-dropdown>
@@ -186,7 +191,9 @@
               <span class="divider">|</span>
               <span class="info-item"><el-icon>
                   <Location />
-                </el-icon> {{ currentItem ? getStationPathText(currentItem.stationId).cityAndRegion : '-' }}/{{ currentItem ? getStationPathText(currentItem.stationId).station : '-' }}</span>
+                </el-icon> {{ currentItem ? getStationPathText(currentItem.stationId).cityAndRegion : '-' }}/{{
+                  currentItem
+                    ? getStationPathText(currentItem.stationId).station : '-' }}</span>
             </div>
             <div class="tags-row">
               <el-tag size="small" :type="getLevelType(currentItem.levelName)" effect="dark">{{
@@ -229,7 +236,9 @@
                   <el-descriptions-item label="身份证号">{{ currentItem.idCard }}</el-descriptions-item>
                   <el-descriptions-item label="真实姓名">{{ currentItem.realName || currentItem.name
                   }}</el-descriptions-item>
-                  <el-descriptions-item label="归属站点">{{ currentItem ? getStationPathText(currentItem.stationId).station : '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="归属站点">{{ currentItem ? getStationPathText(currentItem.stationId).station
+                    :
+                    '-' }}</el-descriptions-item>
                   <el-descriptions-item label="证件有效期">{{ currentItem.idCardExpiry || '-' }}</el-descriptions-item>
                   <el-descriptions-item label="入驻时间">{{ currentItem.createTime }}</el-descriptions-item>
                   <el-descriptions-item label="工作性质">{{ currentItem.workType === 'full_time' ? '全职' : '兼职'
@@ -299,9 +308,10 @@
                     {{ getServiceName(row.service) }}
                   </template>
                 </el-table-column>
-                <el-table-column prop="price" label="收入" width="100">
+                <el-table-column prop="fulfillmentCommission" label="收入" width="100">
                   <template #default="{ row }">
-                    <span style="color: #67c23a; font-weight: bold; font-size: 15px;">+{{ (row.price / 100).toFixed(2)
+                    <span style="color: #67c23a; font-weight: bold; font-size: 15px;">+{{ (row.fulfillmentCommission /
+                      100).toFixed(2)
                     }}</span>
                   </template>
                 </el-table-column>
@@ -415,7 +425,7 @@
                 </el-table-column>
                 <el-table-column prop="count" label="违规次数" width="100" />
                 <el-table-column prop="reason" label="违规原因" show-overflow-tooltip />
-<!--                <el-table-column prop="operatorName" label="操作人" width="100" />-->
+                <!--                <el-table-column prop="operatorName" label="操作人" width="100" />-->
               </el-table>
             </div>
           </el-tab-pane>
@@ -427,18 +437,22 @@
                 <el-table-column prop="createTime" label="投诉时间" width="180" />
                 <el-table-column prop="orderCode" label="订单号" width="160" show-overflow-tooltip />
                 <el-table-column prop="reason" label="投诉原因" show-overflow-tooltip />
-<!--                <el-table-column prop="createBy" label="操作人" width="100" />-->
+                <el-table-column label="投诉图片" width="180">
+                  <template #default="{ row }">
+                    <div v-if="row.photoUrls" style="display: flex; gap: 4px; flex-wrap: wrap;">
+                      <ImagePreview v-for="(url, i) in row.photoUrls.split(',').filter(Boolean)" :key="i" :src="url"
+                        :width="40" :height="40" />
+                    </div>
+                    <span v-else style="color:#c0c4cc;">-</span>
+                  </template>
+                </el-table-column>
+                <!--                <el-table-column prop="createBy" label="操作人" width="100" />-->
               </el-table>
               <div style="margin-top: 20px; display: flex; justify-content: flex-end;">
-                <el-pagination
-                  v-model:current-page="complaintPagination.pageNum"
-                  v-model:page-size="complaintPagination.pageSize"
-                  :page-sizes="[10, 20, 50]"
-                  layout="total, sizes, prev, pager, next"
-                  :total="complaintPagination.total"
-                  @size-change="loadComplaintLogs"
-                  @current-change="loadComplaintLogs"
-                />
+                <el-pagination v-model:current-page="complaintPagination.pageNum"
+                  v-model:page-size="complaintPagination.pageSize" :page-sizes="[10, 20, 50]"
+                  layout="total, sizes, prev, pager, next" :total="complaintPagination.total"
+                  @size-change="loadComplaintLogs" @current-change="loadComplaintLogs" />
               </div>
             </div>
           </el-tab-pane>
@@ -711,6 +725,7 @@ import { listAreaStation as listOnStore } from '@/api/system/areaStation'
 import type { AreaStationVO as SysAreaStationOnStoreVo } from '@/api/system/areaStation/types'
 import fulfillerEnums from '@/json/fulfiller.json'
 import ImageUpload from '@/components/ImageUpload/index.vue'
+import ImagePreview from '@/components/ImagePreview/index.vue'
 import { listAllLevelRights, addLevelRights, updateLevelRights, delLevelRights, changeLevelRightsStatus } from '@/api/fulfiller/levelRights';
 import { listAllLevelConfig, addLevelConfig, updateLevelConfig, delLevelConfig } from '@/api/fulfiller/levelConfig';
 import { useIntervalRefresh } from '@/hooks/useIntervalRefresh';
@@ -1078,7 +1093,7 @@ const submitCreate = async () => {
     return
   }
   // 将选中的服务类型数组转为逗号拼接字符串后提交
-  ;(createDialog.form as any).serviceTypes = createDialog.serviceTypeIds.join(',')
+  ; (createDialog.form as any).serviceTypes = createDialog.serviceTypeIds.join(',')
   try {
     await addFulfiller(createDialog.form as FlfFulfillerForm)
     createDialog.visible = false
@@ -1089,7 +1104,7 @@ const submitCreate = async () => {
 
 const saveEdit = async () => {
   // 将选中的服务类型数组转为逗号拼接字符串后提交
-  ;(editDialog.form as any).serviceTypes = editDialog.serviceTypeIds.join(',')
+  ; (editDialog.form as any).serviceTypes = editDialog.serviceTypeIds.join(',')
   try {
     await updateFulfiller(editDialog.form)
     ElMessage.success('更新成功')

+ 20 - 2
src/views/notice/index.vue

@@ -11,6 +11,8 @@
       <el-tab-pane label="未读消息" name="unread"></el-tab-pane>
       <el-tab-pane label="拒单/取消" name="reject"></el-tab-pane>
       <el-tab-pane label="异常上报" name="anomaly"></el-tab-pane>
+      <el-tab-pane label="服务变更" name="appeal"></el-tab-pane>
+      <el-tab-pane label="商户投诉" name="complaint"></el-tab-pane>
     </el-tabs>
 
     <!-- 消息列表 -->
@@ -91,7 +93,9 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const NOTICE_TYPE = {
   DISPATCH: 0,
   REJECT: 1,
-  ANOMALY: 2
+  ANOMALY: 2,
+  APPEAL: 3,
+  COMPLAINT: 4
 };
 
 const activeTab = ref('all');
@@ -128,6 +132,8 @@ const getIconConfig = (item: any) => {
   if (type === NOTICE_TYPE.ANOMALY) return { icon: 'bug', bgColor: '#f56c6c' };
   if (type === NOTICE_TYPE.REJECT) return { icon: 'message-center', bgColor: '#f56c6c' };
   if (type === NOTICE_TYPE.DISPATCH) return { icon: 'guide', bgColor: '#409eff' };
+  if (type === NOTICE_TYPE.APPEAL) return { icon: 'edit', bgColor: '#e6a23c' };
+  if (type === NOTICE_TYPE.COMPLAINT) return { icon: 'peoples', bgColor: '#f56c6c' };
   return { icon: 'message-center', bgColor: '#909399' };
 };
 
@@ -155,6 +161,10 @@ function handleTabClick(tab: any) {
     queryParams.value.type = NOTICE_TYPE.REJECT as any;
   } else if (paneName === 'anomaly') {
     queryParams.value.type = NOTICE_TYPE.ANOMALY as any;
+  } else if (paneName === 'appeal') {
+    queryParams.value.type = NOTICE_TYPE.APPEAL as any;
+  } else if (paneName === 'complaint') {
+    queryParams.value.type = NOTICE_TYPE.COMPLAINT as any;
   }
 
   getList();
@@ -178,6 +188,12 @@ function handleDetail(item: NoticeVO) {
   } else if (type === NOTICE_TYPE.DISPATCH) {
     detailDialog.tagLabel = '新订单';
     detailDialog.tagType = 'primary';
+  } else if (type === NOTICE_TYPE.APPEAL) {
+    detailDialog.tagLabel = '服务变更';
+    detailDialog.tagType = 'warning';
+  } else if (type === NOTICE_TYPE.COMPLAINT) {
+    detailDialog.tagLabel = '商户投诉';
+    detailDialog.tagType = 'danger';
   } else {
     detailDialog.tagLabel = '系统消息';
     detailDialog.tagType = 'info';
@@ -199,8 +215,10 @@ function handleProcess() {
   const type = detailDialog.type;
   if (type === NOTICE_TYPE.ANOMALY) {
     router.push({ path: '/fulfiller/anamaly', query: { id: detailDialog.businessId } });
-  } else if (type === NOTICE_TYPE.REJECT || type === NOTICE_TYPE.DISPATCH) {
+  } else if (type === NOTICE_TYPE.REJECT || type === NOTICE_TYPE.DISPATCH || type === NOTICE_TYPE.COMPLAINT) {
     router.push({ path: '/order/orderList', query: { id: detailDialog.businessId } });
+  } else if (type === NOTICE_TYPE.APPEAL) {
+    router.push({ path: '/order/appeal', query: { id: detailDialog.businessId } });
   }
 }
 

+ 192 - 27
src/views/order/appeal/index.vue

@@ -56,11 +56,7 @@
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="履约佣金(元)" align="right" prop="fulfillmentCommission" width="120">
-          <template #default="scope">
-            <span>{{ (scope.row.fulfillmentCommission / 100).toFixed(2) }}</span>
-          </template>
-        </el-table-column>
+
         <el-table-column label="提交内容" align="left" prop="reason" :show-overflow-tooltip="true" min-width="200" />
         <el-table-column label="提交时间" align="center" prop="createTime" width="180" sortable>
           <template #default="scope">
@@ -75,8 +71,10 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
+        <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
           <template #default="scope">
+            <el-button link type="primary" v-hasPermi="['order:appeal:query']"
+              @click="handleDetail(scope.row)">详情</el-button>
             <el-button v-if="scope.row.auditStatus === 0 && checkPermi(['order:appeal:audit'])" link type="primary"
               @click="handleAudit(scope.row)">审核</el-button>
             <el-button link type="danger" v-hasPermi="['order:appeal:remove']"
@@ -89,8 +87,8 @@
         v-model:limit="queryParams.pageSize" @pagination="getList" />
     </div>
 
-    <!-- 删除确认详情展示 -->
-    <el-dialog title="确认删除申诉" v-model="openView" width="600px" append-to-body custom-class="detail-dialog">
+    <!-- 详情对话框 -->
+    <el-dialog title="申诉详情" v-model="openDetail" width="600px" append-to-body custom-class="detail-dialog">
       <div class="detail-container">
         <div class="detail-item">
           <span class="label">订单号:</span>
@@ -104,10 +102,67 @@
           <span class="label">履约者:</span>
           <span class="value">{{ form.fulfillerName }}</span>
         </div>
+
+        <div class="detail-item full-width">
+          <span class="label">提交时间:</span>
+          <span class="value">{{ parseTime(form.createTime) }}</span>
+        </div>
+        <div class="detail-item full-width">
+          <span class="label">申诉理由:</span>
+          <div class="reason-content">{{ form.reason }}</div>
+        </div>
+        <div class="detail-item full-width">
+          <span class="label">图片展示:</span>
+          <div class="photo-list" v-if="form.photoUrls">
+            <image-preview v-for="(url, index) in form.photoUrls.split(',')" :key="index" :src="url" :width="120"
+              :height="120" style="margin-right: 10px; margin-bottom: 10px;" />
+          </div>
+        </div>
         <div class="detail-item">
-          <span class="label">履约佣金:</span>
-          <span class="value highlighting">{{ (form.fulfillmentCommission / 100).toFixed(2) }} 元</span>
+          <span class="label">审核状态:</span>
+          <span class="value">
+            <el-tag v-if="form.auditStatus !== undefined"
+              :type="subOrderAppealDict.AuditStatus[form.auditStatus]?.tagType || 'info'">
+              {{ subOrderAppealDict.AuditStatus[form.auditStatus]?.label || form.auditStatus }}
+            </el-tag>
+          </span>
+        </div>
+        <div class="detail-item">
+          <span class="label">审核人:</span>
+          <span class="value">{{ form.auditorName || '-' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">审核时间:</span>
+          <span class="value">{{ parseTime(form.auditTime) || '-' }}</span>
+        </div>
+        <div class="detail-item full-width" v-if="form.rejectReason">
+          <span class="label">驳回理由:</span>
+          <div class="reason-content">{{ form.rejectReason }}</div>
         </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="openDetail = false">关 闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 删除确认详情展示 -->
+    <el-dialog title="确认删除申诉" v-model="openView" width="600px" append-to-body custom-class="detail-dialog">
+      <div class="detail-container">
+        <div class="detail-item">
+          <span class="label">订单号:</span>
+          <span class="value">{{ form.orderCode }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">实际服务:</span>
+          <span class="value">{{ form.serviceName }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">履约者:</span>
+          <span class="value">{{ form.fulfillerName }}</span>
+        </div>
+
         <div class="detail-item full-width">
           <span class="label">提交时间:</span>
           <span class="value">{{ parseTime(form.createTime) }}</span>
@@ -154,22 +209,35 @@
     </el-dialog>
 
     <!-- 审核对话框 -->
-    <el-dialog title="审核操作" v-model="openAudit" width="500px" append-to-body>
-      <el-form :model="auditForm" :rules="auditRules" ref="auditRef" label-width="100px">
-        <el-form-item label="审核结果" prop="result">
-          <el-radio-group v-model="auditForm.result">
-            <el-radio :label="1">通过</el-radio>
-            <el-radio :label="2">驳回</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="驳回原因" prop="reason" v-if="auditForm.result === 2">
-          <el-input v-model="auditForm.reason" type="textarea" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
+    <el-dialog title="申诉审核处理" v-model="openAudit" width="500px" append-to-body class="audit-dialog">
+      <div class="audit-content">
+        <el-form :model="auditForm" :rules="auditRules" ref="auditRef" label-position="top">
+          <el-form-item label="审核结果判定" prop="result">
+            <el-radio-group v-model="auditForm.result" class="full-width-radio">
+              <el-radio-button :label="1">
+                <el-icon>
+                  <CircleCheck />
+                </el-icon> 审核通过
+              </el-radio-button>
+              <el-radio-button :label="2">
+                <el-icon>
+                  <CircleClose />
+                </el-icon> 驳回申请
+              </el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+
+          <transition name="el-zoom-in-top">
+            <el-form-item label="驳回原因说明" prop="reason" v-if="auditForm.result === 2">
+              <el-input v-model="auditForm.reason" type="textarea" :rows="4" placeholder="请详细说明驳回该申请的具体原因..." />
+            </el-form-item>
+          </transition>
+        </el-form>
+      </div>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitAudit">确 定</el-button>
-          <el-button @click="openAudit = false">取 消</el-button>
+          <el-button type="primary" size="large" @click="submitAudit" class="submit-btn">确认提交审核</el-button>
+          <el-button size="large" @click="openAudit = false">取 消</el-button>
         </div>
       </template>
     </el-dialog>
@@ -177,12 +245,14 @@
 </template>
 
 <script setup name="SubOrderAppeal">
-import { listSubOrderAppeal, delSubOrderAppeal, auditSubOrderAppeal } from "@/api/order/subOrderAppeal";
+import { listSubOrderAppeal, delSubOrderAppeal, auditSubOrderAppeal, getSubOrderAppeal } from "@/api/order/subOrderAppeal";
 import { listAllService } from "@/api/service/list";
 import { parseTime } from "@/utils/ruoyi";
 import { checkPermi } from "@/utils/permission";
 import subOrderAppealDict from "@/json/subOrderAppeal.json";
+import { useRoute } from "vue-router";
 
+const route = useRoute();
 const { proxy } = getCurrentInstance();
 
 const subOrderAppealList = ref([]);
@@ -190,6 +260,7 @@ const loading = ref(true);
 const showSearch = ref(true);
 const total = ref(0);
 const openView = ref(false);
+const openDetail = ref(false);
 const openAudit = ref(false);
 const serviceOptions = ref([]);
 
@@ -250,12 +321,18 @@ function handleQuery() {
   getList();
 }
 
-/** 重置按钮操作 */
+/** 重置查询操作 */
 function resetQuery() {
   proxy.resetForm("queryRef");
   handleQuery();
 }
 
+/** 详情按钮操作 */
+function handleDetail(row) {
+  openDetail.value = true;
+  form.value = { ...row };
+}
+
 /** 删除获取详情操作 */
 function handleDelete(row) {
   openView.value = true;
@@ -299,6 +376,22 @@ function submitAudit() {
 
 // 初始化加载
 getServiceList();
+
+// 处理从消息跳转过来的情况
+onMounted(() => {
+  const id = route.query.id;
+  if (id) {
+    getSubOrderAppeal(id).then(response => {
+      const data = response.data;
+      const serviceObj = serviceOptions.value.find(s => s.id === data.service);
+      form.value = {
+        ...data,
+        serviceName: serviceObj ? serviceObj.name : data.service
+      };
+      openDetail.value = true;
+    });
+  }
+});
 </script>
 
 <style lang="scss" scoped>
@@ -413,6 +506,78 @@ getServiceList();
 
 :deep(.dialog-footer) {
   text-align: center;
-  padding-top: 20px;
+  padding-top: 10px;
+  padding-bottom: 10px;
+}
+
+.audit-dialog {
+  :deep(.el-dialog__header) {
+    margin-right: 0;
+    padding: 20px 25px;
+    border-bottom: 1px solid #f0f0f0;
+
+    .el-dialog__title {
+      font-weight: bold;
+      color: #303133;
+    }
+  }
+
+  .audit-content {
+    padding: 10px 15px;
+  }
+
+  .full-width-radio {
+    width: 100%;
+    display: flex;
+
+    :deep(.el-radio-button) {
+      flex: 1;
+
+      .el-radio-button__inner {
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 6px;
+        height: 45px;
+        font-size: 15px;
+      }
+    }
+  }
+
+  .full-width-input {
+    width: 100%;
+
+    :deep(.el-input__wrapper) {
+      height: 40px;
+    }
+  }
+
+  .input-tip {
+    font-size: 12px;
+    color: #909399;
+    margin-top: 8px;
+    line-height: 1.4;
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: bold;
+    padding-bottom: 8px;
+  }
+
+  .submit-btn {
+    width: 160px;
+    height: 45px;
+    font-size: 16px;
+    font-weight: bold;
+    background: linear-gradient(90deg, #409eff 0%, #66b1ff 100%);
+    border: none;
+    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+
+    &:hover {
+      opacity: 0.9;
+      transform: translateY(-1px);
+    }
+  }
 }
 </style>

+ 158 - 66
src/views/order/orderList/components/OrderDetailDrawer.vue

@@ -15,14 +15,20 @@
                 <div class="right-head">
                     <!-- Action Buttons Group -->
                     <div class="detail-actions">
-                        <template v-if="[0, 1, 2, 3].includes(order.status)">
-                            <el-button type="success" icon="Bicycle" @click="emit('dispatch', order)">
-                                {{ order.fulfiller || order.fulfillerName ? '重新派单' : '立即派单' }}
+                        <template v-if="![4, 7].includes(order.status)">
+                            <el-button v-hasPermi="['order:orderList:dispatch']" v-if="order.status === 0"
+                                type="success" icon="Bicycle" @click="emit('dispatch', order)">
+                                立即派单
+                            </el-button>
+                            <el-button v-hasPermi="['order:orderList:redispatch']"
+                                v-if="![0, 4, 7].includes(order.status)" type="warning" icon="Bicycle"
+                                @click="emit('dispatch', order)">
+                                重新派单
                             </el-button>
                         </template>
 
-                        <template v-if="order.status === 0">
-                            <el-button type="danger" plain icon="CircleClose"
+                        <template v-if="[0, 1].includes(order.status)">
+                            <el-button v-hasPermi="['order:orderList:cancel']" type="danger" plain icon="CircleClose"
                                 @click="emit('cancel', order)">取消订单</el-button>
                         </template>
 
@@ -32,17 +38,20 @@
                         </template>
 
                         <template v-if="[3, 4].includes(order.status) && order.mode == 0">
-                            <el-button icon="Notebook" @click="emit('care-summary', order)">护理小结</el-button>
+                            <el-button v-hasPermi="['order:orderList:nursingSummary']" icon="Notebook"
+                                @click="emit('care-summary', order)">护理小结</el-button>
                         </template>
 
-                        <el-dropdown trigger="click" @command="(cmd) => emit('command', cmd, order)"
-                            style="margin-left: 12px;">
+                        <el-dropdown v-if="![7].includes(order.status)" trigger="click"
+                            @command="(cmd) => emit('command', cmd, order)" style="margin-left: 12px;">
                             <el-button icon="More">更多操作</el-button>
                             <template #dropdown>
                                 <el-dropdown-menu>
-                                    <el-dropdown-item command="reward" icon="Trophy">奖惩操作</el-dropdown-item>
-                                    <el-dropdown-item command="remark" icon="EditPen">订单备注</el-dropdown-item>
-                                    <el-dropdown-item command="delete" v-if="[4, 5].includes(order.status)" divided
+                                    <el-dropdown-item v-if="[3, 4].includes(order.status)" command="reward"
+                                        icon="Trophy">奖惩操作</el-dropdown-item>
+                                    <el-dropdown-item v-if="![7].includes(order.status)" command="remark"
+                                        icon="EditPen">订单备注</el-dropdown-item>
+                                    <el-dropdown-item command="delete" v-if="[4, 5, 6].includes(order.status)" divided
                                         icon="Delete" style="color: #f56c6c;">删除订单</el-dropdown-item>
                                 </el-dropdown-menu>
                             </template>
@@ -213,15 +222,14 @@
                                             '普通履约者' }}</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>
                                     </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;">
-                                        <span><span style="color:#909399;">指派时间:</span>{{ order.createTime }}</span>
-                                        <span><span style="color:#909399;">接单时间:</span>{{ order.detail?.receiveTime ||
-                                            order.serviceTime }}</span>
+                                        <span><span style="color:#909399;">指派时间:</span>{{ dispatchTime }}</span>
+                                        <span><span style="color:#909399;">接单时间:</span>{{ acceptTime }}</span>
                                     </div>
                                 </div>
                             </div>
@@ -312,6 +320,44 @@
                                     <div class="log-card">
                                         <div class="l-tit">履约者:{{ complaint.fulfiller }}</div>
                                         <div class="l-txt">{{ complaint.reason }}</div>
+                                        <div class="l-txt" v-if="complaint.photoUrls">
+                                            <span>图片:</span>
+                                            <div style="display: flex; gap: 5px; flex-wrap: wrap; margin-top: 5px;">
+                                                <ImagePreview v-for="(url, imgIndex) in complaint.photoUrls.split(',')"
+                                                    :key="imgIndex" :src="url" :width="40" :height="40" />
+                                            </div>
+                                        </div>
+                                    </div>
+                                </el-timeline-item>
+                            </el-timeline>
+                        </div>
+                    </el-tab-pane>
+
+                    <!-- Tab 6: Service Change Records -->
+                    <el-tab-pane label="服务变更记录" name="serviceChanges">
+                        <div class="tab-pane-content">
+                            <div v-if="serviceChangeList.length === 0" class="empty-state">
+                                <el-result icon="success" title="暂无服务变更" sub-title="该订单暂无服务变更记录"></el-result>
+                            </div>
+                            <el-timeline v-else>
+                                <el-timeline-item v-for="(change, index) in serviceChangeList" :key="index"
+                                    :timestamp="change.createTime" placement="top" color="#409eff">
+                                    <div class="log-card">
+                                        <div class="l-tit">服务变更 - {{ change.service }}</div>
+                                        <div class="l-txt">申诉理由:{{ change.reason }}</div>
+                                        <div class="l-txt" v-if="change.auditStatus === 1" style="color:#67c23a;">
+                                            审核状态:已通过</div>
+                                        <div class="l-txt" v-else-if="change.auditStatus === 2" style="color:#f56c6c;">
+                                            审核状态:已驳回
+                                        </div>
+                                        <div class="l-txt" v-else style="color:#e6a23c;">审核状态:待审核</div>
+                                        <div class="l-txt" v-if="change.photoUrls">
+                                            <span>图片:</span>
+                                            <div style="display: flex; gap: 5px; flex-wrap: wrap; margin-top: 5px;">
+                                                <ImagePreview v-for="(url, imgIndex) in change.photoUrls.split(',')"
+                                                    :key="imgIndex" :src="url" :width="40" :height="40" />
+                                            </div>
+                                        </div>
                                     </div>
                                 </el-timeline-item>
                             </el-timeline>
@@ -358,7 +404,7 @@
                     style="font-size: 20px; font-weight: bold; color: #303133; display: flex; align-items: center; gap: 12px;">
                     <span
                         style="font-size: 14px; padding: 4px 10px; border-radius: 4px; background: #409eff; color: #fff;">{{
-                        getStatusName(order?.status) }}</span>
+                            getStatusName(order?.status) }}</span>
                     <span>服务流程:{{ order?.orderNo || order?.code }}</span>
                 </div>
                 <div style="margin-top: 10px; font-size: 13px; color: #909399;">
@@ -407,9 +453,7 @@
                                 <div v-for="(item, i) in step.media" :key="i">
                                     <!-- 图片:用预加载的 base64 -->
                                     <img v-if="item.type === 'image' && captureBase64Cache[item.url]"
-                                        :src="captureBase64Cache[item.url]"
-                                        decoding="sync"
-                                        loading="eager"
+                                        :src="captureBase64Cache[item.url]" decoding="sync" loading="eager"
                                         style="width: 140px; height: 140px; border-radius: 6px; border: 1px solid #e4e7ed; object-fit: cover; display: block;" />
                                     <!-- 占位白块(当加载失败或还未加载完时) -->
                                     <div v-else-if="item.type === 'image'"
@@ -447,7 +491,9 @@ import { getSubOrderInfo } from '@/api/order/subOrder/index'
 import { getFulfiller } from '@/api/fulfiller/fulfiller/index'
 import { listSubOrderLog, exportSubOrderLogUrl } from '@/api/order/subOrderLog/index'
 import { listComplaintByOrder } from '@/api/fulfiller/complaint'
+import { listSubOrderAppealByOrderId } from '@/api/order/subOrderAppeal'
 import { listByIds, downloadOssBlob } from '@/api/system/oss'
+import ImagePreview from '@/components/ImagePreview/index.vue'
 
 const { proxy } = getCurrentInstance()
 
@@ -489,6 +535,7 @@ const loadSeq = ref(0)
 const orderLogs = ref([])
 const fulfillerLogs = ref([])
 const complaintList = ref([])
+const serviceChangeList = ref([])
 
 const loadOrderLogs = async (order) => {
     const id = order?.id
@@ -496,6 +543,7 @@ const loadOrderLogs = async (order) => {
         orderLogs.value = []
         fulfillerLogs.value = []
         complaintList.value = []
+        serviceChangeList.value = []
         return
     }
     try {
@@ -532,6 +580,13 @@ const loadOrderLogs = async (order) => {
     } catch {
         complaintList.value = []
     }
+
+    try {
+        const serviceChangeRes = await listSubOrderAppealByOrderId(id)
+        serviceChangeList.value = serviceChangeRes?.data || []
+    } catch {
+        serviceChangeList.value = []
+    }
 }
 
 const loadPetAndCustomer = async (order) => {
@@ -612,6 +667,7 @@ const loadPetAndCustomer = async (order) => {
             console.log('Web管理端 - 履约者详情:', fulfiller)
             if (fulfiller) {
                 next.fulfillerAvatar = fulfiller.avatarUrl || fulfiller.avatar || next.fulfillerAvatar
+                next.fulfillerPhone = fulfiller.phone ?? next.fulfillerPhone
                 next.fulfillerStation = fulfiller.stationName ?? next.fulfillerStation
                 next.fulfillerLevelName = fulfiller.levelName ?? next.fulfillerLevelName
             }
@@ -637,11 +693,11 @@ watch(() => props.order, (val) => {
 const activeDetailTab = ref('basic')
 
 const getStatusName = (status) => {
-    const map = { 0: '待派单', 1: '待接单', 2: '待服务', 3: '服务中', 4: '已完成', 5: '已取消' }
+    const map = { 0: '待派单', 1: '待接单', 2: '待服务', 3: '服务中', 4: '已完成', 5: '已取消', 6: '已拒绝', 7: '已关闭' }
     return map[status] || '未知'
 }
 const getStatusTag = (status) => {
-    const map = { 0: 'danger', 1: 'warning', 2: 'primary', 3: 'primary', 4: 'success', 5: 'info' }
+    const map = { 0: 'danger', 1: 'warning', 2: 'primary', 3: 'primary', 4: 'success', 5: 'info', 6: 'danger', 7: 'info' }
     return map[status] || 'info'
 }
 const getTypeName = (type) => {
@@ -653,6 +709,11 @@ const getTransportModeName = (type) => {
     return map[type] || '接送服务'
 }
 
+const previewImage = (url, urls) => {
+    // 直接在新窗口打开图片
+    window.open(url, '_blank')
+}
+
 const getTransportLabel = (t) => {
     if (t === 0 || t === '0') return '接'
     if (t === 1 || t === '1') return '送'
@@ -677,61 +738,92 @@ const getServiceTimeRange = (timeStr) => {
 
 const currentOrderSteps = computed(() => {
     if (!props.order) return { active: 0, steps: [] }
-    const steps = [
-        { title: '商户下单', status: 'created', time: '' },
-        { title: '运营派单', status: 'dispatched', time: '' },
-        { title: '履约接单', status: 'accepted', time: '' },
-        { title: '服务中', status: 'serving', time: '' },
-        { title: '已完成', status: 'completed', time: '' }
-    ]
-    const logs = orderLogs.value || []
     const status = props.order.status
-    let active = 0
+    const logs = orderLogs.value || []
     const findTime = (keyword) => {
         const log = logs.find(l => l.title.includes(keyword) || l.content.includes(keyword))
         return log ? log.time : ''
     }
-    steps[0].time = props.order.createTime || findTime('下单') || findTime('创建')
-    if (steps[0].time) active = 1
-    if ([0].includes(status)) {
-        steps[1].time = findTime('派单') || steps[0].time
-    } else {
-        steps[1].time = findTime('派单') || ''
-    }
-    if ([1, 2, 3, 4].includes(status)) active = 2
-    steps[2].time = findTime('接单')
-    if ([1].includes(status)) {
-        steps[2].title = '待履约者接单'
-    } else if ([2, 3, 4].includes(status)) {
-        steps[2].title = '履约者已接单'
-        active = 3
-    }
-    steps[3].time = findTime('到达') || findTime('出发')
-    if ([2].includes(status)) {
-        steps[3].title = '待服务'
-    } else if ([3].includes(status)) {
-        steps[3].title = '服务进行中'
-        active = 4
-    } else if ([4].includes(status)) {
-        steps[3].title = '服务已完成'
-        active = 4
-    }
-    if (status === 4) {
-        steps[4].time = findTime('完成')
-        active = 5
-    }
+
+    // 已取消 — 特殊两步流程
     if (status === 5) {
+        const createTime = props.order.createTime || findTime('下单') || findTime('创建')
         return {
             active: 1,
             steps: [
-                { title: '商户下单', time: steps[0].time },
+                { title: '商户下单', time: createTime },
                 { title: '已取消', time: findTime('取消') || '订单已取消' }
             ]
         }
     }
+
+    // 已拒绝 — 特殊两步流程
+    if (status === 6) {
+        const createTime = props.order.createTime || findTime('下单') || findTime('创建')
+        return {
+            active: 1,
+            steps: [
+                { title: '商户下单', time: createTime },
+                { title: '已拒绝', time: findTime('拒绝') || '订单已拒绝' }
+            ]
+        }
+    }
+
+    // 已关闭 — 特殊两步流程
+    if (status === 7) {
+        const createTime = props.order.createTime || findTime('下单') || findTime('创建')
+        return {
+            active: 1,
+            steps: [
+                { title: '商户下单', time: createTime },
+                { title: '已关闭', time: findTime('关闭') || '订单已关闭' }
+            ]
+        }
+    }
+
+    // 六步流程:商户下单 → 运营派单 → 履约接单 → 等待服务 → 服务进行 → 订单完成
+    const steps = [
+        { title: '商户下单', time: props.order.createTime || findTime('下单') || findTime('创建') },
+        { title: '运营派单', time: findTime('派单') || '' },
+        { title: '履约接单', time: findTime('接单') || '' },
+        { title: '等待服务', time: findTime('到达') || '' },
+        { title: '服务进行', time: findTime('完成') || '' },
+        { title: '订单完成', time: findTime('完成') || '' }
+    ]
+
+    let active = 1 // 默认停在「运营派单」
+
+    switch (status) {
+        case 0:
+            active = 1
+            break
+        case 1:
+            active = 2
+            steps[2].title = '等待接单'
+            break
+        case 2:
+            active = 3
+            steps[2].title = '已确认接'
+            break
+        case 3:
+            active = 4
+            steps[2].title = '已确认接'
+            steps[3].title = '已到达点'
+            break
+        case 4:
+            active = 6
+            steps[2].title = '已确认接'
+            steps[3].title = '已到达点'
+            break
+    }
+
     return { active, steps }
 })
 
+// 从订单日志中取派单/接单时间 @Author: Antigravity
+const dispatchTime = computed(() => orderLogs.value.find(l => l.title === '系统派单')?.createTime || '-')
+const acceptTime = computed(() => orderLogs.value.find(l => l.title === '接单成功')?.createTime || '-')
+
 const serviceProgressSteps = computed(() => {
     const list = fulfillerLogs.value || []
     return list.map((i) => {
@@ -793,21 +885,21 @@ const loadImageAsBase64 = async (item) => {
             const response = await fetch(url);
             blob = await response.blob();
         }
-        
+
         if (!blob || blob.size === 0) {
             console.warn('[FlowChart] 下载到的 Blob 为空:', url || ossId);
             return '';
         }
-        
+
         console.log(`[FlowChart] 成功获取 Blob: 尺寸=${blob.size}, 类型=${blob.type}, ID=${ossId || 'N/A'}`);
-        
+
         // 如果是报错返回的 JSON
         if (blob.type === 'application/json') {
             const text = await blob.text();
             console.error('[FlowChart] 图片下载返回了错误 JSON:', text);
             return '';
         }
-        
+
         return new Promise((resolve) => {
             const reader = new FileReader();
             reader.onloadend = () => {
@@ -873,7 +965,7 @@ const handleExportProgressImage = async () => {
                 return { url: item.url, b64 };
             })
         );
-        
+
         // 批量更新缓存,确保触发响应式
         const newCache = { ...captureBase64Cache.value };
         results.forEach(({ url, b64 }) => {
@@ -885,7 +977,7 @@ const handleExportProgressImage = async () => {
 
     // 等待 Vue DOM 更新
     await nextTick()
-    
+
     // 获取截图区域并等待内部所有图片 load 完成
     const el = document.getElementById('order-process-capture-area')
     if (!el) {

+ 68 - 32
src/views/order/orderList/index.vue

@@ -15,7 +15,8 @@
             <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 type="success" icon="Download" @click="handleExport" v-hasPermi="['order:orderList:export']">导出Excel</el-button>
+            <el-button type="success" icon="Download" @click="handleExport"
+              v-hasPermi="['order:orderList:export']">导出Excel</el-button>
           </div>
         </div>
 
@@ -27,6 +28,8 @@
           <el-tab-pane label="服务中" name="3" />
           <el-tab-pane label="已完成" name="4" />
           <el-tab-pane label="已取消" name="5" />
+          <el-tab-pane label="已拒绝" name="6" />
+          <el-tab-pane label="已关闭" name="7" />
         </el-tabs>
       </template>
 
@@ -125,7 +128,9 @@
         <el-table-column label="履约电话" width="130">
           <template #default="{ row }">
             <div v-if="row.fulfillerPhone" style="display: flex; align-items: center; gap: 4px;">
-              <el-icon><Phone /></el-icon>
+              <el-icon>
+                <Phone />
+              </el-icon>
               <span>{{ row.fulfillerPhone }}</span>
             </div>
             <span v-else>-</span>
@@ -142,17 +147,21 @@
           </template>
         </el-table-column>
 
-        <el-table-column label="操作" width="200" fixed="right">
+        <el-table-column label="操作" width="240" fixed="right">
           <template #default="{ row }">
             <div class="op-cell">
-              <el-button link type="primary" size="small" @click="handleDetail(row)" v-hasPermi="['order:orderList:query']">详情</el-button>
-              <el-button v-if="row.serviceFlag === false" link type="success" size="small" @click="openActivateDialog(row)" v-hasPermi="['order:orderList:activate']">开通服务</el-button>
-              <el-button v-if="row.status === 0" link type="success" size="small"
-                @click="openDispatchDialog(row)" v-hasPermi="['order:orderList:dispatch']">派单</el-button>
-              <el-button v-if="![0, 4].includes(row.status)" link type="warning" size="small"
+              <el-button link type="primary" size="small" @click="handleDetail(row)"
+                v-hasPermi="['order:orderList:query']">详情</el-button>
+              <el-button v-if="row.serviceFlag === 2 && row.status !== 7" link type="success" size="small"
+                @click="openActivateDialog(row)" v-hasPermi="['order:orderList:activate']">开通服务</el-button>
+              <el-button v-if="row.status === 0" link type="success" size="small" @click="openDispatchDialog(row)"
+                v-hasPermi="['order:orderList:dispatch']">派单</el-button>
+              <el-button v-if="![0, 4, 7].includes(row.status)" link type="warning" size="small"
                 @click="openDispatchDialog(row)" v-hasPermi="['order:orderList:redispatch']">重新派单</el-button>
-              <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small"
-                @click="handleCancel(row)" v-hasPermi="['order:orderList:cancel']">取消</el-button>
+              <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small" @click="handleCancel(row)"
+                v-hasPermi="['order:orderList:cancel']">取消</el-button>
+              <el-button v-if="![4, 5, 6, 7].includes(row.status)" link type="info" size="small"
+                @click="handleClose(row)" v-hasPermi="['order:orderList:close']">关闭</el-button>
 
               <el-dropdown v-if="[3, 4].includes(row.status)" trigger="click"
                 @command="(cmd) => handleCommand(cmd, row)">
@@ -163,8 +172,8 @@
                 </span>
                 <template #dropdown>
                   <el-dropdown-menu>
-                    <el-dropdown-item v-if="row.status === 4 && getServiceMode(row.service) == 0"
-                      command="care_summary" v-hasPermi="['order:orderList:nursingSummary']">护理小结</el-dropdown-item>
+                    <el-dropdown-item v-if="row.status === 4 && getServiceMode(row.service) == 0" command="care_summary"
+                      v-hasPermi="['order:orderList:nursingSummary']">护理小结</el-dropdown-item>
                     <el-dropdown-item command="reward" v-hasPermi="['order:orderList:reward']">奖惩</el-dropdown-item>
                     <el-dropdown-item command="remark" v-hasPermi="['order:orderList:remark']">备注</el-dropdown-item>
                   </el-dropdown-menu>
@@ -186,8 +195,7 @@
     <OrderDetailDrawer v-model:visible="detailVisible" :order="currentOrder" @dispatch="openDispatchDialog"
       @cancel="handleCancel" @command="handleCommand" @care-summary="openCareSummary" />
 
-    <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder"
-      @success="handleSearch" />
+    <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder" @success="handleSearch" />
 
     <CareSummaryDrawer v-model:visible="careSummaryVisible" :order="careSummaryOrder" @submit="saveCareSummary" />
 
@@ -197,14 +205,14 @@
 
     <!-- 开通服务弹窗 -->
     <el-dialog v-model="activateDialogVisible" title="开通服务" width="400px" append-to-body>
-      <el-form :model="activateForm" label-width="80px">
-        <el-form-item label="新的服务" required>
-          <el-select v-model="activateForm.service" placeholder="请选择新的服务" style="width: 100%">
-            <el-option v-for="item in serviceOptions" :key="item.id" :label="item.name" :value="item.id" />
-          </el-select>
-        </el-form-item>
+      <el-form :model="activateForm" label-width="100px">
         <el-form-item label="履约佣金" required>
-          <el-input-number v-model="activateForm.fulfillmentCommission" :min="0" :precision="2" :step="1" placeholder="请输入履约佣金(元)" style="width: 100%" />
+          <el-input-number v-model="activateForm.fulfillmentCommission" :min="0" :precision="2" :step="1"
+            placeholder="请输入履约佣金(元)" style="width: 100%" />
+        </el-form-item>
+        <el-form-item label="服务说明">
+          <el-input v-model="activateForm.serviceSpecification" type="textarea" placeholder="请输入服务说明(选填)" :rows="3"
+            style="width: 100%" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -230,6 +238,7 @@ import { listAllService } from '@/api/service/list/index';
 import { listSubOrder } from '@/api/order/subOrder/index';
 import { getSubOrderInfo } from '@/api/order/subOrder/index';
 import { cancelSubOrder } from '@/api/order/subOrder/index';
+import { closeSubOrder } from '@/api/order/subOrder/index';
 import { remarkSubOrder } from '@/api/order/subOrder/index';
 import { confirmSubOrder } from '@/api/order/subOrder/index';
 import { nursingSummarySubOrder } from '@/api/order/subOrder/index';
@@ -269,7 +278,7 @@ onMounted(() => {
   getServiceList();
   getAreaStationList();
   handleSearch();
-  
+
   if (route.query.id) {
     handleDetail({ id: route.query.id as any });
   }
@@ -412,13 +421,13 @@ const getServiceOrderTypeTag = (row) => {
 };
 
 const getStatusName = (status) => {
-  const map = { 0: '待派单', 1: '待接单', 2: '待服务', 3: '服务中', 4: '已完成', 5: '已取消' };
+  const map = { 0: '待派单', 1: '待接单', 2: '待服务', 3: '服务中', 4: '已完成', 5: '已取消', 6: '已拒绝', 7: '已关闭' };
   return map[status] || '未知';
 };
 
 const getStatusClass = (status) => {
-  const map = { 0: 'pending_dispatch', 1: 'pending_accept', 2: 'pending_service', 3: 'in_service', 4: 'completed', 5: 'cancelled' };
-  return map[status] || 'pending_dispatch';
+  const map = { 0: 'pending', 1: 'waiting', 2: 'waiting', 3: 'processing', 4: 'completed', 5: 'cancelled', 6: 'rejected', 7: 'closed' };
+  return map[status] || '';
 };
 
 const getFulfillerStatusText = (status) => {
@@ -458,28 +467,28 @@ const activateDialogVisible = ref(false);
 const activateLoading = ref(false);
 const activateForm = reactive({
   id: 0,
-  service: '',
-  fulfillmentCommission: 0
+  fulfillmentCommission: 0,
+  serviceSpecification: ''
 });
 
 const openActivateDialog = (row) => {
   activateForm.id = row.id;
-  activateForm.service = row.service;
   activateForm.fulfillmentCommission = row.fulfillmentCommission !== undefined && row.fulfillmentCommission !== null ? row.fulfillmentCommission / 100 : 0;
+  activateForm.serviceSpecification = '';
   activateDialogVisible.value = true;
 };
 
 const submitActivate = async () => {
-  if (!activateForm.service) {
-    ElMessage.warning('请选择服务');
+  if (!activateForm.fulfillmentCommission) {
+    ElMessage.warning('请填写履约佣金');
     return;
   }
   activateLoading.value = true;
   try {
     await activateSubOrder({
       id: activateForm.id,
-      service: activateForm.service,
-      fulfillmentCommission: Math.round(Number(activateForm.fulfillmentCommission) * 100)
+      fulfillmentCommission: Math.round(Number(activateForm.fulfillmentCommission) * 100),
+      serviceSpecification: activateForm.serviceSpecification || undefined
     });
     ElMessage.success('开通服务成功');
     activateDialogVisible.value = false;
@@ -611,6 +620,33 @@ const handleCancel = (row: any) => {
   });
 };
 
+const handleClose = (row: any) => {
+  const statusName = getStatusName(row.status);
+  ElMessageBox.confirm(
+    `<div style="line-height: 1.8;">
+      <p style="color: #f56c6c; font-weight: bold; margin-bottom: 10px;">⚠️ 一旦关闭,该订单将无法进行操作!</p>
+      <p><strong>订单号:</strong>${row.code}</p>
+      <p><strong>服务类型:</strong>${getServiceName(row.service)}</p>
+      <p><strong>宠物名称:</strong>${row.petName || '-'}</p>
+      <p><strong>当前状态:</strong>${statusName}</p>
+    </div>`,
+    '确认关闭订单',
+    {
+      confirmButtonText: '确认关闭',
+      cancelButtonText: '取消',
+      type: 'warning',
+      dangerouslyUseHTMLString: true,
+    }
+  ).then(() => {
+    closeSubOrder({ orderId: row?.id }).then(() => {
+      ElMessage.success('订单已成功关闭');
+      handleSearch();
+    });
+  }).catch(() => {
+    // 用户取消
+  });
+};
+
 // 派单
 const openDispatchDialog = (row) => {
   currentDispatchOrder.value = row;

+ 9 - 11
src/views/order/purchase/index.vue

@@ -51,8 +51,9 @@
                 <div class="pet-select-row">
                   <div v-for="p in currentPets" :key="p.id" class="pet-card" :class="{ active: form.petId === p.id }"
                     @click="form.petId = p.id">
-                    <el-avatar :size="48" :src="p.avatarUrl || p.avatar" shape="square" style="border-radius: 6px;">{{ p.name.charAt(0)
-                    }}</el-avatar>
+                    <el-avatar :size="48" :src="p.avatarUrl || p.avatar" shape="square" style="border-radius: 6px;">{{
+                      p.name.charAt(0)
+                      }}</el-avatar>
                     <div class="pet-info">
                       <div class="name">{{ p.name }}</div>
                       <div class="sub">{{ p.breed }}</div>
@@ -117,7 +118,7 @@
                 </el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="订单佣金 (元)">
+                <el-form-item label="订单佣金 (元)" required>
                   <el-input-number v-model="form.orderCommission" :min="0" :precision="2" :step="1" placeholder="佣金金额"
                     style="width: 100%" />
                 </el-form-item>
@@ -127,16 +128,13 @@
             <div class="divider"></div>
 
             <!-- A. 宠物接送表单 -->
-            <TransportForm v-show="form.type === 'transport'" :transport-data="form.transport"
-              @change="calcPrice" />
+            <TransportForm v-show="form.type === 'transport'" :transport-data="form.transport" @change="calcPrice" />
 
             <!-- B. 上门喂遛表单 -->
-            <FeedingForm v-show="form.type === 'feeding'" :feeding-data="form.feeding"
-              @change="calcPrice" />
+            <FeedingForm v-show="form.type === 'feeding'" :feeding-data="form.feeding" @change="calcPrice" />
 
             <!-- C. 上门洗护表单 -->
-            <WashingForm v-show="form.type === 'washing'" :washing-data="form.washing"
-              @change="calcPrice" />
+            <WashingForm v-show="form.type === 'washing'" :washing-data="form.washing" @change="calcPrice" />
 
           </div>
         </el-card>
@@ -199,8 +197,7 @@
 
     <!-- Dialogs -->
     <!-- Add User Dialog -->
-    <AddUserDialog v-model:visible="userDialogVisible" :tenant-id="currentTenantId"
-      @success="handleUserSuccess" />
+    <AddUserDialog v-model:visible="userDialogVisible" :tenant-id="currentTenantId" @success="handleUserSuccess" />
     <AddPetDialog v-model:visible="petDialogVisible" :user-id="form.userId" :user-options="userOptions"
       @success="handlePetSuccess" />
 
@@ -607,6 +604,7 @@ const validateForm = () => {
   if (!form.userId) return '请选择宠主用户';
   if (!form.petId) return '请选择宠物';
   if (!form.serviceId) return '请选择服务项目';
+  if (!form.orderCommission || form.orderCommission <= 0) return '请填写订单佣金且不能为0';
 
   // 2. 业务详情校验
   if (form.type === 'transport') {

+ 79 - 7
src/views/service/list/index.vue

@@ -74,7 +74,7 @@
     </el-card>
 
     <!-- 添加或修改服务项目对话框 -->
-    <el-dialog :title="dialog.title" v-model="dialog.visible" width="650px" append-to-body>
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body class="service-dialog">
       <el-form ref="serviceFormRef" :model="form" :rules="rules" label-width="100px">
         <el-form-item label="服务名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入服务名称" />
@@ -105,10 +105,10 @@
           <div style="color: #909399; font-size: 12px; margin-top: 4px;">单位:元</div>
         </el-form-item>
         <el-form-item label="服务介绍" prop="introduction">
-          <editor v-model="form.introduction" :min-height="300" />
+          <editor v-model="form.introduction" :min-height="200" />
         </el-form-item>
         <el-form-item label="下单须知" prop="orderInstruction">
-          <editor v-model="form.orderInstruction" :min-height="300" />
+          <editor v-model="form.orderInstruction" :min-height="200" />
         </el-form-item>
         <el-form-item label="备注说明" prop="remark">
           <el-input v-model="form.remark" placeholder="请输入备注说明" type="textarea" :rows="2" />
@@ -142,6 +142,10 @@
 </template>
 
 <script setup name="Service" lang="ts">
+/**
+ * @Author: Antigravity
+ */
+
 import { listService, getService, delService, addService, updateService } from '@/api/service/list';
 import { ServiceVO, ServiceQuery, ServiceForm } from '@/api/service/list/types';
 import { list as listMode } from '@/api/service/mode';
@@ -198,7 +202,13 @@ const data = reactive<PageData<ServiceForm, ServiceQuery>>({
   rules: {
     name: [{ required: true, message: '服务名称不能为空', trigger: 'blur' }],
     icon: [{ required: true, message: '服务图标不能为空', trigger: 'blur' }],
-    mode: [{ required: true, message: '服务模式不能为空', trigger: 'change' }]
+    mode: [{ required: true, message: '服务模式不能为空', trigger: 'change' }],
+    classificationId: [{ required: true, message: '请选择所属分类', trigger: 'change' }],
+    sort: [{ required: true, message: '排序权重不能为空', trigger: 'blur' }],
+    price: [{ required: true, message: '订单服务费不能为空', trigger: 'blur' }],
+    introduction: [{ required: true, message: '服务介绍不能为空', trigger: 'blur' }],
+    orderInstruction: [{ required: true, message: '下单须知不能为空', trigger: 'blur' }],
+    remark: [{ required: true, message: '备注说明不能为空', trigger: 'blur' }]
   }
 });
 
@@ -445,9 +455,16 @@ onMounted(() => {
 }
 
 .step-card {
-  margin-bottom: 12px;
-  background-color: #f8f9fb;
-  border: 1px solid #ebeef5;
+  margin-bottom: 16px;
+  border-radius: 8px;
+  background-color: #fcfdfe;
+  border: 1px dashed #dcdfe6;
+  transition: all 0.3s;
+
+  &:hover {
+    border-style: solid;
+    border-color: var(--el-color-primary);
+  }
 
   .step-header {
     font-size: 13px;
@@ -480,4 +497,59 @@ onMounted(() => {
     padding: 12px 0;
   }
 }
+
+/* 优化富文本编辑器容器样式 */
+:deep(.editor-container) {
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  overflow: hidden;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+
+  &:hover {
+    border-color: var(--el-color-primary);
+    box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
+  }
+
+  .w-e-toolbar {
+    border-bottom: 1px solid #f2f6fc !important;
+    background-color: #fafafa !important;
+    padding: 2px 4px !important;
+    border-top: none !important;
+    border-left: none !important;
+    border-right: none !important;
+  }
+
+  .w-e-text-container {
+    background-color: #fff !important;
+    border: none !important;
+
+    .w-e-text {
+      padding: 16px !important;
+      line-height: 1.6 !important;
+      font-size: 14px;
+      color: #333;
+    }
+  }
+}
+
+/* 优化弹窗内部布局 */
+.service-dialog {
+  :deep(.el-dialog__body) {
+    padding: 20px 30px;
+    max-height: 65vh;
+    overflow-y: auto;
+  }
+}
+
+/* 优化表单项间距 */
+:deep(.el-form-item) {
+  margin-bottom: 22px;
+  
+  .el-form-item__label {
+    font-weight: 500;
+  }
+}
 </style>

+ 1 - 1
vite.config.ts

@@ -20,7 +20,7 @@ export default defineConfig(({ mode, command }) => {
     plugins: createPlugins(env, command === 'build'),
     server: {
       host: '0.0.0.0',
-      allowedHosts: ['admin.cwxtadmin.cn', 'user.cwxtadmin.cn'],
+      allowedHosts: ['admin.admin.cwxt.cn', 'linting.admin.cwxt.cn'],
       port: Number(env.VITE_APP_PORT),
       // open: true,
       proxy: {