Przeglądaj źródła

Merge branch 'master' of http://8.152.4.3:3000/yp_other/pet-system-admin-web

steelwei 1 miesiąc temu
rodzic
commit
abe347d29b

+ 1 - 0
src/api/system/user/types.ts

@@ -37,6 +37,7 @@ export interface UserVO extends BaseEntity {
   phonenumber: string;
   sex: string;
   avatar: string;
+  avatarUrl: string;
   status: string;
   delFlag: string;
   loginIp: string;

+ 1 - 1
src/store/modules/user.ts

@@ -38,7 +38,7 @@ export const useUserStore = defineStore('user', () => {
     if (res) {
       const data = res.data;
       const user = data.user;
-      const profile = user.avatar == '' || user.avatar == null ? defAva : user.avatar;
+      const profile = user.avatarUrl == '' || user.avatarUrl == null ? defAva : user.avatarUrl;
 
       if (data.roles && data.roles.length > 0) {
         // 验证返回的roles是否是一个非空数组

+ 1543 - 0
src/views/order/dispatch/index.vue

@@ -0,0 +1,1543 @@
+<template>
+  <div class="dispatch-container">
+    <!-- Top Filter Bar -->
+    <div class="top-filter-bar glass-panel-sm">
+      <div class="filter-left">
+        <el-radio-group v-model="filters.orderType" size="default" fill="#2d8cf0">
+          <el-radio-button label="all">全部</el-radio-button>
+          <el-radio-button label="transport">宠物接送</el-radio-button>
+          <el-radio-button label="feeding">上门喂遛</el-radio-button>
+          <el-radio-button label="washing">上门洗护</el-radio-button>
+        </el-radio-group>
+      </div>
+      <div class="filter-right">
+        <el-select v-model="filters.city" placeholder="选择城市" style="width: 120px; margin-right: 10px">
+          <el-option label="北京市" value="beijing" />
+          <el-option label="上海市" value="shanghai" />
+        </el-select>
+        <el-select v-model="filters.station" placeholder="选择站点" style="width: 140px">
+          <el-option label="朝阳大悦城站" value="cy" />
+          <el-option label="国贸核心站" value="gm" />
+        </el-select>
+      </div>
+    </div>
+
+    <div class="main-content">
+      <!-- Left: Real Baidu Map Area -->
+      <div class="map-wrapper">
+        <div id="baidu-map" class="map-view"></div>
+
+        <!-- Bottom Left: Map Controls & Stats -->
+        <div class="map-controls-panel">
+          <div class="control-group">
+            <div class="c-btn" :class="{ active: activeMapFilter === 'all' }" @click="setMapFilter('all')">全部</div>
+            <div class="c-btn red" :class="{ active: activeMapFilter === 'merchants' }" @click="setMapFilter('merchants')">商家(16)</div>
+            <div class="c-btn green" :class="{ active: activeMapFilter === 'fulfillers' }" @click="setMapFilter('fulfillers')">履约者(12)</div>
+            <div class="c-btn blue" :class="{ active: activeMapFilter === 'orders' }" @click="setMapFilter('orders')">订单(5)</div>
+            <div class="c-btn gray">灰色表示离线</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Right: Dispatch Control Panel -->
+      <div class="right-panel">
+        <!-- 1. Order Management Section -->
+        <div class="panel-section order-mgmt">
+          <div class="sec-header">
+            <span class="tit">订单</span>
+            <!-- Right Aligned Tabs -->
+            <div class="header-right-tabs">
+              <span class="h-tab-item" :class="{ active: currentOrderTab === 'PendingDispatch' }" @click="currentOrderTab = 'PendingDispatch'">
+                <span class="txt">待派单</span>
+                <span class="num danger">{{ orderStats.pendingDispatch }}</span>
+              </span>
+              <span class="h-tab-item" :class="{ active: currentOrderTab === 'PendingAccept' }" @click="currentOrderTab = 'PendingAccept'">
+                <span class="txt">待接单</span>
+                <span class="num warning">{{ orderStats.pendingAccept }}</span>
+              </span>
+              <span class="h-tab-item" :class="{ active: currentOrderTab === 'Processing' }" @click="currentOrderTab = 'Processing'">
+                <span class="txt">进行中</span>
+                <span class="num primary">{{ orderStats.processing }}</span>
+              </span>
+            </div>
+          </div>
+
+          <!-- Order List -->
+          <div class="list-wrapper">
+            <el-scrollbar>
+              <div v-if="filteredOrders.length === 0" class="empty-state">暂无数据</div>
+              <div v-else v-for="order in filteredOrders" :key="order.id" class="list-card order-card" @click="focusMapPoint(order.lng, order.lat)">
+                <div class="card-left">
+                  <div class="type-tag" :class="order.typeCode">
+                    {{ getShortType(order.typeCode) }}
+                  </div>
+                </div>
+                <div class="card-main">
+                  <template v-if="order.typeCode === 'transport'">
+                    <div class="row-addr" :title="order.pickAddr"><span class="tag pick">取</span> {{ order.pickAddr }}</div>
+                    <div class="row-addr" :title="order.dropAddr"><span class="tag drop">送</span> {{ order.dropAddr }}</div>
+                    <div class="row-time">
+                      <el-icon><Clock /></el-icon> {{ order.time }}
+                      <span class="days-tag" v-if="order.daysLater">{{ order.daysLater }}</span>
+                    </div>
+                  </template>
+                  <template v-else>
+                    <div class="row-addr" :title="order.address"><span class="tag home">址</span> {{ order.address }}</div>
+                    <div class="row-time" style="margin-top: 4px">
+                      <el-icon><Clock /></el-icon> {{ order.time }}
+                      <span class="days-tag" v-if="order.daysLater">{{ order.daysLater }}</span>
+                    </div>
+                  </template>
+                </div>
+
+                <!-- Right: Status & Actions -->
+                <div class="card-right">
+                  <el-tag size="small" :type="getOrderStatusType(order.status)" effect="plain">{{ getOrderStatusText(order.status) }}</el-tag>
+                  <div class="actions">
+                    <el-button v-if="order.status === 'pending_dispatch'" type="primary" size="small" @click.stop="openDispatchDialog(order)"
+                      >派单</el-button
+                    >
+                    <el-button
+                      v-else-if="['pending_accept', 'processing'].includes(order.status)"
+                      type="primary"
+                      size="small"
+                      plain
+                      @click.stop="openDispatchDialog(order)"
+                      >重新派单</el-button
+                    >
+                  </div>
+                </div>
+              </div>
+            </el-scrollbar>
+          </div>
+        </div>
+
+        <!-- 2. Fulfiller Management Section -->
+        <div class="panel-section fulfiller-mgmt">
+          <div class="sec-header no-border">
+            <span class="tit">履约者</span>
+            <!-- Right Aligned Tabs -->
+            <div class="header-right-tabs">
+              <span class="h-tab-item" :class="{ active: currentRiderTab === 'All' }" @click="currentRiderTab = 'All'">
+                <span class="txt">全部</span>
+                <span class="num">12</span>
+              </span>
+              <span class="h-tab-item" :class="{ active: currentRiderTab === 'Working' }" @click="currentRiderTab = 'Working'">
+                <span class="txt">接单中</span>
+                <span class="num success">8</span>
+              </span>
+              <span class="h-tab-item" :class="{ active: currentRiderTab === 'Resting' }" @click="currentRiderTab = 'Resting'">
+                <span class="txt">休息中</span>
+                <span class="num info">3</span>
+              </span>
+              <span class="h-tab-item" :class="{ active: currentRiderTab === 'Disabled' }" @click="currentRiderTab = 'Disabled'">
+                <span class="txt">禁用</span>
+                <span class="num danger">1</span>
+              </span>
+            </div>
+          </div>
+
+          <!-- Rider List -->
+          <div class="list-wrapper">
+            <el-scrollbar>
+              <div v-for="rider in filteredRiders" :key="rider.id" class="list-card rider-card" @click="focusMapPoint(rider.lng, rider.lat)">
+                <div class="card-left relative">
+                  <el-avatar :src="rider.avatar" :size="40" />
+                  <div class="dot" :class="rider.status"></div>
+                </div>
+                <div class="card-main">
+                  <!-- Box 1: Name + Phone + Status (Right) -->
+                  <div class="row-1" style="justify-content: space-between; align-items: flex-start">
+                    <div style="display: flex; align-items: baseline; gap: 8px">
+                      <span class="r-name">{{ rider.name }}</span>
+                      <span class="r-phone">{{ rider.maskPhone }}</span>
+                    </div>
+                    <div class="status-right">
+                      <span class="status-badge" :class="rider.status">{{ getRiderStatusText(rider.status) }}</span>
+                    </div>
+                  </div>
+
+                  <!-- Box 2: Categories -->
+                  <div class="row-2 categories-row" style="margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap">
+                    <span v-for="cat in rider.categories" :key="cat" class="cat-tag" :class="getCategoryClass(cat)">{{ cat }}</span>
+                  </div>
+
+                  <!-- Box 3: Last Service Time -->
+                  <div class="row-3 time-row" style="margin-top: 4px">
+                    <span class="last-time"
+                      >下一单: {{ rider.status === 'offline' || rider.status === 'disabled' ? '--' : rider.lastServiceTime }}</span
+                    >
+                  </div>
+                </div>
+                <div class="card-right-stats">
+                  <div class="stat-box">
+                    <span class="lbl">待接</span>
+                    <span class="val danger">{{ rider.pendingCount }}</span>
+                  </div>
+                  <div class="stat-box">
+                    <span class="lbl">待服</span>
+                    <span class="val warning">{{ rider.todoCount }}</span>
+                  </div>
+                  <el-button link type="primary" size="small" style="margin-top: 4px; padding: 0" @click.stop="handleViewRiderOrders(rider)"
+                    >查看订单</el-button
+                  >
+                </div>
+              </div>
+            </el-scrollbar>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Rider Orders Dialog -->
+    <el-dialog v-model="riderOrdersVisible" title="履约者订单详情" width="1100px" top="6vh" custom-class="rider-orders-dialog">
+      <div class="dialog-content">
+        <!-- 1. Rider Summary Info -->
+        <div class="rider-summary" v-if="currentRiderInfo">
+          <div class="summary-item">
+            <span class="lbl">姓名:</span>
+            <span class="val bold">{{ currentRiderInfo.name }}</span>
+          </div>
+          <div class="summary-item">
+            <span class="lbl">手机号:</span>
+            <span class="val">{{ currentRiderInfo.maskPhone }}</span>
+          </div>
+          <div class="summary-item">
+            <span class="lbl">所属区域:</span>
+            <span class="val">{{ currentRiderInfo.station }}</span>
+          </div>
+          <div class="summary-item">
+            <span class="lbl">工作类型:</span>
+            <span class="val">{{ currentRiderInfo.workType }}</span>
+          </div>
+          <div class="summary-item" style="flex: 1">
+            <span class="lbl">服务类目:</span>
+            <el-tag v-for="tag in currentRiderInfo.categories" :key="tag" size="small" effect="plain" style="margin-right: 4px">{{ tag }}</el-tag>
+          </div>
+        </div>
+
+        <!-- 2. Orders Table -->
+        <div class="rider-orders-list">
+          <el-table :data="currentRiderOrders" stripe style="width: 100%" :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
+            <el-table-column prop="orderNo" label="订单号" width="150" />
+            <el-table-column prop="serviceName" label="服务项目" width="120" show-overflow-tooltip />
+            <el-table-column prop="petName" label="服务宠物" width="100" />
+            <el-table-column prop="customerName" label="客户" width="100" />
+            <el-table-column prop="address" label="服务详细地址" min-width="200" show-overflow-tooltip />
+            <el-table-column prop="time" label="预约时间" width="160" />
+            <el-table-column label="状态" width="100">
+              <template #default="{ row }">
+                <el-tag v-if="row.status === 'pending_accept'" type="warning" size="small">待接单</el-tag>
+                <el-tag v-else-if="row.status === 'pending_service'" type="primary" size="small">待服务</el-tag>
+                <el-tag v-else type="info" size="small">{{ row.status }}</el-tag>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- Dispatch Dialog -->
+    <el-dialog v-model="dispatchDialogVisible" title="派单调度" width="900px" top="5vh" destroy-on-close append-to-body>
+      <div class="dispatch-dialog-content">
+        <!-- Top: Order Info -->
+        <div class="dispatch-order-info box-card" v-if="currentDispatchOrder">
+          <div class="list-card order-card" style="margin: 0; box-shadow: none; cursor: default; border: none">
+            <div class="card-left">
+              <div class="type-tag" :class="currentDispatchOrder.typeCode">
+                {{ getShortType(currentDispatchOrder.typeCode) }}
+              </div>
+            </div>
+            <div class="card-main">
+              <template v-if="currentDispatchOrder.typeCode === 'transport'">
+                <div class="row-addr" :title="currentDispatchOrder.pickAddr">
+                  <span class="tag pick">取</span> {{ currentDispatchOrder.pickAddr }}
+                </div>
+                <div class="row-addr" :title="currentDispatchOrder.dropAddr">
+                  <span class="tag drop">送</span> {{ currentDispatchOrder.dropAddr }}
+                </div>
+              </template>
+              <template v-else>
+                <div class="row-addr" :title="currentDispatchOrder.address"><span class="tag home">址</span> {{ currentDispatchOrder.address }}</div>
+              </template>
+              <div class="row-time" style="margin-top: 4px">
+                <el-icon><Clock /></el-icon> {{ currentDispatchOrder.time }}
+                <span class="days-tag" v-if="currentDispatchOrder.daysLater">{{ currentDispatchOrder.daysLater }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- Current Rider Info (For Re-dispatch) -->
+        <div class="current-rider-section" v-if="currentRider">
+          <div class="select-header" style="margin-bottom: 8px">
+            <span class="tit">当前派单履约者</span>
+          </div>
+          <div class="list-card rider-card" style="margin-bottom: 20px; border: 1px solid #e4e7ed; background: #fafafa; cursor: default">
+            <!-- Reusing Rider Card Layout -->
+            <div class="card-left relative">
+              <el-avatar :src="currentRider.avatar" :size="40" />
+              <div class="dot" :class="currentRider.status"></div>
+            </div>
+            <div class="card-main">
+              <div class="row-1" style="justify-content: space-between; align-items: flex-start">
+                <div style="display: flex; align-items: baseline; gap: 8px">
+                  <span class="r-name">{{ currentRider.name }}</span>
+                  <span class="r-phone">{{ currentRider.maskPhone }}</span>
+                </div>
+                <div class="status-right">
+                  <span class="status-badge" :class="currentRider.status">{{ getRiderStatusText(currentRider.status) }}</span>
+                </div>
+              </div>
+
+              <div class="row-2 categories-row" style="margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap">
+                <span v-for="cat in currentRider.categories" :key="cat" class="cat-tag" :class="getCategoryClass(cat)">{{ cat }}</span>
+              </div>
+
+              <div class="row-3 time-row" style="margin-top: 4px">
+                <span class="last-time"
+                  >下一单: {{ currentRider.status === 'offline' || currentRider.status === 'disabled' ? '--' : currentRider.lastServiceTime }}</span
+                >
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- Middle: Rider Selection -->
+        <div class="dispatch-rider-select">
+          <div class="select-header">
+            <span class="tit">选择履约者 (下一单时间由近及远排序)</span>
+            <el-input v-model="dispatchSearchQuery" placeholder="搜索履约者姓名/手机号" prefix-icon="Search" clearable style="width: 240px" />
+          </div>
+
+          <div class="rider-grid-wrapper">
+            <el-scrollbar height="400px">
+              <div class="rider-grid">
+                <div
+                  v-for="rider in filteredDispatchRiders"
+                  :key="rider.id"
+                  class="list-card rider-card select-card"
+                  :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" />
+                    <div class="dot" :class="rider.status"></div>
+                  </div>
+                  <div class="card-main">
+                    <div class="row-1" style="justify-content: space-between; align-items: flex-start">
+                      <div style="display: flex; align-items: baseline; gap: 8px">
+                        <span class="r-name">{{ rider.name }}</span>
+                        <span class="r-phone">{{ rider.maskPhone }}</span>
+                      </div>
+                      <div class="status-right">
+                        <span class="status-badge" :class="rider.status">{{ getRiderStatusText(rider.status) }}</span>
+                      </div>
+                    </div>
+
+                    <div class="row-2 categories-row" style="margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap">
+                      <span v-for="cat in rider.categories" :key="cat" class="cat-tag" :class="getCategoryClass(cat)">{{ cat }}</span>
+                    </div>
+
+                    <div class="row-3 time-row" style="margin-top: 4px">
+                      <span class="last-time"
+                        >下一单: {{ rider.status === 'offline' || rider.status === 'disabled' ? '--' : rider.lastServiceTime }}</span
+                      >
+                    </div>
+                  </div>
+
+                  <!-- Selected Check -->
+                  <div class="selected-mark" v-if="selectedRiderId === rider.id">
+                    <el-icon><Check /></el-icon>
+                  </div>
+                </div>
+
+                <div v-if="filteredDispatchRiders.length === 0" class="empty-text">暂无符合条件的履约者</div>
+              </div>
+            </el-scrollbar>
+          </div>
+        </div>
+
+        <!-- Bottom: Fee & Submit -->
+        <div class="dispatch-footer">
+          <div class="fee-input">
+            <span class="label">服务费用:</span>
+            <el-input-number v-model="dispatchFee" :min="0" :precision="2" :step="10" placeholder="请输入" style="width: 140px" />
+            <span class="unit">元</span>
+          </div>
+          <div class="btns">
+            <el-button @click="dispatchDialogVisible = false">取消</el-button>
+            <el-button type="primary" @click="handleDispatchSubmit">确认派单</el-button>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, reactive, onMounted, watch } from 'vue';
+import { ElMessage } from 'element-plus';
+
+// --- Data & State ---
+const riderOrdersVisible = ref(false);
+const currentRiderOrders = ref([]);
+const currentRiderInfo = ref(null);
+
+const handleViewRiderOrders = (rider) => {
+  // Mock Rider Info
+  currentRiderInfo.value = {
+    name: rider.name,
+    maskPhone: rider.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
+    station: rider.station + ' - 东城片区',
+    workType: '全职专送',
+    categories: ['宠物接送', '上门喂遛', '洗护套餐']
+  };
+
+  // Mock Data for specific rider
+  currentRiderOrders.value = [
+    {
+      orderNo: 'ORD20240205001',
+      serviceName: '上门喂遛',
+      petName: '金毛(大黄)',
+      customerName: '张先生',
+      address: '北京市朝阳区三里屯SOHO A座 1202',
+      time: '2024-02-05 14:00',
+      status: 'pending_accept'
+    },
+    {
+      orderNo: 'ORD20240205002',
+      serviceName: '宠物接送',
+      petName: '布偶(咪咪)',
+      customerName: '李女士',
+      address: '北京市朝阳区国贸三期 35层',
+      time: '2024-02-05 16:30',
+      status: 'pending_service'
+    },
+    {
+      orderNo: 'ORD20240205003',
+      serviceName: '洗护套餐',
+      petName: '泰迪(可乐)',
+      customerName: '王小姐',
+      address: '北京市海淀区中关村软件园 5号楼',
+      time: '2024-02-06 10:00',
+      status: 'pending_service'
+    }
+  ];
+  riderOrdersVisible.value = true;
+};
+const filters = reactive({
+  orderType: 'all',
+  city: 'beijing',
+  station: 'cy'
+});
+
+const currentOrderTab = ref('PendingDispatch');
+const currentRiderTab = ref('All');
+const activeMapFilter = ref('all');
+
+// Mock Stats
+const orderStats = reactive({
+  pendingDispatch: 3,
+  pendingAccept: 4,
+  processing: 8,
+  delivering: 2,
+  merchants: 16
+});
+
+// Real Params (Beijing Chaoyang Area)
+// Center: 116.4551, 39.9255
+
+// Mock Orders Data (With Real Coords)
+const ordersList = ref([
+  {
+    id: 1,
+    type: '宠物接送',
+    typeCode: 'transport',
+    status: 'pending_dispatch',
+    pickAddr: '北京市朝阳区三里屯SOHO A座',
+    dropAddr: '北京市朝阳区朝阳大悦城',
+    time: '2024-02-07 14:30',
+    daysLater: '今天',
+    lng: 116.4552,
+    lat: 39.9338
+  },
+  {
+    id: 2,
+    type: '上门喂遛',
+    typeCode: 'feeding',
+    status: 'pending_accept',
+    address: '北京市海淀区中关村软件园',
+    riderId: 101,
+    time: '2024-02-08 15:00',
+    daysLater: '明天',
+    lng: 116.298,
+    lat: 40.044
+  },
+  {
+    id: 3,
+    type: '上门洗护',
+    typeCode: 'washing',
+    status: 'processing',
+    address: '北京市朝阳区望京SOHO T3',
+    riderId: 102,
+    time: '2024-02-07 10:00',
+    daysLater: '进行中',
+    lng: 116.486,
+    lat: 39.998
+  },
+  {
+    id: 4,
+    type: '宠物接送',
+    typeCode: 'transport',
+    status: 'completed',
+    pickAddr: '北京市通州区万达广场',
+    dropAddr: '北京市通州区北苑',
+    time: '2024-02-06 09:00',
+    daysLater: '',
+    lng: 116.643,
+    lat: 39.905
+  },
+  {
+    id: 5,
+    type: '上门喂遛',
+    typeCode: 'feeding',
+    status: 'pending_dispatch',
+    address: '北京市朝阳区朝阳公园南门',
+    time: '2024-02-09 16:00',
+    daysLater: '2天后',
+    lng: 116.488,
+    lat: 39.932
+  },
+  {
+    id: 6,
+    type: '宠物接送',
+    typeCode: 'transport',
+    status: 'pending_dispatch',
+    pickAddr: '北京市朝阳区国贸三期',
+    dropAddr: '北京市朝阳区双井富力城',
+    time: '2024-02-07 16:30',
+    daysLater: '今天',
+    lng: 116.463,
+    lat: 39.907
+  }
+]);
+
+// Mock Riders Data (With Real Coords)
+const ridersList = ref([
+  {
+    id: 101,
+    name: '王大力',
+    station: '朝阳站',
+    phone: '13800138000',
+    maskPhone: '138****8000',
+    status: 'online',
+    categories: ['接送', '喂遛'],
+    lastServiceTime: '2024-02-07 11:00',
+    avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
+    pendingCount: 0,
+    todoCount: 2,
+    lng: 116.46,
+    lat: 39.92
+  },
+  {
+    id: 102,
+    name: '李小龙',
+    station: '海淀站',
+    phone: '13912345678',
+    maskPhone: '139****5678',
+    status: 'online',
+    categories: ['接送', '洗护'],
+    lastServiceTime: '2024-02-07 10:30',
+    avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+    pendingCount: 1,
+    todoCount: 3,
+    lng: 116.45,
+    lat: 39.915
+  },
+  {
+    id: 103,
+    name: '张小美',
+    station: '望京站',
+    phone: '13666666666',
+    maskPhone: '136****6666',
+    status: 'online',
+    categories: ['喂遛'],
+    lastServiceTime: '2024-02-07 10:45',
+    avatar: '',
+    pendingCount: 0,
+    todoCount: 0,
+    lng: 116.47,
+    lat: 39.93
+  },
+  {
+    id: 104,
+    name: '赵铁柱',
+    station: '通州站',
+    phone: '13555555555',
+    maskPhone: '135****5555',
+    status: 'offline',
+    categories: ['接送'],
+    lastServiceTime: '2024-02-06 18:00',
+    avatar: '',
+    pendingCount: 0,
+    todoCount: 0,
+    lng: 116.44,
+    lat: 39.91
+  },
+  {
+    id: 105,
+    name: '孙悟空',
+    station: '花果山',
+    phone: '13888888888',
+    maskPhone: '138****8888',
+    status: 'online',
+    categories: ['接送', '喂遛', '洗护'],
+    lastServiceTime: '2024-02-07 09:30',
+    avatar: '',
+    pendingCount: 0,
+    todoCount: 1,
+    lng: 116.48,
+    lat: 39.925
+  }
+]);
+
+// Mock Merchants (Static Coords)
+const merchantList = ref([
+  { id: 201, name: '萌它宠物', orders: 5, lng: 116.453, lat: 39.923, icon: '' },
+  { id: 202, name: '宠爱国际', orders: 0, lng: 116.465, lat: 39.928, icon: '' },
+  { id: 203, name: '顽皮狗', orders: 8, lng: 116.448, lat: 39.918, icon: '' }
+]);
+
+// --- Map Logic ---
+let map = null;
+const ak = 'E4805d16520de693a3fe707cdc962045'; // Public Key
+
+const loadBMapScript = () => {
+  return new Promise((resolve, reject) => {
+    if (window.BMap) {
+      resolve(window.BMap);
+      return;
+    }
+    window.initBMapCallback = () => resolve(window.BMap);
+    const script = document.createElement('script');
+    script.src = `https://api.map.baidu.com/api?v=3.0&ak=${ak}&callback=initBMapCallback`;
+    script.onerror = reject;
+    document.head.appendChild(script);
+  });
+};
+
+const initMap = () => {
+  if (!window.BMap) return;
+  map = new BMap.Map('baidu-map');
+  const point = new BMap.Point(116.4551, 39.9255); // Chaoyang center
+  map.centerAndZoom(point, 14);
+  map.enableScrollWheelZoom(true);
+  map.setMapStyleV2({
+    styleId: '3d71dc5a4ce6228d3e9680188e982438'
+  });
+
+  refreshMarkers();
+};
+
+const refreshMarkers = () => {
+  if (!map) return;
+  map.clearOverlays();
+
+  const filter = activeMapFilter.value;
+
+  // 1. Merchants
+  if (filter === 'all' || filter === 'merchants') {
+    merchantList.value.forEach((m) => {
+      const pt = new BMap.Point(m.lng, m.lat);
+      const iconImg = m.icon || 'https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png';
+
+      const html = `
+            <div style="position:absolute; transform:translate(-50%, -100%); width: 220px; filter: drop-shadow(0 4px 10px rgba(0,0,0,0.2));">
+                <div style="background:#fff; border-radius:8px; padding: 12px; display: flex; align-items: center; gap: 10px; position: relative;">
+                     <!-- Close Icon -->
+                    <div style="position: absolute; top: 4px; right: 8px; color: #999; font-size: 16px; cursor: pointer;">×</div>
+
+                    <!-- Icon Ring -->
+                    <div style="width: 48px; height: 48px; border-radius: 50%; border: 4px solid #F56C6C; padding: 2px; flex-shrink: 0; box-sizing: border-box; display: flex; align-items: center; justify-content: center;">
+                         <img src="${iconImg}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;">
+                    </div>
+
+                    <!-- Info -->
+                    <div style="flex: 1; overflow: hidden;">
+                        <div style="font-weight: bold; font-size: 14px; color: #333; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${m.name}</div>
+                        <div style="font-size: 12px; color: #666;">今日 <span style="color: #F56C6C; font-weight: bold; font-size: 14px;">${m.orders}</span> 单</div>
+                    </div>
+                </div>
+                <!-- Triangle -->
+                <div style="width: 0; height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 10px solid #fff; margin: 0 auto;"></div>
+            </div>
+            `;
+
+      const label = new BMap.Label(html, { position: pt, offset: new BMap.Size(0, 0) });
+      label.setStyle({ border: 'none', background: 'transparent' });
+      map.addOverlay(label);
+    });
+  }
+
+  // 2. Fulfiller
+  if (filter === 'all' || filter === 'fulfillers') {
+    ridersList.value.forEach((r) => {
+      const pt = new BMap.Point(r.lng, r.lat);
+      const borderColor = r.status === 'online' ? '#67C23A' : r.status === 'busy' ? '#409EFF' : '#DCDFE6';
+      const pendingText =
+        r.pendingCount > 0 ? `<span style="color:#67C23A;font-weight:bold;">挂${r.pendingCount}单</span>` : `<span style="color:#999;">挂0单</span>`;
+      const avatar = r.avatar || 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png';
+
+      const html = `
+            <div style="position:absolute; transform:translate(-50%, -100%); width: 200px; filter: drop-shadow(0 4px 10px rgba(0,0,0,0.2));">
+                <div style="background:#fff; border-radius:8px; padding: 10px; display: flex; align-items: center; gap: 10px; position: relative;">
+                    <!-- Close Icon -->
+                    <div style="position: absolute; top: 2px; right: 6px; color: #999; font-size: 14px; cursor: pointer;">×</div>
+
+                    <!-- Avatar Ring -->
+                    <div style="width: 44px; height: 44px; border-radius: 50%; border: 3px solid ${borderColor}; padding: 1px; flex-shrink: 0; box-sizing: border-box;">
+                        <img src="${avatar}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;">
+                    </div>
+
+                    <!-- Info -->
+                    <div style="flex: 1; overflow: hidden;">
+                        <div style="font-weight: bold; font-size: 13px; color: #333; margin-bottom: 2px;">[履约者]${r.name}</div>
+                        <div style="font-size: 12px; color: #999;">${pendingText}</div>
+                    </div>
+                </div>
+                 <!-- Triangle -->
+                <div style="width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 8px solid #fff; margin: 0 auto;"></div>
+            </div>
+            `;
+
+      const label = new BMap.Label(html, { position: pt, offset: new BMap.Size(0, 0) });
+      label.setStyle({ border: 'none', background: 'transparent' });
+      map.addOverlay(label);
+    });
+  }
+
+  // 3. Orders (Blue/Purple)
+  if (filter === 'all' || filter === 'orders') {
+    filteredOrders.value.forEach((o) => {
+      const pt = new BMap.Point(o.lng, o.lat);
+      const marker = new BMap.Marker(pt);
+
+      const labelContent = `<div style="border:1px solid #409EFF;background:#fff;color:#409EFF;padding:2px 6px;border-radius:4px;font-size:12px;">${o.type}</div>`;
+      const label = new BMap.Label(labelContent, { position: pt, offset: new BMap.Size(-20, -35) });
+      label.setStyle({ border: 'none', background: 'transparent' });
+
+      map.addOverlay(marker);
+      map.addOverlay(label);
+    });
+  }
+};
+
+const setMapFilter = (val) => {
+  activeMapFilter.value = val;
+};
+
+const focusMapPoint = (lng, lat) => {
+  if (!map) return;
+  const pt = new BMap.Point(lng, lat);
+  map.panTo(pt);
+  map.setZoom(16);
+};
+
+watch(activeMapFilter, () => {
+  refreshMarkers();
+});
+
+// Also refresh when tab changes if logic depends on it (e.g. only showing active orders)
+watch([currentOrderTab, currentRiderTab], () => {
+  refreshMarkers();
+});
+
+onMounted(() => {
+  loadBMapScript()
+    .then(() => {
+      initMap();
+    })
+    .catch((err) => {
+      console.error('Map loading failed', err);
+      ElMessage.error('地图加载失败,请检查网络');
+    });
+});
+
+const dispatchDialogVisible = ref(false);
+const currentDispatchOrder = ref(null);
+const currentRider = ref(null);
+const dispatchSearchQuery = ref('');
+const selectedRiderId = ref(null);
+const dispatchFee = ref(0);
+
+const openDispatchDialog = (order) => {
+  currentDispatchOrder.value = order;
+  dispatchDialogVisible.value = true;
+  dispatchSearchQuery.value = '';
+  selectedRiderId.value = null;
+  dispatchFee.value = 0;
+
+  // Find current rider for re-dispatch
+  if (order.riderId) {
+    currentRider.value = ridersList.value.find((r) => r.id === order.riderId) || null;
+  } else {
+    currentRider.value = null;
+  }
+};
+
+const filteredDispatchRiders = computed(() => {
+  // User said "Select Accepting Riders". Usually means "Online/Busy".
+  // But the main list logic for "Working" includes online and busy.
+  // Let's filter for riders who are NOT disabled or offline?
+  // Or just show all eligible. Let's show 'online' and 'busy'.
+  let result = ridersList.value.filter((r) => r.status === 'online' || r.status === 'busy');
+
+  if (dispatchSearchQuery.value) {
+    const q = dispatchSearchQuery.value.toLowerCase();
+    result = result.filter((r) => r.name.includes(q) || r.phone.includes(q));
+  }
+  // Sort by Next Order Time
+  // Mock sort: just string compare for now since format is YYYY-MM-DD HH:mm
+  result.sort((a, b) => {
+    return a.lastServiceTime.localeCompare(b.lastServiceTime);
+  });
+  return result;
+});
+
+const handleDispatchSubmit = () => {
+  if (!selectedRiderId.value) {
+    ElMessage.warning('请选择履约者');
+    return;
+  }
+  if (!dispatchFee.value) {
+    ElMessage.warning('请输入服务费用');
+    return;
+  }
+  dispatchDialogVisible.value = false;
+  ElMessage.success('派单成功');
+
+  // update status locally
+  if (currentDispatchOrder.value && currentDispatchOrder.value.status === 'pending_dispatch') {
+    const idx = ordersList.value.findIndex((o) => o.id === currentDispatchOrder.value.id);
+    if (idx !== -1) ordersList.value[idx].status = 'pending_accept';
+  }
+};
+const filteredOrders = computed(() => {
+  let result = ordersList.value;
+  // Tab Filter
+  const statusMap = {
+    'PendingDispatch': 'pending_dispatch',
+    'PendingAccept': 'pending_accept',
+    'Processing': 'processing'
+  };
+  const targetStatus = statusMap[currentOrderTab.value];
+  if (targetStatus) {
+    result = result.filter((o) => o.status === targetStatus);
+  }
+  // Type Filter
+  if (filters.orderType !== 'all') {
+    result = result.filter((o) => o.typeCode === filters.orderType);
+  }
+  return result;
+});
+
+const filteredRiders = computed(() => {
+  if (currentRiderTab.value === 'All') return ridersList.value;
+  const map = {
+    'Working': ['online', 'busy'],
+    'Resting': ['offline'],
+    'Disabled': ['disabled']
+  };
+  const allowed = map[currentRiderTab.value] || [];
+  return ridersList.value.filter((r) => allowed.includes(r.status));
+});
+
+// --- Helpers ---
+const getShortType = (code) => {
+  const map = { 'transport': '接送', 'feeding': '喂遛', 'washing': '洗护' };
+  return map[code] || '订单';
+};
+const getOrderTabLabel = (key) => map[key]; // Simplified in template
+const getOrderStatusText = (status) => {
+  const map = { 'pending_dispatch': '待派单', 'pending_accept': '待接单', 'processing': '进行中', 'completed': '已完成' };
+  return map[status];
+};
+const getOrderStatusType = (status) => {
+  const map = { 'pending_dispatch': 'danger', 'pending_accept': 'warning', 'processing': 'primary', 'completed': 'success' };
+  return map[status];
+};
+const getRiderStatusText = (status) => {
+  const map = { 'online': '接单中', 'busy': '接单中', 'offline': '休息中', 'disabled': '禁用' };
+  return map[status];
+};
+const getCategoryClass = (cat) => {
+  const map = { '接送': 'cat-transport', '喂遛': 'cat-feeding', '洗护': 'cat-washing' };
+  return map[cat] || '';
+};
+</script>
+
+<style scoped>
+.dispatch-container {
+  height: calc(100vh - 84px);
+  display: flex;
+  flex-direction: column;
+  background-color: #f0f2f5;
+  overflow: hidden;
+}
+
+/* 1. Top Bar */
+.top-filter-bar {
+  height: 56px;
+  background: #fff;
+  border-bottom: 1px solid #e4e7ed;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 24px;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
+  z-index: 20;
+}
+
+/* 2. Main Content */
+.main-content {
+  flex: 1;
+  display: flex;
+  position: relative;
+  overflow: hidden;
+}
+
+/* Left Map Area */
+.map-wrapper {
+  flex: 1;
+  position: relative;
+  background: #fcf9f2;
+  overflow: hidden;
+}
+.map-view {
+  width: 100%;
+  height: 100%;
+}
+
+/* Map Controls */
+.map-controls-panel {
+  position: absolute;
+  bottom: 24px;
+  left: 24px;
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  z-index: 10;
+}
+.control-group {
+  background: #fff;
+  border-radius: 8px;
+  padding: 6px;
+  display: flex;
+  gap: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+.c-btn {
+  padding: 6px 16px;
+  border-radius: 6px;
+  font-size: 13px;
+  cursor: pointer;
+  background: #f5f7fa;
+  color: #606266;
+  font-weight: 500;
+  transition: all 0.2s;
+}
+.c-btn.active {
+  background: #2d8cf0;
+  color: #fff;
+}
+.c-btn.red {
+  background: #fef0f0;
+  color: #f56c6c;
+  border: 1px solid #fde2e2;
+}
+.c-btn.green {
+  background: #f0f9eb;
+  color: #67c23a;
+  border: 1px solid #e1f3d8;
+}
+.c-btn.gray {
+  background: #f0f2f5;
+  color: #909399;
+  border: 1px solid #dcdfe6;
+  cursor: default;
+}
+.c-btn.blue {
+  background: #ecf5ff;
+  color: #409eff;
+  border: 1px solid #d9ecff;
+}
+
+/* Right Panel */
+.right-panel {
+  width: 440px;
+  background: #fff;
+  border-left: 1px solid #e4e7ed;
+  display: flex;
+  flex-direction: column;
+  box-shadow: -4px 0 16px rgba(0, 0, 0, 0.05);
+  z-index: 30;
+}
+.panel-section {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+.order-mgmt {
+  border-bottom: 8px solid #f5f7fa;
+  flex: default;
+  height: 50%;
+}
+.fulfiller-mgmt {
+  flex: default;
+  height: 50%;
+}
+
+.sec-header {
+  height: 48px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid #f0f0f0;
+}
+.sec-header.no-border {
+  border-bottom: none;
+  height: 40px;
+}
+.sec-header .tit {
+  font-weight: bold;
+  font-size: 15px;
+  color: #1f2f3d;
+}
+
+/* New Header Tabs Style */
+.header-tabs-wrapper {
+  display: flex;
+  padding: 0 16px 8px;
+  gap: 12px;
+  border-bottom: 1px solid #f5f7fa;
+}
+.header-tab {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  padding: 4px 8px;
+  border-radius: 6px;
+  transition: all 0.2s;
+  min-width: 60px;
+}
+.header-tab:hover {
+  background: #f5f7fa;
+}
+.header-tab.active {
+  background: #ecf5ff;
+}
+.header-tab.active .lbl {
+  color: #409eff;
+  font-weight: bold;
+}
+
+.header-tab .num {
+  font-size: 16px;
+  font-weight: bold;
+  line-height: 1.2;
+}
+.header-tab .lbl {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 2px;
+}
+
+.header-tab .num.danger {
+  color: #f56c6c;
+}
+.header-tab .num.warning {
+  color: #e6a23c;
+}
+.header-tab .num.primary {
+  color: #409eff;
+}
+.header-tab .num.success {
+  color: #67c23a;
+}
+
+.fulfiller-tabs .header-tab {
+  flex-direction: row;
+  gap: 4px;
+  min-width: auto;
+}
+.fulfiller-tabs .header-tab .num {
+  font-size: 14px;
+}
+.fulfiller-tabs .header-tab .lbl {
+  margin-top: 0;
+}
+
+/* Header Right Tabs (Orders) */
+.header-right-tabs {
+  display: flex;
+  gap: 4px;
+}
+.h-tab-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  position: relative;
+  padding: 6px 12px;
+  gap: 6px;
+  border-radius: 4px;
+  transition: all 0.2s;
+  color: #606266;
+}
+.h-tab-item:hover {
+  background: #f5f7fa;
+}
+.h-tab-item.active {
+  background: #ecf5ff;
+}
+.h-tab-item.active .txt {
+  color: #409eff;
+  font-weight: bold;
+}
+.h-tab-item.active::after {
+  display: none;
+}
+
+.h-tab-item .txt {
+  font-size: 14px;
+}
+.h-tab-item .num {
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 0;
+}
+.h-tab-item .num.danger {
+  color: #f56c6c;
+}
+.h-tab-item .num.warning {
+  color: #e6a23c;
+}
+.h-tab-item .num.primary {
+  color: #409eff;
+}
+
+/* List */
+.list-wrapper {
+  flex: 1;
+  overflow: hidden;
+  padding: 8px 12px;
+}
+.empty-state {
+  text-align: center;
+  color: #909399;
+  padding: 20px;
+  font-size: 13px;
+}
+
+.list-card {
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  padding: 12px;
+  margin-bottom: 10px;
+  display: flex;
+  align-items: stretch;
+  gap: 12px;
+  transition: all 0.2s;
+  cursor: pointer;
+}
+.list-card:hover {
+  border-color: #c6e2ff;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.card-left {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+}
+.order-card .type-tag {
+  width: 40px;
+  height: 40px;
+  border-radius: 8px;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+  font-weight: bold;
+}
+.type-tag.transport {
+  background: #e6a23c;
+}
+.type-tag.feeding {
+  background: #67c23a;
+}
+.type-tag.washing {
+  background: #409eff;
+}
+
+.card-main {
+  flex: 1;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  gap: 4px;
+}
+.row-1 {
+  display: flex;
+  align-items: center;
+}
+.o-type {
+  font-weight: bold;
+  font-size: 14px;
+  color: #303133;
+}
+.row-2 {
+  font-size: 12px;
+  color: #606266;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.row-3 {
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.card-right {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  justify-content: center;
+  gap: 8px;
+  margin-left: 8px;
+  flex-shrink: 0;
+}
+.card-right .actions {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.rider-card .card-left .dot {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  border: 2px solid #fff;
+}
+.dot.online {
+  background: #67c23a;
+}
+.dot.busy {
+  background: #409eff;
+}
+.dot.offline {
+  background: #909399;
+}
+
+.r-name {
+  font-weight: bold;
+  font-size: 14px;
+  color: #303133;
+  margin-right: 8px;
+}
+.r-station {
+  font-size: 12px;
+  color: #909399;
+  background: #f4f4f5;
+  padding: 1px 4px;
+  border-radius: 2px;
+}
+.status-desc.online {
+  color: #67c23a;
+}
+.status-desc.busy {
+  color: #409eff;
+}
+.status-desc.offline {
+  color: #909399;
+}
+
+.card-right-stats {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  justify-content: center;
+  gap: 6px;
+}
+.stat-box {
+  font-size: 11px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+.stat-box .val {
+  font-weight: bold;
+  font-size: 13px;
+}
+.stat-box .val.danger {
+  color: #f56c6c;
+}
+.stat-box .val.warning {
+  color: #e6a23c;
+}
+
+.rider-summary {
+  display: flex;
+  align-items: center;
+  gap: 24px;
+  background: #fdfdfd;
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 24px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+  flex-wrap: nowrap;
+  overflow-x: auto;
+}
+.summary-item {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  color: #606266;
+  white-space: nowrap;
+  flex-shrink: 0;
+}
+.summary-item .lbl {
+  color: #909399;
+  margin-right: 6px;
+}
+.summary-item .val {
+  color: #303133;
+  font-weight: 500;
+}
+.summary-item .val.bold {
+  font-weight: bold;
+  font-size: 16px;
+  color: #303133;
+}
+
+.rider-orders-list {
+  border: 1px solid #ebeef5;
+  border-radius: 8px;
+  overflow: hidden; /* Hide potential scrollbars from container */
+}
+
+/* Order Card New Layout */
+.row-addr {
+  font-size: 13px;
+  color: #303133;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  line-height: 1.5;
+}
+.row-addr .tag {
+  font-size: 11px;
+  color: #fff;
+  padding: 1px 4px;
+  border-radius: 4px;
+  flex-shrink: 0;
+  transform: scale(0.9);
+}
+.tag.pick {
+  background: #409eff;
+}
+.tag.drop {
+  background: #e6a23c;
+}
+.tag.home {
+  background: #67c23a;
+}
+
+.row-time {
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+.days-tag {
+  color: #f56c6c;
+  background: #fef0f0;
+  padding: 0 4px;
+  border-radius: 4px;
+  font-size: 11px;
+  border: 1px solid #fde2e2;
+  transform: scale(0.95);
+}
+
+/* Updated Rider Card Layout */
+.r-name {
+  font-size: 14px;
+  font-weight: bold;
+  color: #303133;
+}
+.r-phone {
+  font-size: 12px;
+  color: #909399;
+}
+.categories-right {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+  justify-content: flex-end;
+  max-width: 100px;
+}
+
+.cat-tag {
+  background: #f4f4f5;
+  color: #909399;
+  font-size: 10px;
+  padding: 1px 4px;
+  border-radius: 2px;
+}
+.cat-tag.cat-transport {
+  background: #e6f7ff;
+  color: #1890ff;
+  border: 1px solid #91d5ff;
+}
+.cat-tag.cat-feeding {
+  background: #f6ffed;
+  color: #52c41a;
+  border: 1px solid #b7eb8f;
+}
+.cat-tag.cat-washing {
+  background: #fff0f6;
+  color: #eb2f96;
+  border: 1px solid #ffadd2;
+}
+
+.status-badge {
+  font-size: 11px;
+  padding: 2px 6px;
+  border-radius: 4px;
+  display: inline-block;
+  font-weight: bold;
+}
+.status-badge.online {
+  background: #f0f9eb;
+  color: #67c23a;
+}
+.status-badge.busy {
+  background: #ecf5ff;
+  color: #409eff;
+}
+.status-badge.offline {
+  background: #f4f4f5;
+  color: #909399;
+}
+.status-badge.disabled {
+  background: #fef0f0;
+  color: #f56c6c;
+}
+
+.last-time {
+  font-size: 11px;
+  color: #999;
+}
+
+/* Dispatch Dialog */
+.dispatch-order-info {
+  background: #f5f7fa;
+  padding: 10px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+  border: 1px solid #e4e7ed;
+}
+.dispatch-rider-select .select-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+.dispatch-rider-select .tit {
+  font-weight: bold;
+  font-size: 14px;
+}
+
+.rider-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 12px;
+  padding-right: 10px;
+}
+.rider-card.select-card {
+  cursor: pointer;
+  border: 1px solid #dcdfe6;
+  position: relative;
+  transition: all 0.2s;
+  margin-bottom: 0; /* Override default list card margin */
+}
+.rider-card.select-card:hover {
+  border-color: #409eff;
+}
+.rider-card.select-card.active {
+  border-color: #409eff;
+  background-color: #ecf5ff;
+}
+.selected-mark {
+  position: absolute;
+  top: 0;
+  right: 0;
+  background: #409eff;
+  color: #fff;
+  border-bottom-left-radius: 6px;
+  width: 20px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+}
+
+.dispatch-footer {
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #ebeef5;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.dispatch-footer .fee-input {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+}
+.empty-text {
+  text-align: center;
+  color: #909399;
+  padding: 20px;
+  width: 100%;
+  grid-column: span 2;
+}
+
+.card-main {
+  justify-content: flex-start;
+} /* Override default center */
+</style>

+ 1148 - 0
src/views/order/purchase/index.vue

@@ -0,0 +1,1148 @@
+<template>
+  <div class="page-container">
+    <div class="create-layout">
+
+      <!-- 左侧:下单填写区 -->
+      <div class="form-container">
+        <!-- 1. 服务类型选择 -->
+        <div class="type-selection">
+          <div
+            v-for="item in serviceList"
+            :key="item.type"
+            class="type-card"
+            :class="[item.type, { active: form.type === item.type }]"
+            @click="handleTypeChange(item.type)"
+          >
+            <div class="icon-box"><el-icon><component :is="item.icon" /></el-icon></div>
+            <div class="text">
+              <div class="type-name">{{ item.name }}</div>
+              <div class="type-desc">{{ item.desc }}</div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 2. 基础信息:门店与宠主 -->
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-title">
+              <span class="step-num">02</span> 基础信息
+            </div>
+          </template>
+          <div class="card-body">
+            <el-form label-position="top" class="base-form">
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item>
+                    <template #label>
+                      <div style="display:flex; align-items:center; height: 24px;">
+                        <span>服务门店 (平台代下单)</span>
+                      </div>
+                    </template>
+                    <el-select v-model="form.merchantId" placeholder="请选择商户门店" size="large" style="width: 100%" filterable>
+                      <el-option v-for="m in merchants" :key="m.id" :label="m.name" :value="m.id" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item>
+                    <template #label>
+                      <div style="display:flex; justify-content:space-between; align-items:center; width:100%; height: 24px;">
+                        <span>宠主用户</span>
+                        <el-button type="primary" plain size="small" @click="openAddUser" icon="Plus" style="margin-left: 15px;">添加用户</el-button>
+                      </div>
+                    </template>
+                    <el-select
+                      v-model="form.userId"
+                      placeholder="搜索手机号/姓名"
+                      size="large"
+                      style="width: 100%"
+                      filterable
+                      remote
+                      :remote-method="searchUser"
+                      :loading="userLoading"
+                      @change="handleUserChange"
+                    >
+                      <el-option v-for="u in userOptions" :key="u.id" :label="u.name + ' - ' + u.phone" :value="u.id" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <el-form-item label="选择宠物" v-if="form.userId">
+                <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.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>
+                    </div>
+                    <div class="check-mark" v-if="form.petId === p.id"><el-icon><Check /></el-icon></div>
+                  </div>
+
+                  <!-- Add Button Card (Last Item in Grid) -->
+                  <div class="pet-card add-card" @click="openAddPet">
+                    <el-icon :size="24"><Plus /></el-icon>
+                    <span style="font-size: 15px; font-weight: bold;">新增宠物</span>
+                  </div>
+                </div>
+              </el-form-item>
+            </el-form>
+          </div>
+        </el-card>
+
+        <!-- 3. 业务详情表单 -->
+        <el-card shadow="never" class="section-card form-card" v-if="form.type">
+          <template #header>
+            <div class="card-title">
+              <span class="step-num">03</span>
+              {{ getStepTitle(form.type) }}
+            </div>
+          </template>
+
+          <div class="card-body">
+            <!-- 服务套餐信息 -->
+            <el-form-item label="团购套餐">
+              <el-input v-model="form.groupBuyPackage" placeholder="请输入团购套餐名称 (选填)" clearable />
+            </el-form-item>
+
+            <div class="divider"></div>
+
+            <!-- A. 宠物接送表单 -->
+            <div v-show="form.type === 'transport'" class="business-form">
+              <el-form-item label="接送模式">
+                <el-radio-group v-model="form.transport.subType" size="large" @change="calcPrice('transport')">
+                  <el-radio-button label="round">往返接送</el-radio-button>
+                  <el-radio-button label="pick">单程接 (到店)</el-radio-button>
+                  <el-radio-button label="drop">单程送 (回家)</el-radio-button>
+                </el-radio-group>
+              </el-form-item>
+
+              <div class="route-box">
+                <!-- 接宠段 -->
+                <div class="route-segment" v-if="['round', 'pick'].includes(form.transport.subType)">
+                  <div class="seg-badge start">接</div>
+                  <div class="seg-content">
+                    <el-row :gutter="10">
+                      <el-col :span="8">
+                        <el-cascader v-model="form.transport.pickRegion" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
+                      </el-col>
+                      <el-col :span="16">
+                        <el-input v-model="form.transport.pickDetail" placeholder="详细地址 (街道/门牌号)" prefix-icon="Location" />
+                      </el-col>
+                    </el-row>
+                    <el-row :gutter="10">
+                      <el-col :span="12"><el-input v-model="form.transport.pickContact" placeholder="联系人" /></el-col>
+                      <el-col :span="12"><el-input v-model="form.transport.pickPhone" placeholder="电话" /></el-col>
+                    </el-row>
+                    <el-row :gutter="10">
+                      <el-col :span="24">
+                        <el-date-picker v-model="form.transport.pickTime" type="datetime" placeholder="选择接宠时间" style="width: 100%" />
+                      </el-col>
+                    </el-row>
+                  </div>
+                </div>
+
+                <!-- 门店中转标识 -->
+                <div class="route-connector">
+                  <div class="line"></div>
+                  <div class="store-node"><el-icon><Shop /></el-icon> 服务门店</div>
+                  <div class="line"></div>
+                </div>
+
+                <!-- 送回段 -->
+                <div class="route-segment" v-if="['round', 'drop'].includes(form.transport.subType)">
+                  <div class="seg-badge end">送</div>
+                  <div class="seg-content">
+                    <el-row :gutter="10">
+                      <el-col :span="8">
+                        <el-cascader v-model="form.transport.dropRegion" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
+                      </el-col>
+                      <el-col :span="16">
+                        <el-input v-model="form.transport.dropDetail" placeholder="详细地址" prefix-icon="Location" />
+                      </el-col>
+                    </el-row>
+                    <el-row :gutter="10">
+                      <el-col :span="12"><el-input v-model="form.transport.dropContact" placeholder="联系人" /></el-col>
+                      <el-col :span="12"><el-input v-model="form.transport.dropPhone" placeholder="电话" /></el-col>
+                    </el-row>
+                    <el-row :gutter="10">
+                      <el-col :span="24">
+                        <el-date-picker v-model="form.transport.dropTime" type="datetime" placeholder="预计送回时间 (可选)" style="width: 100%" />
+                      </el-col>
+                    </el-row>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <!-- B. 上门喂遛表单 -->
+            <div v-show="form.type === 'feeding'" class="business-form">
+
+              <div style="margin-bottom: 20px;">
+                <div class="section-label">上门服务地址</div>
+                <el-row :gutter="10">
+                  <el-col :span="8">
+                    <el-cascader v-model="form.feeding.region" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
+                  </el-col>
+                  <el-col :span="16">
+                    <el-input v-model="form.feeding.addressDetail" placeholder="详细地址 (街道/门牌号)" prefix-icon="Location" />
+                  </el-col>
+                </el-row>
+              </div>
+
+              <div style="margin-bottom: 20px;">
+                <div class="section-label" style="display:flex; align-items:center; margin-bottom:10px;">
+                  预约服务时间
+                  <el-tag type="info" size="small" style="margin-left:10px;">共 {{ form.feeding.appointments.length }} 次</el-tag>
+                </div>
+                <div v-for="(item, index) in form.feeding.appointments" :key="index" style="display:flex; align-items:center; margin-bottom:10px;">
+                  <span style="width:30px; color:#999; font-size:12px; font-weight:bold;">{{ index + 1 }}.</span>
+                  <el-date-picker
+                    v-model="item.startTime"
+                    type="datetime"
+                    placeholder="开始时间"
+                    style="width: 200px; margin-right: 5px;"
+                    format="YYYY-MM-DD HH:mm"
+                  />
+                  <span style="margin:0 5px; color:#999;">~</span>
+                  <el-date-picker
+                    v-model="item.endTime"
+                    type="datetime"
+                    placeholder="结束时间 (可选)"
+                    style="width: 200px; margin-right: 15px;"
+                    format="YYYY-MM-DD HH:mm"
+                  />
+
+                  <div style="display:flex; gap:8px; margin-left:5px;">
+                    <el-button v-if="index === form.feeding.appointments.length - 1" type="primary" circle size="small" icon="Plus" @click="addAppointment('feeding')" />
+                    <el-button v-if="form.feeding.appointments.length > 1" type="danger" circle size="small" icon="Minus" @click="removeAppointment('feeding', index)" plain />
+                  </div>
+                </div>
+              </div>
+
+              <div class="remark-section">
+                <div class="section-label">家庭服务及宠物档案备注</div>
+                <el-row :gutter="15">
+                  <el-col :span="12"><el-input v-model="form.feeding.area" placeholder="宠物活动区域" /></el-col>
+                  <el-col :span="12"><el-input v-model="form.feeding.itemLoc" placeholder="物品存放位置" /></el-col>
+                  <el-col :span="12" style="margin-top:10px"><el-input v-model="form.feeding.cleanLoc" placeholder="清洗位置" /></el-col>
+                  <el-col :span="12" style="margin-top:10px"><el-input v-model="form.feeding.foodAmount" placeholder="喂食量标准" /></el-col>
+                  <el-col :span="24" style="margin-top:10px"><el-input v-model="form.feeding.other" type="textarea" :rows="2" placeholder="其他注意事项" /></el-col>
+                </el-row>
+              </div>
+            </div>
+
+            <!-- C. 上门洗护表单 -->
+            <div v-show="form.type === 'washing'" class="business-form">
+
+              <div style="margin-bottom: 20px;">
+                <div class="section-label">上门服务地址</div>
+                <el-row :gutter="10">
+                  <el-col :span="8">
+                    <el-cascader v-model="form.washing.region" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
+                  </el-col>
+                  <el-col :span="16">
+                    <el-input v-model="form.washing.addressDetail" placeholder="详细地址 (街道/门牌号)" prefix-icon="Location" />
+                  </el-col>
+                </el-row>
+              </div>
+
+              <div style="margin-bottom: 20px;">
+                <div class="section-label" style="display:flex; align-items:center; margin-bottom:10px;">
+                  预约服务时间
+                  <el-tag type="info" size="small" style="margin-left:10px;">共 {{ form.washing.appointments.length }} 次</el-tag>
+                </div>
+                <div v-for="(item, index) in form.washing.appointments" :key="index" style="display:flex; align-items:center; margin-bottom:10px;">
+                  <span style="width:30px; color:#999; font-size:12px; font-weight:bold;">{{ index + 1 }}.</span>
+                  <el-date-picker
+                    v-model="item.startTime"
+                    type="datetime"
+                    placeholder="开始时间"
+                    style="width: 200px; margin-right: 5px;"
+                    format="YYYY-MM-DD HH:mm"
+                  />
+                  <span style="margin:0 5px; color:#999;">~</span>
+                  <el-date-picker
+                    v-model="item.endTime"
+                    type="datetime"
+                    placeholder="结束时间 (可选)"
+                    style="width: 200px; margin-right: 15px;"
+                    format="YYYY-MM-DD HH:mm"
+                  />
+
+                  <div style="display:flex; gap:8px; margin-left:5px;">
+                    <el-button v-if="index === form.washing.appointments.length - 1" type="primary" circle size="small" icon="Plus" @click="addAppointment('washing')" />
+                    <el-button v-if="form.washing.appointments.length > 1" type="danger" circle size="small" icon="Minus" @click="removeAppointment('washing', index)" plain />
+                  </div>
+                </div>
+              </div>
+
+              <div class="remark-section">
+                <div class="section-label">服务备注及宠物状态</div>
+                <el-row :gutter="15">
+                  <el-col :span="8">
+                    <el-select v-model="form.washing.petStatus" placeholder="宠物应激状态" style="width:100%">
+                      <el-option label="性格温顺" value="calm" />
+                      <!-- ... options ... -->
+                      <el-option label="胆小怕人" value="shy" />
+                      <el-option label="容易应激" value="stress" />
+                      <el-option label="有攻击性" value="aggressive" />
+                    </el-select>
+                  </el-col>
+                  <el-col :span="8"><el-input v-model="form.washing.cleanLoc" placeholder="清洗位置" /></el-col>
+                  <el-col :span="8"><el-input v-model="form.washing.toolLoc" placeholder="工具/水源位置" /></el-col>
+                  <el-col :span="24" style="margin-top:10px"><el-input v-model="form.washing.other" type="textarea" :rows="2" placeholder="其他注意事项" /></el-col>
+                </el-row>
+              </div>
+            </div>
+
+          </div>
+        </el-card>
+      </div>
+
+      <!-- 右侧:收银台概览 -->
+      <div class="summary-sidebar">
+        <div class="summary-panel">
+          <div class="summary-header">订单概览</div>
+
+          <div class="summary-content">
+            <div class="row" v-if="selectedMerchantName">
+              <span class="label">服务门店</span>
+              <span class="value">{{ selectedMerchantName }}</span>
+            </div>
+            <div class="row" v-if="selectedUserName">
+              <span class="label">客户</span>
+              <span class="value">{{ selectedUserName }}</span>
+            </div>
+            <div class="row" v-if="selectedPetName">
+              <span class="label">服务对象</span>
+              <span class="value action-text">{{ selectedPetName }} ({{ selectedPetBreed }})</span>
+            </div>
+            <div class="divider"></div>
+
+            <div class="service-preview" v-if="form.type">
+              <div class="preview-title">{{ getTypeName(form.type) }}</div>
+
+              <!-- 套餐显示 -->
+              <div class="preview-detail" v-if="selectedPkgName">
+                <div style="font-weight:bold; color:#409eff">{{ selectedPkgName }}</div>
+              </div>
+              <div class="preview-detail" v-else>
+                <div style="color:#e6a23c">非服务套餐 (单次)</div>
+              </div>
+
+              <!-- 接送预览 -->
+              <div v-if="form.type === 'transport'" class="preview-detail">
+                <div>{{ form.transport.subType === 'round' ? '往返接送' : (form.transport.subType === 'pick' ? '单程接' : '单程送') }}</div>
+                <div class="minor">接: {{ form.transport.pickTime ? formatTime(form.transport.pickTime) : '未选时间' }}</div>
+                <div class="minor" v-if="form.transport.subType !== 'pick'">送: {{ form.transport.dropTime ? formatTime(form.transport.dropTime) : '未选' }}</div>
+              </div>
+            </div>
+
+          </div>
+
+          <div class="summary-footer">
+            <el-button type="primary" size="large" class="submit-btn" :disabled="!canSubmit" @click="handleSubmit">
+              立即下单
+            </el-button>
+          </div>
+        </div>
+      </div>
+
+    </div>
+
+    <!-- Dialogs -->
+    <!-- Add User Dialog -->
+    <el-dialog v-model="userDialogVisible" title="新增用户" width="700px" destroy-on-close append-to-body class="add-user-dialog">
+      <el-form :model="userForm" label-width="90px" class="user-form">
+
+        <div style="display: flex; justify-content: center; align-items: center; gap: 20px; margin-bottom: 30px;">
+          <el-upload action="#" :show-file-list="false" :auto-upload="false" :on-change="handleUserAvatarChange">
+            <el-avatar :size="80" :src="userForm.avatar || 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'" style="cursor: pointer; border: 2px solid #e4e7ed;" />
+          </el-upload>
+          <el-button type="primary" link @click="">点击修改头像</el-button>
+        </div>
+
+        <div class="form-section-header">基本资料</div>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="录入来源">
+              <el-select v-model="userForm.source" style="width: 100%" filterable allow-create default-first-option>
+                <el-option label="平台录入" value="平台录入" />
+                <el-option label="萌它宠物连锁录入" value="萌它宠物连锁录入" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="所属区域">
+              <el-select v-model="userForm.area" style="width: 100%" filterable allow-create default-first-option placeholder="请选择或输入">
+                <el-option label="朝阳区" value="朝阳区" />
+                <el-option label="海淀区" value="海淀区" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="姓名" required><el-input v-model="userForm.name" placeholder="请输入姓名" /></el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="电话" required><el-input v-model="userForm.phone" placeholder="请输入电话" /></el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="性别">
+              <el-radio-group v-model="userForm.gender">
+                <el-radio label="男">男</el-radio>
+                <el-radio label="女">女</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <div class="form-section-header">居住信息</div>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="所在地区">
+              <el-cascader v-model="userForm.region" :options="pcaOptions" placeholder="请选择省/市/区" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="详细住址"><el-input v-model="userForm.detailAddress" placeholder="请输入街道/门牌号" /></el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="房屋类型">
+              <el-radio-group v-model="userForm.houseType">
+                <el-radio label="stairs">楼梯</el-radio>
+                <el-radio label="elevator">电梯</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="入门方式">
+              <el-radio-group v-model="userForm.entryMethod">
+                <el-radio label="password">密码开门</el-radio>
+                <el-radio label="key">钥匙开门</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="userForm.entryMethod === 'password'">
+            <el-form-item label="开门密码">
+              <el-input v-model="userForm.entryPassword" placeholder="请输入密码" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="userForm.entryMethod === 'key'">
+            <el-form-item label="钥匙位置">
+              <el-input v-model="userForm.keyLocation" placeholder="如:地毯下" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <div class="form-section-header">其他</div>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="用户标签">
+              <el-select v-model="userSelectedTagIds" multiple placeholder="选择标签" style="width: 100%">
+                <el-option v-for="tag in allUserTags" :key="tag.id" :label="tag.name" :value="tag.id">
+                  <el-tag :type="tag.type" effect="light" size="small">{{ tag.name }}</el-tag>
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="备注说明"><el-input type="textarea" v-model="userForm.remark" rows="3" /></el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div style="text-align: center; margin-top: 20px;">
+          <el-button @click="userDialogVisible = false" size="large" style="width: 120px;">取消</el-button>
+          <el-button type="primary" @click="submitUser" size="large" style="width: 120px;">保存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog v-model="petDialogVisible" title="宠物档案详情" width="800px" top="10vh" class="pet-profile-dialog">
+      <el-tabs v-model="activePetTab" class="pet-tabs">
+        <el-tab-pane label="基本信息" name="basic">
+          <div class="pet-form-content">
+            <!-- Avatar Upload -->
+            <div class="avatar-col">
+              <el-upload
+                class="avatar-uploader"
+                action="#"
+                :show-file-list="false"
+                :auto-upload="false"
+                :on-change="handleAvatarChange"
+              >
+                <img v-if="petForm.avatar" :src="petForm.avatar" class="avatar" />
+                <el-icon v-else class="avatar-uploader-icon" :size="28" color="#8c939d"><Plus /></el-icon>
+              </el-upload>
+              <div style="font-size:12px; color:#999; margin-top:8px; text-align:center">点击上传头像</div>
+            </div>
+
+            <!-- Form Fields -->
+            <el-form :model="petForm" label-width="80px" class="inner-form">
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="宠物姓名" required>
+                    <el-input v-model="petForm.name" placeholder="请输入" />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="所属主人" required>
+                    <el-select v-model="form.userId" disabled placeholder="选择主人" style="width:100%">
+                      <el-option v-for="u in userOptions" :key="u.id" :label="u.name" :value="u.id" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="性别">
+                    <el-radio-group v-model="petForm.gender">
+                      <el-radio label="MM">公</el-radio>
+                      <el-radio label="GG">母</el-radio>
+                    </el-radio-group>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="品种">
+                    <el-select v-model="petForm.breed" placeholder="请选择品种" style="width:100%">
+                      <el-option label="金毛" value="金毛" />
+                      <el-option label="布偶" value="布偶" />
+                      <el-option label="边牧" value="边牧" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="体型">
+                    <el-select v-model="petForm.bodyType" placeholder="选择体型" style="width:100%">
+                      <el-option label="小型" value="small" />
+                      <el-option label="中型" value="medium" />
+                      <el-option label="大型" value="large" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="体重(kg)">
+                    <el-row :gutter="10">
+                      <el-col :span="12"><el-input-number v-model="petForm.weight" :min="0" :step="0.1" controls="false" style="width:100%" /></el-col>
+                      <el-col :span="12"></el-col>
+                    </el-row>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="年龄(岁)">
+                    <el-input-number v-model="petForm.age" :min="0" style="width:100%" />
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+              <el-form-item label="性格关键词">
+                <el-input v-model="petForm.keywords" placeholder="如:活泼、粘人" />
+              </el-form-item>
+
+              <el-form-item label="萌宠性格">
+                <el-input v-model="petForm.desc" type="textarea" placeholder="详细描述" :rows="2" />
+              </el-form-item>
+
+              <el-form-item label="宠物标签">
+                <el-select v-model="petForm.tags" multiple placeholder="选择标签" style="width:100%">
+                  <el-option label="绝育" value="1" />
+                  <el-option label="疫苗齐全" value="2" />
+                </el-select>
+              </el-form-item>
+
+            </el-form>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="家庭信息" name="family">
+          <el-form :model="petForm" label-width="120px">
+            <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-radio-group v-model="petForm.houseType">
+                <el-radio label="stairs">楼梯</el-radio>
+                <el-radio label="elevator">电梯</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="入门方式">
+              <el-radio-group v-model="petForm.entryMethod">
+                <el-radio label="password">密码开门</el-radio>
+                <el-radio label="key">钥匙开门</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="密码" v-if="petForm.entryMethod === 'password'">
+              <el-input v-model="petForm.entryPassword" placeholder="请输入门锁密码" />
+            </el-form-item>
+            <el-form-item label="钥匙位置" v-if="petForm.entryMethod === 'key'">
+              <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-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-switch v-model="petForm.aggression" active-text="是" inactive-text="否" />
+            </el-form-item>
+            <el-form-item label="疫苗情况">
+              <el-input v-model="petForm.vaccine" type="textarea" placeholder="记录疫苗接种情况" />
+            </el-form-item>
+            <el-form-item label="既往病史">
+              <el-input v-model="petForm.medicalHistory" type="textarea" placeholder="如有病史请记录" />
+            </el-form-item>
+            <el-form-item label="过敏史">
+              <el-input v-model="petForm.allergies" type="textarea" placeholder="如有过敏源请记录" />
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <el-button @click="petDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitPet">保存</el-button>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+// --- Mock Data ---
+const merchants = ref([
+  { id: 1, name: '萌它宠物三里屯店' },
+  { id: 2, name: '宠爱国际动物医院' }
+])
+const userOptions = ref([
+  { id: 101, name: '张三', phone: '13812345678' },
+  { id: 102, name: '李四', phone: '13987654321' }
+])
+const mockPets = {
+  101: [
+    { id: 1, name: '旺财', breed: '金毛', avatar: '', region: ['北京市', '市辖区', '朝阳区'], address: '三里屯SOHO A座 1001' },
+    { id: 2, name: '咪咪', breed: '布偶', avatar: '', region: ['北京市', '市辖区', '朝阳区'], address: '三里屯SOHO A座 1001' }
+  ],
+  102: [
+    { id: 3, name: '奥利奥', breed: '边牧', avatar: '', region: ['上海市', '市辖区', '浦东新区'], address: '陆家嘴一号院 5-502' }
+  ]
+}
+
+const serviceList = [
+  { type: 'transport', name: '宠物接送', icon: 'Van', desc: '专车接送 · 全程监护', basePrice: 35 },
+  { type: 'feeding', name: '上门喂遛', icon: 'Food', desc: '喂食添水 · 陪玩遛狗', basePrice: 68 },
+  { type: 'washing', name: '上门洗护', icon: 'Soap', desc: '专业设备 · 深度清洁', basePrice: 88 }
+]
+
+const allPackages = [
+  { id: 10, type: 'transport', name: '包月接送套餐', price: 0 },
+  { id: 11, type: 'feeding', name: '基础喂猫套餐', price: 0 },
+  { id: 12, type: 'feeding', name: '深度陪玩套餐', price: 0 },
+  { id: 13, type: 'washing', name: '精致洗护+美容', price: 0 },
+  { id: 14, type: 'washing', name: '除菌药浴套餐', price: 0 },
+]
+
+// --- State ---
+const userLoading = ref(false)
+const currentPets = ref([])
+
+const form = reactive({
+  merchantId: '',
+  userId: '',
+  petId: '',
+  type: 'transport',
+  groupBuyPackage: '',
+
+  // Sub Forms Data
+  transport: {
+    pkgId: '',
+    price: 0,
+    pickPrice: 35,
+    dropPrice: 35,
+    subType: 'round',
+    pickRegion: [], pickDetail: '', pickContact: '', pickPhone: '', pickTime: '',
+    dropRegion: [], dropDetail: '', dropContact: '', dropPhone: '', dropTime: ''
+  },
+  feeding: {
+    pkgId: '', price: 68,
+    appointments: [{ startTime: '', endTime: '' }],
+    region: [], addressDetail: '',
+    count: 1, dates: [], area: '', itemLoc: '', cleanLoc: '', foodAmount: '', other: ''
+  },
+  washing: {
+    pkgId: '', price: 88,
+    appointments: [{ startTime: '', endTime: '' }],
+    region: [], addressDetail: '',
+    time: '', petStatus: '', cleanLoc: '', toolLoc: '', other: ''
+  }
+})
+
+// Address Autofill Watcher
+watch(() => form.petId, (newId) => {
+  if (!newId) return
+  const pet = currentPets.value.find(p => p.id === newId)
+  if (!pet) return
+
+  const user = userOptions.value.find(u => u.id === form.userId)
+
+  // Fill Transport
+  form.transport.pickRegion = pet.region || []
+  form.transport.pickDetail = pet.address || ''
+  form.transport.pickContact = user?.name || ''
+  form.transport.pickPhone = user?.phone || ''
+
+  form.transport.dropRegion = pet.region || []
+  form.transport.dropDetail = pet.address || ''
+  form.transport.dropContact = user?.name || ''
+  form.transport.dropPhone = user?.phone || ''
+
+  // Fill Feeding
+  form.feeding.region = pet.region || []
+  form.feeding.addressDetail = pet.address || ''
+
+  // Fill Washing
+  form.washing.region = pet.region || []
+  form.washing.addressDetail = pet.address || ''
+})
+
+// Current Active Data Helper
+const activeData = computed(() => {
+  return form[form.type]
+})
+
+// --- Logic ---
+
+const handleTypeChange = (type) => {
+  form.type = type
+  calcPrice(type)
+}
+
+const currentPackages = computed(() => {
+  return allPackages.filter(p => p.type === form.type)
+})
+
+const handlePkgSelect = (id) => {
+  activeData.value.pkgId = id
+  // Price calculation should remain same (base price), just payable changes
+  calcPrice(form.type)
+}
+
+const calcPrice = (type) => {
+  const data = form[type]
+  const base = serviceList.find(s => s.type === type)?.basePrice || 0
+
+  // Always use Base Logic for "Order Value", regardless of package
+  if (type === 'transport') {
+    if(data.subType === 'round') {
+      data.pickPrice = base
+      data.dropPrice = base
+    } else if (data.subType === 'pick') {
+      data.pickPrice = base
+      data.dropPrice = 0
+    } else if (data.subType === 'drop') {
+      data.pickPrice = 0
+      data.dropPrice = base
+    }
+  } else if (type === 'feeding') {
+    data.price = base * data.count
+  } else if (type === 'washing') {
+    data.price = base
+  }
+}
+
+// Appointment Logic
+const addAppointment = (type) => {
+  form[type].appointments.push({ startTime: '', endTime: '' })
+  if(type === 'feeding') {
+    form.feeding.count = form.feeding.appointments.length
+    calcPrice('feeding')
+  }
+}
+
+const removeAppointment = (type, index) => {
+  if (form[type].appointments.length <= 1) return
+  form[type].appointments.splice(index, 1)
+  if(type === 'feeding') {
+    form.feeding.count = form.feeding.appointments.length
+    calcPrice('feeding')
+  }
+}
+
+// Add User Logic
+const userDialogVisible = ref(false)
+const userSelectedTagIds = ref([])
+
+const allUserTags = [
+  { id: 1, name: '优质客户', type: 'success' },
+  { id: 2, name: '潜在流失', type: 'warning' },
+  { id: 3, name: '黑名单', type: 'danger' }
+]
+
+const pcaOptions = [
+  {
+    value: '北京市', label: '北京市',
+    children: [
+      { value: '市辖区', label: '市辖区', children: [ { value: '朝阳区', label: '朝阳区' }, { value: '海淀区', label: '海淀区' } ] }
+    ]
+  },
+  {
+    value: '上海市', label: '上海市',
+    children: [
+      { value: '市辖区', label: '市辖区', children: [ { value: '浦东新区', label: '浦东新区' }, { value: '徐汇区', label: '徐汇区' } ] }
+    ]
+  }
+]
+
+const userForm = reactive({
+  id: null, avatar: '', name: '', phone: '', gender: '男', address: '', detailAddress: '', region: [], remark: '',
+  houseType: 'elevator', entryMethod: 'password', entryPassword: '', keyLocation: '',
+  source: '平台录入', area: ''
+})
+
+const openAddUser = () => {
+  userSelectedTagIds.value = []
+  Object.assign(userForm, {
+    id: null, avatar: '', name: '', phone: '', gender: '男', address: '', detailAddress: '', region: [], remark: '',
+    houseType: 'elevator', entryMethod: 'password', entryPassword: '', keyLocation: '',
+    source: '平台录入', area: ''
+  })
+  userDialogVisible.value = true
+}
+
+const handleUserAvatarChange = (uploadFile) => {
+  userForm.avatar = URL.createObjectURL(uploadFile.raw)
+}
+
+const submitUser = () => {
+  if(!userForm.name || !userForm.phone) {
+    ElMessage.warning('请补全用户必填信息')
+    return
+  }
+  const newUser = {
+    id: Date.now(),
+    name: userForm.name,
+    phone: userForm.phone
+  }
+  userOptions.value.push(newUser)
+  form.userId = newUser.id
+
+  // Clear pets for new user
+  currentPets.value = []
+  form.petId = ''
+
+  userDialogVisible.value = false
+  ElMessage.success('用户添加成功并已选中')
+}
+
+// Add Pet Logic
+const petDialogVisible = ref(false)
+const activePetTab = ref('basic')
+
+const petForm = reactive({
+  name: '', breed: '', gender: 'MM', avatar: '',
+  bodyType: 'small', weight: 0, age: 0, keywords: '', desc: '', tags: [],
+
+  // Family
+  arrivalTime: '', houseType: 'stairs', entryMethod: 'key', entryPassword: '', keyLocation: '',
+  // Health
+  healthStatus: '健康', aggression: false, vaccine: '', medicalHistory: '', allergies: ''
+})
+
+const openAddPet = () => {
+  activePetTab.value = 'basic'
+  Object.assign(petForm, {
+    name: '', breed: '', gender: 'MM', avatar: '',
+    bodyType: 'small', weight: 0, age: 0, keywords: '', desc: '', tags: [],
+    arrivalTime: '', houseType: 'stairs', entryMethod: 'key', entryPassword: '', keyLocation: '',
+    healthStatus: '健康', aggression: false, vaccine: '', medicalHistory: '', allergies: ''
+  })
+  petDialogVisible.value = true
+}
+
+const handleAvatarChange = (uploadFile) => {
+  // Mock upload: create local URL
+  petForm.avatar = URL.createObjectURL(uploadFile.raw)
+}
+
+const submitPet = () => {
+  if(!petForm.name || !petForm.breed) {
+    ElMessage.warning('请补全宠物必填信息')
+    return
+  }
+  const newPet = {
+    id: Date.now(),
+    name: petForm.name,
+    breed: petForm.breed,
+    avatar: petForm.avatar
+  }
+  if(!currentPets.value) currentPets.value = []
+  currentPets.value.push(newPet)
+  form.petId = newPet.id
+  petDialogVisible.value = false
+  ElMessage.success('宠物添加成功')
+}
+
+
+// --- Computed Helpers ---
+const selectedMerchantName = computed(() => merchants.value.find(m => m.id === form.merchantId)?.name)
+const selectedUserName = computed(() => userOptions.value.find(u => u.id === form.userId)?.name)
+const selectedPet = computed(() => currentPets.value.find(p => p.id === form.petId))
+const selectedPetName = computed(() => selectedPet.value?.name)
+const selectedPetBreed = computed(() => selectedPet.value?.breed)
+
+const selectedPkgName = computed(() => {
+  const pkgId = activeData.value.pkgId
+  return allPackages.find(p => p.id === pkgId)?.name || ''
+})
+
+
+
+const canSubmit = computed(() => {
+  if(!form.merchantId || !form.userId || !form.petId) return false
+  return true
+})
+
+// --- Methods ---
+const searchUser = (query) => { /* Mock */ }
+const handleUserChange = (val) => {
+  currentPets.value = mockPets[val] || []
+  form.petId = ''
+}
+const getStepTitle = (type) => {
+  const map = { transport: '填写接送路线与时间', feeding: '选择套餐与服务的细则', washing: '选择套餐与服务的细则' }
+  return map[type]
+}
+const getTypeName = (type) => {
+  const map = { transport: '宠物接送', feeding: '上门喂遛', washing: '上门洗护' }
+  return map[type]
+}
+const formatTime = (time) => {
+  if(!time) return ''
+  const d = new Date(time)
+  return `${d.getMonth()+1}-${d.getDate()} ${d.getHours()}:${d.getMinutes() < 10 ? '0'+d.getMinutes() : d.getMinutes()}`
+}
+
+const handleSubmit = () => {
+  ElMessage.success('下单成功!订单号:ORD20248888')
+}
+
+// Initialize
+onMounted(() => {
+  calcPrice('transport')
+})
+</script>
+
+<style scoped>
+.page-container { padding: 20px; background-color: #f0f2f5; min-height: 100vh; }
+.create-layout { display: flex; gap: 20px; align-items: flex-start; max-width: 1400px; margin: 0 auto; }
+
+/* Left Content */
+.form-container { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 20px; }
+
+.section-card { border-radius: 8px; border: none; }
+.card-title { font-size: 16px; font-weight: bold; color: #303133; display: flex; align-items: center; gap: 10px; }
+.step-num {
+  background: #e6f7ff; color: #1890ff; width: 28px; height: 28px; border-radius: 50%;
+  text-align: center; line-height: 28px; font-family: Impact, sans-serif;
+}
+.base-form .el-form-item { margin-bottom: 18px; }
+
+/* Pet Selection */
+/* Pet Selection */
+.pet-select-row {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+  gap: 15px;
+  width: 100%;
+}
+.pet-card {
+  border: 1px solid #8D9095;
+  border-radius: 8px;
+  padding: 12px 15px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  position: relative;
+  transition: all 0.2s ease-in-out;
+  background: #fff;
+  min-height: 70px;
+}
+.pet-card:hover {
+  border-color: #303133;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
+}
+.pet-card.active {
+  border-color: #409eff;
+  background-color: #fff;
+  box-shadow: 0 0 0 1px #409eff inset;
+}
+.check-mark {
+  position: absolute;
+  right: 0;
+  top: 0;
+  background: #409eff;
+  color: white;
+  width: 28px;
+  height: 18px;
+  border-radius: 0 8px 0 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+}
+.pet-info .name { font-weight: bold; font-size: 15px; color: #303133; margin-bottom: 2px; }
+.pet-info .sub { font-size: 12px; color: #606266; line-height: 1.2; }
+
+.pet-card.add-card {
+  border: 1px solid #8D9095;
+  justify-content: center;
+  align-items: center;
+  color: #303133;
+  flex-direction: row;
+  gap: 8px;
+  background: #fff;
+  box-shadow: none;
+  height: auto;
+  min-height: 70px;
+}
+.pet-card.add-card:hover {
+  border-color: #303133;
+  color: #303133;
+  background: #f9f9f9;
+  transform: translateY(-2px);
+}
+
+/* Dialog Styles */
+.pet-form-content { display: flex; gap: 20px; }
+.avatar-col { width: 120px; display: flex; flex-direction: column; align-items: center; padding-top: 10px; }
+.avatar-uploader {
+  display: inline-block;
+}
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
+}
+.avatar-uploader .el-upload:hover {
+  border-color: var(--el-color-primary);
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 100px;
+  height: 100px;
+  text-align: center;
+  border: 1px dashed #d9d9d9;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.avatar {
+  width: 100px;
+  height: 100px;
+  display: block;
+  border-radius: 50%;
+  object-fit: cover;
+}
+.inner-form { flex: 1; }
+
+/* Type Selection */
+.type-selection { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
+.type-card {
+  background: white; border-radius: 8px; padding: 20px; cursor: pointer; position: relative;
+  display: flex; align-items: center; gap: 15px; transition: all 0.2s;
+  border: 2px solid transparent; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+}
+.type-card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1); }
+.type-card.active { border-color: #409eff; background-color: #f0f9ff; }
+.type-card .icon-box {
+  width: 48px; height: 48px; border-radius: 12px; background: #f2f3f5;
+  display: flex; align-items: center; justify-content: center; font-size: 24px; color: #606266;
+}
+.type-card.active .icon-box { background: #409eff; color: white; }
+/* Colors */
+.type-card.transport.active .icon-box { background: #409eff; }
+.type-card.transport.active { border-color: #409eff; background-color: #f0f9ff; }
+.type-card.feeding.active .icon-box { background: #e6a23c; }
+.type-card.feeding.active { border-color: #e6a23c; background-color: #fdf6ec; }
+.type-card.washing.active .icon-box { background: #67c23a; }
+.type-card.washing.active { border-color: #67c23a; background-color: #f0f9eb; }
+
+.type-name { font-weight: bold; font-size: 16px; color: #303133; margin-bottom: 4px; }
+.type-desc { font-size: 12px; color: #909399; margin-bottom: 4px; }
+.type-price { font-size: 14px; color: #f56c6c; font-weight: bold; }
+
+/* Package Selection Grid */
+.form-section-title { font-weight: bold; margin-bottom: 12px; font-size: 14px; }
+.package-selection-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
+.pkg-select-card {
+  border: 1px solid #dcdfe6; border-radius: 8px; padding: 10px 15px; cursor: pointer; position: relative;
+  background: #fff; transition: all 0.2s; min-height: 56px; display: flex; flex-direction: column; justify-content: center;
+}
+.pkg-select-card:hover { border-color: #409eff; }
+.pkg-select-card.active { border-color: #409eff; background-color: #ecf5ff; }
+.pkg-select-card .pkg-name { font-weight: bold; font-size: 14px; color: #303133; }
+.pkg-select-card .pkg-desc { font-size: 12px; color: #909399; margin-top: 2px; }
+
+/* Business Form */
+.business-form { padding-top: 5px; }
+.route-box { background: #f9f9f9; padding: 20px; border-radius: 8px; border: 1px solid #EBEEF5; }
+.route-segment { display: flex; gap: 15px; }
+.seg-badge {
+  width: 32px; height: 32px; background: #409eff; color: white; border-radius: 8px;
+  text-align: center; line-height: 32px; font-weight: bold; flex-shrink: 0;
+}
+.seg-badge.end { background: #67c23a; }
+.seg-content { flex: 1; display: flex; flex-direction: column; gap: 10px; }
+
+.route-connector { display: flex; align-items: center; justify-content: center; margin: 15px 0; gap: 10px; color: #909399; font-size: 12px; }
+.route-connector .line { height: 1px; width: 80px; background: #dcdfe6; }
+.route-connector .store-node { background: white; padding: 4px 12px; border-radius: 20px; border: 1px solid #dcdfe6; display: flex; align-items: center; gap: 5px; }
+
+.divider { height: 1px; background: #EBEEF5; margin: 15px 0; }
+.remark-section { background: #fdfdfd; border: 1px dashed #dcdfe6; padding: 15px; border-radius: 6px; margin-top: 20px; }
+.section-label { font-size: 13px; font-weight: bold; color: #606266; margin-bottom: 12px; }
+.tip { font-size: 12px; color: #e6a23c; margin-top: 4px; }
+
+/* Sidebar */
+.summary-sidebar { width: 320px; flex-shrink: 0; }
+.summary-panel { background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); position: sticky; top: 20px; }
+.summary-header { background: #304156; color: white; padding: 15px 20px; font-weight: bold; font-size: 16px; border-radius: 8px 8px 0 0; }
+.summary-content { padding: 20px; }
+.row { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; }
+.row .label { color: #909399; }
+.row .value { color: #303133; font-weight: 500; }
+.preview-title { font-weight: bold; margin-bottom: 8px; color: #333; }
+.preview-detail { background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 13px; margin-bottom: 8px; }
+.preview-detail .minor { color: #999; font-size: 12px; margin-top: 2px; }
+.placeholder { color: #C0C4CC; text-align: center; padding: 20px 0; font-size: 13px; font-style: italic; }
+
+
+.summary-footer { background: #f9f9fc; padding: 15px 20px; border-top: 1px solid #ebeef5; text-align: center; border-radius: 0 0 8px 8px; }
+.submit-btn { width: 100%; font-weight: bold; border-radius: 22px; }
+</style>

+ 1 - 1
src/views/system/tenant/index.vue

@@ -219,7 +219,7 @@ const initFormData: TenantForm = {
   remark: '',
   packageId: '',
   expireTime: '',
-  accountCount: 0,
+  accountCount: -1,
   status: '0',
   logo: undefined
 };