فهرست منبع

订单列表完成一半

Huanyi 1 ماه پیش
والد
کامیت
03484f5163

+ 25 - 0
src/api/archieves/changeLog/index.ts

@@ -0,0 +1,25 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ArcChangeLogVO } from '@/api/archieves/changeLog/types';
+
+/**
+ * 分页查询变更日志
+ */
+export const listChangeLog = (targetId: string | number, targetType: string, query?: PageQuery): AxiosPromise<ArcChangeLogVO[]> => {
+  return request({
+    url: '/archieves/changeLog/list',
+    method: 'get',
+    params: { targetId, targetType, ...query }
+  });
+};
+
+/**
+ * 查询全部变更日志(不分页)
+ */
+export const listAllChangeLog = (targetId: string | number, targetType: string): AxiosPromise<ArcChangeLogVO[]> => {
+  return request({
+    url: '/archieves/changeLog/listAll',
+    method: 'get',
+    params: { targetId, targetType }
+  });
+};

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

@@ -0,0 +1,10 @@
+export interface ArcChangeLogVO {
+  id: string | number;
+  targetId: number;
+  targetType: string;
+  changeType: string;
+  content: string;
+  operatorId: number;
+  operatorName: string;
+  createTime: string;
+}

+ 78 - 0
src/api/archieves/customer/index.ts

@@ -0,0 +1,78 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { UsrCustomerVO, UsrCustomerForm, UsrCustomerQuery } from '@/api/archieves/customer/types';
+
+/**
+ * 查询用户列表
+ */
+export const listCustomer = (query?: UsrCustomerQuery): AxiosPromise<UsrCustomerVO[]> => {
+  return request({
+    url: '/archieves/customer/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询全部用户(不分页)
+ */
+export const listAllCustomer = (query?: UsrCustomerQuery): AxiosPromise<UsrCustomerVO[]> => {
+  return request({
+    url: '/archieves/customer/listAll',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询用户详细
+ */
+export const getCustomer = (id: string | number): AxiosPromise<UsrCustomerVO> => {
+  return request({
+    url: '/archieves/customer/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增用户
+ */
+export const addCustomer = (data: UsrCustomerForm) => {
+  return request({
+    url: '/archieves/customer',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改用户
+ */
+export const updateCustomer = (data: UsrCustomerForm) => {
+  return request({
+    url: '/archieves/customer',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除用户
+ */
+export const delCustomer = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/archieves/customer/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 切换用户状态
+ */
+export const changeCustomerStatus = (id: string | number, status: number) => {
+  return request({
+    url: '/archieves/customer/changeStatus',
+    method: 'put',
+    params: { id, status }
+  });
+};

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

@@ -0,0 +1,51 @@
+import { SysTagVO } from '@/api/archieves/tag/types';
+
+export interface UsrCustomerVO {
+  id: string | number;
+  name: string;
+  phone: string;
+  avatar: number;
+  avatarUrl: string;
+  gender: number;
+  birthday: string;
+  idCard: string;
+  areaId: number;
+  areaName: string;
+  stationId: number;
+  stationName: string;
+  address: string;
+  emergencyContact: string;
+  emergencyPhone: string;
+  petCount: number;
+  memberLevel: number;
+  status: number;
+  remark: string;
+  createTime: string;
+  tags: SysTagVO[];
+}
+
+export interface UsrCustomerForm extends BaseEntity {
+  id?: string | number;
+  name?: string;
+  phone?: string;
+  avatar?: number;
+  gender?: number;
+  birthday?: string;
+  idCard?: string;
+  areaId?: number;
+  stationId?: number;
+  address?: string;
+  emergencyContact?: string;
+  emergencyPhone?: string;
+  memberLevel?: number;
+  status?: number;
+  remark?: string;
+  tagIds?: (string | number)[];
+}
+
+export interface UsrCustomerQuery extends PageQuery {
+  keyword?: string;
+  areaId?: number;
+  stationId?: number;
+  status?: number;
+}

+ 66 - 0
src/api/archieves/pet/index.ts

@@ -0,0 +1,66 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { UsrPetVO, UsrPetForm, UsrPetQuery } from '@/api/archieves/pet/types';
+
+/**
+ * 查询宠物列表
+ */
+export const listPet = (query?: UsrPetQuery): AxiosPromise<UsrPetVO[]> => {
+  return request({
+    url: '/archieves/pet/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 按用户查询宠物列表
+ */
+export const listPetByUser = (userId: string | number): AxiosPromise<UsrPetVO[]> => {
+  return request({
+    url: '/archieves/pet/listByUser/' + userId,
+    method: 'get'
+  });
+};
+
+/**
+ * 查询宠物详细
+ */
+export const getPet = (id: string | number): AxiosPromise<UsrPetVO> => {
+  return request({
+    url: '/archieves/pet/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增宠物
+ */
+export const addPet = (data: UsrPetForm) => {
+  return request({
+    url: '/archieves/pet',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改宠物
+ */
+export const updatePet = (data: UsrPetForm) => {
+  return request({
+    url: '/archieves/pet',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除宠物
+ */
+export const delPet = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/archieves/pet/' + id,
+    method: 'delete'
+  });
+};

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

@@ -0,0 +1,71 @@
+import { SysTagVO } from '@/api/archieves/tag/types';
+
+export interface UsrPetVO {
+  id: string | number;
+  userId: number;
+  name: string;
+  avatar: number;
+  avatarUrl: string;
+  type: number;
+  breed: string;
+  gender: number;
+  birthday: string;
+  age: number;
+  weight: number;
+  size: string;
+  isSterilized: number;
+  arrivalTime: string;
+  houseType: string;
+  entryMethod: string;
+  entryPassword: string;
+  keyLocation: string;
+  personality: string;
+  cutePersonality: string;
+  healthStatus: string;
+  aggression: number;
+  vaccineStatus: string;
+  vaccineCert: number;
+  vaccineCertUrl: string;
+  medicalHistory: string;
+  allergies: string;
+  remark: string;
+  createTime: string;
+  ownerName: string;
+  ownerPhone: string;
+  tags: SysTagVO[];
+}
+
+export interface UsrPetForm extends BaseEntity {
+  id?: string | number;
+  userId?: number;
+  name?: string;
+  avatar?: number;
+  type?: number;
+  breed?: string;
+  gender?: number;
+  birthday?: string;
+  age?: number;
+  weight?: number;
+  size?: string;
+  isSterilized?: number;
+  arrivalTime?: string;
+  houseType?: string;
+  entryMethod?: string;
+  entryPassword?: string;
+  keyLocation?: string;
+  personality?: string;
+  cutePersonality?: string;
+  healthStatus?: string;
+  aggression?: number;
+  vaccineStatus?: string;
+  vaccineCert?: number;
+  medicalHistory?: string;
+  allergies?: string;
+  remark?: string;
+  tagIds?: (string | number)[];
+}
+
+export interface UsrPetQuery extends PageQuery {
+  keyword?: string;
+  userId?: number;
+}

+ 67 - 0
src/api/archieves/tag/index.ts

@@ -0,0 +1,67 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SysTagVO, SysTagForm, SysTagQuery } from '@/api/archieves/tag/types';
+
+/**
+ * 查询标签列表
+ */
+export const listTag = (query?: SysTagQuery): AxiosPromise<SysTagVO[]> => {
+  return request({
+    url: '/archieves/tag/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询全部标签(不分页)
+ */
+export const listAllTag = (query?: SysTagQuery): AxiosPromise<SysTagVO[]> => {
+  return request({
+    url: '/archieves/tag/listAll',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询标签详细
+ */
+export const getTag = (id: string | number): AxiosPromise<SysTagVO> => {
+  return request({
+    url: '/archieves/tag/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增标签
+ */
+export const addTag = (data: SysTagForm) => {
+  return request({
+    url: '/archieves/tag',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改标签
+ */
+export const updateTag = (data: SysTagForm) => {
+  return request({
+    url: '/archieves/tag',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除标签
+ */
+export const delTag = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/archieves/tag/' + id,
+    method: 'delete'
+  });
+};

+ 26 - 0
src/api/archieves/tag/types.ts

@@ -0,0 +1,26 @@
+export interface SysTagVO {
+  id: string | number;
+  name: string;
+  category: string;
+  colorType: string;
+  description: string;
+  type: number;
+  status: number;
+  createTime: string;
+}
+
+export interface SysTagForm extends BaseEntity {
+  id?: string | number;
+  name?: string;
+  category?: string;
+  colorType?: string;
+  description?: string;
+  type?: number;
+  status?: number;
+}
+
+export interface SysTagQuery extends PageQuery {
+  name?: string;
+  category?: string;
+  status?: number;
+}

+ 55 - 0
src/api/fulfiller/audit/index.ts

@@ -0,0 +1,55 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { FlfAuditVO, FlfAuditQuery } from './types';
+
+/**
+ * 查询审核记录列表
+ */
+export const listAudit = (query?: FlfAuditQuery): AxiosPromise<FlfAuditVO[]> => {
+  return request({
+    url: '/fulfiller/audit/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询审核记录详细
+ */
+export const getAudit = (id: string | number): AxiosPromise<FlfAuditVO> => {
+  return request({
+    url: '/fulfiller/audit/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 查询待审核数量
+ */
+export const getPendingCount = (): AxiosPromise<number> => {
+  return request({
+    url: '/fulfiller/audit/pendingCount',
+    method: 'get'
+  });
+};
+
+/**
+ * 审核通过
+ */
+export const passAudit = (id: string | number) => {
+  return request({
+    url: '/fulfiller/audit/pass/' + id,
+    method: 'put'
+  });
+};
+
+/**
+ * 审核驳回
+ */
+export const rejectAudit = (id: string | number, rejectReason: string) => {
+  return request({
+    url: '/fulfiller/audit/reject/' + id,
+    method: 'put',
+    params: { rejectReason }
+  });
+};

+ 42 - 0
src/api/fulfiller/audit/types.ts

@@ -0,0 +1,42 @@
+/**
+ * 审核记录 VO
+ */
+export interface FlfAuditVO {
+  id: string | number;
+  fulfillerId: string | number;
+  type: string;
+  name: string;
+  phone: string;
+  gender: string;
+  birthday: string;
+  workType: string;
+  city: string;
+  location: string;
+  realName: string;
+  idCard: string;
+  idValidDate: string;
+  idCardFront: string | number;
+  idCardBack: string | number;
+  idCardFrontUrl: string;
+  idCardBackUrl: string;
+  qualifications: string;
+  qualificationUrls: string[];
+  serviceTypes: string;
+  serviceTypeList: string[];
+  stationId: string | number;
+  stationName: string;
+  status: number;
+  auditBy: string | number;
+  auditTime: string;
+  rejectReason: string;
+  createTime: string;
+}
+
+/**
+ * 审核查询参数
+ */
+export interface FlfAuditQuery extends PageQuery {
+  keyword?: string;
+  type?: string;
+  status?: number;
+}

+ 138 - 0
src/api/fulfiller/pool/index.ts

@@ -0,0 +1,138 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import {
+  FlfFulfillerVO, FlfFulfillerForm, FlfFulfillerQuery,
+  FlfRewardForm, FlfAdjustPointsForm, FlfAdjustBalanceForm,
+  FlfPointsLogVO, FlfBalanceLogVO, FlfRewardLogVO
+} from './types';
+
+/**
+ * 查询履约者列表
+ */
+export const listFulfiller = (query?: FlfFulfillerQuery): AxiosPromise<FlfFulfillerVO[]> => {
+  return request({
+    url: '/fulfiller/fulfiller/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询履约者详细
+ */
+export const getFulfiller = (id: string | number): AxiosPromise<FlfFulfillerVO> => {
+  return request({
+    url: '/fulfiller/fulfiller/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增履约者
+ */
+export const addFulfiller = (data: FlfFulfillerForm) => {
+  return request({
+    url: '/fulfiller/fulfiller',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改履约者
+ */
+export const updateFulfiller = (data: FlfFulfillerForm) => {
+  return request({
+    url: '/fulfiller/fulfiller',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 切换状态
+ */
+export const changeStatus = (id: string | number, status: string) => {
+  return request({
+    url: '/fulfiller/fulfiller/changeStatus',
+    method: 'put',
+    params: { id, status }
+  });
+};
+
+/**
+ * 重置密码
+ */
+export const resetPwd = (id: string | number, password: string) => {
+  return request({
+    url: '/fulfiller/fulfiller/resetPwd',
+    method: 'put',
+    params: { id, password }
+  });
+};
+
+/**
+ * 奖惩操作
+ */
+export const reward = (data: FlfRewardForm) => {
+  return request({
+    url: '/fulfiller/fulfiller/reward',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 调整积分
+ */
+export const adjustPoints = (data: FlfAdjustPointsForm) => {
+  return request({
+    url: '/fulfiller/fulfiller/adjustPoints',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 调整余额
+ */
+export const adjustBalance = (data: FlfAdjustBalanceForm) => {
+  return request({
+    url: '/fulfiller/fulfiller/adjustBalance',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 查询积分日志
+ */
+export const listPointsLog = (fulfillerId: string | number, query?: PageQuery): AxiosPromise<FlfPointsLogVO[]> => {
+  return request({
+    url: '/fulfiller/log/points',
+    method: 'get',
+    params: { fulfillerId, ...query }
+  });
+};
+
+/**
+ * 查询余额日志
+ */
+export const listBalanceLog = (fulfillerId: string | number, query?: PageQuery): AxiosPromise<FlfBalanceLogVO[]> => {
+  return request({
+    url: '/fulfiller/log/balance',
+    method: 'get',
+    params: { fulfillerId, ...query }
+  });
+};
+
+/**
+ * 查询奖惩日志
+ */
+export const listRewardLog = (fulfillerId: string | number, query?: PageQuery): AxiosPromise<FlfRewardLogVO[]> => {
+  return request({
+    url: '/fulfiller/log/reward',
+    method: 'get',
+    params: { fulfillerId, ...query }
+  });
+};

+ 165 - 0
src/api/fulfiller/pool/types.ts

@@ -0,0 +1,165 @@
+/**
+ * 履约者信息 VO
+ */
+export interface FlfFulfillerVO {
+  id: string | number;
+  userId: string | number;
+  name: string;
+  realName: string;
+  phone: string;
+  gender: string;
+  birthday: string;
+  age: number;
+  avatar: string | number;
+  avatarUrl: string;
+  idCard: string;
+  idCardFront: string | number;
+  idCardFrontUrl: string;
+  idCardBack: string | number;
+  idCardBackUrl: string;
+  idCardExpiry: string;
+  serviceTypes: string;
+  cityCode: string;
+  cityName: string;
+  stationId: string | number;
+  stationName: string;
+  workType: string;
+  levelId: string | number;
+  levelName: string;
+  points: number;
+  balance: number;
+  status: string;
+  authId: boolean;
+  authQual: boolean;
+  qualImages: string;
+  qualImageUrls: string[];
+  orderCount: number;
+  rejectCount: number;
+  rating: number;
+  createTime: string;
+  tags: FlfTagVO[];
+}
+
+/**
+ * 履约者标签 VO(简化)
+ */
+export interface FlfTagVO {
+  id: string | number;
+  name: string;
+  colorType: string;
+  description: string;
+  status: number;
+}
+
+/**
+ * 履约者表单
+ */
+export interface FlfFulfillerForm extends BaseEntity {
+  id?: string | number;
+  name?: string;
+  realName?: string;
+  phone?: string;
+  password?: string;
+  gender?: string;
+  birthday?: string;
+  idCard?: string;
+  idCardExpiry?: string;
+  serviceTypes?: string;
+  cityCode?: string;
+  cityName?: string;
+  stationId?: string | number;
+  workType?: string;
+  levelId?: string | number;
+  status?: string;
+  authId?: boolean;
+  authQual?: boolean;
+  tagIds?: (string | number)[];
+}
+
+/**
+ * 履约者查询参数
+ */
+export interface FlfFulfillerQuery extends PageQuery {
+  keyword?: string;
+  status?: string;
+  cityCode?: string;
+  stationId?: string | number;
+  workType?: string;
+}
+
+/**
+ * 奖惩操作
+ */
+export interface FlfRewardForm {
+  fulfillerId: string | number;
+  type: string;
+  target: string;
+  amount: number;
+  reason: string;
+}
+
+/**
+ * 积分调整
+ */
+export interface FlfAdjustPointsForm {
+  fulfillerId: string | number;
+  type: string;
+  amount: number;
+  reason: string;
+}
+
+/**
+ * 余额调整
+ */
+export interface FlfAdjustBalanceForm {
+  fulfillerId: string | number;
+  type: string;
+  subType: string;
+  amount: number;
+  reason: string;
+}
+
+/**
+ * 积分日志
+ */
+export interface FlfPointsLogVO {
+  id: string | number;
+  fulfillerId: string | number;
+  type: string;
+  bizType: string;
+  amount: number;
+  pointsAfter: number;
+  reason: string;
+  operatorId: string | number;
+  createTime: string;
+}
+
+/**
+ * 余额日志
+ */
+export interface FlfBalanceLogVO {
+  id: string | number;
+  fulfillerId: string | number;
+  type: string;
+  subType: string;
+  amount: number;
+  balanceAfter: number;
+  reason: string;
+  operatorId: string | number;
+  createTime: string;
+}
+
+/**
+ * 奖惩日志
+ */
+export interface FlfRewardLogVO {
+  id: string | number;
+  fulfillerId: string | number;
+  type: string;
+  target: string;
+  amount: number;
+  reason: string;
+  operatorId: string | number;
+  operatorName: string;
+  createTime: string;
+}

+ 67 - 0
src/api/fulfiller/tag/index.ts

@@ -0,0 +1,67 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { FlfTagVO, FlfTagForm, FlfTagQuery } from './types';
+
+/**
+ * 查询标签列表
+ */
+export const listTag = (query?: FlfTagQuery): AxiosPromise<FlfTagVO[]> => {
+  return request({
+    url: '/fulfiller/tag/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询全部标签(不分页)
+ */
+export const listAllTag = (query?: FlfTagQuery): AxiosPromise<FlfTagVO[]> => {
+  return request({
+    url: '/fulfiller/tag/listAll',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询标签详细
+ */
+export const getTag = (id: string | number): AxiosPromise<FlfTagVO> => {
+  return request({
+    url: '/fulfiller/tag/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增标签
+ */
+export const addTag = (data: FlfTagForm) => {
+  return request({
+    url: '/fulfiller/tag',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改标签
+ */
+export const updateTag = (data: FlfTagForm) => {
+  return request({
+    url: '/fulfiller/tag',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除标签
+ */
+export const delTag = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/fulfiller/tag/' + id,
+    method: 'delete'
+  });
+};

+ 36 - 0
src/api/fulfiller/tag/types.ts

@@ -0,0 +1,36 @@
+/**
+ * 履约者标签 VO
+ */
+export interface FlfTagVO {
+  id: string | number;
+  name: string;
+  colorType: string;
+  category: string;
+  description: string;
+  type: number;
+  status: number;
+  createTime: string;
+}
+
+/**
+ * 履约者标签表单
+ */
+export interface FlfTagForm extends BaseEntity {
+  id?: string | number;
+  name?: string;
+  colorType?: string;
+  category?: string;
+  description?: string;
+  status?: number;
+}
+
+/**
+ * 履约者标签查询参数
+ */
+export interface FlfTagQuery {
+  pageNum?: number;
+  pageSize?: number;
+  name?: string;
+  category?: string;
+  status?: number;
+}

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

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

+ 16 - 5
src/api/service/list/index.ts

@@ -1,11 +1,22 @@
 import request from '@/utils/request';
-import { ServiceListVO } from './types';
+import { ServiceListVO, ServiceOrderVO } from './types';
 import { AxiosPromise } from 'axios';
 
 // 获取可用服务项目列表
 export function listOnStore(): AxiosPromise<ServiceListVO[]> {
-    return request({
-        url: '/service/list/listOnStore',
-        method: 'get'
-    });
+  return request({
+    url: '/service/list/listOnStore',
+    method: 'get'
+  });
 }
+
+/**
+ * 查询下单页的服务列表
+ * @returns {*}
+ */
+export const listServiceOnOrder = (): AxiosPromise<ServiceOrderVO[]> => {
+  return request({
+    url: '/service/list/listOnOrder',
+    method: 'get'
+  });
+};

+ 10 - 2
src/api/service/list/types.ts

@@ -1,4 +1,12 @@
 export interface ServiceListVO {
-    id: number | string;
-    name: string;
+  id: number | string;
+  name: string;
+}
+
+export interface ServiceOrderVO {
+  id: number;
+  name: string;
+  remark: string;
+  icon: string;
+  mode: number;
 }

+ 256 - 0
src/views/order/management/components/CareSummaryDrawer.vue

@@ -0,0 +1,256 @@
+<template>
+  <el-drawer
+    :model-value="visible"
+    @update:model-value="updateVisible"
+    title="宠物护理工作小结"
+    direction="rtl"
+    size="750px"
+    destroy-on-close
+    class="care-summary-drawer"
+  >
+    <div class="care-summary-container" v-if="order">
+      <!-- Pet Header -->
+      <div class="summary-header">
+        <div class="avatar-wrapper">
+          <el-avatar :size="80" :src="order.petAvatar" shape="circle" class="pet-summary-avatar">{{ order.petName?.charAt(0) }}</el-avatar>
+        </div>
+        <div class="pet-summary-info">
+          <div class="summary-name-row">
+            <span class="name">{{ order.petName }}</span>
+            <div class="tags-group">
+              <el-tag :type="order.petGender==='male'?'':'danger'" effect="light" round>
+                <el-icon><component :is="order.petGender==='male'?'Male':'Female'" /></el-icon>
+                {{ order.petAge }}
+              </el-tag>
+              <el-tag v-for="tag in (order.petTags||[])" :key="tag" type="warning" effect="plain" round>{{ tag }}</el-tag>
+            </div>
+          </div>
+          <div class="summary-sub-row">
+            <div class="info-item">
+              <span class="lbl">品种</span>
+              <span class="val">{{ order.petBreed || '未知' }}</span>
+            </div>
+            <div class="divider-v"></div>
+            <div class="info-item">
+              <span class="lbl">体重</span>
+              <span class="val">{{ order.petWeight }}</span>
+            </div>
+            <div class="divider-v"></div>
+            <div class="info-item">
+              <span class="lbl">主人</span>
+              <span class="val">{{ order.userName || '未知' }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Info Groups -->
+      <div class="summary-section">
+        <div class="sec-title">
+          <span class="icon-box"><el-icon><List /></el-icon></span>
+          基本信息
+        </div>
+        <el-descriptions :column="2" border class="spacious-desc">
+          <el-descriptions-item label="性格关键词">{{ order.petPersonality }}</el-descriptions-item>
+          <el-descriptions-item label="健康状况">
+            <el-tag :type="order.healthStatus==='健康'?'success':'danger'" effect="light" size="small">{{ order.healthStatus }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="疫苗情况">
+            <div class="flex-align">
+              <span style="color:#67c23a; margin-right:8px;" v-if="order.vaccineImg"><el-icon><CircleCheckFilled /></el-icon> 已接种</span>
+              <span v-else style="color:#909399;">未接种</span>
+              <el-image
+                v-if="order.vaccineImg"
+                style="width: 24px; height: 24px; border-radius:4px; vertical-align:middle; cursor:zoom-in;"
+                :src="order.vaccineImg"
+                :preview-src-list="[order.vaccineImg]"
+                :preview-teleported="true"
+              />
+            </div>
+          </el-descriptions-item>
+          <el-descriptions-item label="过敏史">
+            <span :style="{color: order.allergy ? '#f56c6c' : 'inherit'}">{{ order.allergy || '无' }}</span>
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <div class="summary-section">
+        <div class="sec-title">
+          <span class="icon-box text-blue"><el-icon><HomeFilled /></el-icon></span>
+          服务环境
+        </div>
+        <el-descriptions :column="2" border class="spacious-desc">
+          <el-descriptions-item label="到家时间">{{ order.homeTime }}</el-descriptions-item>
+          <el-descriptions-item label="房屋类型">{{ order.houseType }}</el-descriptions-item>
+          <el-descriptions-item label="入户方式" :span="2">
+            <span style="font-weight:bold;">{{ order.entryMethod }}</span>
+            <span style="margin-left:8px; color:#909399;">({{ order.entryDetail }})</span>
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <!-- Service Log -->
+      <div class="summary-section main-log">
+        <div class="sec-title" style="border:none; padding-left:0; margin-bottom:16px;">
+          <div class="left">
+            <span class="icon-box text-orange"><el-icon><Notebook /></el-icon></span>
+            服务内容记录
+          </div>
+          <el-button v-if="!isEditingSummary" type="primary" link icon="Edit" @click="isEditingSummary = true">编辑</el-button>
+        </div>
+
+        <div v-if="isEditingSummary" class="edit-area">
+          <el-input
+            v-model="careSummaryText"
+            type="textarea"
+            :rows="12"
+            placeholder="请输入详细的护理服务小结..."
+            resize="none"
+          />
+          <div class="edit-actions">
+            <el-button @click="isEditingSummary = false">取消</el-button>
+            <el-button type="primary" @click="saveCareSummary">保存内容</el-button>
+          </div>
+        </div>
+        <div v-else class="log-content-box">
+          <pre class="log-text">{{ careSummaryText }}</pre>
+        </div>
+      </div>
+
+      <!-- Footer Info -->
+      <div class="summary-footer">
+        <div class="footer-info">
+          <div class="f-row">
+            <span class="lbl">护宠师</span>
+            <span class="val user-active">{{ order.fulfillerName || '当前履约者' }}</span>
+          </div>
+          <div class="f-row">
+            <span class="lbl">提交时间</span>
+            <span class="val">{{ order.summaryTime || '2024-02-04 17:00' }}</span>
+          </div>
+        </div>
+        <div class="footer-action">
+          <el-button size="large" @click="updateVisible(false)">关闭</el-button>
+        </div>
+      </div>
+    </div>
+  </el-drawer>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  visible: Boolean,
+  order: Object
+})
+
+const emit = defineEmits(['update:visible', 'success'])
+
+const isEditingSummary = ref(false)
+const careSummaryText = ref('')
+
+watch(() => props.visible, (val) => {
+  if (val && props.order) {
+    isEditingSummary.value = false
+    if (!props.order.careSummary) {
+      careSummaryText.value = `1. 精神/身体状态:${props.order.petName}精神状态良好,愿意互动。
+2. 进食/饮水:食欲正常,饮水适当,已清洗碗具。
+3. 排泄情况:排便正常,颜色形状正常,已清理。
+4. 卫生情况:猫砂盆/地面已清理干净,无异味。
+5. 互动情况:陪玩了20分钟,${props.order.petName}很开心。
+6. 特殊情况/备注:无特殊异常。`
+    } else {
+      careSummaryText.value = props.order.careSummary
+    }
+  }
+})
+
+const updateVisible = (val) => {
+  emit('update:visible', val)
+}
+
+const saveCareSummary = () => {
+  if (props.order) {
+    props.order.careSummary = careSummaryText.value
+    if (!props.order.summaryTime) {
+      props.order.summaryTime = '2024-02-04 17:00'
+    }
+    ElMessage.success('护理小结已保存')
+    isEditingSummary.value = false
+    emit('success', careSummaryText.value)
+  }
+}
+</script>
+
+<style scoped>
+/* Enhanced Care Summary Styles */
+.care-summary-drawer :deep(.el-drawer__header) { margin-bottom: 0; padding: 20px 24px; border-bottom: 1px solid #f0f0f0; }
+.care-summary-drawer :deep(.el-drawer__body) { padding: 0; overflow-y: auto; background: #fff; }
+
+.care-summary-container { padding: 32px 40px; }
+
+/* 1. Header */
+.summary-header { display: flex; gap: 24px; align-items: flex-start; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px dashed #e4e7ed; }
+.avatar-wrapper { border: 4px solid #f2f6fc; border-radius: 50%; }
+.pet-summary-info { flex: 1; display:flex; flex-direction:column; gap:12px; padding-top: 4px; }
+
+.summary-name-row { display: flex; align-items: center; gap: 16px; margin-bottom: 4px; }
+.summary-name-row .name { font-size: 24px; font-weight: 800; color: #303133; letter-spacing: 0.5px; }
+.tags-group { display: flex; gap: 8px; align-items: center; }
+
+.summary-sub-row { display: flex; align-items: center; background: #f9fafe; padding: 10px 16px; border-radius: 8px; align-self: flex-start; }
+.info-item { display: flex; flex-direction: column; gap: 2px; }
+.info-item .lbl { font-size: 11px; color: #909399; text-transform: uppercase; }
+.info-item .val { font-size: 14px; font-weight: bold; color: #606266; }
+.divider-v { width: 1px; height: 24px; background: #ebeef5; margin: 0 16px; }
+
+/* 2. Sections */
+.summary-section { margin-bottom: 40px; }
+.sec-title {
+  font-size: 16px; font-weight: 700; color: #303133; margin-bottom: 16px;
+  display:flex; align-items:center; gap:8px;
+  justify-content: space-between;
+}
+.sec-title .left { display: flex; align-items: center; gap: 8px; }
+.icon-box {
+  width: 28px; height: 28px; background: #ecf5ff; color: #409eff; border-radius: 6px;
+  display: flex; align-items: center; justify-content: center; font-size: 16px;
+}
+.icon-box.text-blue { background: #ecf5ff; color: #409eff; }
+.icon-box.text-orange { background: #fdf6ec; color: #e6a23c; }
+
+/* 3. Descriptions */
+.spacious-desc :deep(.el-descriptions__cell) { padding: 12px 16px!important; }
+.spacious-desc :deep(.el-descriptions__label) { width: 100px; color: #606266; font-weight: 500; background: #fafafa; }
+.flex-align { display: flex; align-items: center; }
+
+/* 4. Log Area */
+.main-log { background: #fff; }
+.log-content-box {
+  background: #fff;
+  border: 1px solid #ebeef5; border-radius: 8px;
+  padding: 24px;
+  box-shadow: 0 2px 12px rgba(0,0,0,0.02);
+  position: relative;
+}
+.log-content-box::before {
+  content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: #e6a23c; border-top-left-radius: 8px; border-bottom-left-radius: 8px;
+}
+.log-text {
+  white-space: pre-wrap; font-family: 'Inter', system-ui, sans-serif; margin: 0; line-height: 1.8; font-size: 15px; color: #303133; text-align: justify;
+}
+.edit-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 12px; }
+
+/* 5. Footer */
+.summary-footer {
+  margin-top: 60px; padding-top: 24px; border-top: 1px solid #ebeef5;
+  display: flex; justify-content: space-between; align-items: center;
+}
+.footer-info { display: flex; gap: 32px; }
+.f-row { display: flex; flex-direction: column; gap: 4px; }
+.f-row .lbl { font-size: 12px; color: #909399; }
+.f-row .val { font-size: 15px; font-weight: 600; color: #303133; }
+.f-row .val.user-active { color: #409eff; }
+</style>

+ 52 - 0
src/views/order/management/components/RemarkDialog.vue

@@ -0,0 +1,52 @@
+<template>
+  <el-dialog :model-value="visible" @update:model-value="updateVisible" title="订单备注" width="500px">
+    <div style="margin-bottom:10px; font-size:13px; color:#909399;">
+      <span v-if="data">订单号:{{ data.orderNo }}</span>
+    </div>
+    <el-input
+      v-model="remarkForm"
+      type="textarea"
+      :rows="5"
+      placeholder="请输入订单备注信息..."
+    />
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="updateVisible(false)">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">保存备注</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  visible: Boolean,
+  data: Object
+})
+
+const emit = defineEmits(['update:visible', 'success'])
+
+const remarkForm = ref('')
+
+watch(() => props.visible, (val) => {
+  if (val && props.data) {
+    remarkForm.value = props.data.remark || ''
+  }
+})
+
+const updateVisible = (val) => {
+  emit('update:visible', val)
+}
+
+const handleSubmit = () => {
+  if (props.data) {
+    props.data.remark = remarkForm.value
+    ElMessage.success('备注已更新')
+    updateVisible(false)
+    emit('success', remarkForm.value)
+  }
+}
+</script>

+ 101 - 0
src/views/order/management/components/RewardDialog.vue

@@ -0,0 +1,101 @@
+<template>
+  <el-dialog :model-value="visible" @update:model-value="updateVisible" title="奖惩操作" width="500px">
+    <div v-if="data" style="padding: 0 10px;">
+      <div style="margin-bottom: 20px; font-size: 14px; color: #606266; line-height: 1.6; background: #fdf6ec; padding: 10px; border-radius: 4px;">
+        <div>奖惩履约者:<span style="font-weight: bold; color: #303133;">{{ data.fulfillerName || '未指派' }}</span></div>
+        <div style="font-size: 13px; margin-top: 4px;">订单号:{{ data.orderNo }}</div>
+        <div style="font-size: 13px; margin-top: 4px; display:flex; align-items:center; gap:6px;">
+          服务类型:
+          <el-tag :type="getTypeTag(data.type)" size="small">{{ getTypeName(data.type) }}</el-tag>
+          <el-tag v-if="data.type === 'transport' && data.transportType === 'round'" size="small" effect="plain" type="warning">往返</el-tag>
+          <el-tag v-if="data.splitType === 'pick'" size="small" effect="dark" color="#409eff" style="border:none; color:white;">接</el-tag>
+          <el-tag v-if="data.splitType === 'drop'" size="small" effect="dark" color="#67c23a" style="border:none; color:white;">送</el-tag>
+          <el-tag v-if="data.type === 'transport' && data.transportType === 'pick' && !data.splitType" size="small" effect="plain">单程接</el-tag>
+          <el-tag v-if="data.type === 'transport' && data.transportType === 'drop' && !data.splitType" size="small" effect="plain" type="success">单程送</el-tag>
+        </div>
+      </div>
+
+      <el-form :model="rewardForm" label-width="80px">
+        <el-form-item label="操作类型">
+          <el-radio-group v-model="rewardForm.type">
+            <el-radio label="reward">奖励 (增加)</el-radio>
+            <el-radio label="punish">惩罚 (扣除)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="调整项目">
+          <el-radio-group v-model="rewardForm.item">
+            <el-radio label="points">积分</el-radio>
+            <el-radio label="amount">金额 (元)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="数额" required>
+          <el-input-number v-model="rewardForm.value" :min="1" :step="10" />
+        </el-form-item>
+        <el-form-item label="原因备注" required>
+          <el-input
+            v-model="rewardForm.reason"
+            type="textarea"
+            :rows="3"
+            placeholder="请输入奖惩原因..."
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <el-button @click="updateVisible(false)">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">确认执行</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { reactive, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  visible: Boolean,
+  data: Object
+})
+
+const emit = defineEmits(['update:visible', 'success'])
+
+const rewardForm = reactive({
+  type: 'reward',
+  item: 'points',
+  value: 10,
+  reason: ''
+})
+
+watch(() => props.visible, (val) => {
+  if (val) {
+    rewardForm.type = 'reward'
+    rewardForm.item = 'points'
+    rewardForm.value = 10
+    rewardForm.reason = ''
+  }
+})
+
+const getTypeTag = (type) => {
+  const map = { transport: '', feeding: 'warning', washing: 'success' }
+  return map[type]
+}
+
+const getTypeName = (type) => {
+  const map = { transport: '宠物接送', feeding: '上门喂遛', washing: '上门洗护' }
+  return map[type]
+}
+
+const updateVisible = (val) => {
+  emit('update:visible', val)
+}
+
+const handleSubmit = () => {
+  if (!rewardForm.reason) {
+    ElMessage.warning('请输入奖惩原因')
+    return
+  }
+  ElMessage.success(`操作成功:${rewardForm.type === 'reward' ? '奖励' : '惩罚'}已执行`)
+  updateVisible(false)
+  emit('success')
+}
+</script>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 234 - 399
src/views/order/management/index.vue


+ 247 - 0
template/orderList/components/CareSummaryDrawer.vue

@@ -0,0 +1,247 @@
+<template>
+    <el-drawer
+        v-model="drawerVisible"
+        title="宠物护理工作小结"
+        direction="rtl"
+        size="750px"
+        destroy-on-close
+        class="care-summary-drawer"
+    >
+        <div class="care-summary-container" v-if="order">
+             <!-- Pet Header -->
+             <div class="summary-header">
+                  <div class="avatar-wrapper">
+                      <el-avatar :size="80" :src="order.petAvatar" shape="circle" class="pet-summary-avatar">{{ order.petName?.charAt(0) }}</el-avatar>
+                  </div>
+                  <div class="pet-summary-info">
+                       <div class="summary-name-row">
+                           <span class="name">{{ order.petName }}</span>
+                           <div class="tags-group">
+                               <el-tag :type="order.petGender==='male'?'':'danger'" effect="light" round>
+                                   <el-icon><component :is="order.petGender==='male'?'Male':'Female'" /></el-icon>
+                                   {{ order.petAge }}
+                               </el-tag>
+                               <el-tag v-for="tag in (order.petTags||[])" :key="tag" type="warning" effect="plain" round>{{ tag }}</el-tag>
+                           </div>
+                       </div>
+                       <div class="summary-sub-row">
+                           <div class="info-item">
+                               <span class="lbl">品种</span>
+                               <span class="val">{{ order.petBreed || '未知' }}</span>
+                           </div>
+                           <div class="divider-v"></div>
+                           <div class="info-item">
+                               <span class="lbl">体重</span>
+                               <span class="val">{{ order.petWeight }}</span>
+                           </div>
+                           <div class="divider-v"></div>
+                           <div class="info-item">
+                               <span class="lbl">主人</span>
+                               <span class="val">{{ order.userName || '未知' }}</span>
+                           </div>
+                       </div>
+                  </div>
+             </div>
+
+             <!-- Info Groups -->
+             <div class="summary-section">
+                  <div class="sec-title">
+                      <span class="icon-box"><el-icon><List /></el-icon></span> 
+                      基本信息
+                  </div>
+                  <el-descriptions :column="2" border class="spacious-desc">
+                      <el-descriptions-item label="性格关键词">{{ order.petPersonality }}</el-descriptions-item>
+                      <el-descriptions-item label="健康状况">
+                          <el-tag :type="order.healthStatus==='健康'?'success':'danger'" effect="light" size="small">{{ order.healthStatus }}</el-tag>
+                      </el-descriptions-item>
+                      <el-descriptions-item label="疫苗情况">
+                          <div class="flex-align">
+                              <span style="color:#67c23a; margin-right:8px;" v-if="order.vaccineImg"><el-icon><CircleCheckFilled /></el-icon> 已接种</span>
+                              <span v-else style="color:#909399;">未接种</span>
+                              <el-image 
+                                v-if="order.vaccineImg"
+                                style="width: 24px; height: 24px; border-radius:4px; vertical-align:middle; cursor:zoom-in;"
+                                :src="order.vaccineImg"
+                                :preview-src-list="[order.vaccineImg]"
+                                :preview-teleported="true"
+                              />
+                          </div>
+                      </el-descriptions-item>
+                      <el-descriptions-item label="过敏史">
+                          <span :style="{color: order.allergy ? '#f56c6c' : 'inherit'}">{{ order.allergy || '无' }}</span>
+                      </el-descriptions-item>
+                  </el-descriptions>
+             </div>
+
+             <div class="summary-section">
+                  <div class="sec-title">
+                      <span class="icon-box text-blue"><el-icon><HomeFilled /></el-icon></span>
+                      服务环境
+                  </div>
+                   <el-descriptions :column="2" border class="spacious-desc">
+                      <el-descriptions-item label="到家时间">{{ order.homeTime }}</el-descriptions-item>
+                      <el-descriptions-item label="房屋类型">{{ order.houseType }}</el-descriptions-item>
+                      <el-descriptions-item label="入户方式" :span="2">
+                          <span style="font-weight:bold;">{{ order.entryMethod }}</span> 
+                          <span style="margin-left:8px; color:#909399;">({{ order.entryDetail }})</span>
+                      </el-descriptions-item>
+                  </el-descriptions>
+             </div>
+
+             <!-- Service Log -->
+             <div class="summary-section main-log">
+                  <div class="sec-title" style="border:none; padding-left:0; margin-bottom:16px;">
+                      <div class="left">
+                          <span class="icon-box text-orange"><el-icon><Notebook /></el-icon></span>
+                          服务内容记录
+                      </div>
+                      <el-button v-if="!isEditingSummary" type="primary" link icon="Edit" @click="isEditingSummary = true">编辑</el-button>
+                  </div>
+                  
+                  <div v-if="isEditingSummary" class="edit-area">
+                      <el-input 
+                        v-model="careSummaryText" 
+                        type="textarea" 
+                        :rows="12" 
+                        placeholder="请输入详细的护理服务小结..."
+                        resize="none"
+                      />
+                      <div class="edit-actions">
+                          <el-button @click="isEditingSummary = false">取消</el-button>
+                          <el-button type="primary" @click="saveCareSummary">保存内容</el-button>
+                      </div>
+                  </div>
+                  <div v-else class="log-content-box">
+                      <pre class="log-text">{{ careSummaryText }}</pre>
+                  </div>
+             </div>
+             
+             <!-- Footer Info -->
+             <div class="summary-footer">
+                 <div class="footer-info">
+                     <div class="f-row">
+                         <span class="lbl">护宠师</span>
+                         <span class="val user-active">{{ order.fulfillerName || '当前履约者' }}</span>
+                     </div>
+                     <div class="f-row">
+                         <span class="lbl">提交时间</span>
+                         <span class="val">{{ order.summaryTime || '2024-02-04 17:00' }}</span>
+                     </div>
+                 </div>
+                 <div class="footer-action">
+                     <el-button size="large" @click="drawerVisible = false">关闭</el-button>
+                 </div>
+             </div>
+        </div>
+    </el-drawer>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+    visible: Boolean,
+    order: Object
+})
+const emit = defineEmits(['update:visible', 'submit'])
+
+const drawerVisible = computed({
+    get: () => props.visible,
+    set: (val) => emit('update:visible', val)
+})
+
+const careSummaryText = ref('')
+const isEditingSummary = ref(false)
+
+watch(() => props.visible, (val) => {
+    if (val && props.order) {
+        isEditingSummary.value = false
+        if (!props.order.careSummary) {
+            careSummaryText.value = `1. 精神/身体状态:${props.order.petName}精神状态良好,愿意互动。
+2. 进食/饮水:食欲正常,饮水适当,已清洗碗具。
+3. 排泄情况:排便正常,颜色形状正常,已清理。
+4. 卫生情况:猫砂盆/地面已清理干净,无异味。
+5. 互动情况:陪玩了20分钟,${props.order.petName}很开心。
+6. 特殊情况/备注:无特殊异常。`
+        } else {
+            careSummaryText.value = props.order.careSummary
+        }
+    }
+})
+
+const saveCareSummary = () => {
+    emit('submit', careSummaryText.value)
+    isEditingSummary.value = false
+}
+</script>
+
+<style scoped>
+/* Enhanced Care Summary Styles */
+.care-summary-drawer :deep(.el-drawer__header) { margin-bottom: 0; padding: 20px 24px; border-bottom: 1px solid #f0f0f0; }
+.care-summary-drawer :deep(.el-drawer__body) { padding: 0; overflow-y: auto; background: #fff; }
+
+.care-summary-container { padding: 32px 40px; }
+
+/* 1. Header */
+.summary-header { display: flex; gap: 24px; align-items: flex-start; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px dashed #e4e7ed; }
+.avatar-wrapper { border: 4px solid #f2f6fc; border-radius: 50%; }
+.pet-summary-info { flex: 1; display:flex; flex-direction:column; gap:12px; padding-top: 4px; }
+
+.summary-name-row { display: flex; align-items: center; gap: 16px; margin-bottom: 4px; }
+.summary-name-row .name { font-size: 24px; font-weight: 800; color: #303133; letter-spacing: 0.5px; }
+.tags-group { display: flex; gap: 8px; align-items: center; }
+
+.summary-sub-row { display: flex; align-items: center; background: #f9fafe; padding: 10px 16px; border-radius: 8px; align-self: flex-start; }
+.info-item { display: flex; flex-direction: column; gap: 2px; }
+.info-item .lbl { font-size: 11px; color: #909399; text-transform: uppercase; }
+.info-item .val { font-size: 14px; font-weight: bold; color: #606266; }
+.divider-v { width: 1px; height: 24px; background: #ebeef5; margin: 0 16px; }
+
+/* 2. Sections */
+.summary-section { margin-bottom: 40px; }
+.sec-title { 
+    font-size: 16px; font-weight: 700; color: #303133; margin-bottom: 16px; 
+    display:flex; align-items:center; gap:8px;
+    justify-content: space-between;
+}
+.sec-title .left { display: flex; align-items: center; gap: 8px; }
+.icon-box { 
+    width: 28px; height: 28px; background: #ecf5ff; color: #409eff; border-radius: 6px; 
+    display: flex; align-items: center; justify-content: center; font-size: 16px;
+}
+.icon-box.text-blue { background: #ecf5ff; color: #409eff; }
+.icon-box.text-orange { background: #fdf6ec; color: #e6a23c; }
+
+/* 3. Descriptions */
+.spacious-desc :deep(.el-descriptions__cell) { padding: 12px 16px!important; }
+.spacious-desc :deep(.el-descriptions__label) { width: 100px; color: #606266; font-weight: 500; background: #fafafa; }
+.flex-align { display: flex; align-items: center; }
+
+/* 4. Log Area */
+.main-log { background: #fff; }
+.log-content-box { 
+    background: #fff; 
+    border: 1px solid #ebeef5; border-radius: 8px; 
+    padding: 24px; 
+    box-shadow: 0 2px 12px rgba(0,0,0,0.02);
+    position: relative;
+}
+.log-content-box::before {
+    content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: #e6a23c; border-top-left-radius: 8px; border-bottom-left-radius: 8px;
+}
+.log-text { 
+    white-space: pre-wrap; font-family: 'Inter', system-ui, sans-serif; margin: 0; line-height: 1.8; font-size: 15px; color: #303133; text-align: justify;
+}
+.edit-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 12px; }
+
+/* 5. Footer */
+.summary-footer { 
+    margin-top: 60px; padding-top: 24px; border-top: 1px solid #ebeef5; 
+    display: flex; justify-content: space-between; align-items: center;
+}
+.footer-info { display: flex; gap: 32px; }
+.f-row { display: flex; flex-direction: column; gap: 4px; }
+.f-row .lbl { font-size: 12px; color: #909399; }
+.f-row .val { font-size: 15px; font-weight: 600; color: #303133; }
+.f-row .val.user-active { color: #409eff; }
+</style>

+ 578 - 0
template/orderList/components/DispatchDialog.vue

@@ -0,0 +1,578 @@
+<template>
+    <el-dialog v-model="dialogVisible" title="派单调度" width="900px" top="5vh" destroy-on-close append-to-body>
+        <div class="dispatch-dialog-content">
+            <!-- Top: Order Info (OrderDispatch Style) -->
+            <div class="dispatch-order-info" v-if="order">
+                <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="order.typeCode">
+                            {{ getShortType(order.typeCode) }}
+                        </div>
+                    </div>
+                    <div class="card-main">
+                        <template v-if="order.typeCode === 'transport'">
+                            <div class="row-addr" :title="order.toAddress || order.pickAddr || order.dropAddr">
+                                <span class="tag home">址</span> {{ order.toAddress || order.pickAddr || order.dropAddr
+                                }}
+                            </div>
+                        </template>
+                        <template v-else>
+                            <div class="row-addr" :title="order.address">
+                                <span class="tag home">址</span> {{ order.address }}
+                            </div>
+                        </template>
+                        <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>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Current Rider Info (If Exists) -->
+            <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;">
+                    <div class="card-left relative">
+                        <el-avatar :src="currentRider.avatar" :size="40" />
+                    </div>
+                    <div class="card-main">
+                        <div class="row-1"
+                            style="justify-content: space-between; align-items: flex-start; display: flex;">
+                            <div style="display:flex; align-items:baseline; gap:8px;">
+                                <span class="r-name">{{ currentRider.name || '--' }}</span>
+                                <span class="r-phone">{{ currentRider.phone || '--' }}</span>
+                                <el-tag v-if="currentRider.status" size="small" :type="getStatusType(currentRider.status)" effect="plain">
+                                    {{ getStatusText(currentRider.status) }}
+                                </el-tag>
+                            </div>
+                        </div>
+
+                        <div class="row-2 categories-row"
+                            style="margin-top: 6px; display:flex; gap:4px; flex-wrap:wrap;">
+                            <el-tag v-for="cat in (currentRider.tags || [])" :key="cat" size="small"
+                                :type="getTagType(cat)" effect="plain">{{ getTagText(cat) }}</el-tag>
+                        </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 class="rider-scroll">
+                        <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>
+                                <div class="card-main">
+                                    <div class="row-1" style="justify-content: space-between; align-items: flex-start; display: flex;">
+                                        <div style="display:flex; align-items:baseline; gap:8px;">
+                                            <span class="r-name">{{ rider.name || '--' }}</span>
+                                            <span class="r-phone">{{ rider.phone || '--' }}</span>
+                                        </div>
+                                        <el-tag v-if="rider.status" size="small" :type="getStatusType(rider.status)" effect="plain">
+                                            {{ getStatusText(rider.status) }}
+                                        </el-tag>
+                                    </div>
+
+                                    <div class="row-2 categories-row"
+                                        style="margin-top: 6px; display:flex; gap:4px; flex-wrap:wrap;">
+                                        <el-tag v-for="cat in (rider.tags || [])" :key="cat" size="small"
+                                            :type="getTagType(cat)" effect="plain">{{ getTagText(cat) }}</el-tag>
+                                    </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 class="rider-pagination" style="margin-top: 20px;">
+                    <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize"
+                        :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" :total="total"
+                        @current-change="loadRiders" @size-change="handlePageSizeChange" />
+                </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="dialogVisible = false">取消</el-button>
+                    <el-button type="primary" :disabled="!canSubmit" @click="handleDispatchSubmit">确认派单</el-button>
+                </div>
+            </div>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { pageFulfillerOnOrder } from '@/api/fulfiller/pool'
+import { listAllTag } from '@/api/fulfiller/tag'
+
+const props = defineProps({
+    visible: Boolean,
+    order: Object
+})
+const emit = defineEmits(['update:visible', 'submit'])
+
+const dialogVisible = computed({
+    get: () => props.visible,
+    set: (val) => emit('update:visible', val)
+})
+
+const ridersList = ref([])
+const total = ref(0)
+const pageNum = ref(1)
+const pageSize = ref(10)
+
+const allTags = ref([])
+const tagMap = computed(() => {
+    const map = {}
+    for (const t of (allTags.value || [])) {
+        if (t && t.id !== undefined && t.id !== null) map[t.id] = t
+    }
+    return map
+})
+
+const currentRider = ref(null)
+const dispatchSearchQuery = ref('')
+const selectedRiderId = ref(null)
+const dispatchFee = ref(0)
+
+const loadAllTags = async () => {
+    if (allTags.value && allTags.value.length > 0) return
+    try {
+        const res = await listAllTag({ category: 'fulfiller' })
+        allTags.value = res?.data || []
+    } catch {
+        allTags.value = []
+    }
+}
+
+const loadRiders = async () => {
+    try {
+        const res = await pageFulfillerOnOrder({
+            content: dispatchSearchQuery.value || undefined,
+            pageNum: pageNum.value,
+            pageSize: pageSize.value
+        })
+        ridersList.value = res?.rows || []
+        total.value = res?.total || 0
+
+        if (props.order?.riderId) {
+            currentRider.value = ridersList.value.find(r => r.id === props.order.riderId) || null
+        }
+    } catch {
+        ridersList.value = []
+        total.value = 0
+    }
+}
+
+const handlePageSizeChange = (size) => {
+    pageSize.value = size
+    pageNum.value = 1
+    loadRiders()
+}
+
+watch(() => props.visible, (val) => {
+    if (val && props.order) {
+        currentRider.value = null
+        dispatchSearchQuery.value = ''
+        selectedRiderId.value = null
+        dispatchFee.value = 0
+        pageNum.value = 1
+        loadAllTags()
+        loadRiders()
+    }
+})
+
+const getTagText = (tagId) => {
+    const t = tagMap.value?.[tagId]
+    return t?.name || String(tagId)
+}
+
+const getTagType = (tagId) => {
+    const t = tagMap.value?.[tagId]
+    const type = t?.colorType
+    if (type === 'success' || type === 'warning' || type === 'danger' || type === 'info') return type
+    return ''
+}
+
+watch(dispatchSearchQuery, () => {
+    pageNum.value = 1
+    loadRiders()
+})
+
+const getShortType = (code) => {
+    const map = { 'transport': '接送', 'feeding': '喂遛', 'washing': '洗护' }
+    return map[code] || '订单'
+}
+
+const getStatusText = (status) => {
+    const statusMap = {
+        resting: '休息',
+        busy: '接单中',
+        disabled: '禁用'
+    }
+    return statusMap[status] || status
+}
+
+const getStatusType = (status) => {
+    const typeMap = {
+        resting: 'info',
+        busy: 'success',
+        disabled: 'danger'
+    }
+    return typeMap[status] || 'info'
+}
+
+const filteredDispatchRiders = computed(() => {
+    return ridersList.value || []
+})
+
+const canSubmit = computed(() => {
+    return !!selectedRiderId.value && !!dispatchFee.value
+})
+
+const handleDispatchSubmit = () => {
+    if (!selectedRiderId.value) {
+        ElMessage.warning('请选择履约者')
+        return
+    }
+    if (!dispatchFee.value) {
+        ElMessage.warning('请输入服务费用')
+        return
+    }
+    const rider = ridersList.value.find(r => r.id === selectedRiderId.value)
+    emit('submit', {
+        riderId: rider.id,
+        riderName: rider.name,
+        fee: dispatchFee.value
+    })
+    dialogVisible.value = false
+}
+</script>
+
+<style scoped>
+/* Dispatch Dialog Styles */
+.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-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);
+}
+
+.dispatch-order-info {
+    background: #f5f7fa;
+    padding: 10px;
+    border-radius: 4px;
+    margin-bottom: 20px;
+    border: 1px solid #e4e7ed;
+    display: block;
+}
+
+.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-scroll {
+    max-height: 45vh;
+}
+
+.rider-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+    padding-right: 10px;
+}
+
+.rider-pagination {
+    margin-top: 10px;
+}
+
+.rider-card.select-card {
+    cursor: pointer;
+    border: 1px solid #dcdfe6;
+    position: relative;
+    transition: all 0.2s;
+    margin-bottom: 0;
+}
+
+.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;
+}
+
+.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-phone {
+    font-size: 12px;
+    color: #909399;
+}
+
+.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;
+}
+
+.cat-tag {
+    background: #f4f4f5;
+    color: #909399;
+    font-size: 10px;
+    padding: 1px 4px;
+    border-radius: 2px;
+    margin-right: 4px;
+}
+
+.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;
+}
+
+.last-time {
+    font-size: 11px;
+    color: #999;
+}
+
+.empty-text {
+    text-align: center;
+    color: #909399;
+    padding: 20px;
+    width: 100%;
+    grid-column: span 2;
+}
+
+.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;
+}
+</style>

+ 929 - 0
template/orderList/components/OrderDetailDrawer.vue

@@ -0,0 +1,929 @@
+<template>
+    <el-drawer v-model="drawerVisible" title="订单详情" direction="rtl" size="60%" class="order-detail-drawer">
+        <div class="detail-container" v-if="order">
+            <!-- 1. Header Status -->
+            <div class="detail-header">
+                <div class="left-head">
+                    <span class="order-no">{{ order.orderNo }}</span>
+                    <el-tag :type="getStatusTag(order.status)" effect="dark" class="status-tag">{{
+                        getStatusName(order.status) }}</el-tag>
+                    <el-tag effect="plain" class="type-tag"
+                        :type="order.type === 'transport' ? '' : (order.type === 'feeding' ? 'warning' : 'danger')">
+                        {{ getTypeName(order.type) }}
+                    </el-tag>
+                </div>
+                <div class="right-head">
+                    <!-- Action Buttons Group -->
+                    <div class="detail-actions">
+                        <template v-if="[0, 1, 2].includes(order.status)">
+                            <el-button type="success" icon="Bicycle" @click="emit('dispatch', order)">
+                                {{ order.fulfiller || order.fulfillerName ? '重新派单' : '立即派单' }}
+                            </el-button>
+                        </template>
+
+                        <template v-if="order.status === 0">
+                            <el-button type="danger" plain icon="CircleClose"
+                                @click="emit('cancel', order)">取消订单</el-button>
+                        </template>
+
+                        <template v-if="order.status === 3">
+                            <el-button type="primary" icon="CircleCheck"
+                                @click="emit('command', 'complete', order)">确认完成</el-button>
+                        </template>
+
+                        <template v-if="[3, 4].includes(order.status)">
+                            <el-button icon="Notebook" @click="emit('care-summary', order)">护理小结</el-button>
+                        </template>
+
+                        <el-dropdown trigger="click" @command="(cmd) => emit('command', cmd, order)"
+                            style="margin-left: 12px;">
+                            <el-button icon="More">更多操作</el-button>
+                            <template #dropdown>
+                                <el-dropdown-menu>
+                                    <el-dropdown-item command="reward" icon="Trophy">奖惩操作</el-dropdown-item>
+                                    <el-dropdown-item command="remark" icon="EditPen">订单备注</el-dropdown-item>
+                                    <el-dropdown-item command="delete" v-if="[5, 4].includes(order.status)" divided
+                                        icon="Delete" style="color: #f56c6c;">删除订单</el-dropdown-item>
+                                </el-dropdown-menu>
+                            </template>
+                        </el-dropdown>
+                    </div>
+                </div>
+            </div>
+
+            <div class="detail-scroll-area">
+                <!-- 2. Progress Section -->
+                <div class="progress-section">
+                    <el-steps :active="currentOrderSteps.active" finish-status="success" align-center
+                        class="custom-steps">
+                        <el-step v-for="(step, index) in currentOrderSteps.steps" :key="index" :title="step.title"
+                            :description="step.time" />
+                    </el-steps>
+                </div>
+
+                <!-- 3. Top Info: Pet & User -->
+                <div class="top-info-row">
+                    <!-- Left: Pet Info -->
+                    <div class="info-section pet-section">
+                        <div class="sec-header">
+                            <span class="label">宠物档案</span>
+                            <el-tag size="small" effect="plain">{{ order.petBreed }}</el-tag>
+                        </div>
+                        <div class="pet-basic-row">
+                            <el-avatar :size="50" :src="order.petAvatar" shape="square" class="pet-avatar-lg">{{
+                                (order.petName || '').charAt(0) }}</el-avatar>
+                            <div class="pet-names">
+                                <div class="b-name">{{ order.petName }}
+                                    <el-icon v-if="order.petGender === 'male'" color="#409eff">
+                                        <Male />
+                                    </el-icon>
+                                    <el-icon v-else color="#f56c6c">
+                                        <Female />
+                                    </el-icon>
+                                </div>
+                                <div class="b-tags">
+                                    <el-tag size="small" type="info">{{ order.petAge || '未知年龄' }}</el-tag>
+                                    <el-tag size="small" type="info">{{ order.petWeight || '未知体重' }}</el-tag>
+                                </div>
+                            </div>
+                        </div>
+                        <el-descriptions :column="2" size="small" class="pet-desc" border>
+                            <el-descriptions-item label="绝育状态">{{ order.petSterilized ? '已绝育' : '未绝育'
+                            }}</el-descriptions-item>
+                            <el-descriptions-item label="疫苗状态"><span style="color:#67c23a">{{ order.petVaccine || '未知'
+                            }}</span></el-descriptions-item>
+                            <el-descriptions-item label="性格特点">{{ order.petCharacter || '温顺' }}</el-descriptions-item>
+                            <el-descriptions-item label="健康状况">{{ order.petHealth || '健康' }}</el-descriptions-item>
+                        </el-descriptions>
+                    </div>
+
+                    <!-- Right: User Info -->
+                    <div class="info-section user-section">
+                        <div class="sec-header">
+                            <span class="label">用户信息</span>
+                        </div>
+                        <div class="user-content">
+                            <div class="u-row">
+                                <el-avatar :size="40" :src="order.userAvatar">{{ (order.userName || '').charAt(0)
+                                }}</el-avatar>
+                                <div class="u-info">
+                                    <div class="nm">{{ order.userName }}</div>
+                                    <div class="ph">{{ order.contactPhone }}</div>
+                                </div>
+                            </div>
+                            <div class="addr-box">
+                                <div class="addr-label">服务地址</div>
+                                <div class="addr-txt">{{ order.city }}{{ order.district }} {{ order.address || '' }}
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 4. Bottom Tabs -->
+                <el-tabs v-model="activeDetailTab" class="detail-tabs type-card">
+                    <!-- Tab 1: Basic Info -->
+                    <el-tab-pane label="订单基础信息" name="basic">
+                        <div class="tab-pane-content">
+                            <div class="section-block">
+                                <div class="sec-title-bar">基础业务信息</div>
+                                <el-descriptions :column="3" border size="default" class="custom-desc">
+                                    <el-descriptions-item label="系统单号">{{ order.orderNo }}</el-descriptions-item>
+                                    <el-descriptions-item label="服务类型">
+                                        {{ getTypeName(order.type) }}
+                                        <el-tag size="small" v-if="order.type === 'transport'" style="margin-left:5px"
+                                            effect="light">{{ getTransportModeName(order.transportType) }}</el-tag>
+                                    </el-descriptions-item>
+
+                                    <el-descriptions-item label="归属门店">{{ order.merchantName }}
+                                        ({{ Number(order.platformId) === 1 ? '门店下单' : '平台代下单' }})</el-descriptions-item>
+                                    <el-descriptions-item label="宠主信息">{{ order.userName }} / {{ order.contactPhone
+                                    }}</el-descriptions-item>
+                                    <el-descriptions-item label="服务费用" label-class-name="money-label">
+                                        <span style="color:#f56c6c; font-weight:bold;">¥ {{ order.fulfillerFee }}</span>
+                                    </el-descriptions-item>
+
+                                    <el-descriptions-item label="预约时间">{{ getServiceTimeRange(order.serviceTime)
+                                    }}</el-descriptions-item>
+                                    <el-descriptions-item label="团购套餐">{{ order.groupBuyPackage || '未使用团购套餐'
+                                    }}</el-descriptions-item>
+                                    <el-descriptions-item label="创建时间">{{ order.createTime }}</el-descriptions-item>
+
+                                    <el-descriptions-item label="订单备注" :span="3">
+                                        {{ order.remark || '暂无备注' }}
+                                    </el-descriptions-item>
+                                </el-descriptions>
+                            </div>
+
+                            <div v-if="order.type === 'transport'" class="section-block transport-split-block">
+                                <div class="sec-title-bar">接送任务详情</div>
+                                <div class="transport-one">
+                                    <div class="t-row">
+                                        <el-tag size="small" effect="plain" class="sub-badge">{{
+                                            getTransportLabel(order.subOrderType) }}</el-tag>
+                                        <span class="time">{{ order.serviceTime }}</span>
+                                    </div>
+                                    <div class="t-row">
+                                        <span class="t-k">起点</span>
+                                        <span class="t-v">{{ order.detail?.fromAddress || order.detail?.pickAddr || '--'
+                                            }}</span>
+                                    </div>
+                                    <div class="t-row">
+                                        <span class="t-k">终点</span>
+                                        <span class="t-v">{{ order.detail?.toAddress || order.detail?.dropAddr ||
+                                            order.toAddress ||
+                                            '--' }}</span>
+                                    </div>
+                                    <div class="t-row sub">
+                                        <span class="t-v">{{ order.contact || order.userName || '--' }} {{
+                                            order.contactPhoneNumber
+                                            || order.contactPhone || '--' }}</span>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div v-if="['feeding', 'washing'].includes(order.type)" class="section-block">
+                                <div class="sec-title-bar">服务执行要求</div>
+                                <el-descriptions :column="2" border size="default" class="custom-desc">
+                                    <el-descriptions-item label="服务地址" :span="2">{{ order.detail.area || order.address
+                                    }}</el-descriptions-item>
+                                </el-descriptions>
+                            </div>
+                        </div>
+                    </el-tab-pane>
+
+                    <!-- Tab 2: Fulfiller Info -->
+                    <el-tab-pane label="指派履约者" name="fulfiller">
+                        <div class="tab-pane-content">
+                            <div v-if="order.fulfillerName" class="fulfiller-card">
+                                <div class="f-left">
+                                    <el-avatar :size="60" :src="order.fulfillerAvatar">{{ order.fulfillerName.charAt(0)
+                                    }}</el-avatar>
+                                </div>
+                                <div class="f-right">
+                                    <div class="f-row1">
+                                        <span class="f-name">{{ order.fulfillerName }}</span>
+                                        <el-tag size="small" type="primary" effect="plain" round>Lv1 普通</el-tag>
+                                    </div>
+                                    <div class="f-row2">
+                                        <span>联系电话:{{ order.fulfillerPhone || '138****0000' }}</span>
+                                        <span class="sep">|</span>
+                                        <span>归属区域:{{ order.fulfillerStation || '朝阳一站' }}</span>
+                                    </div>
+                                    <div class="f-row3"
+                                        style="margin-top: 8px; font-size: 13px; color: #606266; background: #f9fafe; padding: 8px; border-radius: 4px; display: flex; gap: 20px;">
+                                        <span><span style="color:#909399;">指派时间:</span>{{ order.createTime }}</span>
+                                        <span><span style="color:#909399;">接单时间:</span>{{ order.detail?.receiveTime ||
+                                            order.serviceTime }}</span>
+                                    </div>
+                                </div>
+                            </div>
+                            <div v-else class="empty-state">
+                                <el-result icon="info" title="暂无履约者" sub-title="该订单尚未指派履约人员"></el-result>
+                            </div>
+                        </div>
+                    </el-tab-pane>
+
+                    <!-- Tab 3: Service Progress -->
+                    <el-tab-pane label="服务进度" name="service">
+                        <div class="tab-pane-content">
+                            <div v-if="serviceProgressSteps.length === 0" class="empty-progress"
+                                style="padding:40px; text-align:center; color:#909399;">
+                                <el-result icon="info" title="待接单" sub-title="履约者接单后将在此记录服务进度"></el-result>
+                            </div>
+
+                            <el-timeline style="padding: 10px 20px;" v-else>
+                                <el-timeline-item v-for="(step, index) in serviceProgressSteps" :key="index"
+                                    :timestamp="step.time" placement="top" :color="step.color" :icon="step.icon"
+                                    size="large">
+                                    <div class="progress-card">
+                                        <h4 class="p-title">{{ step.title }}</h4>
+                                        <p class="p-desc">{{ step.desc }}</p>
+                                        <div class="p-media" v-if="step.media && step.media.length">
+                                            <div v-for="(item, i) in step.media" :key="i" class="media-item">
+                                                <el-image v-if="item.type === 'image'" :src="item.url"
+                                                    :preview-src-list="step.media.map(m => m.url)" fit="cover"
+                                                    class="p-img" :preview-teleported="true" />
+                                            </div>
+                                        </div>
+                                    </div>
+                                </el-timeline-item>
+                            </el-timeline>
+                        </div>
+                    </el-tab-pane>
+
+                    <!-- Tab 4: Logs -->
+                    <el-tab-pane label="订单日志" name="logs">
+                        <div class="tab-pane-content">
+                            <div style="display: flex; justify-content: flex-end; margin-bottom: 15px;">
+                                <el-button type="primary" size="small" icon="Download"
+                                    @click="handleExportLogs">导出日志Excel</el-button>
+                            </div>
+                            <el-timeline>
+                                <el-timeline-item v-for="(log, index) in (orderLogs || [])" :key="index" :timestamp="''"
+                                    :type="'primary'" :icon="undefined" placement="top">
+                                    <div class="log-card">
+                                        <div class="l-tit">{{ log.title }}</div>
+                                        <div class="l-txt">{{ log.content }}</div>
+                                    </div>
+                                </el-timeline-item>
+                            </el-timeline>
+                        </div>
+                    </el-tab-pane>
+                </el-tabs>
+            </div>
+        </div>
+    </el-drawer>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { getPet } from '@/api/archieves/pet'
+import { getCustomer } from '@/api/archieves/customer'
+import { listSubOrderLog } from '@/api/order/subOrderLog'
+
+const props = defineProps({
+    visible: Boolean,
+    order: Object
+})
+
+const emit = defineEmits(['update:visible', 'dispatch', 'cancel', 'command', 'care-summary'])
+
+const drawerVisible = computed({
+    get: () => props.visible,
+    set: (val) => emit('update:visible', val)
+})
+
+const orderDetail = ref(null)
+const order = computed(() => orderDetail.value || props.order)
+
+const loadSeq = ref(0)
+
+const orderLogs = ref([])
+const fulfillerLogs = ref([])
+
+const loadOrderLogs = async (order) => {
+    const id = order?.id
+    if (!id) {
+        orderLogs.value = []
+        fulfillerLogs.value = []
+        return
+    }
+    try {
+        const res = await listSubOrderLog({ orderId: id })
+        const list = res?.data?.data || res?.data || []
+        const arr = Array.isArray(list) ? list : []
+        orderLogs.value = arr.filter(i => Number(i?.logType) === 0)
+        fulfillerLogs.value = arr.filter(i => Number(i?.logType) === 1)
+    } catch {
+        orderLogs.value = []
+        fulfillerLogs.value = []
+    }
+}
+
+const loadPetAndCustomer = async (order) => {
+    const seq = ++loadSeq.value
+    const next = { ...(order || {}) }
+
+    const petId = next?.pet || next?.petId
+    if (petId) {
+        try {
+            const res = await getPet(petId)
+            const pet = res?.data
+            if (pet) {
+                next.petName = pet.name ?? next.petName
+                next.petAvatar = pet.avatarUrl ?? next.petAvatar
+                next.petGender = pet.gender ?? next.petGender
+                next.petAge = (pet.age !== undefined && pet.age !== null) ? `${pet.age}岁` : next.petAge
+                next.petWeight = (pet.weight !== undefined && pet.weight !== null) ? `${pet.weight}kg` : next.petWeight
+                next.petBreed = pet.breed ?? next.petBreed
+                next.petSterilized = (pet.isSterilized !== undefined && pet.isSterilized !== null)
+                    ? (Number(pet.isSterilized) === 1)
+                    : next.petSterilized
+                next.petVaccine = pet.vaccineStatus ?? next.petVaccine
+                next.petCharacter = pet.personality ?? next.petCharacter
+                next.petHealth = pet.healthStatus ?? next.petHealth
+            }
+        } catch {
+        }
+    }
+
+    const customerId = next?.customer || next?.customerId
+    if (customerId) {
+        try {
+            const res = await getCustomer(customerId)
+            const customer = res?.data
+            if (customer) {
+                next.userName = customer.name ?? next.userName
+                next.userAvatar = customer.avatarUrl ?? next.userAvatar
+                next.contactPhone = customer.phone ?? next.contactPhone
+                next.city = customer.areaName ?? next.city
+                next.address = customer.address ?? next.address
+            }
+        } catch {
+        }
+    }
+
+    if (seq !== loadSeq.value) return
+    orderDetail.value = next
+}
+
+watch(() => props.order, (val) => {
+    if (!val) {
+        orderDetail.value = null
+        orderLogs.value = []
+        fulfillerLogs.value = []
+        return
+    }
+    loadPetAndCustomer(val)
+    loadOrderLogs(val)
+}, { immediate: true, deep: true })
+
+const activeDetailTab = ref('basic')
+
+const getStatusName = (status) => {
+    const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消' }
+    return map[status] || '未知'
+}
+const getStatusTag = (status) => {
+    const map = { 0: 'danger', 1: 'warning', 2: 'primary', 3: 'warning', 4: 'success', 5: 'info' }
+    return map[status] || 'info'
+}
+const getTypeName = (type) => {
+    const map = { transport: '宠物接送', feeding: '上门喂遛', washing: '上门洗护' }
+    return map[type]
+}
+const getTransportModeName = (type) => {
+    const map = { round: '往返接送', pick: '单程接(到店)', drop: '单程送(回家)' }
+    return map[type] || '接送服务'
+}
+
+const getTransportLabel = (t) => {
+    if (t === 0 || t === '0') return '接'
+    if (t === 1 || t === '1') return '送'
+    if (t === 2 || t === '2') return '单程接'
+    if (t === 3 || t === '3') return '单程送'
+    return '接送'
+}
+const getServiceTimeRange = (timeStr) => {
+    if (!timeStr) return '--'
+    try {
+        if (timeStr.length < 16) return timeStr
+        let timePart = timeStr.substring(11, 16)
+        let [hh, mm] = timePart.split(':').map(Number)
+        let endH = hh + 2
+        if (endH >= 24) endH -= 24
+        let endHStr = endH.toString().padStart(2, '0')
+        return `${timeStr}-${endHStr}:${mm.toString().padStart(2, '0')}`
+    } catch (e) {
+        return timeStr
+    }
+}
+
+const currentOrderSteps = computed(() => {
+    if (!props.order) return { active: 0, steps: [] }
+    const steps = [
+        { title: '商户下单', status: 'created', time: '' },
+        { title: '运营派单', status: 'dispatched', time: '' },
+        { title: '履约接单', status: 'accepted', time: '' },
+        { title: '服务中', status: 'serving', time: '' },
+        { title: '待商家确认', status: 'confirming', time: '' },
+        { title: '已完成', status: 'completed', time: '' }
+    ]
+    const logs = orderLogs.value || []
+    const status = props.order.status
+    let active = 0
+    const findTime = (keyword) => {
+        const log = logs.find(l => l.title.includes(keyword) || l.content.includes(keyword))
+        return log ? log.time : ''
+    }
+    steps[0].time = props.order.createTime || findTime('下单') || findTime('创建')
+    if (steps[0].time) active = 1
+    if ([0].includes(status)) {
+        steps[1].time = findTime('派单') || steps[0].time
+    } else {
+        steps[1].time = findTime('派单') || ''
+    }
+    if ([1, 2, 3, 4].includes(status)) active = 2
+    steps[2].time = findTime('接单')
+    if ([1].includes(status)) {
+        steps[2].title = '待履约者接单'
+    } else if ([2, 3, 4].includes(status)) {
+        steps[2].title = '履约者已接单'
+        active = 3
+    }
+    steps[3].time = findTime('到达') || findTime('出发')
+    if ([2].includes(status)) {
+        steps[3].title = '服务进行中'
+    } else if ([3, 4].includes(status)) {
+        steps[3].title = '服务已完成'
+        active = 4
+    }
+    steps[4].time = findTime('等待商家确认') || findTime('待验收')
+    if ([3].includes(status)) {
+        steps[4].title = '待商家确认'
+    } else if ([4].includes(status)) {
+        steps[4].title = '商家已确认'
+        active = 5
+    }
+    if (status === 4) {
+        steps[5].time = findTime('完成')
+        active = 6
+    }
+    if (status === 5) {
+        return {
+            active: 1,
+            steps: [
+                { title: '商户下单', time: steps[0].time },
+                { title: '已取消', time: findTime('取消') || '订单已取消' }
+            ]
+        }
+    }
+    return { active, steps }
+})
+
+const serviceProgressSteps = computed(() => {
+    const list = fulfillerLogs.value || []
+    return list.map((i) => {
+        const photos = (i?.photos || '')
+            .split(',')
+            .map(s => s.trim())
+            .filter(Boolean)
+            .map(url => ({ type: 'image', url }))
+        return {
+            title: i?.title || '--',
+            time: '',
+            icon: undefined,
+            color: '#ff9900',
+            desc: i?.content || '',
+            media: photos
+        }
+    })
+})
+
+const handleExportLogs = () => {
+    const logs = orderLogs.value || []
+    if (logs.length === 0) {
+        ElMessage.warning('暂无日志可导出')
+        return
+    }
+    let csvContent = "时间,类型,标题,内容\n"
+    logs.forEach(log => {
+        const time = ''
+        const type = log.logType ?? ''
+        const title = (log.title || '').replace(/"/g, '""')
+        const content = (log.content || '').replace(/"/g, '""')
+        csvContent += `${time},${type},"${title}","${content}"\n`
+    })
+    const blob = new Blob(["\uFEFF" + csvContent], { type: 'text/csv;charset=utf-8;' })
+    const url = URL.createObjectURL(blob)
+    const link = document.createElement("a")
+    link.href = url
+    link.download = `OrderLogs_${props.order.orderNo}.csv`
+    link.click()
+    URL.revokeObjectURL(url)
+    ElMessage.success('导出成功')
+}
+</script>
+
+<style scoped>
+/* Detail Styles */
+.order-detail-drawer :deep(.el-drawer__body) {
+    padding: 0 !important;
+}
+
+.detail-container {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    background: #f5f7fa;
+}
+
+.detail-header {
+    background: #fff;
+    padding: 20px 24px;
+    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.left-head {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+}
+
+.order-no {
+    font-size: 20px;
+    font-weight: bold;
+    color: #303133;
+}
+
+.type-tag {
+    font-weight: normal;
+}
+
+.detail-actions {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+}
+
+.detail-scroll-area {
+    flex: 1;
+    overflow-y: auto;
+    padding: 20px 24px;
+}
+
+/* Progress */
+.progress-section {
+    background: #fff;
+    padding: 30px 20px 20px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+}
+
+.custom-steps :deep(.el-step__title) {
+    font-size: 13px;
+}
+
+/* Top Info Row */
+.top-info-row {
+    display: flex;
+    gap: 20px;
+    margin-bottom: 20px;
+    align-items: stretch;
+}
+
+.info-section {
+    flex: 1;
+    background: #fff;
+    border-radius: 8px;
+    padding: 15px;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+}
+
+.sec-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #f2f2f2;
+}
+
+.sec-header .label {
+    font-weight: bold;
+    font-size: 15px;
+    color: #303133;
+    border-left: 3px solid #409eff;
+    padding-left: 8px;
+}
+
+/* Pet Section */
+.pet-basic-row {
+    display: flex;
+    gap: 15px;
+    margin-bottom: 15px;
+    align-items: center;
+}
+
+.pet-avatar-lg {
+    border-radius: 8px;
+    background: #ecf5ff;
+    color: #409eff;
+    font-size: 20px;
+    font-weight: bold;
+}
+
+.pet-names {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+}
+
+.b-name {
+    font-size: 18px;
+    font-weight: bold;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+}
+
+.b-tags {
+    display: flex;
+    gap: 5px;
+}
+
+.pet-desc :deep(.el-descriptions__label) {
+    width: 70px;
+}
+
+/* User Section */
+.u-row {
+    display: flex;
+    gap: 12px;
+    align-items: center;
+    margin-bottom: 12px;
+}
+
+.u-info .nm {
+    font-weight: bold;
+    font-size: 15px;
+    color: #303133;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+}
+
+.u-info .ph {
+    font-size: 13px;
+    color: #909399;
+    margin-top: 2px;
+}
+
+.addr-box {
+    background: #fdf6ec;
+    padding: 8px 10px;
+    border-radius: 4px;
+    margin-bottom: 10px;
+}
+
+.addr-label {
+    font-size: 12px;
+    color: #e6a23c;
+    margin-bottom: 2px;
+    font-weight: bold;
+}
+
+.addr-txt {
+    font-size: 13px;
+    color: #606266;
+    line-height: 1.4;
+}
+
+/* Tabs */
+.detail-tabs {
+    background: #fff;
+    padding: 10px 20px;
+    border-radius: 8px;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+    min-height: 400px;
+}
+
+.tab-pane-content {
+    padding: 15px 0;
+}
+
+/* Fulfiller Card inside Tab */
+.fulfiller-card {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    padding: 20px;
+    background: #fff;
+    border: 1px solid #ebeef5;
+    border-radius: 8px;
+}
+
+.f-right {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+}
+
+.f-row1 {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+}
+
+.f-name {
+    font-size: 18px;
+    font-weight: bold;
+    color: #303133;
+}
+
+.f-row2 {
+    font-size: 13px;
+    color: #606266;
+    display: flex;
+    gap: 10px;
+}
+
+.sep {
+    color: #e4e7ed;
+}
+
+.empty-state {
+    padding: 40px 0;
+    text-align: center;
+}
+
+/* Progress Card Styles */
+.progress-card {
+    background: #f8fcfb;
+    border-radius: 8px;
+    padding: 12px;
+    border: 1px solid #ebeef5;
+}
+
+.p-title {
+    margin: 0 0 8px;
+    font-size: 15px;
+    font-weight: bold;
+    color: #303133;
+}
+
+.p-desc {
+    margin: 0 0 12px;
+    color: #606266;
+    font-size: 13px;
+    line-height: 1.5;
+}
+
+.p-media {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+}
+
+.media-item {
+    display: inline-block;
+}
+
+.p-img {
+    width: 80px;
+    height: 80px;
+    border-radius: 4px;
+    border: 1px solid #e4e7ed;
+    cursor: pointer;
+}
+
+/* New Transport Split Styles */
+.transport-split-block {
+    margin-top: 20px;
+}
+
+.transport-grid {
+    display: flex;
+    gap: 20px;
+}
+
+.transport-card {
+    flex: 1;
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    overflow: hidden;
+    background: #fff;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
+}
+
+.transport-card .t-header {
+    background: #f5f7fa;
+    padding: 10px 15px;
+    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.transport-card .t-header .time {
+    font-size: 13px;
+    font-weight: bold;
+    color: #f56c6c;
+}
+
+.transport-card .t-body {
+    padding: 15px;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.transport-card .row {
+    display: flex;
+    align-items: flex-start;
+    gap: 8px;
+    font-size: 14px;
+    color: #303133;
+    line-height: 1.4;
+}
+
+.transport-card .row.sub {
+    color: #909399;
+    font-size: 13px;
+    margin-top: 4px;
+}
+
+.transport-card .row .el-icon {
+    margin-top: 3px;
+}
+
+.transport-one {
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    background: #fff;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
+    padding: 14px 16px;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.transport-one .t-row {
+    display: flex;
+    align-items: baseline;
+    gap: 10px;
+}
+
+.transport-one .t-row .time {
+    font-size: 13px;
+    font-weight: bold;
+    color: #f56c6c;
+}
+
+.transport-one .t-k {
+    width: 40px;
+    color: #909399;
+    font-size: 13px;
+}
+
+.transport-one .t-v {
+    color: #303133;
+    font-size: 14px;
+    line-height: 1.4;
+}
+
+.transport-one .t-row.sub .t-v {
+    color: #909399;
+    font-size: 13px;
+}
+
+/* Logs */
+.log-card {
+    background: #f4f4f5;
+    padding: 10px 15px;
+    border-radius: 4px;
+    position: relative;
+    top: -5px;
+    width: 100%;
+}
+
+.l-tit {
+    font-weight: bold;
+    font-size: 14px;
+    margin-bottom: 4px;
+    color: #303133;
+}
+
+.l-txt {
+    font-size: 13px;
+    color: #606266;
+    line-height: 1.5;
+}
+</style>

+ 47 - 0
template/orderList/components/RemarkDialog.vue

@@ -0,0 +1,47 @@
+<template>
+    <el-dialog v-model="dialogVisible" title="订单备注" width="500px">
+        <div style="margin-bottom:10px; font-size:13px; color:#909399;">
+            <span v-if="order">订单号:{{ order.orderNo }}</span>
+        </div>
+        <el-input 
+            v-model="remarkForm" 
+            type="textarea" 
+            :rows="5" 
+            placeholder="请输入订单备注信息..." 
+        />
+        <template #footer>
+            <span class="dialog-footer">
+                <el-button @click="dialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="handleSubmit">保存备注</el-button>
+            </span>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+    visible: Boolean,
+    order: Object
+})
+const emit = defineEmits(['update:visible', 'submit'])
+
+const dialogVisible = computed({
+    get: () => props.visible,
+    set: (val) => emit('update:visible', val)
+})
+
+const remarkForm = ref('')
+
+watch(() => props.visible, (val) => {
+    if (val && props.order) {
+        remarkForm.value = props.order.remark || ''
+    }
+})
+
+const handleSubmit = () => {
+    emit('submit', remarkForm.value)
+    dialogVisible.value = false
+}
+</script>

+ 99 - 0
template/orderList/components/RewardDialog.vue

@@ -0,0 +1,99 @@
+<template>
+    <el-dialog v-model="dialogVisible" title="奖惩操作" width="500px">
+        <div v-if="order" style="padding: 0 10px;">
+            <div style="margin-bottom: 20px; font-size: 14px; color: #606266; line-height: 1.6; background: #fdf6ec; padding: 10px; border-radius: 4px;">
+                 <div>奖惩履约者:<span style="font-weight: bold; color: #303133;">{{ order.fulfillerName || '未指派' }}</span></div>
+                 <div style="font-size: 13px; margin-top: 4px;">订单号:{{ order.orderNo }}</div> 
+                 <div style="font-size: 13px; margin-top: 4px; display:flex; align-items:center; gap:6px;">
+                     服务类型:
+                     <el-tag :type="getTypeTag(order.type)" size="small">{{ getTypeName(order.type) }}</el-tag>
+                     <el-tag v-if="order.type === 'transport' && order.transportType === 'round'" size="small" effect="plain" type="warning">往返</el-tag>
+                     <el-tag v-if="order.splitType === 'pick'" size="small" effect="dark" color="#409eff" style="border:none; color:white;">接</el-tag>
+                     <el-tag v-if="order.splitType === 'drop'" size="small" effect="dark" color="#67c23a" style="border:none; color:white;">送</el-tag>
+                     <el-tag v-if="order.type === 'transport' && order.transportType === 'pick' && !order.splitType" size="small" effect="plain">单程接</el-tag>
+                     <el-tag v-if="order.type === 'transport' && order.transportType === 'drop' && !order.splitType" size="small" effect="plain" type="success">单程送</el-tag>
+                 </div>
+            </div>
+            
+            <el-form :model="rewardForm" label-width="80px">
+                <el-form-item label="操作类型">
+                    <el-radio-group v-model="rewardForm.type">
+                        <el-radio label="reward">奖励 (增加)</el-radio>
+                        <el-radio label="punish">惩罚 (扣除)</el-radio>
+                    </el-radio-group>
+                </el-form-item>
+                <el-form-item label="调整项目">
+                    <el-radio-group v-model="rewardForm.item">
+                        <el-radio label="points">积分</el-radio>
+                        <el-radio label="amount">金额 (元)</el-radio>
+                    </el-radio-group>
+                </el-form-item>
+                <el-form-item label="数额" required>
+                    <el-input-number v-model="rewardForm.value" :min="1" :step="10" />
+                </el-form-item>
+                <el-form-item label="原因备注" required>
+                    <el-input 
+                        v-model="rewardForm.reason" 
+                        type="textarea" 
+                        :rows="3" 
+                        placeholder="请输入奖惩原因..." 
+                    />
+                </el-form-item>
+            </el-form>
+        </div>
+        <template #footer>
+            <el-button @click="dialogVisible = false">取消</el-button>
+            <el-button type="primary" @click="handleSubmit">确认执行</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import { reactive, computed, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+    visible: Boolean,
+    order: Object
+})
+const emit = defineEmits(['update:visible', 'submit'])
+
+const dialogVisible = computed({
+    get: () => props.visible,
+    set: (val) => emit('update:visible', val)
+})
+
+const rewardForm = reactive({
+    type: 'reward',
+    item: 'points',
+    value: 10,
+    reason: ''
+})
+
+watch(() => props.visible, (val) => {
+    if (val) {
+        rewardForm.type = 'reward'
+        rewardForm.item = 'points'
+        rewardForm.value = 10
+        rewardForm.reason = ''
+    }
+})
+
+const getTypeTag = (type) => {
+    const map = { transport: '', feeding: 'warning', washing: 'success' }
+    return map[type]
+}
+const getTypeName = (type) => {
+    const map = { transport: '宠物接送', feeding: '上门喂遛', washing: '上门洗护' }
+    return map[type]
+}
+
+const handleSubmit = () => {
+    if(!rewardForm.reason) {
+        ElMessage.warning('请输入奖惩原因')
+        return
+    }
+    emit('submit', rewardForm)
+    dialogVisible.value = false
+}
+</script>

+ 793 - 0
template/orderList/index.vue

@@ -0,0 +1,793 @@
+<template>
+  <div class="page-container">
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="card-header">
+          <span class="title">订单列表</span>
+          <div class="right-panel">
+            <el-radio-group v-model="filters.service" size="default" @change="handleSearch">
+              <el-radio-button label="">全部类型</el-radio-button>
+              <el-radio-button v-for="item in serviceOptions" :key="item.id" :label="item.id">{{ item.name }}</el-radio-button>
+            </el-radio-group>
+            <el-input
+              v-model="filters.content"
+              placeholder="订单号/商户/宠主/手机号"
+              class="search-input"
+              prefix-icon="Search"
+              clearable
+              @clear="handleSearch"
+              @keyup.enter="handleSearch"
+            />
+            <el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
+          </div>
+        </div>
+
+        <el-tabs v-model="filters.status" class="status-tabs" @tab-change="handleStatusTabChange">
+          <el-tab-pane label="全部订单" name="" />
+          <el-tab-pane label="待派单" name="0" />
+          <el-tab-pane label="待接单" name="1" />
+          <el-tab-pane label="服务中" name="2" />
+          <el-tab-pane label="待商家确认" name="3" />
+          <el-tab-pane label="已完成" name="4" />
+          <el-tab-pane label="已取消" name="5" />
+        </el-tabs>
+      </template>
+
+      <el-table :data="tableData" style="width: 100%" v-loading="loading" :header-cell-style="{ background: '#f5f7fa' }">
+        <el-table-column prop="code" label="订单号" width="170" fixed="left" />
+
+        <el-table-column label="服务类型" width="190">
+          <template #default="{ row }">
+            <div class="service-type-cell">
+              <el-tag>{{ getServiceName(row.service) }}</el-tag>
+              <el-tag v-if="getServiceModeTag(row)" class="sub-tag" type="warning" effect="plain">{{ getServiceModeTag(row) }}</el-tag>
+              <el-tag v-if="getServiceOrderTypeTag(row)" class="sub-tag" :type="getServiceOrderTypeTag(row).type" effect="dark">{{
+                getServiceOrderTypeTag(row).label
+              }}</el-tag>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="宠物信息" min-width="150">
+          <template #default="{ row }">
+            <div class="pet-info">
+              <el-avatar :size="30" class="avatar-type">{{ row.petName?.charAt(0) }}</el-avatar>
+              <div class="pet-detail">
+                <div class="pet-name">
+                  {{ row.petName }}
+                </div>
+                <div class="pet-breed">{{ row.petBreed }}</div>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="所属用户" width="120" prop="customerName">
+          <template #default="{ row }">
+            <span style="font-weight: 500">{{ row.customerName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="城市/区域" width="140">
+          <template #default="{ row }">
+            <div>{{ getCityDistrictText(row).city || '-' }}</div>
+            <div class="sub-text">{{ getCityDistrictText(row).district || '-' }}</div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="商户/下单人" min-width="160">
+          <template #default="{ row }">
+            <div class="merchant-info">
+              <div>{{ row.storeName }}</div>
+              <div class="sub-text" v-if="row.placerUsername">{{ row.placerUsername }}</div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="createTime" label="下单时间" width="165" sortable>
+          <template #default="{ row }">
+            <span class="time-text">{{ row.createTime }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="createTime" label="预约服务时间" width="165" sortable>
+          <template #default="{ row }">
+            <span class="time-text">{{ row.serviceTime }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="订单状态" width="100">
+          <template #default="{ row }">
+            <div class="status-cell">
+              <div class="status-dot" :class="getStatusClass(row.status)"></div>
+              <span>{{ getStatusName(row.status) }}</span>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="履约信息" width="140">
+          <template #default="{ row }">
+            <div v-if="row.fulfillerName" class="fulfiller-info">
+              <span class="fulfiller-name">{{ row.fulfillerName }}</span>
+              <span class="fulfiller-fee" v-if="row.price !== null && row.price !== undefined">¥{{ row.price }}</span>
+            </div>
+            <span v-else class="text-gray">暂未指派</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="200" fixed="right">
+          <template #default="{ row }">
+            <div class="op-cell">
+              <el-button link type="primary" size="small" @click="handleDetail(row)">详情</el-button>
+              <el-button v-if="row.status === 0" link type="success" size="small" @click="openDispatchDialog(row)">派单</el-button>
+              <el-button v-if="[1, 2].includes(row.status)" link type="warning" size="small" @click="openDispatchDialog(row)">重新派单</el-button>
+              <el-button v-if="[0, 1].includes(row.status)" link type="danger" size="small" @click="handleCancel(row)">取消</el-button>
+
+              <el-dropdown v-if="[2, 3, 4].includes(row.status)" trigger="click" @command="(cmd) => handleCommand(cmd, row)">
+                <span class="el-dropdown-link">
+                  更多<el-icon class="el-icon--right">
+                    <ArrowDown />
+                  </el-icon>
+                </span>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item v-if="row.status === 3" command="complete">确认完成</el-dropdown-item>
+                    <el-dropdown-item v-if="[3, 4].includes(row.status)" command="care_summary">护理小结</el-dropdown-item>
+                    <el-dropdown-item command="reward">奖惩</el-dropdown-item>
+                    <el-dropdown-item command="remark">备注</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container">
+        <el-pagination
+          v-model:current-page="pagination.current"
+          v-model:page-size="pagination.size"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="pagination.total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <!-- 组件 -->
+    <OrderDetailDrawer
+      v-model:visible="detailVisible"
+      :order="currentOrder"
+      @dispatch="openDispatchDialog"
+      @cancel="handleCancel"
+      @command="handleCommand"
+      @care-summary="openCareSummary"
+    />
+
+    <DispatchDialog v-model:visible="dispatchDialogVisible" :order="currentDispatchOrder" @submit="handleDispatchSubmit" />
+
+    <CareSummaryDrawer v-model:visible="careSummaryVisible" :order="careSummaryOrder" @submit="saveCareSummary" />
+
+    <RewardDialog v-model:visible="rewardDialogVisible" :order="currentOperateRow" @submit="handleRewardSubmit" />
+
+    <RemarkDialog v-model:visible="remarkDialogVisible" :order="currentOperateRow" @submit="handleRemarkSubmit" />
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, nextTick } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import OrderDetailDrawer from './components/OrderDetailDrawer.vue';
+import DispatchDialog from './components/DispatchDialog.vue';
+import CareSummaryDrawer from './components/CareSummaryDrawer.vue';
+import RewardDialog from './components/RewardDialog.vue';
+import RemarkDialog from './components/RemarkDialog.vue';
+import { listOnStore as listServiceOnStore } from '@/api/service/list/index';
+import { listSubOrder } from '@/api/order/subOrder/index';
+import { dispatchSubOrder } from '@/api/order/subOrder/index';
+import { getSubOrderInfo } from '@/api/order/subOrder/index';
+import { cancelSubOrder } from '@/api/order/subOrder/index';
+import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation';
+import { getStore } from '@/api/system/store';
+
+const loading = ref(false);
+
+const filters = reactive({
+  service: '',
+  status: '',
+  content: ''
+});
+
+const pagination = reactive({
+  current: 1,
+  size: 10,
+  total: 100
+});
+
+const tableData = ref([]);
+const serviceOptions = ref([]);
+const areaStationList = ref([]);
+const areaStationMap = ref({});
+const storeMap = ref({});
+
+onMounted(() => {
+  getServiceList();
+  getAreaStationList();
+  handleSearch();
+});
+
+const getServiceList = () => {
+  listServiceOnStore().then((res) => {
+    serviceOptions.value = res.data || [];
+  });
+};
+
+const getAreaStationList = () => {
+  listAreaStationOnStore().then((res) => {
+    const list = res.data || [];
+    areaStationList.value = list;
+    const map = {};
+    for (const item of list) {
+      if (item && item.id !== undefined && item.id !== null) map[item.id] = item;
+    }
+    areaStationMap.value = map;
+  });
+};
+
+const getCityDistrictText = (row) => {
+  if (!row) return { city: '', district: '' };
+  const map = areaStationMap.value || {};
+  let stationId = row.site;
+  if (!map[stationId] && row.store) {
+    const store = (storeMap.value || {})[row.store];
+    if (store?.site) stationId = store.site;
+  }
+  if (!stationId) return { city: '', district: '' };
+
+  const station = map[stationId];
+  if (!station) return { city: '', district: '' };
+
+  const parent = station.parentId ? map[station.parentId] : undefined;
+  if (!parent) return { city: station.name || '', district: '' };
+
+  if (parent.type === 0) return { city: parent.name || '', district: '' };
+  if (parent.type === 1) {
+    const city = parent.parentId ? map[parent.parentId] : undefined;
+    return { city: city?.name || '', district: parent.name || '' };
+  }
+
+  return { city: '', district: parent.name || '' };
+};
+
+const handleStatusTabChange = async () => {
+  pagination.current = 1;
+  await nextTick();
+  handleSearch();
+};
+
+const handleSearch = () => {
+  loading.value = true;
+  listSubOrder({
+    pageNum: pagination.current,
+    pageSize: pagination.size,
+    service: filters.service !== '' ? filters.service : undefined,
+    status: filters.status !== '' ? Number(filters.status) : undefined,
+    content: filters.content || undefined
+  })
+    .then((res) => {
+      tableData.value = res.rows || [];
+      pagination.total = res.total || 0;
+      loadStoresForRows(tableData.value);
+      loading.value = false;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+const loadStoresForRows = async (rows) => {
+  const map = storeMap.value || {};
+  const ids = Array.from(new Set((rows || []).map((r) => r?.store).filter(Boolean)));
+  const missing = ids.filter((id) => map[id] === undefined);
+  if (missing.length === 0) return;
+  await Promise.all(
+    missing.map(async (id) => {
+      try {
+        const res = await getStore(id);
+        if (res?.data) map[id] = res.data;
+      } catch {
+        map[id] = null;
+      }
+    })
+  );
+  storeMap.value = { ...map };
+};
+
+const handleSizeChange = (val) => {
+  pagination.size = val;
+  handleSearch();
+};
+const handleCurrentChange = (val) => {
+  pagination.current = val;
+  handleSearch();
+};
+
+const getServiceName = (serviceId) => {
+  const item = serviceOptions.value.find((i) => i.id === serviceId);
+  return item ? item.name : '未知服务';
+};
+
+const getServiceModeTag = (row) => {
+  const t = row?.type;
+  if (t === 0 || t === '0' || t === 1 || t === '1') return '往返';
+  return '';
+};
+
+const getServiceOrderTypeTag = (row) => {
+  const t = row?.type;
+  if (t === 0 || t === '0') return { label: '接', type: 'primary' };
+  if (t === 1 || t === '1') return { label: '送', type: 'success' };
+  if (t === 2 || t === '2') return { label: '单程接', type: 'primary' };
+  if (t === 3 || t === '3') return { label: '单程送', type: 'success' };
+  return null;
+};
+
+const getStatusName = (status) => {
+  const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消' };
+  return map[status] || '未知';
+};
+
+const getStatusClass = (status) => {
+  const map = { 0: 'pending_dispatch', 1: 'pending_accept', 2: 'serving', 3: 'pending_confirm', 4: 'completed', 5: 'cancelled' };
+  return map[status] || 'pending_dispatch';
+};
+
+const getFulfillerStatusText = (status) => {
+  const statusMap = {
+    resting: '休息',
+    busy: '接单中',
+    disabled: '禁用'
+  };
+  return statusMap[status] || status;
+};
+
+const getFulfillerStatusType = (status) => {
+  const typeMap = {
+    resting: 'info',
+    busy: 'success',
+    disabled: 'danger'
+  };
+  return typeMap[status] || 'info';
+};
+
+// 弹窗状态管理
+const detailVisible = ref(false);
+const currentOrder = ref(null);
+
+const dispatchDialogVisible = ref(false);
+const currentDispatchOrder = ref(null);
+
+const careSummaryVisible = ref(false);
+const careSummaryOrder = ref(null);
+
+const rewardDialogVisible = ref(false);
+const remarkDialogVisible = ref(false);
+const currentOperateRow = ref(null);
+
+// 详情
+const handleDetail = async (row) => {
+  const typeName = getServiceName(row?.service);
+  const isTransport = row?.mode === 1 || row?.mode === '1';
+  const typeCode = isTransport ? 'transport' : typeName?.includes('喂') || typeName?.includes('遛') ? 'feeding' : 'washing';
+  currentOrder.value = {
+    ...row,
+    orderNo: row?.code || row?.orderCode || row?.orderNo || row?.orderNumber || row?.no || '',
+    type: row?.typeCode || row?.type || typeCode,
+    serviceItem: getServiceName(row?.service) || row?.serviceName || row?.service || '',
+    userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
+    address: '某小区5号楼2单元101',
+    groupBuyPackage: '',
+    transportType: row.splitType || row.transportType,
+    detail: {
+      ...row.detail,
+      pickTime: '2024-02-05 09:30',
+      pickAddr: row.detail?.pickAddr || '北京市朝阳区某小区5号楼2单元101',
+      pickContact: '李先生',
+      pickPhone: '13812345678',
+      dropTime: '2024-02-05 18:30',
+      dropAddr: row.detail?.dropAddr || '北京市朝阳区某小区5号楼2单元101',
+      dropContact: '李先生',
+      dropPhone: '13812345678',
+      packageName: row.detail?.packageName || '精细洗护套餐A',
+      petStatus: '胆小,需安抚',
+      area: '北京市朝阳区某小区5号楼2单元101'
+    },
+    petGender: 'male',
+    petAge: '2岁',
+    petWeight: '15kg',
+    petVaccine: '已接种',
+    petSterilized: true,
+    petCharacter: '活泼好动,喜欢球类玩具',
+    petHealth: '健康良好',
+    fulfillerAvatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+    fulfillerPhone: '13812345678',
+    fulfillerStation: '朝阳服务站',
+    orderLogs: [
+      { time: '2024-02-04 09:30', title: '订单创建', content: '商户提交订单', icon: 'Document' },
+      { time: '2024-02-04 10:00', title: '系统派单', content: '指派给 王大力', icon: 'Bicycle' },
+      { time: '2024-02-04 10:05', title: '接单成功', content: '履约者已确认接单', icon: 'CircleCheck' },
+      { time: '2024-02-04 13:55', title: '到达服务点', content: '履约者已打卡', icon: 'Location' }
+    ]
+  };
+
+  try {
+    const res = await getSubOrderInfo(row?.id);
+    const info = res?.data?.data || res?.data;
+    if (info) {
+      currentOrder.value = {
+        ...(currentOrder.value || {}),
+        id: info.id,
+        orderNo: info.code || currentOrder.value?.orderNo,
+        code: info.code,
+        subOrderType: info.type,
+        status: info.status ?? currentOrder.value?.status,
+        mode: info.mode ?? currentOrder.value?.mode,
+        type: info.mode === 1 || info.mode === '1' ? 'transport' : currentOrder.value?.type,
+        transportType: info.mode === 1 || info.mode === '1' ? 'round' : currentOrder.value?.transportType,
+        serviceTime: info.serviceTime || currentOrder.value?.serviceTime,
+        endServiceTime: info.endServiceTime || currentOrder.value?.endServiceTime,
+        contact: info.contact || currentOrder.value?.contact,
+        contactPhoneNumber: info.contactPhoneNumber || currentOrder.value?.contactPhoneNumber,
+        toAddress: info.toAddress || currentOrder.value?.toAddress,
+        address:
+          info.mode === 1 || info.mode === '1'
+            ? info.toAddress || currentOrder.value?.address
+            : info.address || info.toAddress || currentOrder.value?.address,
+        price: info.price !== undefined && info.price !== null ? Number(info.price) / 100 : currentOrder.value?.price,
+        fulfillerFee: info.price !== undefined && info.price !== null ? Number(info.price) / 100 : currentOrder.value?.fulfillerFee,
+        merchantName: info.storeName || currentOrder.value?.merchantName,
+        platformId: info.platformId ?? currentOrder.value?.platformId,
+        groupBuyPackage: info.groupPurchasePackageName || currentOrder.value?.groupBuyPackage,
+        fulfiller: info.fulfiller ?? currentOrder.value?.fulfiller,
+        detail: {
+          ...(currentOrder.value?.detail || {}),
+          pickTime: info.serviceTime || currentOrder.value?.detail?.pickTime,
+          pickAddr: info.fromAddress || currentOrder.value?.detail?.pickAddr,
+          pickContact: info.contact || currentOrder.value?.detail?.pickContact,
+          pickPhone: info.contactPhoneNumber || currentOrder.value?.detail?.pickPhone,
+          dropTime: info.endServiceTime || currentOrder.value?.detail?.dropTime,
+          dropAddr: info.toAddress || currentOrder.value?.detail?.dropAddr,
+          dropContact: info.contact || currentOrder.value?.detail?.dropContact,
+          dropPhone: info.contactPhoneNumber || currentOrder.value?.detail?.dropPhone,
+          fromAddress: info.fromAddress || currentOrder.value?.detail?.fromAddress,
+          toAddress: info.toAddress || currentOrder.value?.detail?.toAddress,
+          area: info.area || info.address || info.toAddress || currentOrder.value?.detail?.area,
+          packageName: info.packageName || info.servicePackageName || info.package || currentOrder.value?.detail?.packageName,
+          petStatus: info.petStatus || info.specialRequirement || info.requirement || currentOrder.value?.detail?.petStatus,
+          fromCode: info.fromCode || currentOrder.value?.detail?.fromCode,
+          toCode: info.toCode || currentOrder.value?.detail?.toCode
+        }
+      };
+    }
+  } catch {}
+  detailVisible.value = true;
+};
+
+// 取消订单
+const handleCancel = (row) => {
+  ElMessageBox.confirm('确认取消该订单吗?', '提示', { type: 'warning' }).then(() => {
+    cancelSubOrder({ orderId: row?.id }).then(() => {
+      ElMessage.success('订单已取消');
+      handleSearch();
+    });
+  });
+};
+
+// 派单
+const openDispatchDialog = (row) => {
+  const typeName = getServiceName(row?.service);
+  const isTransport = row?.mode === 1 || row?.mode === '1';
+  const typeCode = isTransport ? 'transport' : typeName?.includes('喂') || typeName?.includes('遛') ? 'feeding' : 'washing';
+
+  const t = row?.subOrderType ?? row?.type;
+  const transportType =
+    t === 0 || t === '0' || t === 1 || t === '1'
+      ? 'round'
+      : t === 2 || t === '2'
+        ? 'pick'
+        : t === 3 || t === '3'
+          ? 'drop'
+          : row?.splitType || row?.transportType;
+
+  const toAddress = row?.toAddress || '';
+  const pickAddr = isTransport ? toAddress : '';
+  const dropAddr = isTransport ? toAddress : '';
+  const address = isTransport ? '' : toAddress;
+
+  const orderObj = {
+    id: row.id,
+    typeCode,
+    transportType,
+    time: row.serviceTime || row.appointTime || row.createTime,
+    status: row.status,
+    address,
+    pickAddr,
+    dropAddr,
+    riderId: row.riderId || row.fulfiller || null
+  };
+  currentDispatchOrder.value = orderObj;
+  dispatchDialogVisible.value = true;
+};
+
+const handleDispatchSubmit = (payload) => {
+  if (!currentDispatchOrder.value) return;
+  const priceFen = Math.round(Number(payload.fee || 0) * 100);
+  dispatchSubOrder({
+    orderId: currentDispatchOrder.value.id,
+    fulfiller: payload.riderId,
+    price: priceFen
+  }).then(() => {
+    ElMessage.success('派单成功');
+    const row = tableData.value.find((r) => r.id === currentDispatchOrder.value.id);
+    if (row) {
+      row.status = 1;
+      row.fulfillerName = payload.riderName || 'Unknown';
+      row.price = payload.fee;
+    }
+    handleSearch();
+  });
+};
+
+// 护理小结
+const openCareSummary = (row) => {
+  careSummaryOrder.value = {
+    ...row,
+    petAge: '3岁',
+    petGender: 'male',
+    petTags: ['易过敏', '胆小'],
+    petWeight: '30 kg',
+    petPersonality: '活泼,超级粘人,喜欢玩球',
+    homeTime: '2023-01-01',
+    houseType: '电梯',
+    entryMethod: '密码开门',
+    entryDetail: '密码: 123456 (仅限服务期间使用)',
+    healthStatus: '健康',
+    vaccineImg: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
+    allergy: '海鲜'
+  };
+  careSummaryVisible.value = true;
+};
+
+const saveCareSummary = (text) => {
+  if (careSummaryOrder.value) {
+    careSummaryOrder.value.careSummary = text;
+    ElMessage.success('护理小结已保存');
+    handleSearch();
+  }
+};
+
+// 奖惩
+const openRewardDialog = (row) => {
+  currentOperateRow.value = row;
+  rewardDialogVisible.value = true;
+};
+const handleRewardSubmit = (form) => {
+  ElMessage.success(`操作成功:${form.type === 'reward' ? '奖励' : '惩罚'}已执行`);
+};
+
+// 备注
+const openRemarkDialog = (row) => {
+  currentOperateRow.value = row;
+  remarkDialogVisible.value = true;
+};
+const handleRemarkSubmit = (text) => {
+  if (currentOperateRow.value) {
+    currentOperateRow.value.remark = text;
+    ElMessage.success('备注已更新');
+  }
+};
+
+// 更多操作
+const handleCommand = (cmd, row) => {
+  if (cmd === 'reward') openRewardDialog(row);
+  if (cmd === 'remark') openRemarkDialog(row);
+  if (cmd === 'care_summary') openCareSummary(row);
+  if (cmd === 'complete') {
+    ElMessageBox.confirm('确认将该订单手动标记为完成吗?', '提示', { type: 'warning' }).then(() => {
+      row.status = 4;
+      ElMessage.success('订单已标记完成');
+    });
+  }
+  if (cmd === 'delete') {
+    ElMessageBox.confirm('确认删除该订单吗?此操作不可恢复', '警告', { type: 'error' }).then(() => {
+      tableData.value = tableData.value.filter((item) => item.id !== row.id);
+      ElMessage.success('订单已删除');
+    });
+  }
+};
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.title {
+  font-weight: bold;
+  font-size: 18px;
+}
+
+.right-panel {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.search-input {
+  width: 220px;
+}
+
+.status-tabs {
+  margin-top: 10px;
+  margin-bottom: -10px;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+
+/* Table Content Styles */
+.service-type-cell {
+  display: flex;
+  flex-direction: row;
+  gap: 4px;
+  align-items: center;
+}
+
+.sub-tag {
+  font-size: 11px;
+  height: 20px;
+  padding: 0 5px;
+}
+
+.pet-info {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.pet-info .el-avatar {
+  background: #e0eaff;
+  color: #409eff;
+  font-weight: bold;
+  flex-shrink: 0;
+}
+
+.pet-info .avatar-feeding {
+  background: #fdf6ec;
+  color: #e6a23c;
+}
+
+.pet-info .avatar-washing {
+  background: #f0f9eb;
+  color: #67c23a;
+}
+
+.pet-detail {
+  display: flex;
+  flex-direction: column;
+  line-height: 1.4;
+}
+
+.pet-name {
+  font-weight: bold;
+  font-size: 14px;
+  color: #303133;
+}
+
+.pet-breed {
+  color: #909399;
+  font-weight: normal;
+  font-size: 12px;
+}
+
+.merchant-info {
+  display: flex;
+  flex-direction: column;
+  line-height: 1.4;
+}
+
+.sub-text {
+  font-size: 12px;
+  color: #999;
+}
+
+.text-gray {
+  color: #ccc;
+  font-style: italic;
+}
+
+.time-text {
+  font-size: 13px;
+  color: #606266;
+}
+
+.status-cell {
+  display: flex;
+  align-items: center;
+}
+
+.status-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  margin-right: 6px;
+  background-color: #909399;
+}
+
+.status-dot.pending_dispatch {
+  background-color: #f56c6c;
+  box-shadow: 0 0 4px rgba(245, 108, 108, 0.4);
+}
+
+.status-dot.pending_accept {
+  background-color: #e6a23c;
+}
+
+.status-dot.serving {
+  background-color: #409eff;
+}
+
+.status-dot.pending_confirm {
+  background-color: #bf24e8;
+}
+
+.status-dot.completed {
+  background-color: #67c23a;
+}
+
+.status-dot.cancelled {
+  background-color: #909399;
+}
+
+.fulfiller-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.fulfiller-name {
+  font-weight: 500;
+  color: #333;
+}
+
+.fulfiller-fee {
+  font-size: 12px;
+  color: #e6a23c;
+}
+
+.op-cell {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.el-dropdown-link {
+  cursor: pointer;
+  color: #409eff;
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  line-height: 1;
+  height: 24px;
+}
+</style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است