Huanyi 12 часов назад
Родитель
Сommit
6bc473ab4c
100 измененных файлов с 2865 добавлено и 2376 удалено
  1. 34 0
      api/system/oss.js
  2. 11 0
      api/system/store.js
  3. 33 0
      api/system/user.js
  4. 187 0
      components/center-select/index.vue
  5. 14 1
      components/nav-bar/index.vue
  6. 19 15
      index.html
  7. 18 6
      main.js
  8. 3 3
      manifest.json
  9. 89 341
      pages/index/index.vue
  10. 2 1
      pages/login/index.vue
  11. 1 1
      pages/my/agreement/detail/index.vue
  12. 2 4
      pages/my/complaint/list/index.vue
  13. 23 15
      pages/my/complaint/submit/index.vue
  14. 10 9
      pages/my/fee/statistics/index.vue
  15. 335 206
      pages/my/index.vue
  16. 319 65
      pages/my/pet/add/index.vue
  17. 42 33
      pages/my/pet/detail/index.vue
  18. 235 88
      pages/my/pet/edit/index.vue
  19. 43 19
      pages/my/pet/list/index.vue
  20. 63 39
      pages/my/settings/account-delete/index.vue
  21. 120 52
      pages/my/settings/change-password/index.vue
  22. 16 8
      pages/my/settings/index.vue
  23. 2 2
      pages/my/settings/password/index.vue
  24. 147 67
      pages/my/settings/profile/index.vue
  25. 224 156
      pages/my/user/add/index.vue
  26. 2 2
      pages/my/user/detail/index.vue
  27. 251 173
      pages/my/user/edit/index.vue
  28. 38 13
      pages/my/user/list/index.vue
  29. 405 952
      pages/order/apply/index.vue
  30. 46 45
      pages/order/detail/index.vue
  31. 50 41
      pages/order/list/index.vue
  32. 50 3
      pages/service/all/index.vue
  33. 2 2
      pages/service/detail/index.vue
  34. 2 2
      pages/store/apply/index.vue
  35. 1 0
      static/icon/cancel.svg
  36. 1 0
      static/icon/finished.svg
  37. 0 0
      static/icon/in-service.svg
  38. 0 0
      static/icon/male.svg
  39. 1 0
      static/icon/my/agreement.svg
  40. 0 0
      static/icon/my/complaint-management.svg
  41. BIN
      static/icon/my/customerservice/online.png
  42. BIN
      static/icon/my/customerservice/phone.png
  43. 1 0
      static/icon/my/fee-statistic.svg
  44. 0 0
      static/icon/my/pet-archieves.svg
  45. 1 0
      static/icon/my/service-customer.svg
  46. 0 0
      static/icon/my/system-settings.svg
  47. 1 0
      static/icon/my/user-management.svg
  48. 1 0
      static/icon/pending-dispatch.svg
  49. 0 0
      static/icon/pet.svg
  50. 0 0
      static/icon/remale.svg
  51. 1 0
      static/icon/waiting-accept.svg
  52. 1 0
      static/icon/waiting-service.svg
  53. BIN
      static/images/feed-walk.png
  54. BIN
      static/images/index-header.png
  55. BIN
      static/images/index-notice.png
  56. BIN
      static/images/laundry-clean.png
  57. BIN
      static/images/my-agreement.png
  58. BIN
      static/images/my-cancel.png
  59. BIN
      static/images/my-complaint.png
  60. BIN
      static/images/my-customer.png
  61. BIN
      static/images/my-customerservice.png
  62. BIN
      static/images/my-fee.png
  63. BIN
      static/images/my-finished.png
  64. BIN
      static/images/my-header.png
  65. BIN
      static/images/my-inservice.png
  66. BIN
      static/images/my-pendingaccept.png
  67. BIN
      static/images/my-pendingdispatch.png
  68. BIN
      static/images/my-pendingservice.png
  69. BIN
      static/images/my-pet.png
  70. BIN
      static/images/my-systemsetting.png
  71. BIN
      static/images/pickup-dropoff.png
  72. 13 4
      uni.promisify.adaptor.js
  73. BIN
      unpackage/cache/apk/__UNI__F19BBAD_cm.apk
  74. 1 1
      unpackage/cache/apk/apkurl
  75. 0 0
      unpackage/cache/apk/cmManifestCache.json
  76. 1 1
      unpackage/cache/wgt/__UNI__F19BBAD/app-config-service.js
  77. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/app-service.js
  78. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/manifest.json
  79. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/index/index.css
  80. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/login/index.css
  81. 1 1
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/agreement/detail/index.css
  82. 1 1
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/agreement/list/index.css
  83. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/complaint/list/index.css
  84. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/complaint/submit/index.css
  85. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/fee/statistics/index.css
  86. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/index.css
  87. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/add/index.css
  88. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/detail/index.css
  89. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/edit/index.css
  90. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/list/index.css
  91. 0 1
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/account-delete/index.css
  92. 0 1
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/change-password/index.css
  93. 1 1
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/index.css
  94. 0 1
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/profile/index.css
  95. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/add/index.css
  96. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/detail/index.css
  97. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/edit/index.css
  98. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/list/index.css
  99. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/order/apply/index.css
  100. 0 0
      unpackage/cache/wgt/__UNI__F19BBAD/pages/order/detail/index.css

+ 34 - 0
api/system/oss.js

@@ -0,0 +1,34 @@
+import { request } from '@/utils/request'
+
+// 上传文件到 OSS
+// @Author: Antigravity
+export function uploadFile(filePath) {
+  return new Promise((resolve, reject) => {
+    const token = uni.getStorageSync('token') || ''
+    // 使用 import() 动态获取配置,避免循环依赖或加载顺序问题
+    import('@/utils/config').then(({ BASE_URL, DEFAULT_HEADERS }) => {
+      uni.uploadFile({
+        url: BASE_URL + '/resource/oss/upload',
+        filePath: filePath,
+        name: 'file',
+        header: {
+          'Authorization': token ? `Bearer ${token}` : '',
+          ...DEFAULT_HEADERS
+        },
+        success: (res) => {
+          const resData = JSON.parse(res.data)
+          if (resData.code === 200) {
+            resolve(resData.data)
+          } else {
+            uni.showToast({ title: resData.msg || '上传失败', icon: 'none' })
+            reject(resData.msg)
+          }
+        },
+        fail: (err) => {
+          uni.showToast({ title: '网络异常', icon: 'none' })
+          reject(err)
+        }
+      })
+    }).catch(reject)
+  })
+}

+ 11 - 0
api/system/store.js

@@ -12,3 +12,14 @@ export function listStoreOnOrder(query) {
     params: query
   })
 }
+
+/**
+ * 获取当前用户可下单的服务ID列表
+ * @Author: Antigravity
+ */
+export function listMyServices() {
+  return request({
+    url: '/system/store/listMyServices',
+    method: 'get'
+  })
+}

+ 33 - 0
api/system/user.js

@@ -39,3 +39,36 @@ export function cancelUser() {
     method: 'delete'
   })
 }
+
+// 上传头像
+// @Author: Antigravity
+export function uploadAvatar(filePath) {
+  return new Promise((resolve, reject) => {
+    const token = uni.getStorageSync('token') || ''
+    import('@/utils/config').then(({ BASE_URL }) => {
+      uni.uploadFile({
+        url: BASE_URL + '/system/user/profile/avatar',
+        filePath: filePath,
+        name: 'avatarfile',
+        header: {
+          'Authorization': token ? `Bearer ${token}` : '',
+          'clientid': 'fe63fea7be31b0200b496d08bc6b517d',
+          'X-Platform-Code': 'MfJkMNMW2JKXBuPcbP2rxkD3ynXmReAZZFm4fN7cAGwGJdKCmd'
+        },
+        success: (res) => {
+          const resData = JSON.parse(res.data)
+          if (resData.code === 200) {
+            resolve(resData.data)
+          } else {
+            uni.showToast({ title: resData.msg || '上传失败', icon: 'none' })
+            reject(resData.msg)
+          }
+        },
+        fail: (err) => {
+          uni.showToast({ title: '网络异常', icon: 'none' })
+          reject(err)
+        }
+      })
+    })
+  })
+}

+ 187 - 0
components/center-select/index.vue

@@ -0,0 +1,187 @@
+<template>
+	<view class="center-select-mask" v-if="modelValue" @click="onClose" @touchmove.stop.prevent>
+		<view class="center-select-container" @click.stop>
+			<view class="select-header">
+				<text class="select-title">{{ title }}</text>
+				<!-- CSS 绘制的关闭按钮 @Author: Antigravity -->
+				<view class="close-btn" @click="onClose">
+					<view class="line line1"></view>
+					<view class="line line2"></view>
+				</view>
+			</view>
+			<scroll-view scroll-y class="select-content">
+				<view 
+					v-for="(item, index) in options" 
+					:key="index" 
+					class="select-item"
+					:class="{ 'active': isSelected(item) }"
+					@click="onSelect(item)"
+				>
+					<text class="item-label">{{ getLabel(item) }}</text>
+					<!-- CSS 绘制的选中对勾 @Author: Antigravity -->
+					<view class="checkmark" v-if="isSelected(item)"></view>
+				</view>
+				<view class="empty-tip" v-if="options.length === 0">暂无选项</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+/**
+ * @Author: Antigravity
+ */
+import { ref } from 'vue'
+
+const props = defineProps({
+	modelValue: Boolean, // 控制显示隐藏
+	title: {
+		type: String,
+		default: '请选择'
+	},
+	options: {
+		type: Array,
+		default: () => []
+	},
+	value: {
+		type: [String, Number],
+		default: ''
+	},
+	labelKey: {
+		type: String,
+		default: 'label'
+	},
+	valueKey: {
+		type: String,
+		default: 'value'
+	}
+})
+
+const emit = defineEmits(['update:modelValue', 'select'])
+
+const getLabel = (item) => {
+	if (typeof item === 'string' || typeof item === 'number') return item
+	return item[props.labelKey]
+}
+
+const getValue = (item) => {
+	if (typeof item === 'string' || typeof item === 'number') return item
+	return item[props.valueKey]
+}
+
+const isSelected = (item) => {
+	return getValue(item) === props.value
+}
+
+const onSelect = (item) => {
+	emit('select', item)
+	emit('update:modelValue', false)
+}
+
+const onClose = () => {
+	emit('update:modelValue', false)
+}
+</script>
+
+<style lang="scss" scoped>
+.center-select-mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.6);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 9999;
+	backdrop-filter: blur(2px);
+}
+
+.center-select-container {
+	width: 600rpx;
+	background: #ffffff;
+	border-radius: 32rpx;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+	max-height: 70vh;
+	animation: popIn 0.25s ease-out;
+}
+
+@keyframes popIn {
+	from { transform: scale(0.8); opacity: 0; }
+	to { transform: scale(1); opacity: 1; }
+}
+
+.select-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 32rpx 40rpx;
+	border-bottom: 2rpx solid #f2f2f2;
+	
+	.select-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+/* 原生 CSS 关闭按钮 X @Author: Antigravity */
+.close-btn {
+	width: 40rpx;
+	height: 40rpx;
+	position: relative;
+	.line {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		width: 30rpx;
+		height: 4rpx;
+		background: #999;
+		border-radius: 4rpx;
+	}
+	.line1 { transform: translate(-50%, -50%) rotate(45deg); }
+	.line2 { transform: translate(-50%, -50%) rotate(-45deg); }
+}
+
+.select-content {
+	flex: 1;
+	padding: 10rpx 0;
+}
+
+.select-item {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 32rpx 40rpx;
+	min-height: 100rpx;
+	box-sizing: border-box;
+	
+	&:active { background: #f7f8fa; }
+	
+	&.active {
+		.item-label { color: #ff9500; font-weight: bold; }
+	}
+	
+	.item-label { font-size: 30rpx; color: #444; }
+}
+
+/* 原生 CSS 对勾 @Author: Antigravity */
+.checkmark {
+	width: 12rpx;
+	height: 24rpx;
+	border-right: 4rpx solid #ff9500;
+	border-bottom: 4rpx solid #ff9500;
+	transform: rotate(45deg);
+	margin-right: 10rpx;
+}
+
+.empty-tip {
+	padding: 60rpx 0;
+	text-align: center;
+	color: #999;
+	font-size: 26rpx;
+}
+</style>

+ 14 - 1
components/nav-bar/index.vue

@@ -3,7 +3,8 @@
 		<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
 		<view class="nav-content">
 			<view class="left-box" @click="goBack" v-if="showBack">
-				<uni-icons type="left" size="20" :color="color"></uni-icons>
+				<!-- 使用 H5 原生 CSS 绘制返回箭头,防止打包后图标丢失 @Author: Antigravity -->
+				<view class="back-arrow" :style="{ borderColor: color }"></view>
 			</view>
 			<view class="title-box">
 				<text class="title-text" :style="{ color: color }">{{ title }}</text>
@@ -69,6 +70,18 @@ const goBack = () => {
 	position: absolute;
 	left: 32rpx;
 	z-index: 10;
+	/* 扩大点击区域 */
+	padding: 20rpx 40rpx 20rpx 0;
+}
+
+/* 原生 CSS 绘制返回箭头 @Author: Antigravity */
+.back-arrow {
+	width: 22rpx;
+	height: 22rpx;
+	border-left: 4rpx solid;
+	border-top: 4rpx solid;
+	transform: rotate(-45deg);
+	background: transparent;
 }
 
 .title-box {

+ 19 - 15
index.html

@@ -1,16 +1,20 @@
-<!doctype html>
-<html lang="en">
-
-<head>
-  <meta charset="UTF-8" />
-  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
-  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>pet-app</title>
-</head>
-
-<body>
-  <div id="app"></div>
-  <script type="module" src="/src/main.js"></script>
-</body>
-
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title>好萌友(测试版)</title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
 </html>

+ 18 - 6
main.js

@@ -1,11 +1,23 @@
 import App from './App'
-import './uni.promisify.adaptor'
-
 import { createSSRApp } from 'vue'
+import * as Pinia from 'pinia'
+import './uni.promisify.adaptor'
 
 export function createApp() {
-	const app = createSSRApp(App)
-	return {
-		app
-	}
+  const app = createSSRApp(App)
+  app.use(Pinia.createPinia())
+  // 全局错误捕获,将错误写入 localStorage 以便调试
+  app.config.errorHandler = (err, vm, info) => {
+    const msg = `[Vue Error] ${info}: ${err && err.message ? err.message : String(err)}`
+    console.error(msg)
+    try {
+      const logs = JSON.parse(uni.getStorageSync('__debug_logs') || '[]')
+      logs.unshift({ t: new Date().toISOString(), msg })
+      uni.setStorageSync('__debug_logs', JSON.stringify(logs.slice(0, 20)))
+    } catch(e) {}
+  }
+  return {
+    app,
+    Pinia
+  }
 }

+ 3 - 3
manifest.json

@@ -1,9 +1,9 @@
 {
-    "name" : "好萌友",
+    "name" : "好萌友(测试版)",
     "appid" : "__UNI__F19BBAD",
     "description" : "宠物服务商家端",
-    "versionName" : "1.0.0",
-    "versionCode" : 10000,
+    "versionName" : "1.0.20t",
+    "versionCode" : 21,
     "transformPx" : false,
     "app-plus" : {
         "usingComponents" : true,

+ 89 - 341
pages/index/index.vue

@@ -1,81 +1,48 @@
 <template>
 	<view class="home-page">
-		<!-- 搜索栏 -->
-		<view class="search-wrapper">
-<!-- <view class="location-box" @click="onLocationClick">
-				<text class="location-text">昆明市</text>
-				<uni-icons type="bottom" size="10" color="#fff"></uni-icons>
-			</view> -->
-			<view class="search-bar" @click="onSearchClick">
-				<uni-icons type="search" size="16" color="#999"></uni-icons>
-				<text class="search-placeholder">搜索宠物名/主人</text>
-			</view>
-		</view>
-
-		<!-- 轮播图 -->
-		<view class="banner-swipe-wrapper">
-			<swiper class="my-swipe" :autoplay="true" :interval="3000" :circular="true" indicator-dots
-				indicator-active-color="#ffffff" indicator-color="rgba(255,255,255,0.5)">
-				<swiper-item v-for="(image, index) in bannerImages" :key="index">
-					<image :src="image" class="swipe-img" mode="aspectFill"></image>
-				</swiper-item>
-			</swiper>
+		<!-- 顶部背景与搜索区域 @Author: Antigravity -->
+		<view class="header-section">
+			<image src="/static/images/index-header.png" class="header-bg" mode="widthFix"></image>
 		</view>
 
-		<!-- 系统通知栏 -->
+		<!-- 系统通知栏 @Author: Antigravity -->
 		<view class="notice-section">
 			<view class="notice-bar">
-				<uni-icons type="sound" size="14" color="#ed6a0c"></uni-icons>
-				<scroll-view scroll-x class="notice-scroll" :show-scrollbar="false">
-					<text class="notice-text">【重要通知】尊敬的用户,由于近期接送需求激增,请各位宠主尽量提前24小时进行服务预约,感谢您的理解与配合!</text>
-				</scroll-view>
+				<image src="/static/images/index-notice.png" class="notice-icon" mode="aspectFit"></image>
+				<text class="notice-text">【重要通知】尊敬的用户,由于近期接送需求激增,请各位宠主尽量提前24小时进行服务预约,感谢您的理解与配合!</text>
 			</view>
 		</view>
 
-		<!-- 重构后的对称服务栅格布局 -->
-		<view class="new-service-grid" v-if="services.length >= 3">
-			<!-- 顶部主打服务 (Hero Card) -->
-			<view class="hero-card" @click="goToDetail(services[0])">
-				<view class="card-content">
-					<text class="card-title">{{ services[0].name }}</text>
-					<text class="card-desc">{{ services[0].remark }}</text>
-				</view>
-				<image :src="services[0].iconUrl" class="hero-icon" mode="aspectFit"></image>
-			</view>
-
-			<!-- 底部双列服务 (Secondary Cards) -->
-			<view class="card-row">
-				<view class="sub-card" v-for="(item, index) in services.slice(1, 3)" :key="index"
-					@click="goToDetail(item)">
-					<view class="card-content">
-						<text class="sub-title">{{ item.name }}</text>
-						<text class="sub-desc">{{ item.remark }}</text>
+		<!-- 业务导航区 (左1右2) @Author: Antigravity -->
+		<view class="main-nav-section">
+			<view class="nav-container">
+				<!-- 左侧大按钮: 宠物接送 -->
+				<view class="nav-card large-card">
+					<image src="/static/images/pickup-dropoff.png" class="card-bg" mode="aspectFill"></image>
+					<view class="card-info">
+						<text class="card-title">宠物接送</text>
+						<text class="card-desc">支持单操/往返服务</text>
 					</view>
-					<image :src="item.iconUrl" class="sub-icon" mode="aspectFit"></image>
 				</view>
-			</view>
-		</view>
-
-		<!-- 全部服务列表 -->
-		<view class="section-header">
-			<text class="section-title">全部服务</text>
-			<view class="more-link" @click="goToServices">
-				<text>查看全部</text>
-				<uni-icons type="right" size="12" color="#999"></uni-icons>
-			</view>
-		</view>
 
-		<view class="recommend-list">
-			<view class="recommend-card" v-for="(item, index) in services.slice(3, 8)" :key="index"
-				@click="goToDetail(item)">
-				<image :src="item.iconUrl" class="item-img" mode="aspectFill"></image>
-				<view class="item-info">
-					<view class="item-header">
-						<text class="item-title">{{ item.name }}</text>
+				<!-- 右侧分栏 -->
+				<view class="right-stack">
+					<!-- 右上: 上门喂遛 -->
+					<view class="nav-card small-card">
+						<image src="/static/images/feed-walk.png" class="card-bg" mode="aspectFill"></image>
+						<view class="card-info">
+							<text class="card-title">上门喂遛</text>
+							<text class="card-desc">每次上门基础费用</text>
+						</view>
 					</view>
-					<text class="item-desc">{{ item.remark }}</text>
-					<view class="item-bottom">
-						<text class="tag" v-if="serviceModeEnum[item.mode]">{{ serviceModeEnum[item.mode] }}</text>
+					
+					<!-- 右下: 上门洗护 -->
+					<view class="nav-card small-card">
+						<image src="/static/images/laundry-clean.png" class="card-bg" mode="aspectFill"></image>
+						<view class="card-info">
+							<text class="card-title">上门洗护</text>
+							<text class="card-desc">每次上门基础费用</text>
+						</view>
 					</view>
 				</view>
 			</view>
@@ -86,331 +53,112 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
-import { onShow } from '@dcloudio/uni-app'
 import customTabbar from '@/components/custom-tabbar/index.vue'
-import { listAllService } from '@/api/service/list'
-import { getAppSetting } from '@/api/system/appSetting'
-import serviceModeEnum from '@/json/serviceMode.json'
-
-const bannerImages = ref([])
-const services = ref([])
-
-const fetchServices = async () => {
-	try {
-		// @Author: Antigravity
-		const res = await listAllService()
-		// 兼容直接返回数组或返回包装对象的情况
-		services.value = res.rows || res || []
-	} catch (error) {
-		console.error("Failed to fetch services", error)
-	}
-}
 
-// 获取首页轮播图配置 @Author: Antigravity
-const fetchBanners = async () => {
-	try {
-		// 获取“好萌友”端配置 (ID 2) @Author: Antigravity
-		const res = await getAppSetting(2)
-		const data = res.data || res
-		if (data && data.homeBannerUrls) {
-			bannerImages.value = data.homeBannerUrls.split(',').filter(Boolean)
-		}
-	} catch (error) {
-		console.error('Failed to fetch banners', error)
-	}
-}
-
-onShow(() => {
-	fetchServices()
-	fetchBanners()
-})
-
-const onLocationClick = () => {
-	uni.showToast({ title: '城市选择功能即将上线', icon: 'none' })
-}
-
-const onSearchClick = () => {
-	// 跳转搜索页(预留)
-}
-
-const goToServices = () => {
-	uni.reLaunch({ url: '/pages/service/all/index' })
-}
-
-const goToDetail = (item) => {
-	// @Author: Antigravity
-	// 存储完整的服务数据,供详情页读取
-	uni.setStorageSync('currentService', item)
-	
-	if (item.name === '托运') {
-		uni.reLaunch({ url: '/pages/service/all/index' })
-		return
-	}
-	uni.navigateTo({
-		url: `/pages/service/detail/index?service=${item.name}`
-	})
-}
+/** 首页脚本 @Author: Antigravity */
 </script>
 
 <style lang="scss" scoped>
 .home-page {
-	background-image: linear-gradient(to bottom, #FFD93D 0%, #FFD93D 440rpx, #f2f2f2 440rpx, #f2f2f2 100%);
+	background: #f8f9fb;
 	min-height: 100vh;
 	padding-bottom: 120rpx;
 }
 
-.search-wrapper {
-	padding: 24rpx 32rpx;
-	padding-top: calc(var(--status-bar-height, 44px) + 20rpx);
-	display: flex;
-	align-items: center;
-	gap: 16rpx;
-}
-
-.location-box {
-	display: flex;
-	align-items: center;
-	gap: 6rpx;
-	flex-shrink: 0;
-}
-
-.location-text {
-	font-size: 28rpx;
-	color: #fff;
-	font-weight: bold;
-}
-
-.search-bar {
-	flex: 1;
-	display: flex;
-	align-items: center;
-	background: rgba(255, 255, 255, 0.85);
-	border-radius: 40rpx;
-	padding: 16rpx 24rpx;
-	gap: 12rpx;
-}
-
-.search-placeholder {
-	font-size: 26rpx;
-	color: #999;
-}
-
-.banner-swipe-wrapper {
-	padding: 0 32rpx 24rpx;
-}
-
-.my-swipe {
-	border-radius: 32rpx;
-	overflow: hidden;
-	height: 320rpx;
+/* ====== 顶部搜索与背景 ====== */
+.header-section {
+	position: relative;
+	width: 100%;
 }
-
-.swipe-img {
+.header-bg {
 	width: 100%;
-	height: 320rpx;
+	display: block;
 }
 
+/* ====== 通知栏 ====== */
 .notice-section {
-	padding: 0 32rpx 24rpx;
+	padding: 20rpx 32rpx;
 }
-
 .notice-bar {
 	display: flex;
 	align-items: center;
-	background: #fffbe8;
-	border-radius: 16rpx;
-	padding: 12rpx 20rpx;
-	gap: 12rpx;
-	height: 64rpx;
-	overflow: hidden;
+	background: #fffbef;
+	border-radius: 20rpx;
+	padding: 16rpx 24rpx;
+	gap: 16rpx;
 }
-
-.notice-scroll {
-	flex: 1;
-	white-space: nowrap;
+.notice-icon {
+	width: 40rpx;
+	height: 40rpx;
+	flex-shrink: 0;
 }
-
 .notice-text {
 	font-size: 24rpx;
-	color: #ed6a0c;
+	color: #e6a23c;
+	line-height: 1.4;
 }
 
-.new-service-grid {
-	padding: 0 32rpx;
-	display: flex;
-	flex-direction: column;
-	gap: 24rpx;
-	margin-bottom: 24rpx;
+/* ====== 核心卡片布局 @Author: Antigravity ====== */
+.main-nav-section {
+	padding: 10rpx 32rpx;
 }
-
-.hero-card {
-	height: 260rpx;
-	background: linear-gradient(135deg, #60a5fa 0%, #2563eb 100%);
-	border-radius: 32rpx;
-	padding: 40rpx;
-	position: relative;
-	overflow: hidden;
+.nav-container {
 	display: flex;
-	align-items: center;
-	box-shadow: 0 12rpx 32rpx rgba(37, 99, 235, 0.18);
+	height: 380rpx;
+	gap: 20rpx;
 }
 
-.card-content {
-	z-index: 1;
-	display: flex;
-	flex-direction: column;
-}
-
-.card-title {
-	font-size: 38rpx;
-	color: #fff;
-	font-weight: 800;
-	letter-spacing: 2rpx;
-}
-
-.card-desc {
-	font-size: 24rpx;
-	color: rgba(255, 255, 255, 0.9);
-	margin-top: 12rpx;
-}
-
-.hero-icon {
-	position: absolute;
-	right: 20rpx;
-	bottom: -10rpx;
-	width: 240rpx;
-	height: 240rpx;
-	opacity: 0.85;
-}
-
-.card-row {
-	display: flex;
-	gap: 24rpx;
-}
-
-.sub-card {
-	flex: 1;
-	height: 200rpx;
-	border-radius: 32rpx;
-	padding: 30rpx;
+.nav-card {
+	border-radius: 24rpx;
 	position: relative;
 	overflow: hidden;
-	box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
-}
-
-.sub-card:nth-child(1) {
-	background: linear-gradient(135deg, #34d399 0%, #059669 100%);
-}
-
-.sub-card:nth-child(2) {
-	background: linear-gradient(135deg, #fb923c 0%, #ea580c 100%);
-}
-
-.sub-title {
-	font-size: 30rpx;
-	color: #fff;
-	font-weight: bold;
-	display: block;
-}
-
-.sub-desc {
-	font-size: 20rpx;
-	color: rgba(255, 255, 255, 0.85);
-	margin-top: 8rpx;
-	display: block;
+	background: #fff;
 }
 
-.sub-icon {
+.card-bg {
 	position: absolute;
-	right: 4rpx;
-	bottom: 4rpx;
-	width: 100rpx;
-	height: 100rpx;
-	opacity: 0.8;
-}
-
-.section-header {
-	padding: 50rpx 32rpx 20rpx;
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
-}
-
-.section-title {
-	font-size: 36rpx;
-	font-weight: bold;
-	color: #333;
-}
-
-.more-link {
-	display: flex;
-	align-items: center;
-	gap: 4rpx;
-	font-size: 26rpx;
-	color: #999;
-}
-
-.recommend-list {
-	padding: 20rpx 32rpx 40rpx;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 1;
 }
 
-.recommend-card {
-	background: #fff;
-	border-radius: 24rpx;
-	padding: 24rpx;
-	display: flex;
-	gap: 24rpx;
-	margin-bottom: 24rpx;
-	box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+.card-info {
+	position: relative;
+	z-index: 2;
+	padding: 30rpx 24rpx;
 }
 
-.item-img {
-	width: 180rpx;
-	height: 180rpx;
-	border-radius: 16rpx;
+.large-card {
+	flex: 1;
 }
-
-.item-info {
+.right-stack {
 	flex: 1;
 	display: flex;
 	flex-direction: column;
-	justify-content: space-between;
+	gap: 20rpx;
 }
-
-.item-header {
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
+.small-card {
+	flex: 1;
 }
 
-.item-title {
+.card-title {
 	font-size: 32rpx;
+	font-weight: 800;
 	color: #333;
-	font-weight: bold;
-}
-
-.booked {
-	font-size: 24rpx;
-	color: #999;
-}
-
-.item-desc {
-	font-size: 26rpx;
-	color: #666;
-	margin: 12rpx 0;
+	margin-bottom: 4rpx;
+	display: block;
 }
+/* 根据图片色系微调文字颜色以匹配 UI 稿 */
+.large-card .card-title { color: #d35400; }
+.right-stack .small-card:first-child .card-title { color: #2c5ba7; }
+.right-stack .small-card:last-child .card-title { color: #c0392b; }
 
-.item-bottom {
-	display: flex;
-	align-items: center;
+.card-desc {
+	font-size: 20rpx;
+	color: #8e8e8e;
+	display: block;
 }
 
-.tag {
-	background-color: #e3f2fd;
-	color: #2196f3;
-	font-size: 22rpx;
-	padding: 6rpx 16rpx;
-	border-radius: 8rpx;
-}
 </style>

+ 2 - 1
pages/login/index.vue

@@ -346,11 +346,12 @@ const showAgreement = async (agreementId) => {
 	height: 104rpx;
 	font-size: 34rpx;
 	font-weight: 700;
-	color: #333;
+	color: #fff;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
 	border: none;
 	border-radius: 52rpx;
 	letter-spacing: 4rpx;
+	&::after { border: none; }
 }
 
 .footer-hint {

+ 1 - 1
pages/my/agreement/detail/index.vue

@@ -59,7 +59,7 @@ const fetchAgreement = async (id) => {
 <style lang="scss" scoped>
 .agreement-detail-page { min-height: 100vh; background: #f7f8fa; padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); }
 .content-card { background: #fff; margin: 24rpx; border-radius: 24rpx; padding: 40rpx 32rpx; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03); }
-.title { display: block; font-size: 36rpx; font-weight: 800; color: #333; margin-bottom: 32rpx; position: relative; padding-bottom: 24rpx; border-bottom: 1rpx solid #f5f5f5; }
+.title { display: block; font-size: 36rpx; font-weight: 800; color: #333; margin-bottom: 32rpx; position: relative; padding-bottom: 24rpx; border-bottom: 2rpx solid #EEEEEE; }
 .rich-content { display: block; font-size: 28rpx; color: #555; line-height: 1.8; word-break: break-all; }
 .loading-state { text-align: center; padding: 100rpx 0; color: #999; font-size: 28rpx; }
 </style>

+ 2 - 4
pages/my/complaint/list/index.vue

@@ -135,9 +135,7 @@ const previewImage = (urls, index) => {
 	display: flex;
 	justify-content: space-between;
 	align-items: center;
-	padding-bottom: 20rpx;
-	border-bottom: 1rpx solid #f5f5f5;
-	margin-bottom: 20rpx;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .left-box {
@@ -208,7 +206,7 @@ const previewImage = (urls, index) => {
 .card-footer {
 	margin-top: 24rpx;
 	padding-top: 20rpx;
-	border-top: 1rpx solid #f5f5f5;
+	border-top: 2rpx solid #EEEEEE;
 	display: flex;
 	justify-content: flex-end;
 }

+ 23 - 15
pages/my/complaint/submit/index.vue

@@ -7,18 +7,18 @@
 			<view class="type-card card-shadow">
 				<view class="type-item" :class="{ 'active': !praiseFlag }" @click="praiseFlag = false">
 					<view class="icon-wrap bad">
-						<uni-icons type="info-filled" size="24" color="#F44336" v-if="!praiseFlag"></uni-icons>
-						<uni-icons type="info" size="24" color="#999" v-else></uni-icons>
+						<text class="type-emoji">{{ !praiseFlag ? '👎' : '👎🏻' }}</text>
 					</view>
 					<text class="type-text">不赞</text>
+					<text class="type-sub" v-if="!praiseFlag">投诉</text>
 				</view>
 				<view class="type-divider"></view>
 				<view class="type-item" :class="{ 'active': praiseFlag }" @click="praiseFlag = true">
 					<view class="icon-wrap good">
-						<uni-icons type="hand-up-filled" size="24" color="#4CAF50" v-if="praiseFlag"></uni-icons>
-						<uni-icons type="hand-up" size="24" color="#999" v-else></uni-icons>
+						<text class="type-emoji">{{ praiseFlag ? '👍' : '👍🏻' }}</text>
 					</view>
 					<text class="type-text">赞</text>
+					<text class="type-sub" v-if="praiseFlag">好评</text>
 				</view>
 			</view>
 
@@ -211,27 +211,34 @@ const handleConfirmSubmit = async () => {
 .type-card {
 	display: flex;
 	align-items: center;
-	height: 120rpx;
-	padding: 0 20rpx;
+	height: 140rpx;
+	padding: 0 16rpx;
     .type-item {
         flex: 1;
         display: flex;
+        flex-direction: column;
         align-items: center;
         justify-content: center;
-        gap: 16rpx;
+        gap: 8rpx;
         transition: all 0.3s;
-        height: 80rpx;
-        border-radius: 16rpx;
+        height: 110rpx;
+        border-radius: 20rpx;
         &.active {
-            background-color: #fdfdfd;
-            .type-text { font-weight: bold; color: #333; }
+            background-color: #f6f6f6;
+            .type-text { font-weight: bold; color: #333; font-size: 30rpx; }
+            .type-emoji { transform: scale(1.3); }
+            .type-sub { opacity: 1; }
         }
         .icon-wrap {
-            width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center;
+            width: 64rpx; height: 64rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center;
         }
-        .type-text { font-size: 28rpx; color: #999; }
+        .icon-wrap.bad { background: rgba(244,67,54,0.08); }
+        .icon-wrap.good { background: rgba(76,175,80,0.08); }
+        .type-emoji { font-size: 36rpx; line-height: 1; transition: transform 0.3s ease; }
+        .type-text { font-size: 28rpx; color: #999; transition: all 0.3s; }
+        .type-sub { font-size: 20rpx; color: #bbb; opacity: 0; transition: opacity 0.3s; }
     }
-    .type-divider { width: 1rpx; height: 40rpx; background-color: #eee; }
+    .type-divider { width: 2rpx; height: 60rpx; background: linear-gradient(180deg, transparent, #EEEEEE, transparent); border-radius: 2rpx; }
 }
 
 /* 表单板块 */
@@ -268,7 +275,7 @@ const handleConfirmSubmit = async () => {
 		}
 	}
 	.upload-add {
-		aspect-ratio: 1; border-radius: 12rpx; border: 2rpx dashed #eee;
+		aspect-ratio: 1; border-radius: 12rpx; border: 2rpx dashed #EEEEEE;
 		display: flex; flex-direction: column; align-items: center; justify-content: center;
 		background: #fafafa;
 		.add-text { font-size: 22rpx; color: #ccc; margin-top: 8rpx; }
@@ -288,6 +295,7 @@ const handleConfirmSubmit = async () => {
 		height: 96rpx; line-height: 96rpx; border-radius: 48rpx; 
 		background: #333; color: #fff; font-size: 32rpx; font-weight: bold; border: none;
 		transition: all 0.3s;
+		&::after { border: none; }
 		&.is-praise { background: #FF9500; color: #fff; }
 		&:active { transform: scale(0.98); opacity: 0.9; }
 	}

+ 10 - 9
pages/my/fee/statistics/index.vue

@@ -192,9 +192,10 @@ onLoad(async () => {
 }
 
 .total-title {
-	font-size: 28rpx;
+	font-size: 30rpx;
 	opacity: 0.9;
 	margin-bottom: 20rpx;
+	font-weight: 600;
 }
 
 .total-amount {
@@ -211,7 +212,7 @@ onLoad(async () => {
 	background: rgba(255, 255, 255, 0.2);
 	padding: 12rpx 32rpx;
 	border-radius: 40rpx;
-	font-size: 24rpx;
+	font-size: 26rpx;
 	display: flex;
 	align-items: center;
 	gap: 8rpx;
@@ -238,20 +239,20 @@ onLoad(async () => {
 }
 
 .stats-value {
-	font-size: 36rpx;
+	font-size: 34rpx;
 	font-weight: 800;
 	color: #1a1a1a;
 }
 
 .stats-label {
-	font-size: 22rpx;
+	font-size: 26rpx;
 	color: #999;
 }
 
 .stats-divider {
 	width: 2rpx;
 	height: 60rpx;
-	background: #f0f0f0;
+	background: #EEEEEE;
 }
 
 .detail-section {
@@ -273,7 +274,7 @@ onLoad(async () => {
 }
 
 .header-text {
-	font-size: 32rpx;
+	font-size: 30rpx;
 	font-weight: bold;
 	color: #333;
 }
@@ -300,7 +301,7 @@ onLoad(async () => {
 }
 
 .detail-title {
-	font-size: 28rpx;
+	font-size: 30rpx;
 	font-weight: bold;
 	color: #333;
 }
@@ -318,13 +319,13 @@ onLoad(async () => {
 }
 
 .detail-amount {
-	font-size: 32rpx;
+	font-size: 34rpx;
 	font-weight: bold;
 	color: #ff5252;
 }
 
 .detail-no {
-	font-size: 22rpx;
+	font-size: 24rpx;
 	color: #ccc;
 }
 </style>

+ 335 - 206
pages/my/index.vue

@@ -1,8 +1,9 @@
 <template>
 	<view class="my-page">
 		<!-- 顶部背景墙 -->
-		<view class="header-curve">
-			<view class="user-block" @click="goToLogin">
+		<view class="header-bg">
+			<image src="/static/images/my-header.png" class="header-img" mode="widthFix"></image>
+			<view class="user-block" @click="goToLogin" @longpress="onDebugTap">
 				<image class="user-avatar" :src="userInfo?.avatarUrl || '/static/images/default-avatar.png'"
 					mode="aspectFill"></image>
 				<view class="user-info">
@@ -10,9 +11,8 @@
 					<text class="user-desc-text">{{ userInfo ? (userInfo.remark || '这位用户很懒,什么都没写 🐾') : '登录后享受更多权益 🐾'
 					}}</text>
 				</view>
-				<uni-icons v-if="!userInfo" type="right" size="16" color="rgba(100, 70, 20, 0.4)"></uni-icons>
+				<view v-if="!userInfo" class="css-right-arrow-gold"></view>
 			</view>
-			<view class="wave-shape"></view>
 		</view>
 
 		<!-- 我的服务订单区 -->
@@ -21,7 +21,7 @@
 				<text class="card-title">我的服务订单</text>
 				<view class="card-more" @click="goToOrder('all')">
 					<text>查看全部</text>
-					<uni-icons type="right" size="12" color="#a39686"></uni-icons>
+					<view class="css-right-arrow-small"></view>
 				</view>
 			</view>
 			<view class="order-nav">
@@ -34,13 +34,16 @@
 			</view>
 		</view>
 
-		<!-- 主功能网格 -->
-		<view class="menu-grid">
-			<view class="menu-box" v-for="item in menuItems" :key="item.path" @click="goToMenu(item)">
-				<view class="menu-icon-wrap">
-					<image class="custom-icon" :src="item.icon" mode="aspectFit"></image>
+		<!-- 服务与工具 -->
+		<view class="cute-card tool-wrap">
+			<view class="card-head">
+				<text class="card-title">服务与工具</text>
+			</view>
+			<view class="tool-grid">
+				<view class="tool-item" v-for="item in menuItems" :key="item.path || item.title" @click="goToMenu(item)">
+					<image class="custom-icon tool-icon" :src="item.icon" mode="aspectFit"></image>
+					<text class="tool-text">{{ item.title }}</text>
 				</view>
-				<text class="menu-text">{{ item.title }}</text>
 			</view>
 		</view>
 
@@ -53,7 +56,7 @@
 			<view class="service-popup" @click.stop>
 				<view class="service-header">
 					<text class="service-title">联系客服</text>
-					<uni-icons type="closeempty" size="20" color="#999" @click="closeServicePopup"></uni-icons>
+					<view class="css-close-btn" @click="closeServicePopup"></view>
 				</view>
 
 				<view class="qr-section">
@@ -64,28 +67,28 @@
 				</view>
 
 				<view class="service-list">
-					<view class="service-row" @click="openOnlineService">
-						<view class="service-row-icon-box green">
-							<uni-icons type="chat-filled" size="24" color="#fff"></uni-icons>
+					<view class="service-row">
+						<view class="service-icon-box online-bg">
+							<image class="service-icon-img" src="/static/icon/my/customerservice/online.png" mode="aspectFit"></image>
 						</view>
 						<view class="service-info">
 							<text class="service-name">在线客服</text>
 							<text class="service-desc">{{ customerSetting.wechatAccount || '企业微信专属客服在线解答' }}</text>
 						</view>
-						<view class="call-btn-mini green-btn">
+						<view class="call-btn-mini green-btn" @click="openOnlineService">
 							<text>去咨询</text>
 						</view>
 					</view>
 
-					<view class="service-row" @click="callServicePhone">
-						<view class="service-row-icon-box orange">
-							<uni-icons type="phone-filled" size="24" color="#fff"></uni-icons>
+					<view class="service-row">
+						<view class="service-icon-box phone-bg">
+							<image class="service-icon-img" src="/static/icon/my/customerservice/phone.png" mode="aspectFit"></image>
 						</view>
 						<view class="service-info">
 							<text class="service-name">客服电话</text>
 							<text class="service-desc">{{ customerSetting.phoneNumber || '暂无电话' }}</text>
 						</view>
-						<view class="call-btn-mini orange-btn">
+						<view class="call-btn-mini orange-btn" @click="callServicePhone">
 							<text>拨打</text>
 						</view>
 					</view>
@@ -97,147 +100,221 @@
 	</view>
 </template>
 
-<script setup>
+<script>
 // @Author: Antigravity
-import { ref, reactive } from 'vue'
-import { onShow } from '@dcloudio/uni-app'
 import { getInfo } from '@/api/system/user'
 import { getCustomerServiceSetting } from '@/api/system/customerServiceSetting'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 import orderStatusData from '@/json/orderStatus.json'
 
-const userInfo = ref(null)
-const showServicePopup = ref(false)
-const customerSetting = reactive({
-	wechatAccount: '',
-	phoneNumber: '',
-	qrCode: '',
-	qrCodeUrl: '',
-	enterpriseWechatLink: ''
-})
-
-const fetchUserInfo = async () => {
-	const token = uni.getStorageSync('token')
-	if (token) {
-		try {
-			const res = await getInfo()
-			if (res && res.user) {
-				userInfo.value = res.user
-			}
-		} catch (error) {
-			console.error('获取用户信息失败', error)
-		}
-	} else {
-		userInfo.value = null
-	}
-}
-
-// 获取客服配置 (ID=2)
-const fetchCustomerSetting = async () => {
-	try {
-		const res = await getCustomerServiceSetting(2)
-		if (res) {
-			Object.assign(customerSetting, res)
+// 订单状态图标映射
+const iconMap = {
+	0: '/static/images/my-pendingdispatch.png',
+	1: '/static/images/my-pendingaccept.png',
+	2: '/static/images/my-inservice.png',
+	3: '/static/images/my-pendingservice.png',
+	4: '/static/images/my-finished.png',
+	5: '/static/images/my-cancel.png'
+}
+
+export default {
+	components: { customTabbar },
+	data() {
+		return {
+			userInfo: null,
+			showServicePopup: false,
+			debugTapCount: 0,  // 调试触发计数器
+			customerSetting: {
+				wechatAccount: '',
+				phoneNumber: '',
+				qrCode: '',
+				qrCodeUrl: '',
+				enterpriseWechatLink: ''
+			},
+			// 订单状态导航
+			orderItems: orderStatusData.map(item => ({
+				key: item.value,
+				label: item.label,
+				icon: iconMap[item.value] || '/static/images/my-pendingdispatch.png'
+			})),
+			// 功能菜单
+			menuItems: [
+				{ title: '宠物档案', icon: '/static/images/my-pet.png', path: '/pages/my/pet/list/index' },
+				{ title: '用户管理', icon: '/static/images/my-customer.png', path: '/pages/my/user/list/index' },
+				{ title: '投诉管理', icon: '/static/images/my-complaint.png', path: '/pages/my/complaint/list/index' },
+				{ title: '服务费统计', icon: '/static/images/my-fee.png', path: '/pages/my/fee/statistics/index' },
+				{ title: '客服中心', icon: '/static/images/my-customerservice.png', path: '' },
+				{ title: '协议中心', icon: '/static/images/my-agreement.png', path: '/pages/my/agreement/list/index' },
+				{ title: '系统设置', icon: '/static/images/my-systemsetting.png', path: '/pages/my/settings/index' }
+			]
 		}
-	} catch (error) {
-		console.error('获取客服配置失败', error)
-	}
-}
-
-onShow(() => {
-	fetchUserInfo()
-	fetchCustomerSetting()
-})
-
-const goToLogin = () => {
-	if (!userInfo.value) {
-		uni.navigateTo({ url: '/pages/login/index' })
-	}
-}
-
-const goToOrder = (statusValue) => {
-	if (statusValue === 'all') {
-		uni.reLaunch({ url: '/pages/order/list/index' })
-	} else {
-		uni.reLaunch({ url: `/pages/order/list/index?status=${statusValue}` })
-	}
-}
-
-const goToMenu = (item) => {
-	if (item.title === '客服中心') {
-		showServicePopup.value = true
-		return
-	}
-	if (item.path) {
-		uni.navigateTo({ url: item.path })
-	}
-}
-
-const closeServicePopup = () => {
-	showServicePopup.value = false
-}
-
-const previewQRCode = () => {
-	if (!customerSetting.qrCodeUrl) return
-	uni.previewImage({
-		urls: [customerSetting.qrCodeUrl]
-	})
-}
-
-const openOnlineService = () => {
-	if (customerSetting.enterpriseWechatLink) {
-		// #ifdef H5
-		window.location.href = customerSetting.enterpriseWechatLink
-		// #endif
-		// #ifndef H5
-		uni.setClipboardData({
-			data: customerSetting.wechatAccount || customerSetting.enterpriseWechatLink,
-			success: () => {
-				uni.showToast({ title: '客服账号已复制,请在微信中添加', icon: 'none' })
+	},
+	onShow() {
+		this.fetchUserInfo()
+		this.fetchCustomerSetting()
+	},
+	methods: {
+		// 获取当前登录用户信息
+		async fetchUserInfo() {
+			const token = uni.getStorageSync('token')
+			if (token) {
+				try {
+					const res = await getInfo()
+					if (res && res.user) {
+						this.userInfo = res.user
+					}
+				} catch (error) {
+					console.error('获取用户信息失败', error)
+				}
+			} else {
+				this.userInfo = null
 			}
-		})
-		// #endif
-	} else {
-		uni.showToast({ title: '在线客服暂未配置', icon: 'none' })
-	}
-}
-
-const callServicePhone = () => {
-	if (!customerSetting.phoneNumber) {
-		uni.showToast({ title: '暂无客服电话', icon: 'none' })
-		return
+		},
+		// 获取客服配置 (ID=2)
+		async fetchCustomerSetting() {
+			try {
+				const res = await getCustomerServiceSetting(2)
+				if (res) {
+					// 商户端 request 拦截器已处理 res.data,此处 res 即为配置对象
+					Object.assign(this.customerSetting, res)
+				}
+			} catch (error) {
+				console.error('获取客服配置失败', error)
+			}
+		},
+		// 跳转到登录页(未登录时)
+		goToLogin() {
+			if (!this.userInfo) {
+				uni.navigateTo({ url: '/pages/login/index' })
+			}
+		},
+		// 跳转到订单列表
+		goToOrder(statusValue) {
+			if (statusValue === 'all') {
+				uni.reLaunch({ url: '/pages/order/list/index' })
+			} else {
+				uni.reLaunch({ url: `/pages/order/list/index?status=${statusValue}` })
+			}
+		},
+		// 菜单项点击处理
+		goToMenu(item) {
+			if (item.title === '客服中心') {
+				this.showServicePopup = true
+				return
+			}
+			if (item.path) {
+				uni.navigateTo({ url: item.path })
+			}
+		},
+		// 关闭客服弹窗
+		closeServicePopup() {
+			this.showServicePopup = false
+		},
+		// 预览客服二维码
+		previewQRCode() {
+			if (!this.customerSetting.qrCodeUrl) return
+			uni.previewImage({
+				urls: [this.customerSetting.qrCodeUrl]
+			})
+		},
+		// 打开在线客服
+		openOnlineService() {
+			if (this.customerSetting.enterpriseWechatLink) {
+				// #ifdef H5
+				window.location.href = this.customerSetting.enterpriseWechatLink
+				// #endif
+				// #ifndef H5
+				uni.setClipboardData({
+					data: this.customerSetting.wechatAccount || this.customerSetting.enterpriseWechatLink,
+					success: () => {
+						uni.showToast({ title: '客服账号已复制,请在微信中添加', icon: 'none' })
+					}
+				})
+				// #endif
+			} else {
+				uni.showToast({ title: '在线客服暂未配置', icon: 'none' })
+			}
+		},
+		// 拨打客服电话(多级降级策略,兼容 Vite CLI 和 HBuilderX 原生两种打包方式)
+		callServicePhone() {
+			const phoneNumber = String(this.customerSetting.phoneNumber || '').trim()
+			if (!phoneNumber) {
+				uni.showToast({ title: '暂无客服电话', icon: 'none' })
+				return
+			}
+			try {
+				// 策略1:App Plus 原生 plus.device.dial(HBuilderX 原生项目可用)
+				// eslint-disable-next-line no-undef
+				if (typeof plus !== 'undefined' && plus.device && plus.device.dial) {
+					// eslint-disable-next-line no-undef
+					plus.device.dial(phoneNumber, false)
+					return
+				}
+				// 策略2:tel: URI Scheme(适用于 Vite CLI 打包的 WebView App,Android/iOS 均会拦截并跳转拨号盘)
+				if (typeof window !== 'undefined') {
+					window.location.href = 'tel:' + phoneNumber
+					return
+				}
+				// 策略3:最终兜底 uni.makePhoneCall
+				uni.makePhoneCall({
+					phoneNumber,
+					fail: (err) => {
+						uni.showModal({
+							title: '拨号失败',
+							content: JSON.stringify(err),
+							showCancel: false
+						})
+					}
+				})
+			} catch (e) {
+				uni.showModal({
+					title: '拨号异常',
+					content: e.message || String(e),
+					showCancel: false
+				})
+			}
+		},
+		// 长按头像区备触发调试日志查看
+		onDebugTap() {
+			this.debugTapCount++
+			if (this.debugTapCount >= 3) {
+				this.debugTapCount = 0
+				this.showDebugLog()
+			}
+		},
+		// 读取并展示全局错误日志
+		showDebugLog() {
+			try {
+				const logs = JSON.parse(uni.getStorageSync('__debug_logs') || '[]')
+				// eslint-disable-next-line no-undef
+				const plusAvail = typeof plus !== 'undefined' ? 'yes' : 'no'
+				// eslint-disable-next-line no-undef
+				const plusDial = (typeof plus !== 'undefined' && plus.device && plus.device.dial) ? 'yes' : 'no'
+				const winAvail = typeof window !== 'undefined' ? 'yes' : 'no'
+				const makePhoneType = typeof uni.makePhoneCall
+				const baseInfo = `plus:${plusAvail} | plus.device.dial:${plusDial} | window:${winAvail} | makePhoneCall:${makePhoneType} | 日志:${logs.length}条`
+				const logText = logs.length > 0
+					? logs.slice(0, 5).map(l => `${l.t}\n${l.msg}`).join('\n---\n')
+					: '暂无错误日志'
+				uni.showModal({
+					title: '调试信息',
+					content: `${baseInfo}\n\n${logText}`,
+					showCancel: true,
+					cancelText: '清队日志',
+					confirmText: '关闭',
+					success: (res) => {
+						if (res.cancel) {
+							uni.removeStorageSync('__debug_logs')
+							uni.showToast({ title: '日志已清队', icon: 'none' })
+						}
+					}
+				})
+			} catch(e) {
+				uni.showModal({ title: '读取日志失败', content: e.message || String(e), showCancel: false })
+			}
+		}
 	}
-	uni.makePhoneCall({
-		phoneNumber: customerSetting.phoneNumber
-	})
 }
-
-// 订单图标 - 从 orderStatus.json 生成
-const iconMap = {
-	0: '/static/icon/order-wait.svg',
-	1: '/static/icon/order-accept.svg',
-	2: '/static/icon/order-service.svg',
-	3: '/static/icon/order-service.svg',
-	4: '/static/icon/order-done.svg',
-	5: '/static/icon/order-cancel.svg'
-}
-
-const orderItems = orderStatusData.map(item => ({
-	key: item.value,
-	label: item.label,
-	icon: iconMap[item.value] || '/static/icon/order-service.svg'
-}))
-
-// 功能菜单
-const menuItems = [
-	{ title: '宠物档案', icon: '/static/icon/pet-archive.svg', path: '/pages/my/pet/list/index' },
-	{ title: '用户管理', icon: '/static/icon/user-manage.svg', path: '/pages/my/user/list/index' },
-	{ title: '投诉管理', icon: '/static/icon/review.svg', path: '/pages/my/complaint/list/index' },
-	{ title: '服务费统计', icon: '/static/icon/service-fee.svg', path: '/pages/my/fee/statistics/index' },
-	{ title: '客服中心', icon: '/static/icon/service-center.svg', path: '' },
-	{ title: '协议中心', icon: '/static/icon/agreement.svg', path: '/pages/my/agreement/list/index' },
-	{ title: '系统设置', icon: '/static/icon/settings.svg', path: '/pages/my/settings/index' }
-]
 </script>
 
 <style lang="scss" scoped>
@@ -284,7 +361,8 @@ const menuItems = [
 	justify-content: space-between;
 	align-items: center;
 	padding: 40rpx;
-	border-bottom: 2rpx solid #faf8f5;
+	border-bottom: 2rpx solid #EEEEEE;
+	position: relative;
 }
 
 .service-title {
@@ -293,6 +371,26 @@ const menuItems = [
 	color: #4a3e2e;
 }
 
+/* CSS 关闭叉号 @Author: Antigravity */
+.css-close-btn {
+	width: 44rpx;
+	height: 44rpx;
+	position: relative;
+	&::before, &::after {
+		content: '';
+		position: absolute;
+		top: 20rpx;
+		left: 8rpx;
+		width: 28rpx;
+		height: 4rpx;
+		background-color: #ccc;
+		transform: rotate(45deg);
+		border-radius: 4rpx;
+	}
+	&::after { transform: rotate(-45deg); }
+	&:active { opacity: 0.6; }
+}
+
 .qr-section {
 	padding: 48rpx 0;
 	display: flex;
@@ -332,11 +430,11 @@ const menuItems = [
 	display: flex;
 	align-items: center;
 	padding: 32rpx 0;
-	border-bottom: 2rpx solid #fcfaf5;
+	border-bottom: 2rpx solid #EEEEEE;
 	&:last-child { border-bottom: none; }
 }
 
-.service-row-icon-box {
+.service-icon-box {
 	width: 88rpx;
 	height: 88rpx;
 	border-radius: 28rpx;
@@ -344,8 +442,19 @@ const menuItems = [
 	align-items: center;
 	justify-content: center;
 	margin-right: 24rpx;
-	&.green { background: #5ec686; box-shadow: 0 8rpx 16rpx rgba(94, 198, 134, 0.2); }
-	&.orange { background: #f7ca3e; box-shadow: 0 8rpx 16rpx rgba(247, 202, 62, 0.2); }
+	&.online-bg {
+		background: linear-gradient(135deg, #71d192 0%, #4caf50 100%);
+		box-shadow: 0 8rpx 16rpx rgba(76, 175, 80, 0.25);
+	}
+	&.phone-bg {
+		background: linear-gradient(135deg, #ffcc33 0%, #f7ca3e 100%);
+		box-shadow: 0 8rpx 16rpx rgba(247, 202, 62, 0.25);
+	}
+}
+
+.service-icon-img {
+	width: 56rpx;
+	height: 56rpx;
 }
 
 .service-info {
@@ -368,31 +477,64 @@ const menuItems = [
 }
 
 .call-btn-mini {
-	padding: 12rpx 28rpx;
+	padding: 10rpx 26rpx;
 	border-radius: 20rpx;
 	font-size: 24rpx;
 	font-weight: 700;
 	transition: transform 0.2s;
+	background: transparent;
+	border: 2rpx solid transparent;
 	&:active { transform: scale(0.95); }
-	&.green-btn { background: #eef9f2; color: #5ec686; }
-	&.orange-btn { background: #fef6df; color: #f7ca3e; }
+	&.green-btn { border-color: #5ec686; color: #5ec686; }
+	&.orange-btn { border-color: #f7ca3e; color: #f7ca3e; }
 }
 
 
-.header-curve {
+/* ====== 顶部背景图区域(替换原纯色背景) ====== */
+.header-bg {
 	position: relative;
-	background: #f7ca3e;
-	padding: 120rpx 40rpx 160rpx;
+	padding-top: calc(var(--status-bar-height, 44px) + 20rpx);
+	height: 440rpx;
 	overflow: hidden;
 }
 
+.header-img {
+	width: 100%;
+	height: 100%;
+	display: block;
+	object-fit: cover;
+}
+
 .user-block {
 	display: flex;
 	align-items: center;
-	position: relative;
+	position: absolute;
+	top: 50%;
+	transform: translateY(-50%);
+	left: 40rpx;
+	right: 40rpx;
 	z-index: 2;
 }
 
+/* CSS 右箭头 (金色) @Author: Antigravity */
+.css-right-arrow-gold {
+	width: 16rpx;
+	height: 16rpx;
+	border-right: 4rpx solid rgba(100, 70, 20, 0.4);
+	border-top: 4rpx solid rgba(100, 70, 20, 0.4);
+	transform: rotate(45deg);
+	margin-left: auto;
+}
+
+/* CSS 右箭头 (小号) @Author: Antigravity */
+.css-right-arrow-small {
+	width: 12rpx;
+	height: 12rpx;
+	border-right: 3rpx solid #a39686;
+	border-top: 3rpx solid #a39686;
+	transform: rotate(45deg);
+}
+
 .user-avatar {
 	width: 140rpx;
 	height: 140rpx;
@@ -423,16 +565,6 @@ const menuItems = [
 	font-weight: 600;
 }
 
-.wave-shape {
-	position: absolute;
-	bottom: -80rpx;
-	left: 0;
-	width: 100%;
-	height: 160rpx;
-	background: #fcfaf5;
-	border-radius: 50%;
-	transform: scaleX(1.5);
-}
 
 .cute-card {
 	background: #fff;
@@ -465,7 +597,7 @@ const menuItems = [
 .card-more {
 	display: flex;
 	align-items: center;
-	gap: 4rpx;
+	gap: 10rpx;
 	font-size: 24rpx;
 	color: #a39686;
 	font-weight: 600;
@@ -480,63 +612,60 @@ const menuItems = [
 	display: flex;
 	flex-direction: column;
 	align-items: center;
-	gap: 16rpx;
+	gap: 12rpx;
 }
 
 .icon-bulb {
-	width: 92rpx;
-	height: 92rpx;
-	background: #fef6df;
-	border-radius: 32rpx;
+	width: 84rpx;
+	height: 84rpx;
+	background: transparent;
+	border-radius: 28rpx;
 	display: flex;
 	align-items: center;
 	justify-content: center;
 }
 
-.menu-grid {
-	display: grid;
-	grid-template-columns: repeat(4, 1fr);
-	gap: 24rpx;
-	padding: 0 32rpx;
+/* ====== 服务与工具网格卡片 ====== */
+.tool-wrap {
 	margin-top: 40rpx;
 }
 
-.menu-box {
-	background: #fff;
-	border-radius: 36rpx;
-	padding: 36rpx 12rpx;
+.tool-grid {
 	display: flex;
-	flex-direction: column;
-	align-items: center;
-	justify-content: center;
-	gap: 24rpx;
-	box-shadow: 0 8rpx 24rpx rgba(220, 212, 196, 0.3);
+	flex-wrap: wrap;
 }
 
-.menu-icon-wrap {
-	width: 80rpx;
-	height: 80rpx;
-	border-radius: 50%;
-	background: #fef6df;
+.tool-item {
+	width: 25%;
 	display: flex;
+	flex-direction: column;
 	align-items: center;
-	justify-content: center;
+	padding: 24rpx 0;
+	gap: 10rpx;
+
+	&:active {
+		opacity: 0.7;
+	}
+}
+
+.tool-icon {
+	width: 52rpx;
+	height: 52rpx;
 }
 
 .custom-icon {
-	width: 44rpx;
-	height: 44rpx;
+	width: 48rpx;
+	height: 48rpx;
 }
 
-.nav-label,
-.menu-text {
+.tool-text {
 	font-size: 22rpx;
-	font-weight: 700;
-	color: #6d5b45;
-	text-align: center;
+	color: #4a3e2e;
+	font-weight: 600;
 }
 
 .nav-label {
+	font-size: 22rpx;
 	font-weight: 600;
 }
 

+ 319 - 65
pages/my/pet/add/index.vue

@@ -2,6 +2,12 @@
 	<view class="pet-add-page">
 		<nav-bar title="新增宠物档案"></nav-bar>
 		
+		<!-- 宠物预览/头像上传 @Author: Antigravity -->
+		<view class="avatar-section" @click="handleChooseAvatar">
+			<image :src="tempAvatarUrl || '/static/images/profile.png'" class="avatar-img" mode="aspectFill"></image>
+			<view class="avatar-tip">点击上传宠物头像</view>
+		</view>
+
 		<!-- 基础信息 -->
 		<view class="section-title">基础信息</view>
 		<view class="form-card">
@@ -9,29 +15,26 @@
 				<text class="form-label require">宠物名称</text>
 				<input class="form-input" v-model="form.name" placeholder="请输入宠物名称" />
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showCustomerSearch = true">
 				<text class="form-label require">所属主人</text>
-				<picker :range="customerOptions" range-key="name" @change="onCustomerChange">
-					<view class="picker-value" :class="{'placeholder': !form.userId}">{{ getCustomerLabel }}</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': !form.userId}">{{ getCustomerLabel }}</view>
+				<!-- CSS 绘制右箭头 @Author: Antigravity -->
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showBreedPicker = true">
 				<text class="form-label require">品种</text>
-				<view class="form-combox-wrapper">
-					<uni-combox :candidates="breedCandidates" v-model="form.breed" placeholder="可选择也可自填品种" emptyTips="未找到对应品种,可直接输入"></uni-combox>
-				</view>
+				<view class="picker-value" :class="{'placeholder': !form.breed}">{{ form.breed || '可选择或自填品种' }}</view>
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showGenderSelect = true">
 				<text class="form-label">性别</text>
-				<picker :range="genderOptions" range-key="label" @change="onGenderChange">
-					<view class="picker-value">{{ getGenderLabel }}</view>
-				</picker>
+				<view class="picker-value">{{ getGenderLabel }}</view>
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showSizeSelect = true">
 				<text class="form-label require">体型</text>
-				<picker :range="sizeOptions" range-key="label" @change="onSizeChange">
-					<view class="picker-value" :class="{'placeholder': !form.size}">{{ getSizeLabel }}</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': !form.size}">{{ getSizeLabel }}</view>
+				<view class="right-arrow"></view>
 			</view>
 			<view class="form-item">
 				<text class="form-label require">年龄(岁)</text>
@@ -59,17 +62,15 @@
 		<!-- 家庭信息 -->
 		<view class="section-title">家庭信息</view>
 		<view class="form-card">
-			<view class="form-item">
+			<view class="form-item" @click="showHouseTypeSelect = true">
 				<text class="form-label require">房屋类型</text>
-				<picker :range="houseTypeOptions" range-key="label" @change="onHouseTypeChange">
-					<view class="picker-value" :class="{'placeholder': !form.houseType}">{{ getHouseTypeLabel }}</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': !form.houseType}">{{ getHouseTypeLabel }}</view>
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showEntryMethodSelect = true">
 				<text class="form-label require">入门方式</text>
-				<picker :range="entryMethodOptions" range-key="label" @change="onEntryMethodChange">
-					<view class="picker-value" :class="{'placeholder': !form.entryMethod}">{{ getEntryMethodLabel }}</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': !form.entryMethod}">{{ getEntryMethodLabel }}</view>
+				<view class="right-arrow"></view>
 			</view>
 			<view class="form-item" v-if="form.entryMethod === 'password'">
 				<text class="form-label require">门锁密码</text>
@@ -84,23 +85,20 @@
 		<!-- 健康状况 -->
 		<view class="section-title">健康状况</view>
 		<view class="form-card">
-			<view class="form-item">
+			<view class="form-item" @click="showHealthSelect = true">
 				<text class="form-label require">健康状态</text>
-				<picker :range="healthStatusOptions" @change="onHealthChange">
-					<view class="picker-value">{{ form.healthStatus || '请选择' }}</view>
-				</picker>
+				<view class="picker-value">{{ form.healthStatus || '请选择' }}</view>
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showVaccineSelect = true">
 				<text class="form-label require">疫苗接种</text>
-				<picker :range="vaccineOptions" @change="onVaccineChange">
-					<view class="picker-value">{{ form.vaccineStatus || '请选择' }}</view>
-				</picker>
+				<view class="picker-value">{{ form.vaccineStatus || '请选择' }}</view>
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showAggressionSelect = true">
 				<text class="form-label require">攻击倾向</text>
-				<picker :range="aggressionOptions" range-key="label" @change="onAggressionChange">
-					<view class="picker-value">{{ getAggressionLabel }}</view>
-				</picker>
+				<view class="picker-value">{{ getAggressionLabel }}</view>
+				<view class="right-arrow"></view>
 			</view>
 			<view class="form-item vertical">
 				<text class="form-label require">既往病史</text>
@@ -120,18 +118,93 @@
 		<view class="footer-bar">
 			<button class="save-btn" @click="onSave">保存档案</button>
 		</view>
+
+		<!-- 居中重构:主人搜索弹窗 @Author: Antigravity -->
+		<view class="center-modal-mask" v-if="showCustomerSearch" @click="showCustomerSearch = false">
+			<view class="center-modal-content" @click.stop>
+				<view class="modal-header">
+					<view class="search-box">
+						<view class="search-icon"></view> 
+						<input class="search-input" v-model="searchKeyword" placeholder="搜索主人姓名或手机号" focus />
+						<view class="clear-icon" v-if="searchKeyword" @click="searchKeyword = ''"></view>
+					</view>
+				</view>
+				<scroll-view scroll-y class="customer-list-scroll">
+					<view class="customer-item" v-for="item in filteredCustomers" :key="item.id" @click="selectCustomer(item)">
+						<view class="item-info">
+							<text class="name">{{ item.name }}</text>
+							<text class="phone">{{ item.phone }}</text>
+						</view>
+						<view class="checkmark" v-if="form.userId === item.id"></view>
+					</view>
+					<view class="empty-tip" v-if="filteredCustomers.length === 0">未找到匹配的主人</view>
+				</scroll-view>
+				<view class="modal-footer">
+					<button class="modal-close-btn" @click="showCustomerSearch = false">关闭</button>
+				</view>
+			</view>
+		</view>
+
+		<!-- 居中重构:品种选择弹窗 @Author: Antigravity -->
+		<view class="center-modal-mask" v-if="showBreedPicker" @click="closeBreedPicker">
+			<view class="center-modal-content" @click.stop>
+				<view class="modal-header">
+					<view class="search-box">
+						<view class="search-icon"></view>
+						<input class="search-input" v-model="breedSearchKeyword" placeholder="查找品种..." focus />
+					</view>
+				</view>
+				<scroll-view scroll-y class="customer-list-scroll">
+					<view class="customer-item" v-for="(item, index) in filteredBreeds" :key="index" @click="selectBreed(item)">
+						<text class="name">{{ item }}</text>
+						<view class="checkmark" v-if="form.breed === item"></view>
+					</view>
+				</scroll-view>
+				<view class="breed-footer">
+					<input class="breed-custom-input" v-model="customBreedText" placeholder="或手动录入新品种" />
+					<view class="add-confirm-btn" @click="confirmCustomBreed">添加</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 各项居中选择器组件实例 @Author: Antigravity -->
+		<center-select v-model="showGenderSelect" title="选择性别" :options="genderOptions" :value="form.gender" @select="(item) => form.gender = item.value" />
+		<center-select v-model="showSizeSelect" title="选择体型" :options="sizeOptions" :value="form.size" @select="(item) => form.size = item.value" />
+		<center-select v-model="showHouseTypeSelect" title="房屋类型" :options="houseTypeOptions" :value="form.houseType" @select="(item) => form.houseType = item.value" />
+		<center-select v-model="showEntryMethodSelect" title="入门方式" :options="entryMethodOptions" :value="form.entryMethod" @select="(item) => form.entryMethod = item.value" />
+		<center-select v-model="showHealthSelect" title="当前健康状态" :options="healthStatusOptions" :value="form.healthStatus" @select="(item) => form.healthStatus = item" />
+		<center-select v-model="showVaccineSelect" title="疫苗接种状态" :options="vaccineOptions" :value="form.vaccineStatus" @select="(item) => form.vaccineStatus = item" />
+		<center-select v-model="showAggressionSelect" title="是否有攻击倾向" :options="aggressionOptions" :value="form.aggression" @select="(item) => form.aggression = item.value" />
+
 	</view>
 </template>
 
 <script setup>
-import { ref, reactive, computed } from 'vue'
+/**
+ * @Author: Antigravity
+ */
+import { ref, reactive, computed, watch } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import { addPet } from '@/api/archieves/pet'
 import { listAllCustomer } from '@/api/archieves/customer'
 import { getDicts } from '@/api/system/dict/data'
+import { uploadFile } from '@/api/system/oss'
 import navBar from '@/components/nav-bar/index.vue'
+import centerSelect from '@/components/center-select/index.vue'
 import customerEnums from '@/json/customer.json'
 
+// 状态控制
+const showGenderSelect = ref(false)
+const showSizeSelect = ref(false)
+const showHouseTypeSelect = ref(false)
+const showEntryMethodSelect = ref(false)
+const showHealthSelect = ref(false)
+const showVaccineSelect = ref(false)
+const showAggressionSelect = ref(false)
+const showCustomerSearch = ref(false)
+const showBreedPicker = ref(false)
+const tempAvatarUrl = ref('')
+
 const genderOptions = [{ label: '未知', value: 0 }, { label: '公', value: 1 }, { label: '母', value: 2 }]
 const sizeOptions = [{ label: '小型(0-10kg)', value: 'small' }, { label: '中型(10-25kg)', value: 'medium' }, { label: '大型(25kg+)', value: 'large' }]
 const { houseTypeOptions, entryMethodOptions } = customerEnums
@@ -141,26 +214,33 @@ const aggressionOptions = [{ label: '否', value: 0 }, { label: '是', value: 1
 
 const customerOptions = ref([])
 const breedCandidates = ref([])
+const searchKeyword = ref('')
+const breedSearchKeyword = ref('')
+const customBreedText = ref('')
 
-onLoad(async () => {
+const fetchCustomers = async () => {
 	try {
         const res = await listAllCustomer({ status: 0 })
-        customerOptions.value = Array.isArray(res) ? res : (res?.data || [])
-    } catch(e) {
-		console.error('获取主人列表失败', e)
-	}
+        const records = Array.isArray(res) ? res : (res?.data || [])
+        customerOptions.value = records.map(item => ({
+            id: item.id || item.userId,
+            name: item.name || item.customerName,
+            phone: item.phone || item.contactPhoneNumber || item.phone
+        }))
+    } catch(e) { console.error('获取主人列表失败', e) }
+}
 
+onLoad(async () => {
+	await fetchCustomers()
 	try {
 		const dictRes = await getDicts('sys_pet_breed')
 		const list = Array.isArray(dictRes) ? dictRes : (dictRes?.data || [])
-		// 提取中文名给 combox,RuoYi 系统对应的字段可能是 dictLabel/dictValue
 		breedCandidates.value = list.map(item => item.dictLabel || item.dictValue || item.label || item.value)
-	} catch(e) {
-		console.error('获取宠物品种字典失败', e)
-	}
+	} catch(e) { console.error('获取宠物品种字典失败', e) }
 })
 
 const form = reactive({
+	avatar: '',
 	name: '', userId: '', breed: '', gender: 0, age: '', weight: '', size: '',
 	houseType: '', entryMethod: '', entryPassword: '', keyLocation: '',
 	personality: '', cutePersonality: '', healthStatus: '健康', aggression: 0,
@@ -178,14 +258,73 @@ const getHouseTypeLabel = computed(() => houseTypeOptions.find(i => i.value ===
 const getEntryMethodLabel = computed(() => entryMethodOptions.find(i => i.value === form.entryMethod)?.label || '请选择')
 const getAggressionLabel = computed(() => aggressionOptions.find(i => i.value === form.aggression)?.label || '请选择')
 
-const onCustomerChange = (e) => { form.userId = customerOptions.value[e.detail.value]?.id }
-const onGenderChange = (e) => { form.gender = genderOptions[e.detail.value].value }
-const onSizeChange = (e) => { form.size = sizeOptions[e.detail.value].value }
-const onHouseTypeChange = (e) => { form.houseType = houseTypeOptions[e.detail.value].value }
-const onEntryMethodChange = (e) => { form.entryMethod = entryMethodOptions[e.detail.value].value }
-const onHealthChange = (e) => { form.healthStatus = healthStatusOptions[e.detail.value] }
-const onVaccineChange = (e) => { form.vaccineStatus = vaccineOptions[e.detail.value] }
-const onAggressionChange = (e) => { form.aggression = aggressionOptions[e.detail.value].value }
+const filteredCustomers = computed(() => {
+	if (!searchKeyword.value) return customerOptions.value
+	const kw = searchKeyword.value.toLowerCase().trim()
+	return customerOptions.value.filter(item => 
+		(item.name && item.name.toLowerCase().includes(kw)) || 
+		(item.phone && item.phone.includes(kw))
+	)
+})
+
+const selectCustomer = (customer) => {
+	form.userId = customer.id
+	showCustomerSearch.value = false
+	searchKeyword.value = ''
+}
+
+const filteredBreeds = computed(() => {
+	if (!breedSearchKeyword.value.trim()) return breedCandidates.value
+	const kw = breedSearchKeyword.value.toLowerCase().trim()
+	return breedCandidates.value.filter(item => item.toLowerCase().includes(kw))
+})
+
+const selectBreed = (breed) => {
+	form.breed = breed
+	showBreedPicker.value = false
+	breedSearchKeyword.value = ''
+}
+
+const closeBreedPicker = () => {
+	showBreedPicker.value = false
+	breedSearchKeyword.value = ''
+	customBreedText.value = ''
+}
+
+const confirmCustomBreed = () => {
+	const val = customBreedText.value.trim()
+	if (!val) return
+	form.breed = val
+	if (!breedCandidates.value.includes(val)) {
+		breedCandidates.value.push(val)
+	}
+	showBreedPicker.value = false
+	breedSearchKeyword.value = ''
+	customBreedText.value = ''
+}
+
+// 选择并上传宠物头像
+// @Author: Antigravity
+const handleChooseAvatar = () => {
+    uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: async (res) => {
+            try {
+                uni.showLoading({ title: '上传中...' });
+                const uploadRes = await uploadFile(res.tempFilePaths[0]);
+                form.avatar = uploadRes.ossId; // 提交给后端的 ID
+                tempAvatarUrl.value = uploadRes.url; // 用于预览的 URL
+                uni.hideLoading();
+                uni.showToast({ title: '头像上传成功', icon: 'success' });
+            } catch (e) {
+                uni.hideLoading();
+                console.error('上传头像失败', e);
+            }
+        }
+    });
+}
 
 const onSave = async () => {
   if (!form.name) return uni.showToast({ title: '请输入宠物名称', icon: 'none' })
@@ -196,8 +335,6 @@ const onSave = async () => {
   if (!form.age) return uni.showToast({ title: '请输入年龄', icon: 'none' })
   if (!form.houseType) return uni.showToast({ title: '请选择家庭房屋类型', icon: 'none' })
   if (!form.entryMethod) return uni.showToast({ title: '请选择入门方式', icon: 'none' })
-  if (form.entryMethod === 'password' && !form.entryPassword) return uni.showToast({ title: '请输入门锁密码', icon: 'none' })
-  if (form.entryMethod === 'key' && !form.keyLocation) return uni.showToast({ title: '请输入钥匙存放位置', icon: 'none' })
   if (!form.healthStatus) return uni.showToast({ title: '请选择健康状态', icon: 'none' })
   if (!form.vaccineStatus) return uni.showToast({ title: '请选择疫苗情况', icon: 'none' })
   if (!form.medicalHistory) return uni.showToast({ title: '请输入既往病史', icon: 'none' })
@@ -218,23 +355,140 @@ const onSave = async () => {
 
 <style lang="scss" scoped>
 .pet-add-page { min-height: 100vh; background: #f7f8fa; padding-bottom: calc(140rpx + env(safe-area-inset-bottom)); }
+
+.avatar-section {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 40rpx 0;
+    background-color: #fff;
+    margin-bottom: 20rpx;
+    border-bottom: 2rpx solid #f0f0f0;
+    .avatar-img {
+        width: 160rpx;
+        height: 160rpx;
+        border-radius: 80rpx;
+        background-color: #f7f8fa;
+        border: 4rpx solid #fff;
+        box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
+    }
+    .avatar-tip {
+        margin-top: 16rpx;
+        font-size: 24rpx;
+        color: #ff9500;
+        font-weight: 500;
+    }
+}
 .section-title { font-size: 28rpx; font-weight: bold; color: #666; padding: 24rpx 32rpx 12rpx; }
 .form-card { background: #fff; border-radius: 24rpx; padding: 8rpx 32rpx; margin: 0 24rpx; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 2rpx solid #EEEEEE; position: relative; }
 .form-item:last-child { border-bottom: none; }
 .form-item.vertical { flex-direction: column; align-items: flex-start; border-bottom: none; }
-.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 16rpx; box-sizing: border-box; border-radius: 12rpx; }
+.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 16rpx; box-sizing: border-box; border-radius: 12rpx; font-size: 28rpx; }
 .form-label { width: 220rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
 .form-label.require::before { content: '*'; color: #f56c6c; margin-right: 4rpx; }
 .form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.form-combox-wrapper { flex: 1; min-width: 0; }
-.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; min-height: 40rpx; }
+.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; margin-right: 16rpx; }
 .picker-value.placeholder { color: #ccc; }
+
 .footer-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); z-index: 100; }
-.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; }
+.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; &::after { border: none; } }
+
+/* CSS 绘制右箭头 @Author: Antigravity */
+.right-arrow {
+	width: 14rpx;
+	height: 14rpx;
+	border-right: 3rpx solid #ccc;
+	border-top: 3rpx solid #ccc;
+	transform: rotate(45deg);
+	flex-shrink: 0;
+}
+
+/* 居中弹窗通用样式 @Author: Antigravity */
+.center-modal-mask {
+	position: fixed;
+	top: 0; left: 0; right: 0; bottom: 0;
+	background: rgba(0,0,0,0.6);
+	z-index: 1000;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	backdrop-filter: blur(2px);
+}
+.center-modal-content {
+	width: 620rpx;
+	max-height: 80vh;
+	background: #fff;
+	border-radius: 32rpx;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	animation: popIn 0.3s ease-out;
+}
+@keyframes popIn {
+	from { transform: scale(0.8); opacity: 0; }
+	to { transform: scale(1); opacity: 1; }
+}
 
-:deep(.uni-combox) { border: none !important; }
-:deep(.uni-combox__input-box) { display: flex; justify-content: flex-end; padding-right: 0; }
-:deep(.uni-combox__input) { text-align: right; font-size: 28rpx; padding-right: 0;}
-:deep(.uni-combox__input-plholder) { color: #ccc; text-align: right; font-size: 28rpx; padding-right: 0;}
+.modal-header { padding: 32rpx; border-bottom: 2rpx solid #f2f2f2; }
+.search-box {
+	height: 72rpx; background: #f5f5f5; border-radius: 36rpx;
+	display: flex; align-items: center; padding: 0 24rpx; gap: 12rpx;
+}
+/* CSS 搜索图标 @Author: Antigravity */
+.search-icon {
+	width: 24rpx; height: 24rpx; border: 3rpx solid #999; border-radius: 50%;
+	position: relative;
+	&::after {
+		content: ''; position: absolute; right: -8rpx; bottom: -8rpx;
+		width: 12rpx; height: 3rpx; background: #999; transform: rotate(45deg);
+	}
+}
+.search-input { flex: 1; font-size: 28rpx; color: #333; }
+/* CSS 清除图标 @Author: Antigravity */
+.clear-icon {
+	width: 32rpx; height: 32rpx; background: #ccc; border-radius: 50%;
+	position: relative;
+	&::before, &::after {
+		content: ''; position: absolute; top: 14rpx; left: 6rpx; width: 20rpx; height: 3rpx;
+		background: #fff; transform: rotate(45deg);
+	}
+	&::after { transform: rotate(-45deg); }
+}
+
+.customer-list-scroll { flex: 1; padding: 0 32rpx; max-height: 50vh; }
+.customer-item {
+	display: flex; align-items: center; justify-content: space-between;
+	padding: 30rpx 0; border-bottom: 2rpx solid #f9f9f9;
+}
+.customer-item:last-child { border-bottom: none; }
+.name { font-size: 32rpx; color: #333; font-weight: bold; }
+.phone { font-size: 24rpx; color: #999; margin-top: 4rpx; }
+
+/* CSS 对勾 @Author: Antigravity */
+.checkmark {
+	width: 12rpx; height: 24rpx; border-right: 4rpx solid #ff9500; border-bottom: 4rpx solid #ff9500;
+	transform: rotate(45deg); margin-right: 10rpx;
+}
+
+.modal-footer { padding: 24rpx 32rpx; border-top: 2rpx solid #f2f2f2; }
+.modal-close-btn {
+	width: 100%; height: 72rpx; line-height: 72rpx; background: #f5f5f5;
+	border-radius: 36rpx; font-size: 28rpx; color: #666; border: none;
+	&::after { border: none; }
+}
+
+/* 品种录入底部 */
+.breed-footer {
+	padding: 24rpx 32rpx; background: #fafafa; border-top: 2rpx solid #f2f2f2;
+	display: flex; gap: 16rpx; align-items: center;
+}
+.breed-custom-input {
+	flex: 1; height: 72rpx; background: #eee; border-radius: 12rpx;
+	padding: 0 20rpx; font-size: 26rpx;
+}
+.add-confirm-btn {
+	padding: 0 30rpx; height: 72rpx; line-height: 72rpx;
+	background: #ff9500; color: #fff; border-radius: 12rpx; font-size: 26rpx; font-weight: bold;
+}
 </style>

+ 42 - 33
pages/my/pet/detail/index.vue

@@ -1,11 +1,11 @@
 <template>
 	<view class="pet-detail-page">
-		<nav-bar title="宠物档案详情" bgColor="transparent" fontColor="#fff"></nav-bar>
+		<nav-bar title="宠物档案详情" bgColor="#ffecd2" color="#333"></nav-bar>
 		
 		<view class="pet-hero">
-			<image :src="petInfo.avatar || defaultAvatar" class="hero-img" mode="aspectFill"></image>
-			<view class="hero-overlay"></view>
+			<view class="hero-bg"></view>
 			<view class="hero-content">
+				<image :src="petInfo.avatarUrl || petInfo.avatar || defaultAvatar" class="hero-avatar" mode="aspectFill"></image>
 				<view class="hero-main">
 					<text class="pet-name">{{ petInfo.name || '加载中...' }}</text>
 					<view class="tag-list">
@@ -202,43 +202,53 @@ const goToEdit = () => {
 
 .pet-hero {
 	position: relative;
-	height: 520rpx;
+	height: 460rpx;
 	width: 100%;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	overflow: hidden;
 }
 
-.hero-img {
-	width: 100%;
-	height: 100%;
+.hero-bg {
+	position: absolute;
+	top: 0; left: 0; right: 0; bottom: 0;
+	background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
 }
 
-.hero-overlay {
-	position: absolute;
-	top: 0;
-	left: 0;
-	right: 0;
-	bottom: 0;
-	background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0) 40%, rgba(0,0,0,0.8) 100%);
+.hero-avatar {
+	width: 200rpx;
+	height: 200rpx;
+	border-radius: 50%;
+	border: 6rpx solid #fff;
+	box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
+	z-index: 5;
+	flex-shrink: 0;
 }
 
 .hero-content {
-	position: absolute;
-	bottom: 60rpx;
-	left: 40rpx;
-	right: 40rpx;
+	position: relative;
 	z-index: 5;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	margin-top: 20rpx;
+	padding: 0 48rpx;
 }
 
 .hero-main {
 	display: flex;
 	align-items: center;
-	margin-bottom: 20rpx;
+	justify-content: center;
+	margin-bottom: 12rpx;
+	gap: 16rpx;
 }
 
 .pet-name {
-	font-size: 56rpx;
+	font-size: 44rpx;
 	font-weight: 800;
-	color: #fff;
-	margin-right: 24rpx;
+	color: #333;
 }
 
 .tag-list {
@@ -247,20 +257,20 @@ const goToEdit = () => {
 }
 
 .gender-tag, .size-tag {
-	padding: 4rpx 20rpx;
+	padding: 6rpx 20rpx;
 	border-radius: 30rpx;
 	font-size: 22rpx;
-	background: rgba(255, 255, 255, 0.2);
-	color: #fff;
-	backdrop-filter: blur(4rpx);
+	background: rgba(255, 255, 255, 0.85);
+	color: #555;
 }
 
-.gender-tag.male { background: rgba(0, 122, 255, 0.6); }
-.gender-tag.female { background: rgba(255, 45, 85, 0.6); }
+.gender-tag.male { background: #e3f2fd; color: #1565c0; }
+.gender-tag.female { background: #fce4ec; color: #c2185b; }
 
 .pet-summary {
-	font-size: 28rpx;
-	color: rgba(255, 255, 255, 0.9);
+	font-size: 26rpx;
+	color: #777;
+	text-align: center;
 }
 
 .detail-container {
@@ -320,7 +330,7 @@ const goToEdit = () => {
 }
 
 .value {
-	font-size: 28rpx;
+	font-size: 26rpx;
 	color: #333;
 	font-weight: 500;
 }
@@ -334,7 +344,6 @@ const goToEdit = () => {
 .value.highlight {
 	color: #ff9500;
 	font-weight: bold;
-	font-size: 32rpx;
 }
 
 .value.green { color: #4caf50; }
@@ -362,7 +371,7 @@ const goToEdit = () => {
 	width: 100%;
 	height: 88rpx;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
 	border-radius: 44rpx;
 	font-size: 32rpx;

+ 235 - 88
pages/my/pet/edit/index.vue

@@ -2,8 +2,16 @@
 	<view class="pet-edit-page">
 		<nav-bar title="编辑宠物档案"></nav-bar>
 		
+		<!-- 宠物预览/头像修改 @Author: Antigravity -->
+		<view class="avatar-section" @click="handleChooseAvatar">
+			<image :src="tempAvatarUrl || '/static/images/profile.png'" class="avatar-img" mode="aspectFill"></image>
+			<view class="avatar-tip">点击修改宠物头像</view>
+		</view>
+
 		<view v-if="loading" class="loading-state">
-			<uni-load-more status="loading"></uni-load-more>
+			<!-- CSS 原生 Loading @Author: Antigravity -->
+			<view class="spinner"></view>
+			<text class="loading-txt">加载中...</text>
 		</view>
 
 		<block v-else>
@@ -14,35 +22,30 @@
 					<text class="form-label require">宠物名称</text>
 					<input class="form-input" v-model="form.name" placeholder="请输入宠物名称" />
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showTypeSelect = true">
 					<text class="form-label require">宠物类型</text>
-					<picker :range="typeOptions" range-key="label" @change="onTypeChange">
-						<view class="picker-value">{{ getTypeLabel }}</view>
-					</picker>
+					<view class="picker-value">{{ getTypeLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showCustomerSelect = true">
 					<text class="form-label require">所属主人</text>
-					<picker :range="customerOptions" range-key="name" @change="onCustomerChange">
-						<view class="picker-value" :class="{'placeholder': !form.userId}">{{ getCustomerLabel }}</view>
-					</picker>
+					<view class="picker-value" :class="{'placeholder': !form.userId}">{{ getCustomerLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showBreedPicker = true">
 					<text class="form-label require">品种</text>
-					<view class="form-combox-wrapper">
-						<uni-combox :candidates="breedCandidates" v-model="form.breed" placeholder="请选择或输入品种" emptyTips="未找到对应品种,可直接输入"></uni-combox>
-					</view>
+					<view class="picker-value" :class="{'placeholder': !form.breed}">{{ form.breed || '可选择或自填品种' }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showGenderSelect = true">
 					<text class="form-label">性别</text>
-					<picker :range="genderOptions" range-key="label" @change="onGenderChange">
-						<view class="picker-value">{{ getGenderLabel }}</view>
-					</picker>
+					<view class="picker-value">{{ getGenderLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showSizeSelect = true">
 					<text class="form-label require">体型</text>
-					<picker :range="sizeOptions" range-key="label" @change="onSizeChange">
-						<view class="picker-value" :class="{'placeholder': !form.size}">{{ getSizeLabel }}</view>
-					</picker>
+					<view class="picker-value" :class="{'placeholder': !form.size}">{{ getSizeLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
 				<view class="form-item">
 					<text class="form-label require">年龄(岁)</text>
@@ -70,17 +73,15 @@
 			<!-- 家庭信息 -->
 			<view class="section-title">家庭信息</view>
 			<view class="form-card">
-				<view class="form-item">
+				<view class="form-item" @click="showHouseTypeSelect = true">
 					<text class="form-label require">房屋类型</text>
-					<picker :range="houseTypeOptions" range-key="label" @change="onHouseTypeChange">
-						<view class="picker-value" :class="{'placeholder': !form.houseType}">{{ getHouseTypeLabel }}</view>
-					</picker>
+					<view class="picker-value" :class="{'placeholder': !form.houseType}">{{ getHouseTypeLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showEntryMethodSelect = true">
 					<text class="form-label require">入门方式</text>
-					<picker :range="entryMethodOptions" range-key="label" @change="onEntryMethodChange">
-						<view class="picker-value" :class="{'placeholder': !form.entryMethod}">{{ getEntryMethodLabel }}</view>
-					</picker>
+					<view class="picker-value" :class="{'placeholder': !form.entryMethod}">{{ getEntryMethodLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
 				<view class="form-item" v-if="form.entryMethod === 'password'">
 					<text class="form-label require">门锁密码</text>
@@ -95,23 +96,20 @@
 			<!-- 健康状况 -->
 			<view class="section-title">健康状况</view>
 			<view class="form-card">
-				<view class="form-item">
+				<view class="form-item" @click="showHealthSelect = true">
 					<text class="form-label require">健康状态</text>
-					<picker :range="healthStatusOptions" @change="onHealthChange">
-						<view class="picker-value">{{ form.healthStatus || '请选择' }}</view>
-					</picker>
+					<view class="picker-value">{{ form.healthStatus || '请选择' }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showVaccineSelect = true">
 					<text class="form-label require">疫苗接种</text>
-					<picker :range="vaccineOptions" @change="onVaccineChange">
-						<view class="picker-value">{{ form.vaccineStatus || '请选择' }}</view>
-					</picker>
+					<view class="picker-value">{{ form.vaccineStatus || '请选择' }}</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="form-item">
+				<view class="form-item" @click="showAggressionSelect = true">
 					<text class="form-label require">攻击倾向</text>
-					<picker :range="aggressionOptions" range-key="label" @change="onAggressionChange">
-						<view class="picker-value">{{ getAggressionLabel }}</view>
-					</picker>
+					<view class="picker-value">{{ getAggressionLabel }}</view>
+					<view class="right-arrow"></view>
 				</view>
 				<view class="form-item vertical">
 					<text class="form-label require">既往病史</text>
@@ -131,21 +129,73 @@
 			<view class="footer-bar">
 				<button class="save-btn" @click="onSave">保存修改</button>
 			</view>
+
+			<!-- 品种选择中心弹窗 @Author: Antigravity -->
+			<view class="center-modal-mask" v-if="showBreedPicker" @click="closeBreedPicker">
+				<view class="center-modal-content" @click.stop>
+					<view class="modal-header">
+						<view class="search-box">
+							<view class="search-icon"></view>
+							<input class="search-input" v-model="breedSearchKeyword" placeholder="搜索品种..." focus />
+						</view>
+					</view>
+					<scroll-view scroll-y class="customer-list-scroll">
+						<view class="customer-item" v-for="(item, index) in filteredBreeds" :key="index" @click="selectBreed(item)">
+							<text class="name">{{ item }}</text>
+							<view class="checkmark" v-if="form.breed === item"></view>
+						</view>
+					</scroll-view>
+					<view class="breed-footer">
+						<input class="breed-custom-input" v-model="customBreedText" placeholder="或手动录入新品种" />
+						<view class="add-confirm-btn" @click="confirmCustomBreed">更新</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 居中选择器实例群 @Author: Antigravity -->
+			<center-select v-model="showTypeSelect" title="宠物类型" :options="typeOptions" :value="form.type" @select="(item) => form.type = item.value" />
+			<center-select v-model="showCustomerSelect" title="所属主人" :options="customerOptions" labelKey="name" valueKey="id" :value="form.userId" @select="(item) => form.userId = item.id" />
+			<center-select v-model="showGenderSelect" title="选择性别" :options="genderOptions" :value="form.gender" @select="(item) => form.gender = item.value" />
+			<center-select v-model="showSizeSelect" title="选择体型" :options="sizeOptions" :value="form.size" @select="(item) => form.size = item.value" />
+			<center-select v-model="showHouseTypeSelect" title="房屋类型" :options="houseTypeOptions" :value="form.houseType" @select="(item) => form.houseType = item.value" />
+			<center-select v-model="showEntryMethodSelect" title="入门方式" :options="entryMethodOptions" :value="form.entryMethod" @select="(item) => form.entryMethod = item.value" />
+			<center-select v-model="showHealthSelect" title="当前健康状态" :options="healthStatusOptions" :value="form.healthStatus" @select="(item) => form.healthStatus = item" />
+			<center-select v-model="showVaccineSelect" title="疫苗接种状态" :options="vaccineOptions" :value="form.vaccineStatus" @select="(item) => form.vaccineStatus = item" />
+			<center-select v-model="showAggressionSelect" title="是否有攻击倾向" :options="aggressionOptions" :value="form.aggression" @select="(item) => form.aggression = item.value" />
+
 		</block>
 	</view>
 </template>
 
 <script setup>
-// @Author: Antigravity
+/**
+ * @Author: Antigravity
+ */
 import { ref, reactive, computed } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import { getPet, updatePet } from '@/api/archieves/pet'
 import { listAllCustomer } from '@/api/archieves/customer'
 import { getDicts } from '@/api/system/dict/data'
+import { uploadFile } from '@/api/system/oss'
 import navBar from '@/components/nav-bar/index.vue'
+import centerSelect from '@/components/center-select/index.vue'
 import customerEnums from '@/json/customer.json'
 
 const loading = ref(true)
+
+// 弹窗状态控制
+const showTypeSelect = ref(false)
+const showCustomerSelect = ref(false)
+const showGenderSelect = ref(false)
+const showSizeSelect = ref(false)
+const showHouseTypeSelect = ref(false)
+const showEntryMethodSelect = ref(false)
+const showHealthSelect = ref(false)
+const showVaccineSelect = ref(false)
+const showAggressionSelect = ref(false)
+const showBreedPicker = ref(false)
+const tempAvatarUrl = ref('')
+
 const typeOptions = [{ label: '猫', value: 1 }, { label: '狗', value: 2 }, { label: '其他', value: 3 }]
 const genderOptions = [{ label: '未知', value: 0 }, { label: '公', value: 1 }, { label: '母', value: 2 }]
 const sizeOptions = [{ label: '小型(0-10kg)', value: 'small' }, { label: '中型(10-25kg)', value: 'medium' }, { label: '大型(25kg+)', value: 'large' }]
@@ -157,9 +207,11 @@ const aggressionOptions = [{ label: '否', value: 0 }, { label: '是', value: 1
 const customerOptions = ref([])
 const breedCandidates = ref([])
 const petId = ref(null)
+const breedSearchKeyword = ref('')
+const customBreedText = ref('')
 
 const form = reactive({
-	id: '', name: '', userId: '', type: 1, breed: '', gender: 0, age: '', weight: '', size: '',
+	id: '', avatar: '', name: '', userId: '', type: 1, breed: '', gender: 0, age: '', weight: '', size: '',
 	arrivalTime: '', houseType: '', entryMethod: '', entryPassword: '', keyLocation: '',
 	personality: '', cutePersonality: '', healthStatus: '健康', aggression: 0,
 	vaccineStatus: '无', medicalHistory: '', allergies: '', remark: ''
@@ -168,9 +220,7 @@ const form = reactive({
 onLoad(async (options) => {
 	if (options.id) {
 		petId.value = options.id
-		// 1. 获取所有必要的基础数据(客人列表、品种字典)
 		await initData()
-		// 2. 获取详情并回显
 		await fetchDetail()
 	} else {
 		uni.showToast({ title: '参数错误', icon: 'none' })
@@ -184,21 +234,24 @@ const initData = async () => {
 			listAllCustomer({ status: 0 }),
 			getDicts('sys_pet_breed')
 		])
-		customerOptions.value = Array.isArray(custRes) ? custRes : (custRes?.data || [])
+		const records = Array.isArray(custRes) ? custRes : (custRes?.data || [])
+		customerOptions.value = records.map(item => ({
+            id: item.id || item.userId,
+            name: item.name || item.customerName
+        }))
 		const dictList = Array.isArray(dictRes) ? dictRes : (dictRes?.data || [])
 		breedCandidates.value = dictList.map(item => item.dictLabel || item.dictValue || item.label || item.value)
-	} catch(e) {
-		console.error('初始化数据失败', e)
-	}
+	} catch(e) { console.error('初始化数据失败', e) }
 }
 
 const fetchDetail = async () => {
 	try {
 		loading.value = true
 		const res = await getPet(petId.value)
-		if (res) {
-			// 将接口返回的数据合并到响应式表单对象中
-			Object.assign(form, res)
+		if (res) { 
+			const data = res.data || res
+			Object.assign(form, data) 
+			tempAvatarUrl.value = data.avatarUrl || '' // 回显全路径 URL
 		}
 	} catch (e) {
 		console.error('获取宠物详情失败', e)
@@ -207,78 +260,172 @@ const fetchDetail = async () => {
 	}
 }
 
-// 标签转换逻辑
 const getTypeLabel = computed(() => typeOptions.find(o => o.value === form.type)?.label || '请选择')
-const onTypeChange = (e) => { form.type = typeOptions[e.detail.value].value }
-
 const getCustomerLabel = computed(() => {
 	const opt = customerOptions.value.find(o => String(o.id) === String(form.userId))
 	return opt ? opt.name : '请选择主人'
 })
-const onCustomerChange = (e) => { form.userId = customerOptions.value[e.detail.value].id }
-
 const getGenderLabel = computed(() => genderOptions.find(o => o.value === form.gender)?.label || '未知')
-const onGenderChange = (e) => { form.gender = genderOptions[e.detail.value].value }
-
 const getSizeLabel = computed(() => sizeOptions.find(o => o.value === form.size)?.label || '请选择')
-const onSizeChange = (e) => { form.size = sizeOptions[e.detail.value].value }
-
 const getHouseTypeLabel = computed(() => houseTypeOptions.find(o => o.value === form.houseType)?.label || '请选择')
-const onHouseTypeChange = (e) => { form.houseType = houseTypeOptions[e.detail.value].value }
-
 const getEntryMethodLabel = computed(() => entryMethodOptions.find(o => o.value === form.entryMethod)?.label || '请选择')
-const onEntryMethodChange = (e) => { form.entryMethod = entryMethodOptions[e.detail.value].value }
+const getAggressionLabel = computed(() => aggressionOptions.find(o => o.value === form.aggression)?.label || '否')
 
-const onHealthChange = (e) => { form.healthStatus = healthStatusOptions[e.detail.value] }
-const onVaccineChange = (e) => { form.vaccineStatus = vaccineOptions[e.detail.value] }
+const filteredBreeds = computed(() => {
+	if (!breedSearchKeyword.value.trim()) return breedCandidates.value
+	const kw = breedSearchKeyword.value.toLowerCase().trim()
+	return breedCandidates.value.filter(item => item.toLowerCase().includes(kw))
+})
 
-const getAggressionLabel = computed(() => aggressionOptions.find(o => o.value === form.aggression)?.label || '否')
-const onAggressionChange = (e) => { form.aggression = aggressionOptions[e.detail.value].value }
+const selectBreed = (breed) => {
+	form.breed = breed
+	showBreedPicker.value = false
+	breedSearchKeyword.value = ''
+}
+
+const closeBreedPicker = () => {
+	showBreedPicker.value = false
+	breedSearchKeyword.value = ''
+	customBreedText.value = ''
+}
+
+// 选择并上传/修改宠物头像
+// @Author: Antigravity
+const handleChooseAvatar = () => {
+    uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: async (res) => {
+            try {
+                uni.showLoading({ title: '上传中...' });
+                const uploadRes = await uploadFile(res.tempFilePaths[0]);
+                form.avatar = uploadRes.ossId; // 提交给后端的 ID
+                tempAvatarUrl.value = uploadRes.url; // 用于预览的全路径 URL
+                uni.hideLoading();
+                uni.showToast({ title: '头像上传成功', icon: 'success' });
+            } catch (e) {
+                uni.hideLoading();
+                console.error('上传头像失败', e);
+            }
+        }
+    });
+}
+
+const confirmCustomBreed = () => {
+	const val = customBreedText.value.trim()
+	if (!val) return
+	form.breed = val
+	if (!breedCandidates.value.includes(val)) { breedCandidates.value.push(val) }
+	showBreedPicker.value = false
+	breedSearchKeyword.value = ''
+	customBreedText.value = ''
+}
 
 const onSave = async () => {
-	// 校验逻辑
 	if (!form.name) return uni.showToast({ title: '请输入宠物名称', icon: 'none' })
 	if (!form.userId) return uni.showToast({ title: '请选择所属主人', icon: 'none' })
 	if (!form.breed) return uni.showToast({ title: '请输入或选择品种', icon: 'none' })
 	if (!form.size) return uni.showToast({ title: '请选择体型', icon: 'none' })
 	if (!form.age) return uni.showToast({ title: '请输入年龄', icon: 'none' })
-	if (!form.weight) return uni.showToast({ title: '请输入体重', icon: 'none' })
-	if (!form.houseType) return uni.showToast({ title: '请选择房屋类型', icon: 'none' })
-	if (!form.entryMethod) return uni.showToast({ title: '请选择入门方式', icon: 'none' })
-	
+
 	try {
 		uni.showLoading({ title: '保存中' })
 		await updatePet(form)
 		uni.hideLoading()
 		uni.showToast({ title: '保存成功', icon: 'success' })
 		setTimeout(() => uni.navigateBack(), 1000)
-	} catch (error) {
-		uni.hideLoading()
-	}
+	} catch (error) { uni.hideLoading() }
 }
 </script>
 
 <style lang="scss" scoped>
 .pet-edit-page { min-height: 100vh; background: #f7f8fa; padding-bottom: calc(140rpx + env(safe-area-inset-bottom)); }
-.loading-state { padding-top: 100rpx; }
-.section-title { font-size: 28rpx; font-weight: bold; color: #666; padding: 24rpx 32rpx 12rpx; }
+
+.avatar-section {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 40rpx 0;
+    background-color: #fff;
+    margin-bottom: 20rpx;
+    border-bottom: 2rpx solid #f0f0f0;
+    .avatar-img {
+        width: 160rpx;
+        height: 160rpx;
+        border-radius: 80rpx;
+        background-color: #f7f8fa;
+        border: 4rpx solid #fff;
+        box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
+    }
+    .avatar-tip {
+        margin-top: 16rpx;
+        font-size: 24rpx;
+        color: #ff9500;
+        font-weight: 500;
+    }
+}
+
+/* CSS 原生 Loading 方案 @Author: Antigravity */
+.loading-state {
+	padding-top: 15vh; display: flex; flex-direction: column; align-items: center; justify-content: center;
+	.loading-txt { font-size: 24rpx; color: #999; margin-top: 20rpx; }
+}
+.spinner {
+	width: 40rpx; height: 40rpx; border: 4rpx solid #f3f3f3; border-top: 4rpx solid #ff9500;
+	border-radius: 50%; animation: spin 1s linear infinite;
+}
+@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
+
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; padding: 32rpx 32rpx 16rpx; }
 .form-card { background: #fff; border-radius: 24rpx; padding: 8rpx 32rpx; margin: 0 24rpx; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 2rpx solid #EEEEEE; position: relative; }
 .form-item:last-child { border-bottom: none; }
-.form-item.vertical { flex-direction: column; align-items: flex-start; border-bottom: none; }
-.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 16rpx; box-sizing: border-box; border-radius: 12rpx; }
+.form-item.vertical { flex-direction: column; align-items: stretch; border-bottom: none; gap: 0; }
+.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 20rpx; box-sizing: border-box; border-radius: 12rpx; font-size: 28rpx; color: #333; line-height: 1.6; }
 .form-label { width: 220rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
 .form-label.require::before { content: '*'; color: #f56c6c; margin-right: 4rpx; }
 .form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.form-combox-wrapper { flex: 1; min-width: 0; }
-.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; min-height: 40rpx; }
+.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; margin-right: 16rpx; min-height: 40rpx; }
 .picker-value.placeholder { color: #ccc; }
 
+/* CSS 绘制图标群 @Author: Antigravity */
+.right-arrow {
+	width: 14rpx; height: 14rpx; border-right: 3rpx solid #ccc; border-top: 3rpx solid #ccc; transform: rotate(45deg);
+}
+.checkmark {
+	width: 12rpx; height: 24rpx; border-right: 4rpx solid #ff9500; border-bottom: 4rpx solid #ff9500; transform: rotate(45deg); margin-right: 10rpx;
+}
+.search-icon {
+	width: 24rpx; height: 24rpx; border: 3rpx solid #999; border-radius: 50%; position: relative;
+	&::after { content: ''; position: absolute; right: -8rpx; bottom: -8rpx; width: 12rpx; height: 3rpx; background: #999; transform: rotate(45deg); }
+}
+
 .footer-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); z-index: 100; }
-.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; }
+.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; &::after { border: none; } }
 
-:deep(.uni-combox) { border: none !important; }
-:deep(.uni-combox__input-box) { display: flex; justify-content: flex-end; padding-right: 0; }
-:deep(.uni-combox__input) { text-align: right; font-size: 28rpx; padding-right: 0;}
-:deep(.uni-combox__input-plholder) { color: #ccc; text-align: right; font-size: 28rpx; padding-right: 0;}
+/* 居中弹窗样式 @Author: Antigravity */
+.center-modal-mask {
+	position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 1000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(2px);
+}
+.center-modal-content {
+	width: 620rpx; max-height: 80vh; background: #fff; border-radius: 32rpx; display: flex; flex-direction: column; overflow: hidden; animation: popIn 0.3s ease-out;
+}
+@keyframes popIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
+
+.modal-header { padding: 32rpx; border-bottom: 2rpx solid #f2f2f2; }
+.search-box {
+	height: 72rpx; background: #f5f5f5; border-radius: 36rpx; display: flex; align-items: center; padding: 0 24rpx; gap: 12rpx;
+}
+.search-input { flex: 1; font-size: 28rpx; color: #333; }
+
+.customer-list-scroll { flex: 1; padding: 0 32rpx; max-height: 50vh; }
+.customer-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 0; border-bottom: 2rpx solid #f9f9f9; }
+.name { font-size: 32rpx; color: #333; font-weight: bold; }
+
+.breed-footer {
+	padding: 24rpx 32rpx; background: #fafafa; border-top: 2rpx solid #f2f2f2; display: flex; gap: 16rpx; align-items: center;
+}
+.breed-custom-input { flex: 1; height: 72rpx; background: #eee; border-radius: 12rpx; padding: 0 20rpx; font-size: 26rpx; }
+.add-confirm-btn { padding: 0 30rpx; height: 72rpx; line-height: 72rpx; background: #ff9500; color: #fff; border-radius: 12rpx; font-size: 26rpx; font-weight: bold; }
 </style>

+ 43 - 19
pages/my/pet/list/index.vue

@@ -4,10 +4,10 @@
 		<!-- 顶部操作栏 -->
 		<view class="action-bar">
 			<view class="search-box">
-				<uni-icons type="search" size="14" color="#999"></uni-icons>
+				<text class="search-icon">🔍</text>
 				<input type="text" v-model="searchKeyword" placeholder="搜索宠物名/主人" class="search-input" confirm-type="search" @confirm="onSearch" />
 			</view>
-			<button size="mini" class="add-btn" @click="goToAdd">+ 新增档案</button>
+			<view class="add-btn" @click="goToAdd">+ 新增档案</view>
 		</view>
 
 		<!-- 宠物档案卡片列表 -->
@@ -26,9 +26,9 @@
 					</view>
 					<view class="card-footer">
 						<view class="action-btn-group">
-							<text class="btn-item detail" @click.stop="goToDetail(pet)">详情</text>
-							<text class="btn-item edit" @click.stop="goToEdit(pet)">编辑</text>
-							<text class="btn-item delete" @click.stop="onDelete(pet)">删除</text>
+							<view class="btn-item detail" @click.stop="goToDetail(pet)">详情</view>
+							<view class="btn-item edit" @click.stop="goToEdit(pet)">编辑</view>
+							<view class="btn-item delete" @click.stop="onDelete(pet)">删除</view>
 						</view>
 					</view>
 				</view>
@@ -146,29 +146,43 @@ const onDelete = (pet) => {
 
 .search-box {
 	flex: 1;
+	height: 72rpx;
 	display: flex;
 	align-items: center;
 	background: #f5f5f5;
-	border-radius: 32rpx;
-	padding: 12rpx 20rpx;
+	border-radius: 36rpx;
+	padding: 0 24rpx;
 	gap: 12rpx;
+	box-sizing: border-box;
+}
+
+.search-icon {
+	font-size: 28rpx;
+	line-height: 1;
+	flex-shrink: 0;
 }
 
 .search-input {
 	flex: 1;
 	font-size: 26rpx;
 	background: transparent;
+	height: 72rpx;
+	line-height: 72rpx;
 }
 
 .add-btn {
-	font-size: 24rpx;
+	height: 72rpx;
+	line-height: 72rpx;
+	padding: 0 28rpx;
+	font-size: 25rpx;
 	font-weight: bold;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
-	border-radius: 32rpx;
-	padding: 12rpx 28rpx;
+	border-radius: 36rpx;
+	margin: 0;
 	white-space: nowrap;
+	text-align: center;
 }
 
 .list-container {
@@ -246,8 +260,8 @@ const onDelete = (pet) => {
 
 .card-footer {
 	margin-top: auto;
-	border-top: 1rpx solid #f8f8f8;
-	padding-top: 16rpx;
+	border-top: 2rpx solid #EEEEEE;
+	padding-top: 20rpx;
 }
 
 .action-btn-group {
@@ -258,21 +272,31 @@ const onDelete = (pet) => {
 
 .btn-item {
 	font-size: 24rpx;
-	padding: 8rpx 20rpx;
-	border-radius: 12rpx;
-	border: 1rpx solid #eee;
+	height: 56rpx;
+	line-height: 56rpx;
+	padding: 0 24rpx;
+	border-radius: 28rpx;
+	border: 2rpx solid;
 	color: #666;
+	text-align: center;
+	white-space: nowrap;
 }
 
 .btn-item.detail {
 	background: #fdf6ec;
-	border-color: #faecd8;
-	color: #e6a23c;
+	border-color: #f5d89a;
+	color: #b88230;
+}
+
+.btn-item.edit {
+	background: #ecf5ff;
+	border-color: #a0cfff;
+	color: #409eff;
 }
 
 .btn-item.delete {
 	color: #f56c6c;
-	border-color: #fde2e2;
+	border-color: #fbc4c4;
 	background: #fef0f0;
 }
 </style>

+ 63 - 39
pages/my/settings/account-delete/index.vue

@@ -1,32 +1,36 @@
 <template>
 	<view class="account-delete-page">
-		<view class="warning-card">
-			<uni-icons type="info-filled" size="48" color="#f44336"></uni-icons>
-			<text class="warning-title">账号注销须知</text>
-			<text class="warning-text">注销账号后,以下数据将被永久删除且不可恢复:</text>
-			<view class="warning-list">
-				<text class="warning-item">• 个人信息及宠物档案</text>
-				<text class="warning-item">• 历史订单记录</text>
-				<text class="warning-item">• 评价与投诉记录</text>
-				<text class="warning-item">• 账户余额(如有)</text>
+		<nav-bar title="账号注销" bgColor="#f7f8fa" color="#000"></nav-bar>
+		
+		<view class="form-container">
+			<view class="warning-card">
+				<view class="warning-icon">!</view>
+				<text class="warning-title">账号注销须知</text>
+				<text class="warning-text">注销账号后,以下数据将被永久删除且不可恢复:</text>
+				<view class="warning-list">
+					<text class="warning-item">• 个人信息</text>
+					<text class="warning-item">• 账户余额</text>
+				</view>
 			</view>
-		</view>
-		<view class="confirm-section">
-			<view class="check-row">
-				<checkbox-group @change="onCheckChange">
-					<label class="check-label">
-						<checkbox :checked="confirmed" color="#f44336" style="transform: scale(0.7);" />
-						<text>我已了解上述风险,确认注销</text>
-					</label>
-				</checkbox-group>
+
+			<view class="confirm-section">
+				<view class="check-row">
+					<checkbox-group @change="onCheckChange">
+						<label class="check-label">
+							<checkbox :checked="confirmed" color="#f44336" style="transform: scale(0.7);" />
+							<text>我已了解上述风险,确认注销</text>
+						</label>
+					</checkbox-group>
+				</view>
+				<button class="delete-btn" :disabled="!confirmed" @click="onDelete">确认注销账号</button>
 			</view>
-			<button class="delete-btn" :disabled="!confirmed" @click="onDelete">确认注销账号</button>
 		</view>
 	</view>
 </template>
 
 <script setup>
 import { ref } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
 import { cancelUser } from '@/api/system/user'
 
 const confirmed = ref(false)
@@ -39,14 +43,12 @@ const onDelete = () => {
 		success: async (res) => {
 			if (res.confirm) {
 				try {
-					// @Author: Antigravity
 					await cancelUser()
 					uni.showToast({ title: '账号已注销', icon: 'success' })
-					// 注销成功后清除 token 并跳转回登录页
 					uni.removeStorageSync('token')
 					setTimeout(() => uni.reLaunch({ url: '/pages/login/index' }), 1500)
 				} catch (error) {
-					// 错误处理由 request.js 统一提示,此处无需额外处理
+					// 错误处理由 request.js 统一提示
 				}
 			}
 		}
@@ -58,7 +60,11 @@ const onDelete = () => {
 .account-delete-page {
 	min-height: 100vh;
 	background: #f7f8fa;
-	padding: 40rpx 32rpx;
+	padding: 0 32rpx;
+}
+
+.form-container {
+	margin-top: 20rpx;
 }
 
 .warning-card {
@@ -67,36 +73,53 @@ const onDelete = () => {
 	padding: 48rpx 32rpx;
 	display: flex;
 	flex-direction: column;
-	align-items: center;
+	align-items: flex-start;
+	text-align: left;
+	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.warning-icon {
+	width: 80rpx;
+	height: 80rpx;
+	line-height: 80rpx;
 	text-align: center;
+	border-radius: 50%;
+	background: #fff3e0;
+	color: #ff9800;
+	font-size: 48rpx;
+	font-weight: bold;
+	margin-bottom: 24rpx;
+	border: 2rpx solid #ffe0b2;
 }
 
 .warning-title {
-	font-size: 36rpx;
-	font-weight: 800;
-	color: #f44336;
-	margin: 24rpx 0 16rpx;
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #333;
+	margin-bottom: 20rpx;
 }
 
 .warning-text {
 	font-size: 28rpx;
 	color: #666;
 	margin-bottom: 24rpx;
+	line-height: 1.6;
 }
 
 .warning-list {
-	align-self: flex-start;
+	width: 100%;
 }
 
 .warning-item {
 	display: block;
-	font-size: 26rpx;
-	color: #999;
-	line-height: 2;
+	font-size: 28rpx;
+	color: #666;
+	line-height: 2.2;
 }
 
 .confirm-section {
-	margin-top: 48rpx;
+	margin-top: 40rpx;
+	padding-bottom: 40rpx;
 }
 
 .check-row {
@@ -106,23 +129,24 @@ const onDelete = () => {
 .check-label {
 	display: flex;
 	align-items: center;
-	font-size: 26rpx;
+	font-size: 28rpx;
 	color: #666;
 }
 
 .delete-btn {
 	width: 100%;
-	height: 96rpx;
+	height: 88rpx;
 	background: #f44336;
 	color: #fff;
 	border: none;
-	border-radius: 48rpx;
-	font-size: 32rpx;
+	border-radius: 44rpx;
+	font-size: 30rpx;
 	font-weight: bold;
-	line-height: 96rpx;
+	line-height: 88rpx;
+	&::after { border: none; }
 }
 
 .delete-btn[disabled] {
 	background: #ccc;
 }
-</style>
+</style>

+ 120 - 52
pages/my/settings/change-password/index.vue

@@ -2,33 +2,48 @@
 	<view class="change-password-page">
 		<nav-bar title="修改密码" bgColor="#fff" color="#000"></nav-bar>
 		<view class="form-container">
-			<uni-forms ref="formRef" :model="formData" :rules="rules">
-				<view class="form-group">
-					<uni-forms-item label="旧密码" name="oldPassword" required label-width="160rpx">
-						<uni-easyinput type="password" v-model="formData.oldPassword" placeholder="请输入当前密码"
-							:inputBorder="false" />
-					</uni-forms-item>
-					<view class="line"></view>
-					<uni-forms-item label="新密码" name="newPassword" required label-width="160rpx">
-						<uni-easyinput type="password" v-model="formData.newPassword" placeholder="6-20位新密码"
-							:inputBorder="false" />
-					</uni-forms-item>
-					<view class="line"></view>
-					<uni-forms-item label="确认密码" name="confirmPassword" required label-width="160rpx">
-						<uni-easyinput type="password" v-model="formData.confirmPassword" placeholder="请再次输入新密码"
-							:inputBorder="false" />
-					</uni-forms-item>
+			<view class="form-group">
+				<view class="form-item">
+					<label class="form-label">旧密码</label>
+					<input class="form-input" type="text" :password="!showOldPwd" v-model="formData.oldPassword"
+						placeholder="请输入当前密码" placeholder-class="input-placeholder" />
+					<view class="pwd-toggle" @click="showOldPwd = !showOldPwd">
+						<text class="toggle-icon">{{ showOldPwd ? '隐藏' : '显示' }}</text>
+					</view>
 				</view>
-			</uni-forms>
+				<view class="line"></view>
+				<view class="form-item">
+					<label class="form-label">新密码</label>
+					<input class="form-input" type="text" :password="!showNewPwd" v-model="formData.newPassword"
+						placeholder="6-20位新密码" placeholder-class="input-placeholder" />
+					<view class="pwd-toggle" @click="showNewPwd = !showNewPwd">
+						<text class="toggle-icon">{{ showNewPwd ? '隐藏' : '显示' }}</text>
+					</view>
+				</view>
+				<view class="line"></view>
+				<view class="form-item">
+					<label class="form-label">确认密码</label>
+					<input class="form-input" type="text" :password="!showConfirmPwd" v-model="formData.confirmPassword"
+						placeholder="请再次输入新密码" placeholder-class="input-placeholder" />
+					<view class="pwd-toggle" @click="showConfirmPwd = !showConfirmPwd">
+						<text class="toggle-icon">{{ showConfirmPwd ? '隐藏' : '显示' }}</text>
+					</view>
+				</view>
+			</view>
 
 			<view class="btn-group">
 				<button class="submit-btn" @click="submit">提交修改</button>
 			</view>
 
 			<view class="tip-text">
-				<uni-icons type="info" size="14" color="#999"></uni-icons>
+				<text class="tip-icon">ℹ️</text>
 				<text>密码修改成功后,系统将自动安全退出,需重新登录。</text>
 			</view>
+
+			<!-- 错误提示 -->
+			<view v-if="errorMsg" class="error-toast">
+				<text>{{ errorMsg }}</text>
+			</view>
 		</view>
 	</view>
 </template>
@@ -38,56 +53,60 @@ import { ref, reactive } from 'vue'
 import navBar from '@/components/nav-bar/index.vue'
 import { updateUserPwd } from '@/api/system/user'
 
-const formRef = ref(null)
 const formData = reactive({
 	oldPassword: '',
 	newPassword: '',
 	confirmPassword: ''
 })
 
-const rules = {
-	oldPassword: {
-		rules: [{ required: true, errorMessage: '请输入旧密码' }]
-	},
-	newPassword: {
-		rules: [
-			{ required: true, errorMessage: '请输入新密码' },
-			{ minLength: 6, maxLength: 20, errorMessage: '密码长度在 6-20 位之间' }
-		]
-	},
-	confirmPassword: {
-		rules: [
-			{ required: true, errorMessage: '请确认新密码' },
-			{
-				validateFunction: function (rule, value, data, callback) {
-					if (value !== data.newPassword) {
-						callback('两次输入的密码不一致')
-					}
-					return true
-				}
-			}
-		]
+const showOldPwd = ref(false)
+const showNewPwd = ref(false)
+const showConfirmPwd = ref(false)
+const errorMsg = ref('')
+
+const validate = () => {
+	errorMsg.value = ''
+	if (!formData.oldPassword) {
+		errorMsg.value = '请输入旧密码'
+		return false
+	}
+	if (!formData.newPassword) {
+		errorMsg.value = '请输入新密码'
+		return false
+	}
+	if (formData.newPassword.length < 6 || formData.newPassword.length > 20) {
+		errorMsg.value = '密码长度在 6-20 位之间'
+		return false
+	}
+	if (!formData.confirmPassword) {
+		errorMsg.value = '请确认新密码'
+		return false
 	}
+	if (formData.confirmPassword !== formData.newPassword) {
+		errorMsg.value = '两次输入的密码不一致'
+		return false
+	}
+	return true
 }
 
 const submit = async () => {
+	if (!validate()) return
+
 	try {
-		await formRef.value.validate()
 		uni.showLoading({ title: '修改中...' })
 
 		await updateUserPwd(formData.oldPassword, formData.newPassword)
-		
+
 		uni.hideLoading()
 		uni.showToast({ title: '密码修改成功,请重新登录', icon: 'success' })
-		
-		// 清除令牌并跳转回登录页
+
 		setTimeout(() => {
 			uni.removeStorageSync('token')
 			uni.reLaunch({ url: '/pages/login/index' })
 		}, 1500)
 	} catch (err) {
 		uni.hideLoading()
-		console.log('表单校验失败或请求失败', err)
+		console.log('请求失败', err)
 	}
 }
 </script>
@@ -106,9 +125,41 @@ const submit = async () => {
 	padding: 0 32rpx;
 }
 
+.form-item {
+	display: flex;
+	align-items: center;
+	padding: 28rpx 0;
+}
+
+.form-label {
+	width: 140rpx;
+	flex-shrink: 0;
+	font-size: 30rpx;
+	color: #333;
+	font-weight: 500;
+}
+
+.form-input {
+	flex: 1;
+	font-size: 28rpx;
+	color: #333;
+	height: 48rpx;
+	line-height: 48rpx;
+}
+
+.pwd-toggle {
+	margin-left: 16rpx;
+	padding: 8rpx 16rpx;
+
+	.toggle-icon {
+		font-size: 24rpx;
+		color: #999;
+	}
+}
+
 .line {
-	height: 1rpx;
-	background: #f5f5f5;
+	height: 2rpx;
+	background: #EEEEEE;
 	margin: 0;
 }
 
@@ -120,11 +171,12 @@ const submit = async () => {
 	height: 88rpx;
 	line-height: 88rpx;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
 	border-radius: 44rpx;
 	font-size: 30rpx;
 	font-weight: bold;
+	&::after { border: none; }
 }
 
 .tip-text {
@@ -136,8 +188,24 @@ const submit = async () => {
 	color: #999;
 }
 
-:deep(.uni-forms-item) {
-	border: none !important;
-	padding: 10rpx 0;
+.tip-icon {
+	font-size: 28rpx;
+}
+
+.error-toast {
+	margin: 20rpx 32rpx 0;
+	padding: 16rpx 24rpx;
+	background: #fff2f0;
+	border-radius: 10rpx;
+
+	text {
+		color: #ff4d4f;
+		font-size: 26rpx;
+	}
+}
+
+.input-placeholder {
+	color: #c0c4cc;
+	font-size: 28rpx;
 }
 </style>

+ 16 - 8
pages/my/settings/index.vue

@@ -5,23 +5,23 @@
 			<view class="cell-group">
 				<view class="cell-item" @click="goTo('/pages/my/settings/profile/index')">
 					<text class="cell-title">个人信息</text>
-					<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+					<text class="arrow">›</text>
 				</view>
 				<view class="cell-item" @click="goTo('/pages/my/settings/change-password/index')">
 					<text class="cell-title">修改密码</text>
-					<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+					<text class="arrow">›</text>
 				</view>
 				<view class="cell-item" @click="onClearCache">
 					<text class="cell-title">清除缓存</text>
 					<text class="cell-value">{{ cacheSize }}</text>
-					<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+					<text class="arrow">›</text>
 				</view>
 			</view>
 
 			<view class="cell-group danger-group">
 				<view class="cell-item" @click="goTo('/pages/my/settings/account-delete/index')">
 					<text class="cell-title danger">账号注销</text>
-					<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+					<text class="arrow">›</text>
 				</view>
 			</view>
 		</view>
@@ -121,7 +121,7 @@ const onLogout = () => {
 	display: flex;
 	align-items: center;
 	padding: 32rpx;
-	border-bottom: 1rpx solid #f5f5f5;
+	border-bottom: 2rpx solid #eee;
 }
 
 .cell-item:last-child {
@@ -144,6 +144,12 @@ const onLogout = () => {
 	margin-right: 8rpx;
 }
 
+.arrow {
+	font-size: 36rpx;
+	color: #ccc;
+	line-height: 1;
+}
+
 .danger-group {
 	margin-top: 40rpx;
 }
@@ -156,10 +162,12 @@ const onLogout = () => {
 	width: 100%;
 	height: 88rpx;
 	background: #fff;
-	color: #333;
-	border: none;
+	color: #ee0a24;
+	border: 2rpx solid #ee0a24;
 	border-radius: 44rpx;
 	font-size: 30rpx;
-	line-height: 88rpx;
+	font-weight: 500;
+	line-height: 84rpx;
+	&::after { border: none; }
 }
 </style>

+ 2 - 2
pages/my/settings/password/index.vue

@@ -41,7 +41,7 @@ const onSave = () => {
 	display: flex;
 	align-items: center;
 	padding: 28rpx 0;
-	border-bottom: 1rpx solid #f5f5f5;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .form-item:last-child {
@@ -67,7 +67,7 @@ const onSave = () => {
 	width: 100%;
 	height: 96rpx;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
 	border-radius: 48rpx;
 	font-size: 32rpx;

+ 147 - 67
pages/my/settings/profile/index.vue

@@ -3,32 +3,39 @@
 		<nav-bar title="个人信息" bgColor="#fff" color="#000"></nav-bar>
 		
 		<view class="form-container" v-if="!loading">
-			<uni-forms ref="formRef" :model="formData" :rules="rules">
-				<view class="form-group">
-					<uni-forms-item label="用户昵称" name="nickName" required label-width="160rpx">
-						<uni-easyinput v-model="formData.nickName" placeholder="请输入昵称" :inputBorder="false" />
-					</uni-forms-item>
-					<view class="line"></view>
-					
-					<uni-forms-item label="手机号码" name="phonenumber" required label-width="160rpx">
-						<uni-easyinput type="number" v-model="formData.phonenumber" placeholder="请输入手机号码" :inputBorder="false" />
-					</uni-forms-item>
-					<view class="line"></view>
-
-					<uni-forms-item label="邮箱" name="email" label-width="160rpx">
-						<uni-easyinput v-model="formData.email" placeholder="请输入邮箱" :inputBorder="false" />
-					</uni-forms-item>
-					<view class="line"></view>
-
-					<uni-forms-item label="性别" name="sex" label-width="160rpx">
-						<picker :range="sexOptions" range-key="label" @change="onSexChange">
-							<view class="picker-value" :class="{'placeholder': formData.sex === undefined || formData.sex === ''}">
-								{{ getSexLabel }}
-							</view>
-						</picker>
-					</uni-forms-item>
+			<view class="avatar-section" @click="handleChooseAvatar">
+				<image :src="formData.avatar || '/static/images/profile.png'" class="avatar-img" mode="aspectFill"></image>
+				<view class="avatar-tip">点击更换头像</view>
+			</view>
+
+			<view class="form-group">
+				<view class="form-item">
+					<text class="form-label require">用户昵称</text>
+					<input class="form-input" v-model="formData.nickName" placeholder="请输入昵称" placeholder-class="input-placeholder" />
+				</view>
+				<view class="line"></view>
+
+				<view class="form-item">
+					<text class="form-label require">手机号码</text>
+					<input class="form-input" type="number" v-model="formData.phonenumber" placeholder="请输入手机号码" placeholder-class="input-placeholder" />
+				</view>
+				<view class="line"></view>
+
+				<view class="form-item">
+					<text class="form-label">邮箱</text>
+					<input class="form-input" v-model="formData.email" placeholder="请输入邮箱" placeholder-class="input-placeholder" />
 				</view>
-			</uni-forms>
+				<view class="line"></view>
+
+				<view class="form-item">
+					<text class="form-label">性别</text>
+					<picker :range="sexOptions" range-key="label" @change="onSexChange">
+						<view class="picker-value" :class="{'placeholder': formData.sex === undefined || formData.sex === ''}">
+							{{ getSexLabel }}
+						</view>
+					</picker>
+				</view>
+			</view>
 
 			<view class="btn-group">
 				<button class="submit-btn" @click="submit">保存修改</button>
@@ -42,15 +49,15 @@
 import { ref, reactive, computed } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import navBar from '@/components/nav-bar/index.vue'
-import { getInfo, updateUserProfile } from '@/api/system/user'
+import { getInfo, updateUserProfile, uploadAvatar } from '@/api/system/user'
 
 const loading = ref(true)
-const formRef = ref(null)
 const formData = reactive({
+	avatar: '',
 	nickName: '',
 	phonenumber: '',
 	email: '',
-	sex: '' // 0男 1女 2未知
+	sex: ''
 })
 
 const sexOptions = [
@@ -59,25 +66,11 @@ const sexOptions = [
 	{ label: '未知', value: '2' }
 ]
 
-const rules = {
-	nickName: {
-		rules: [{ required: true, errorMessage: '请输入昵称' }]
-	},
-	phonenumber: {
-		rules: [
-			{ required: true, errorMessage: '请输入手机号码' },
-			{ pattern: /^1[3-9]\d{9}$/, errorMessage: '请输入正确的手机号码' }
-		]
-	},
-	email: {
-		rules: [{ format: 'email', errorMessage: '请输入正确的邮箱格式' }]
-	}
-}
-
 onLoad(async () => {
     try {
         const res = await getInfo()
         if (res && res.user) {
+            formData.avatar = res.user.avatarUrl || ''
             formData.nickName = res.user.nickName || ''
             formData.phonenumber = res.user.phonenumber || ''
             formData.email = res.user.email || ''
@@ -99,9 +92,52 @@ const onSexChange = (e) => {
     formData.sex = sexOptions[e.detail.value].value
 }
 
+// 选择并上传头像
+const handleChooseAvatar = () => {
+    uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: async (res) => {
+            const tempFilePaths = res.tempFilePaths;
+            try {
+                uni.showLoading({ title: '上传中...' });
+                const uploadRes = await uploadAvatar(tempFilePaths[0]);
+                formData.avatar = uploadRes.imgUrl;
+                uni.hideLoading();
+                uni.showToast({ title: '头像上传成功', icon: 'success' });
+            } catch (e) {
+                uni.hideLoading();
+                console.error('上传头像失败', e);
+            }
+        }
+    });
+}
+
+const validate = () => {
+	if (!formData.nickName || !formData.nickName.trim()) {
+		uni.showToast({ title: '请输入昵称', icon: 'none' })
+		return false
+	}
+	if (!formData.phonenumber || !formData.phonenumber.trim()) {
+		uni.showToast({ title: '请输入手机号码', icon: 'none' })
+		return false
+	}
+	if (!/^1[3-9]\d{9}$/.test(formData.phonenumber)) {
+		uni.showToast({ title: '请输入正确的手机号码', icon: 'none' })
+		return false
+	}
+	if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
+		uni.showToast({ title: '请输入正确的邮箱格式', icon: 'none' })
+		return false
+	}
+	return true
+}
+
 const submit = async () => {
+	if (!validate()) return
+
 	try {
-		await formRef.value.validate()
 		uni.showLoading({ title: '保存中...' })
 
 		await updateUserProfile(formData)
@@ -125,7 +161,28 @@ const submit = async () => {
 }
 
 .form-container {
-    padding-top: 20rpx;
+    padding-top: 0;
+}
+
+.avatar-section {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 60rpx 0;
+    background-color: #fff;
+    margin-bottom: 20rpx;
+    border-bottom: 2rpx solid #f0f0f0;
+    .avatar-img {
+        width: 160rpx;
+        height: 160rpx;
+        border-radius: 80rpx;
+        background-color: #f7f8fa;
+    }
+    .avatar-tip {
+        margin-top: 20rpx;
+        font-size: 24rpx;
+        color: #999;
+    }
 }
 
 .form-group {
@@ -135,9 +192,44 @@ const submit = async () => {
 	padding: 0 32rpx;
 }
 
+.form-item {
+	display: flex;
+	align-items: center;
+	padding: 24rpx 0;
+}
+
+.form-label {
+	width: 200rpx;
+	font-size: 28rpx;
+	color: #333;
+	flex-shrink: 0;
+	&::before {
+		content: '*';
+		color: transparent;
+		margin-right: 4rpx;
+	}
+	&.require::before {
+		color: #f56c6c;
+	}
+}
+
+.form-input {
+	flex: 1;
+	height: 48rpx;
+	line-height: 48rpx;
+	font-size: 28rpx;
+	color: #333;
+	text-align: right;
+}
+
+.input-placeholder {
+	color: #c0c4cc;
+	font-size: 28rpx;
+}
+
 .line {
-	height: 1rpx;
-	background: #f5f5f5;
+	height: 2rpx;
+	background: #eee;
 	margin: 0;
 }
 
@@ -149,36 +241,24 @@ const submit = async () => {
 	height: 88rpx;
 	line-height: 88rpx;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
 	border-radius: 44rpx;
 	font-size: 30rpx;
 	font-weight: bold;
+	&::after { border: none; }
 }
 
 .picker-value {
     flex: 1;
-    font-size: 26rpx;
-    color: #666;
+    font-size: 28rpx;
+    color: #333;
     text-align: right;
-    min-height: 40rpx;
-    line-height: 72rpx;
+    height: 48rpx;
+    line-height: 48rpx;
     padding-right: 20rpx;
 }
 .picker-value.placeholder {
-    color: #999;
-}
-
-:deep(.uni-forms-item) {
-	border: none !important;
-	padding: 10rpx 0;
-}
-:deep(.uni-forms-item__label) {
-    font-size: 28rpx;
-    color: #333;
-}
-:deep(.uni-easyinput__content-input) {
-    text-align: right;
-    font-size: 26rpx;
+    color: #c0c4cc;
 }
-</style>
+</style>

+ 224 - 156
pages/my/user/add/index.vue

@@ -1,6 +1,21 @@
 <template>
 	<view class="user-add-page">
-		<NavBar title="新增用户" bgColor="#fff" color="#000"></NavBar>
+		<nav-bar title="新增用户" bgColor="#fff" color="#000"></nav-bar>
+
+		<!-- 头像上传 -->
+		<view class="avatar-section">
+			<view class="avatar-wrap" @click="chooseAvatar">
+				<image v-if="avatarDisplayUrl" :src="avatarDisplayUrl" class="avatar-img" mode="aspectFill"></image>
+				<view v-else class="avatar-placeholder">
+					<!-- CSS 手绘人像图标 @Author: Antigravity -->
+					<view class="avatar-icon">
+						<view class="head"></view>
+						<view class="body"></view>
+					</view>
+				</view>
+				<text class="avatar-tip">点击修改头像</text>
+			</view>
+		</view>
 
 		<!-- 基本资料 -->
 		<view class="section-title">基本资料</view>
@@ -13,46 +28,42 @@
 				<text class="form-label require">手机号</text>
 				<input class="form-input" v-model="form.phone" type="number" placeholder="请输入手机号" />
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showGenderSelect = true">
 				<text class="form-label">性别</text>
-				<picker :range="genderOptions" range-key="label" @change="onGenderChange">
-					<view class="picker-value" :class="{'placeholder': form.gender === undefined}">
-						{{ getGenderLabel }}
-					</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': form.gender === undefined}">
+					{{ getGenderLabel }}
+				</view>
+				<view class="right-arrow"></view>
 			</view>
 		</view>
 
 		<!-- 居住信息 -->
 		<view class="section-title">居住信息</view>
 		<view class="form-card">
-			<view class="form-item">
+			<view class="form-item" @click="openStationModal">
 				<text class="form-label require">所属站点</text>
-				<picker mode="multiSelector" :range="stationOptions" range-key="name" @change="onStationChange" @columnchange="onStationColumnChange" :value="stationIndex">
-					<view class="picker-value" :class="{'placeholder': form.stationId === undefined}">
-						{{ getStationLabel }}
-					</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': form.stationId === undefined}">
+					{{ getStationLabel }}
+				</view>
+				<view class="right-arrow"></view>
 			</view>
 			<view class="form-item">
 				<text class="form-label require">详细住址</text>
 				<input class="form-input" v-model="form.address" placeholder="请输入街道/门牌号" />
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showHouseTypeSelect = true">
 				<text class="form-label">房屋类型</text>
-				<picker :range="houseTypeOptions" range-key="label" @change="onHouseTypeChange">
-					<view class="picker-value" :class="{'placeholder': !form.houseType}">
-						{{ getHouseTypeLabel }}
-					</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': !form.houseType}">
+					{{ getHouseTypeLabel }}
+				</view>
+				<view class="right-arrow"></view>
 			</view>
-			<view class="form-item">
+			<view class="form-item" @click="showEntryMethodSelect = true">
 				<text class="form-label require">入门方式</text>
-				<picker :range="entryMethodOptions" range-key="label" @change="onEntryMethodChange">
-					<view class="picker-value" :class="{'placeholder': !form.entryMethod}">
-						{{ getEntryMethodLabel }}
-					</view>
-				</picker>
+				<view class="picker-value" :class="{'placeholder': !form.entryMethod}">
+					{{ getEntryMethodLabel }}
+				</view>
+				<view class="right-arrow"></view>
 			</view>
 			<view class="form-item" v-if="form.entryMethod === 'password'">
 				<text class="form-label require">开门密码</text>
@@ -77,131 +88,178 @@
 		<view class="footer-bar">
 			<button class="save-btn" :loading="saving" @click="onSave">新增用户</button>
 		</view>
+
+		<!-- 站点三级联动居中弹窗 @Author: Antigravity -->
+		<view class="center-modal-mask" v-if="showStationModal" @click="showStationModal = false" @touchmove.stop.prevent>
+			<view class="center-modal-content station-modal" @click.stop>
+				<view class="modal-header">
+					<text class="modal-title">请选择所属站点</text>
+					<view class="close-btn" @click="showStationModal = false"></view>
+				</view>
+				
+				<view class="step-indicator">
+					<view class="step-item" :class="{ 'active': currentStep === 0 }" @click="currentStep = 0">
+						{{ selectedCityName || '请选择城市' }}
+					</view>
+					<view class="step-divider">/</view>
+					<view class="step-item" :class="{ 'active': currentStep === 1 }" @click="selectedCityId ? (currentStep = 1) : null">
+						{{ selectedAreaName || (currentStep === 1 ? '请选择区域' : '区域') }}
+					</view>
+					<view class="step-divider">/</view>
+					<view class="step-item" :class="{ 'active': currentStep === 2 }" @click="selectedAreaId ? (currentStep = 2) : null">
+						{{ selectedStationName || (currentStep === 2 ? '请选择站点' : '站点') }}
+					</view>
+				</view>
+
+				<scroll-view scroll-y class="modal-list-scroll">
+					<view 
+						class="list-item" 
+						v-for="item in currentList" 
+						:key="item.id" 
+						@click="onStepSelect(item)"
+					>
+						<text class="item-text">{{ item.name }}</text>
+						<view class="checkmark" v-if="isStepChecked(item)"></view>
+					</view>
+					<view class="empty-tip" v-if="currentList.length === 0">该目录下暂无站点信息</view>
+				</scroll-view>
+			</view>
+		</view>
+
+		<!-- 选择器组件实例 -->
+		<center-select v-model="showGenderSelect" title="选择性别" :options="genderOptions" :value="form.gender" @select="(item) => form.gender = item.value" />
+		<center-select v-model="showHouseTypeSelect" title="房屋类型" :options="houseTypeOptions" :value="form.houseType" @select="(item) => form.houseType = item.value" />
+		<center-select v-model="showEntryMethodSelect" title="入门方式" :options="entryMethodOptions" :value="form.entryMethod" @select="onEntryMethodChangeDetail" />
 	</view>
 </template>
 
 <script setup>
-// @Author: Antigravity
+/**
+ * @Author: Antigravity
+ */
 import { ref, reactive, computed } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
-import NavBar from '@/components/nav-bar/index.vue'
+import navBar from '@/components/nav-bar/index.vue'
+import centerSelect from '@/components/center-select/index.vue'
 import { addCustomer } from '@/api/archieves/customer'
 import { getInfo } from '@/api/system/user'
 import { listAreaStation } from '@/api/system/areaStation'
+import { uploadFile } from '@/api/system/oss'
 import customerEnums from '@/json/customer.json'
 
 const { houseTypeOptions, entryMethodOptions } = customerEnums
 const genderOptions = [{ label: '男', value: 0 }, { label: '女', value: 1 }]
 
 const saving = ref(false)
-const stationOptions = ref([[], [], []])
-const stationIndex = ref([0, 0, 0])
 const allStationNodes = ref([])
+const avatarDisplayUrl = ref('')
+
+// 弹窗状态
+const showGenderSelect = ref(false)
+const showHouseTypeSelect = ref(false)
+const showEntryMethodSelect = ref(false)
+const showStationModal = ref(false)
+
+// 站点选择级联逻辑
+const currentStep = ref(0)
+const selectedCityId = ref(null)
+const selectedCityName = ref('')
+const selectedAreaId = ref(null)
+const selectedAreaName = ref('')
+const selectedStationId = ref(null)
+const selectedStationName = ref('')
 
 const form = reactive({
-    name: '',
-    phone: '',
-    gender: undefined,
-    areaId: undefined,
-    stationId: undefined,
-    address: '',
-    houseType: '',
-    entryMethod: '',
-    entryPassword: '',
-    keyLocation: '',
-    remark: ''
+    name: '', phone: '', gender: undefined, areaId: undefined, stationId: undefined,
+    address: '', houseType: '', entryMethod: '', entryPassword: '', keyLocation: '', remark: '', avatar: undefined
 })
 
 onLoad(async () => {
     try {
         const stationRes = await listAreaStation()
         allStationNodes.value = Array.isArray(stationRes) ? stationRes : (stationRes?.data || [])
-        initStationPicker()
-    } catch (err) {
-        console.error('获取站点失败', err)
-    }
+    } catch (err) { console.error('获取站点失败', err) }
 })
 
-// 初始化三级联动
-const initStationPicker = () => {
-    const nodes = allStationNodes.value
-    // 第一级:城市 (parentId == 0)
-    const cities = nodes.filter(n => String(n.parentId) === '0')
-    if (cities.length === 0) return
-
-    // 第二级:默认第一个城市的区域
-    const areas = nodes.filter(n => String(n.parentId) === String(cities[0].id))
-    
-    // 第三级:默认第一个区域的站点
-    const stations = areas.length > 0 ? nodes.filter(n => String(n.parentId) === String(areas[0].id)) : []
-
-    stationOptions.value = [cities, areas, stations]
-    stationIndex.value = [0, 0, 0]
+const openStationModal = () => {
+	currentStep.value = 0
+	showStationModal.value = true
 }
 
-// 监听滚动变动
-const onStationColumnChange = (e) => {
-    const column = e.detail.column
-    const value = e.detail.value
-    stationIndex.value[column] = value
-    const nodes = allStationNodes.value
-
-    if (column === 0) {
-        // 切城市:更新区域和站点
-        const selectedCity = stationOptions.value[0][value]
-        if (selectedCity) {
-            const newAreas = nodes.filter(n => String(n.parentId) === String(selectedCity.id))
-            stationOptions.value[1] = newAreas
-            stationOptions.value[2] = newAreas.length > 0 ? nodes.filter(n => String(n.parentId) === String(newAreas[0].id)) : []
-        } else {
-            stationOptions.value[1] = []
-            stationOptions.value[2] = []
-        }
-        stationIndex.value.splice(1, 1, 0)
-        stationIndex.value.splice(2, 1, 0)
-    } else if (column === 1) {
-        // 切区域:更新站点
-        const selectedArea = stationOptions.value[1][value]
-        if (selectedArea) {
-            const newStations = nodes.filter(n => String(n.parentId) === String(selectedArea.id))
-            stationOptions.value[2] = newStations
-        } else {
-            stationOptions.value[2] = []
-        }
-        stationIndex.value.splice(2, 1, 0)
-    }
+const currentList = computed(() => {
+	if (currentStep.value === 0) {
+		return allStationNodes.value.filter(n => String(n.parentId) === '0' || !n.parentId)
+	} else if (currentStep.value === 1) {
+		return allStationNodes.value.filter(n => String(n.parentId) === String(selectedCityId.value))
+	} else {
+		return allStationNodes.value.filter(n => String(n.parentId) === String(selectedAreaId.value))
+	}
+})
+
+const onStepSelect = (item) => {
+	if (currentStep.value === 0) {
+		selectedCityId.value = item.id
+		selectedCityName.value = item.name
+		selectedAreaId.value = null
+		selectedAreaName.value = ''
+		selectedStationId.value = null
+		selectedStationName.value = ''
+		currentStep.value = 1
+	} else if (currentStep.value === 1) {
+		selectedAreaId.value = item.id
+		selectedAreaName.value = item.name
+		selectedStationId.value = null
+		selectedStationName.value = ''
+		currentStep.value = 2
+	} else {
+		selectedStationId.value = item.id
+		selectedStationName.value = item.name
+		form.stationId = item.id
+		form.areaId = selectedAreaId.value
+		showStationModal.value = false
+	}
 }
 
-// 确认选择
-const onStationChange = (e) => {
-    stationIndex.value = e.detail.value
-    const stations = stationOptions.value[2]
-    const selectedStation = stations[stationIndex.value[2]]
-    if (selectedStation && String(selectedStation.type) === '2') {
-        form.stationId = selectedStation.id
-        form.areaId = selectedStation.parentId
-    } else {
-        uni.showToast({ title: '请选择到具体的站点层级', icon: 'none' })
-    }
+const isStepChecked = (item) => {
+	if (currentStep.value === 0) return selectedCityId.value === item.id
+	if (currentStep.value === 1) return selectedAreaId.value === item.id
+	return selectedStationId.value === item.id
 }
 
+const getStationLabel = computed(() => {
+    if (!form.stationId) return '请选择'
+    return `${selectedCityName.value} - ${selectedAreaName.value} - ${selectedStationName.value}`
+})
+
 const getGenderLabel = computed(() => genderOptions.find(o => o.value === form.gender)?.label || '请选择')
 const getHouseTypeLabel = computed(() => houseTypeOptions.find(o => o.value === form.houseType)?.label || '请选择')
 const getEntryMethodLabel = computed(() => entryMethodOptions.find(o => o.value === form.entryMethod)?.label || '请选择')
 
-const getStationLabel = computed(() => {
-    if (!form.stationId) return '请选择'
-    const city = stationOptions.value[0][stationIndex.value[0]]?.name || ''
-    const area = stationOptions.value[1][stationIndex.value[1]]?.name || ''
-    const station = stationOptions.value[2][stationIndex.value[2]]?.name || ''
-    return `${city} - ${area} - ${station}`
-})
-const onGenderChange = (e) => { form.gender = genderOptions[e.detail.value].value }
-const onHouseTypeChange = (e) => { form.houseType = houseTypeOptions[e.detail.value].value }
-const onEntryMethodChange = (e) => {
-    form.entryMethod = entryMethodOptions[e.detail.value].value
-    form.entryPassword = ''
-    form.keyLocation = ''
+const onEntryMethodChangeDetail = (item) => {
+	form.entryMethod = item.value
+	form.entryPassword = ''
+	form.keyLocation = ''
+}
+
+// 选择并上传头像
+const chooseAvatar = () => {
+    uni.chooseImage({
+        count: 1, 
+        sizeType: ['compressed'],
+        success: async (res) => {
+            try {
+                uni.showLoading({ title: '上传中...' })
+                const uploadRes = await uploadFile(res.tempFilePaths[0])
+                form.avatar = uploadRes.ossId
+                avatarDisplayUrl.value = uploadRes.url
+                uni.hideLoading()
+                uni.showToast({ title: '修改成功', icon: 'success' })
+            } catch (err) {
+                uni.hideLoading()
+                console.error('头像上传失败', err)
+            }
+        }
+    })
 }
 
 const onSave = async () => {
@@ -210,72 +268,82 @@ const onSave = async () => {
     if (!form.stationId) return uni.showToast({ title: '请选择所属站点', icon: 'none' })
     if (!form.address) return uni.showToast({ title: '请输入详细住址', icon: 'none' })
     if (!form.entryMethod) return uni.showToast({ title: '请选择入门方式', icon: 'none' })
-    if (form.entryMethod === 'password' && !form.entryPassword) return uni.showToast({ title: '请输入开门密码', icon: 'none' })
-    if (form.entryMethod === 'key' && !form.keyLocation) return uni.showToast({ title: '请输入钥匙位置', icon: 'none' })
 
     saving.value = true
     try {
         const submitData = { ...form }
-        let tenantId = uni.getStorageSync('tenantId')
-        
-        // 如果本地没有获取到,重新调用接口获取一遍
-        if (!tenantId) {
-            const userRes = await getInfo()
-            if (userRes && userRes.user && userRes.user.tenantId) {
-                tenantId = userRes.user.tenantId
-                uni.setStorageSync('tenantId', tenantId) // 顺便存回本地
-            }
-        }
-        
+        let tenantId = uni.getStorageSync('tenantId') || (await getInfo())?.user?.tenantId
         if (tenantId) submitData.tenantId = tenantId
-        
         await addCustomer(submitData)
         uni.showToast({ title: '新增成功', icon: 'success' })
         setTimeout(() => uni.navigateBack(), 1000)
-    } catch(err) {
-        // 500 错误由 request 拦截器统一弹出 res.msg,无需二次封装
-    } finally {
-        saving.value = false
-    }
+    } catch(err) { } finally { saving.value = false }
 }
 </script>
 
 <style lang="scss" scoped>
 .user-add-page { min-height: 100vh; background: #f7f8fa; padding-bottom: calc(140rpx + env(safe-area-inset-bottom)); }
 
-.section-title {
-    font-size: 28rpx; font-weight: bold; color: #666;
-    padding: 24rpx 32rpx 12rpx;
+.avatar-section { display: flex; justify-content: center; padding: 40rpx 0 20rpx; }
+.avatar-wrap { display: flex; flex-direction: column; align-items: center; gap: 12rpx; }
+.avatar-img { width: 144rpx; height: 144rpx; border-radius: 50%; border: 4rpx solid #fff; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); }
+.avatar-placeholder { width: 144rpx; height: 144rpx; border-radius: 50%; background: #f5f5f5; border: 2rpx dashed #EEEEEE; display: flex; align-items: center; justify-content: center; }
+.avatar-tip { font-size: 22rpx; color: #999; }
+
+/* CSS 手绘人像图标 @Author: Antigravity */
+.avatar-icon {
+	width: 60rpx; height: 60rpx; position: relative;
+	.head { width: 30rpx; height: 30rpx; border-radius: 50%; background: #ddd; position: absolute; top: 0; left: 50%; transform: translateX(-50%); }
+	.body { width: 50rpx; height: 26rpx; border-radius: 20rpx 20rpx 0 0; background: #ddd; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); }
 }
 
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; padding: 32rpx 32rpx 16rpx; }
 .form-card { background: #fff; border-radius: 24rpx; padding: 8rpx 32rpx; margin: 0 24rpx 24rpx; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 2rpx solid #EEEEEE; position: relative; }
 .form-item:last-child { border-bottom: none; }
-.form-item.vertical { flex-direction: column; align-items: flex-start; }
-.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 16rpx; box-sizing: border-box; border-radius: 12rpx; font-size: 28rpx; }
+.form-item.vertical { flex-direction: column; align-items: stretch; gap: 0; }
+.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 20rpx; box-sizing: border-box; border-radius: 12rpx; font-size: 28rpx; color: #333; line-height: 1.6; }
 
-.form-label { width: 200rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
+.form-label { width: 220rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
 .form-label.require::before { content: '*'; color: #f56c6c; margin-right: 4rpx; }
 .form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; min-height: 40rpx; }
+.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; margin-right: 16rpx; }
 .picker-value.placeholder { color: #ccc; }
 
-.footer-bar {
-	position: fixed;
-	bottom: 0;
-	left: 0;
-	right: 0;
-	background: #fff;
-	padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
-	box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
-	z-index: 100;
+/* CSS 绘制右箭头 @Author: Antigravity */
+.right-arrow {
+	width: 14rpx; height: 14rpx; border-right: 3rpx solid #ccc; border-top: 3rpx solid #ccc; transform: rotate(45deg); flex-shrink: 0;
+}
+
+.footer-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); z-index: 100; }
+.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; &::after { border: none; } }
+
+/* 居中弹窗通用样式 @Author: Antigravity */
+.center-modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4rpx); }
+.center-modal-content { width: 620rpx; background: #fff; border-radius: 32rpx; display: flex; flex-direction: column; overflow: hidden; animation: popIn 0.3s ease-out; }
+@keyframes popIn { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } }
+
+.modal-header { padding: 32rpx; border-bottom: 2rpx solid #f2f2f2; position: relative; text-align: center; }
+.modal-title { font-size: 32rpx; font-weight: bold; color: #333; }
+/* X 关闭图标 @Author: Antigravity */
+.close-btn { 
+	position: absolute; right: 30rpx; top: 32rpx; width: 40rpx; height: 40rpx;
+	&::before, &::after { content: ''; position: absolute; top: 18rpx; left: 5rpx; width: 30rpx; height: 4rpx; background: #999; transform: rotate(45deg); border-radius: 4rpx; }
+	&::after { transform: rotate(-45deg); }
+}
+
+.step-indicator { 
+	display: flex; align-items: center; justify-content: center; padding: 24rpx; background: #fdfdfd; border-bottom: 2rpx solid #f9f9f9; gap: 8rpx;
+	.step-item { font-size: 24rpx; color: #999; max-width: 150rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+	.step-item.active { color: #ff9500; font-weight: bold; }
 }
 
-.save-btn {
-    width: 100%;
-    height: 88rpx;
-    background: linear-gradient(90deg, #ffd53f, #ff9500);
-    color: #333; border: none; border-radius: 44rpx;
-    font-size: 32rpx; font-weight: bold; line-height: 88rpx;
+.modal-list-scroll { flex: 1; max-height: 60vh; padding: 0 32rpx; }
+.list-item { 
+	display: flex; align-items: center; justify-content: space-between; padding: 32rpx 0; border-bottom: 2rpx solid #f9f9f9; 
+	.item-text { font-size: 30rpx; color: #333; }
 }
+/* 对勾 @Author: Antigravity */
+.checkmark { width: 12rpx; height: 24rpx; border-right: 4rpx solid #ff9500; border-bottom: 4rpx solid #ff9500; transform: rotate(45deg); }
+.empty-tip { padding: 80rpx 0; text-align: center; color: #ccc; font-size: 26rpx; }
 </style>

+ 2 - 2
pages/my/user/detail/index.vue

@@ -104,11 +104,11 @@ const goToEdit = () => {
 .section-title::before { content: ''; display: inline-block; width: 6rpx; height: 28rpx; background: #ffd53f; margin-right: 12rpx; border-radius: 4rpx; }
 .section-title:first-child { margin-top: 0; }
 
-.info-row { display: flex; justify-content: space-between; padding: 24rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.info-row { display: flex; justify-content: space-between; padding: 24rpx 0; border-bottom: 2rpx solid #EEEEEE; }
 .info-row:last-child { border-bottom: none; }
 .label { font-size: 28rpx; color: #999; flex-shrink: 0; margin-right: 20rpx; }
 .value { font-size: 28rpx; color: #333; font-weight: 500; text-align: right; word-break: break-all; }
 .value.orange { color: #ff9800; }
 
-.edit-btn { margin: 40rpx 32rpx; width: calc(100% - 64rpx); height: 96rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 48rpx; font-size: 32rpx; font-weight: bold; line-height: 96rpx; }
+.edit-btn { margin: 40rpx 32rpx; width: calc(100% - 64rpx); height: 96rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border: none; border-radius: 48rpx; font-size: 32rpx; font-weight: bold; line-height: 96rpx; }
 </style>

+ 251 - 173
pages/my/user/edit/index.vue

@@ -1,230 +1,271 @@
 <template>
 	<view class="user-edit-page">
-		<NavBar title="编辑用户" bgColor="#fff" color="#000"></NavBar>
-
-		<!-- 基本资料 -->
-		<view class="section-title">基本资料</view>
-		<view class="form-card">
-			<view class="form-item">
-				<text class="form-label require">姓名</text>
-				<input class="form-input" v-model="form.name" placeholder="请输入姓名" />
-			</view>
-			<view class="form-item">
-				<text class="form-label require">手机号</text>
-				<input class="form-input" v-model="form.phone" type="number" placeholder="请输入手机号" />
+		<nav-bar title="编辑用户" bgColor="#fff" color="#000"></nav-bar>
+
+		<view v-if="loading" class="loading-state">
+			<view class="spinner"></view>
+			<text class="loading-txt">同步资料中...</text>
+		</view>
+
+		<block v-else>
+			<!-- 头像上传 -->
+			<view class="avatar-section">
+				<view class="avatar-wrap" @click="chooseAvatar">
+					<image v-if="avatarDisplayUrl" :src="avatarDisplayUrl" class="avatar-img" mode="aspectFill"></image>
+					<view v-else class="avatar-placeholder">
+						<view class="avatar-icon">
+							<view class="head"></view>
+							<view class="body"></view>
+						</view>
+					</view>
+					<text class="avatar-tip">点击修改头像</text>
+				</view>
 			</view>
-			<view class="form-item">
-				<text class="form-label">性别</text>
-				<picker :range="genderOptions" range-key="label" @change="onGenderChange">
+
+			<!-- 基本资料 -->
+			<view class="section-title">基本资料</view>
+			<view class="form-card">
+				<view class="form-item">
+					<text class="form-label require">姓名</text>
+					<input class="form-input" v-model="form.name" placeholder="请输入姓名" />
+				</view>
+				<view class="form-item">
+					<text class="form-label require">手机号</text>
+					<input class="form-input" v-model="form.phone" type="number" placeholder="请输入手机号" />
+				</view>
+				<view class="form-item" @click="showGenderSelect = true">
+					<text class="form-label">性别</text>
 					<view class="picker-value" :class="{'placeholder': form.gender === undefined}">
 						{{ getGenderLabel }}
 					</view>
-				</picker>
+					<view class="right-arrow"></view>
+				</view>
 			</view>
-		</view>
 
-		<!-- 居住信息 -->
-		<view class="section-title">居住信息</view>
-		<view class="form-card">
-			<view class="form-item">
-				<text class="form-label require">所属站点</text>
-				<picker mode="multiSelector" :range="stationOptions" range-key="name" @change="onStationChange" @columnchange="onStationColumnChange" :value="stationIndex">
+			<!-- 居住信息 -->
+			<view class="section-title">居住信息</view>
+			<view class="form-card">
+				<view class="form-item" @click="openStationModal">
+					<text class="form-label require">所属站点</text>
 					<view class="picker-value" :class="{'placeholder': form.stationId === undefined}">
 						{{ getStationLabel }}
 					</view>
-				</picker>
-			</view>
-			<view class="form-item">
-				<text class="form-label require">详细住址</text>
-				<input class="form-input" v-model="form.address" placeholder="请输入街道/门牌号" />
-			</view>
-			<view class="form-item">
-				<text class="form-label">房屋类型</text>
-				<picker :range="houseTypeOptions" range-key="label" @change="onHouseTypeChange">
+					<view class="right-arrow"></view>
+				</view>
+				<view class="form-item">
+					<text class="form-label require">详细住址</text>
+					<input class="form-input" v-model="form.address" placeholder="请输入街道/门牌号" />
+				</view>
+				<view class="form-item" @click="showHouseTypeSelect = true">
+					<text class="form-label">房屋类型</text>
 					<view class="picker-value" :class="{'placeholder': !form.houseType}">
 						{{ getHouseTypeLabel }}
 					</view>
-				</picker>
-			</view>
-			<view class="form-item">
-				<text class="form-label require">入门方式</text>
-				<picker :range="entryMethodOptions" range-key="label" @change="onEntryMethodChange">
+					<view class="right-arrow"></view>
+				</view>
+				<view class="form-item" @click="showEntryMethodSelect = true">
+					<text class="form-label require">入门方式</text>
 					<view class="picker-value" :class="{'placeholder': !form.entryMethod}">
 						{{ getEntryMethodLabel }}
 					</view>
-				</picker>
+					<view class="right-arrow"></view>
+				</view>
+				<view class="form-item" v-if="form.entryMethod === 'password'">
+					<text class="form-label require">开门密码</text>
+					<input class="form-input" v-model="form.entryPassword" placeholder="请输入密码" />
+				</view>
+				<view class="form-item" v-if="form.entryMethod === 'key'">
+					<text class="form-label require">钥匙位置</text>
+					<input class="form-input" v-model="form.keyLocation" placeholder="如:地毯下" />
+				</view>
 			</view>
-			<view class="form-item" v-if="form.entryMethod === 'password'">
-				<text class="form-label require">开门密码</text>
-				<input class="form-input" v-model="form.entryPassword" placeholder="请输入密码" />
+
+			<!-- 其他 -->
+			<view class="section-title">其他</view>
+			<view class="form-card">
+				<view class="form-item vertical">
+					<text class="form-label">备注说明</text>
+					<textarea class="form-textarea" v-model="form.remark" placeholder="请输入备注" />
+				</view>
 			</view>
-			<view class="form-item" v-if="form.entryMethod === 'key'">
-				<text class="form-label require">钥匙位置</text>
-				<input class="form-input" v-model="form.keyLocation" placeholder="如:地毯下" />
+
+			<!-- 底部固定操作栏 -->
+			<view class="footer-bar">
+				<button class="save-btn" :loading="saving" @click="onSave">保存修改</button>
 			</view>
-		</view>
+		</block>
 
-		<!-- 其他 -->
-		<view class="section-title">其他</view>
-		<view class="form-card">
-			<view class="form-item vertical">
-				<text class="form-label">备注说明</text>
-				<textarea class="form-textarea" v-model="form.remark" placeholder="请输入备注" />
+		<!-- 站点选择弹窗 @Author: Antigravity -->
+		<view class="center-modal-mask" v-if="showStationModal" @click="showStationModal = false" @touchmove.stop.prevent>
+			<view class="center-modal-content station-modal" @click.stop>
+				<view class="modal-header">
+					<text class="modal-title">所属站点修改</text>
+					<view class="close-btn" @click="showStationModal = false"></view>
+				</view>
+				<view class="step-indicator">
+					<view class="step-item" :class="{ 'active': currentStep === 0 }" @click="currentStep = 0">{{ selectedCityName || '城市' }}</view>
+					<view class="step-divider">/</view>
+					<view class="step-item" :class="{ 'active': currentStep === 1 }" @click="selectedCityId ? currentStep = 1 : null">{{ selectedAreaName || '区域' }}</view>
+					<view class="step-divider">/</view>
+					<view class="step-item" :class="{ 'active': currentStep === 2 }" @click="selectedAreaId ? currentStep = 2 : null">{{ selectedStationName || '站点' }}</view>
+				</view>
+				<scroll-view scroll-y class="modal-list-scroll">
+					<view class="list-item" v-for="item in currentList" :key="item.id" @click="onStepSelect(item)">
+						<text class="item-text">{{ item.name }}</text>
+						<view class="checkmark" v-if="isStepChecked(item)"></view>
+					</view>
+					<view class="empty-tip" v-if="currentList.length === 0">暂无数据</view>
+				</scroll-view>
 			</view>
 		</view>
 
-		<!-- 底部固定操作栏 -->
-		<view class="footer-bar">
-			<button class="save-btn" :loading="saving" @click="onSave">保存修改</button>
-		</view>
+		<center-select v-model="showGenderSelect" title="性别修改" :options="genderOptions" :value="form.gender" @select="(item) => form.gender = item.value" />
+		<center-select v-model="showHouseTypeSelect" title="房屋类型" :options="houseTypeOptions" :value="form.houseType" @select="(item) => form.houseType = item.value" />
+		<center-select v-model="showEntryMethodSelect" title="入门方式" :options="entryMethodOptions" :value="form.entryMethod" @select="onEntryMethodChangeDetail" />
 	</view>
 </template>
 
 <script setup>
-// @Author: Antigravity
+/**
+ * @Author: Antigravity
+ */
 import { ref, reactive, computed } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
-import NavBar from '@/components/nav-bar/index.vue'
+import navBar from '@/components/nav-bar/index.vue'
+import centerSelect from '@/components/center-select/index.vue'
 import { getCustomer, updateCustomer } from '@/api/archieves/customer'
 import { listAreaStation } from '@/api/system/areaStation'
+import { uploadFile } from '@/api/system/oss'
 import customerEnums from '@/json/customer.json'
 
 const { houseTypeOptions, entryMethodOptions } = customerEnums
 const genderOptions = [{ label: '男', value: 0 }, { label: '女', value: 1 }]
 
+const loading = ref(true)
 const saving = ref(false)
-const stationOptions = ref([[], [], []])
-const stationIndex = ref([0, 0, 0])
 const allStationNodes = ref([])
+const avatarDisplayUrl = ref('')
+
+const showGenderSelect = ref(false)
+const showHouseTypeSelect = ref(false)
+const showEntryMethodSelect = ref(false)
+const showStationModal = ref(false)
+
+const currentStep = ref(0)
+const selectedCityId = ref(null)
+const selectedCityName = ref('')
+const selectedAreaId = ref(null)
+const selectedAreaName = ref('')
+const selectedStationId = ref(null)
+const selectedStationName = ref('')
 
 const form = reactive({
-    id: undefined,
-    name: '',
-    phone: '',
-    gender: undefined,
-    areaId: undefined,
-    stationId: undefined,
-    address: '',
-    houseType: '',
-    entryMethod: '',
-    entryPassword: '',
-    keyLocation: '',
-    remark: ''
+    id: undefined, name: '', phone: '', gender: undefined, areaId: undefined, stationId: undefined,
+    address: '', houseType: '', entryMethod: '', entryPassword: '', keyLocation: '', remark: '', avatar: undefined
 })
 
 onLoad(async (options) => {
     try {
-        uni.showLoading({ title: '加载中...' })
-        // 1. 获取站点数据
+        loading.value = true
         const stationRes = await listAreaStation()
         allStationNodes.value = Array.isArray(stationRes) ? stationRes : (stationRes?.data || [])
         
-        // 2. 如果是编辑,获取用户详情
         if (options.id) {
             const res = await getCustomer(options.id)
             if (res) {
-                Object.assign(form, res)
+                const data = res.data || res
+                Object.assign(form, data)
+                avatarDisplayUrl.value = data.avatarUrl || ''
+                resolveStationNames()
             }
         }
-        
-        // 3. 初始化选择器并尝试回定位
-        initStationPicker()
-    } catch(err) {
-        console.error(err)
-    } finally {
-        uni.hideLoading()
-    }
+    } catch(err) { console.error(err) } finally { loading.value = false }
 })
 
-// 初始化三级联动
-const initStationPicker = () => {
-    const nodes = allStationNodes.value
-    if (nodes.length === 0) return
-
-    const cities = nodes.filter(n => String(n.parentId) === '0')
-    
-    // 如果已有 stationId,尝试找索引
-    let cIdx = 0, aIdx = 0, sIdx = 0
-    if (form.stationId) {
-        const targetStation = nodes.find(n => String(n.id) === String(form.stationId))
-        if (targetStation) {
-            const targetArea = nodes.find(n => String(n.id) === String(targetStation.parentId))
-            if (targetArea) {
-                cIdx = cities.findIndex(n => String(n.id) === String(targetArea.parentId))
-                cIdx = cIdx === -1 ? 0 : cIdx
-                
-                const areas = nodes.filter(n => String(n.parentId) === String(cities[cIdx].id))
-                aIdx = areas.findIndex(n => String(n.id) === String(targetArea.id))
-                aIdx = aIdx === -1 ? 0 : aIdx
-                
-                const stations = nodes.filter(n => String(n.parentId) === String(areas[aIdx].id))
-                sIdx = stations.findIndex(n => String(n.id) === String(targetStation.id))
-                sIdx = sIdx === -1 ? 0 : sIdx
-                
-                stationOptions.value = [cities, areas, stations]
-                stationIndex.value = [cIdx, aIdx, sIdx]
-                return
-            }
-        }
-    }
+// 根据 ID 反查名称
+const resolveStationNames = () => {
+	if (!form.stationId) return
+	const nodes = allStationNodes.value
+	const station = nodes.find(n => String(n.id) === String(form.stationId))
+	if (station) {
+		selectedStationId.value = station.id
+		selectedStationName.value = station.name
+		const area = nodes.find(n => String(n.id) === String(station.parentId))
+		if (area) {
+			selectedAreaId.value = area.id
+			selectedAreaName.value = area.name
+			const city = nodes.find(n => String(n.id) === String(area.parentId))
+			if (city) {
+				selectedCityId.value = city.id
+				selectedCityName.value = city.name
+			}
+		}
+	}
+}
 
-    // 默认初始化
-    const areas = nodes.filter(n => String(n.parentId) === String(cities[0]?.id || ''))
-    const stations = areas.length > 0 ? nodes.filter(n => String(n.parentId) === String(areas[0].id)) : []
-    stationOptions.value = [cities, areas, stations]
-    stationIndex.value = [0, 0, 0]
+const openStationModal = () => {
+	currentStep.value = 0
+	showStationModal.value = true
 }
 
-const onStationColumnChange = (e) => {
-    const column = e.detail.column
-    const value = e.detail.value
-    stationIndex.value[column] = value
-    const nodes = allStationNodes.value
-
-    if (column === 0) {
-        const selectedCity = stationOptions.value[0][value]
-        const newAreas = selectedCity ? nodes.filter(n => String(n.parentId) === String(selectedCity.id)) : []
-        stationOptions.value[1] = newAreas
-        stationOptions.value[2] = newAreas.length > 0 ? nodes.filter(n => String(n.parentId) === String(newAreas[0].id)) : []
-        stationIndex.value[1] = 0
-        stationIndex.value[2] = 0
-    } else if (column === 1) {
-        const selectedArea = stationOptions.value[1][value]
-        const newStations = selectedArea ? nodes.filter(n => String(n.parentId) === String(selectedArea.id)) : []
-        stationOptions.value[2] = newStations
-        stationIndex.value[2] = 0
-    }
+const currentList = computed(() => {
+	if (currentStep.value === 0) return allStationNodes.value.filter(n => String(n.parentId) === '0' || !n.parentId)
+	if (currentStep.value === 1) return allStationNodes.value.filter(n => String(n.parentId) === String(selectedCityId.value))
+	return allStationNodes.value.filter(n => String(n.parentId) === String(selectedAreaId.value))
+})
+
+const onStepSelect = (item) => {
+	if (currentStep.value === 0) {
+		selectedCityId.value = item.id; selectedCityName.value = item.name
+		selectedAreaId.value = null; selectedAreaName.value = ''
+		selectedStationId.value = null; selectedStationName.value = ''
+		currentStep.value = 1
+	} else if (currentStep.value === 1) {
+		selectedAreaId.value = item.id; selectedAreaName.value = item.name
+		selectedStationId.value = null; selectedStationName.value = ''
+		currentStep.value = 2
+	} else {
+		selectedStationId.value = item.id; selectedStationName.value = item.name
+		form.stationId = item.id; form.areaId = selectedAreaId.value
+		showStationModal.value = false
+	}
 }
 
-const onStationChange = (e) => {
-    stationIndex.value = e.detail.value
-    const stations = stationOptions.value[2]
-    const selectedStation = stations[stationIndex.value[2]]
-    if (selectedStation && String(selectedStation.type) === '2') {
-        form.stationId = selectedStation.id
-        form.areaId = selectedStation.parentId
-    } else {
-        uni.showToast({ title: '请选择到具体的站点层级', icon: 'none' })
-    }
+const isStepChecked = (item) => {
+	if (currentStep.value === 0) return selectedCityId.value === item.id
+	if (currentStep.value === 1) return selectedAreaId.value === item.id
+	return selectedStationId.value === item.id
 }
 
+const getStationLabel = computed(() => form.stationId ? `${selectedCityName.value} - ${selectedAreaName.value} - ${selectedStationName.value}` : '请选择')
 const getGenderLabel = computed(() => genderOptions.find(o => o.value === form.gender)?.label || '请选择')
 const getHouseTypeLabel = computed(() => houseTypeOptions.find(o => o.value === form.houseType)?.label || '请选择')
 const getEntryMethodLabel = computed(() => entryMethodOptions.find(o => o.value === form.entryMethod)?.label || '请选择')
-const getStationLabel = computed(() => {
-    if (!form.stationId) return '请选择'
-    const city = stationOptions.value[0][stationIndex.value[0]]?.name || ''
-    const area = stationOptions.value[1][stationIndex.value[1]]?.name || ''
-    const station = stationOptions.value[2][stationIndex.value[2]]?.name || ''
-    return `${city} - ${area} - ${station}`
-})
 
-const onGenderChange = (e) => { form.gender = genderOptions[e.detail.value].value }
-const onHouseTypeChange = (e) => { form.houseType = houseTypeOptions[e.detail.value].value }
-const onEntryMethodChange = (e) => {
-    form.entryMethod = entryMethodOptions[e.detail.value].value
-    form.entryPassword = ''
-    form.keyLocation = ''
+const onEntryMethodChangeDetail = (item) => {
+	form.entryMethod = item.value
+	form.entryPassword = ''; form.keyLocation = ''
+}
+
+// 选择并上传头像
+const chooseAvatar = () => {
+    uni.chooseImage({
+        count: 1, 
+        sizeType: ['compressed'],
+        success: async (res) => {
+            try {
+                uni.showLoading({ title: '上传中...' })
+                const uploadRes = await uploadFile(res.tempFilePaths[0])
+                form.avatar = uploadRes.ossId
+                avatarDisplayUrl.value = uploadRes.url
+                uni.hideLoading()
+                uni.showToast({ title: '修改成功', icon: 'success' })
+            } catch (err) {
+                uni.hideLoading()
+                console.error('头像上传失败', err)
+            }
+        }
+    })
 }
 
 const onSave = async () => {
@@ -233,32 +274,69 @@ const onSave = async () => {
     if (!form.stationId) return uni.showToast({ title: '请选择所属站点', icon: 'none' })
     if (!form.address) return uni.showToast({ title: '请输入详细住址', icon: 'none' })
     if (!form.entryMethod) return uni.showToast({ title: '请选择入门方式', icon: 'none' })
-    
+
     saving.value = true
     try {
         await updateCustomer(form)
         uni.showToast({ title: '保存成功', icon: 'success' })
         setTimeout(() => uni.navigateBack(), 1000)
-    } catch(err) {
-    } finally {
-        saving.value = false
-    }
+    } catch(err) { } finally { saving.value = false }
 }
 </script>
 
 <style lang="scss" scoped>
 .user-edit-page { min-height: 100vh; background: #f7f8fa; padding-bottom: calc(140rpx + env(safe-area-inset-bottom)); }
-.section-title { font-size: 28rpx; font-weight: bold; color: #666; padding: 24rpx 32rpx 12rpx; }
+
+.loading-state { padding-top: 15vh; display: flex; flex-direction: column; align-items: center; justify-content: center; }
+.spinner { width: 44rpx; height: 44rpx; border: 4rpx solid #f3f3f3; border-top: 4rpx solid #ff9500; border-radius: 50%; animation: spin 1s linear infinite; }
+.loading-txt { font-size: 24rpx; color: #999; margin-top: 20rpx; }
+@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
+
+.avatar-section { display: flex; justify-content: center; padding: 40rpx 0 20rpx; }
+.avatar-wrap { display: flex; flex-direction: column; align-items: center; gap: 12rpx; }
+.avatar-img { width: 144rpx; height: 144rpx; border-radius: 50%; border: 4rpx solid #fff; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); }
+.avatar-placeholder { width: 144rpx; height: 144rpx; border-radius: 50%; background: #f5f5f5; border: 2rpx dashed #EEEEEE; display: flex; align-items: center; justify-content: center; }
+.avatar-icon {
+	width: 60rpx; height: 60rpx; position: relative;
+	.head { width: 30rpx; height: 30rpx; border-radius: 50%; background: #ddd; position: absolute; top: 0; left: 50%; transform: translateX(-50%); }
+	.body { width: 50rpx; height: 26rpx; border-radius: 20rpx 20rpx 0 0; background: #ddd; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); }
+}
+.avatar-tip { font-size: 22rpx; color: #999; }
+
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; padding: 32rpx 32rpx 16rpx; }
 .form-card { background: #fff; border-radius: 24rpx; padding: 8rpx 32rpx; margin: 0 24rpx 24rpx; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 2rpx solid #EEEEEE; position: relative; }
 .form-item:last-child { border-bottom: none; }
-.form-item.vertical { flex-direction: column; align-items: flex-start; }
-.form-item.vertical .form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 16rpx; box-sizing: border-box; border-radius: 12rpx; font-size: 28rpx; }
-.form-label { width: 200rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
+.form-item.vertical { flex-direction: column; align-items: stretch; }
+.form-textarea { width: 100%; height: 160rpx; margin-top: 16rpx; background: #f9f9f9; padding: 20rpx; box-sizing: border-box; border-radius: 12rpx; font-size: 28rpx; }
+
+.form-label { width: 220rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
 .form-label.require::before { content: '*'; color: #f56c6c; margin-right: 4rpx; }
 .form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; min-height: 40rpx; }
+.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; margin-right: 16rpx; }
 .picker-value.placeholder { color: #ccc; }
+
+.right-arrow { width: 14rpx; height: 14rpx; border-right: 3rpx solid #ccc; border-top: 3rpx solid #ccc; transform: rotate(45deg); }
 .footer-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); z-index: 100; }
-.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; }
+.save-btn { width: 100%; height: 88rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border: none; border-radius: 44rpx; font-size: 32rpx; font-weight: bold; line-height: 88rpx; }
+
+.center-modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4rpx); }
+.center-modal-content { width: 620rpx; background: #fff; border-radius: 32rpx; display: flex; flex-direction: column; overflow: hidden; animation: popIn 0.3s ease-out; }
+@keyframes popIn { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } }
+
+.modal-header { padding: 32rpx; border-bottom: 2rpx solid #f2f2f2; position: relative; text-align: center; }
+.modal-title { font-size: 32rpx; font-weight: bold; color: #333; }
+.close-btn { 
+	position: absolute; right: 30rpx; top: 32rpx; width: 40rpx; height: 40rpx;
+	&::before, &::after { content: ''; position: absolute; top: 18rpx; left: 5rpx; width: 30rpx; height: 4rpx; background: #999; transform: rotate(45deg); border-radius: 4rpx; }
+	&::after { transform: rotate(-45deg); }
+}
+
+.step-indicator { display: flex; align-items: center; justify-content: center; padding: 24rpx; background: #fdfdfd; border-bottom: 2rpx solid #f9f9f9; gap: 8rpx; }
+.step-item { font-size: 24rpx; color: #999; max-width: 150rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.step-item.active { color: #ff9500; font-weight: bold; }
+.modal-list-scroll { flex: 1; max-height: 55vh; padding: 0 32rpx; }
+.list-item { display: flex; align-items: center; justify-content: space-between; padding: 32rpx 0; border-bottom: 2rpx solid #f9f9f9; }
+.checkmark { width: 12rpx; height: 24rpx; border-right: 4rpx solid #ff9500; border-bottom: 4rpx solid #ff9500; transform: rotate(45deg); }
+.empty-tip { padding: 80rpx 0; text-align: center; color: #ccc; }
 </style>

+ 38 - 13
pages/my/user/list/index.vue

@@ -188,11 +188,12 @@ const onStatusChange = (e, user) => {
 
 .search-box {
 	flex: 1;
+	height: 64rpx;
 	display: flex;
 	align-items: center;
 	background: #f5f5f5;
 	border-radius: 32rpx;
-	padding: 12rpx 20rpx;
+	padding: 0 24rpx;
 	gap: 12rpx;
 }
 
@@ -200,28 +201,40 @@ const onStatusChange = (e, user) => {
 	flex: 1;
 	font-size: 26rpx;
 	background: transparent;
+	height: 100%;
 }
 
 .filter-btn {
+	height: 64rpx;
 	display: flex;
 	align-items: center;
 	gap: 8rpx;
 	background: #f5f5f5;
 	border-radius: 32rpx;
-	padding: 12rpx 20rpx;
+	padding: 0 24rpx;
 	font-size: 24rpx;
 	color: #666;
 }
 
 .add-btn {
+	height: 64rpx;
+	line-height: 64rpx;
 	font-size: 24rpx;
 	font-weight: bold;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
 	border-radius: 32rpx;
-	padding: 12rpx 24rpx;
+	padding: 0 28rpx;
+	margin: 0;
 	white-space: nowrap;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+
+	&::after {
+		border: none;
+	}
 }
 
 .list-container {
@@ -240,7 +253,7 @@ const onStatusChange = (e, user) => {
 	align-items: center;
 	margin-bottom: 24rpx;
 	padding-bottom: 24rpx;
-	border-bottom: 1rpx solid #f9f9f9;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .user-avatar {
@@ -303,8 +316,8 @@ const onStatusChange = (e, user) => {
 
 .info-grid {
 	display: flex;
-	background: #fdfdfd;
-	border: 1rpx solid #f2f2f2;
+	background: transparent;
+	border: 2rpx solid #EEEEEE;
 	border-radius: 12rpx;
 	padding: 20rpx;
 	margin-bottom: 20rpx;
@@ -333,9 +346,10 @@ const onStatusChange = (e, user) => {
 }
 
 .source-box {
-	background: #fff8e1;
+	background: transparent;
 	padding: 16rpx;
-	border-radius: 8rpx;
+	border-radius: 12rpx;
+	border: 1rpx solid #f9e8d4;
 }
 
 .source-tag {
@@ -353,19 +367,30 @@ const onStatusChange = (e, user) => {
 
 .card-actions {
 	display: flex;
-	justify-content: flex-end;
-	gap: 20rpx;
+	justify-content: center;
+	gap: 40rpx;
 	margin-top: 24rpx;
 	padding-top: 24rpx;
-	border-top: 1rpx dashed #eee;
+	border-top: 2rpx dashed #EEEEEE;
 }
 
 .action-btn {
+	width: 160rpx;
+	height: 64rpx;
+	line-height: 60rpx;
 	border: 1rpx solid #e0e0e0;
 	color: #666;
 	font-size: 24rpx;
 	background: transparent;
 	border-radius: 12rpx;
-	padding: 8rpx 24rpx;
+	padding: 0;
+	margin: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+
+	&::after {
+		border: none;
+	}
 }
 </style>

+ 405 - 952
pages/order/apply/index.vue

@@ -1,13 +1,15 @@
 <template>
 	<view class="order-apply-page">
 		<nav-bar title="下单预约"></nav-bar>
+		
 		<view class="apply-content">
 			<!-- 01 服务类型 -->
 			<text class="section-title">01 服务类型</text>
 			<view class="card service-info-card">
 				<view class="service-type-display">
 					<view :class="['service-icon-box', activeService]">
-						<uni-icons :type="serviceIcon" size="22" color="#fff"></uni-icons>
+						<!-- CSS 手绘服务图标 @Author: Antigravity -->
+						<view class="pure-css-icon" :class="serviceIconClass"></view>
 					</view>
 					<view class="service-info-text">
 						<text class="main-name">{{ currentServiceName }}</text>
@@ -19,39 +21,39 @@
 			<!-- 02 基础信息 -->
 			<text class="section-title">02 基础信息</text>
 			<view class="card basic-info-card">
-				<view class="field-row" @click="showShopPicker = true">
-					<text class="field-label">服务门店</text>
-					<text :class="['field-value', !formData.shopName ? 'placeholder' : '']">{{ formData.shopName ||
-						'请选择商户门店' }}</text>
-					<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+				<view class="field-item" @click="showShopSelect = true">
+					<text class="field-label require">服务门店</text>
+					<text :class="['field-value', !formData.shopName ? 'placeholder' : '']">{{ formData.shopName || '请选择商户门店' }}</text>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="field-row" @click="showUserPopup = true">
-					<text class="field-label">宠主用户</text>
+				<view class="field-item" @click="showUserSelect = true">
+					<text class="field-label require">宠主用户</text>
 					<view class="field-value-wrap">
 						<template v-if="selectedUser">
 							<text class="selected-name">{{ selectedUser.name }}</text>
-							<text class="selected-phone">{{ selectedUser.phone }}</text>
+							<text class="selected-phone">{{ selectedUser.phone || selectedUser.phoneNumber }}</text>
 						</template>
-						<text v-else class="placeholder">搜索手机号/姓名</text>
+						<text v-else class="placeholder">点击搜索</text>
 					</view>
+					<view class="right-arrow"></view>
 				</view>
-				<view class="field-row" @click="openPetPicker">
-					<text class="field-label">选择宠物</text>
-					<text :class="['field-value', !formData.petName ? 'placeholder' : '']">{{ formData.petName ||
-						'点击选择宠物档案' }}</text>
-					<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+				<view class="field-item" @click="openPetPicker">
+					<text class="field-label require">选择宠物</text>
+					<text :class="['field-value', !formData.petName ? 'placeholder' : '']">{{ formData.petName || '选择宠物档案' }}</text>
+					<view class="right-arrow"></view>
 				</view>
 			</view>
 
 			<!-- 03 业务表单 - 宠物接送 -->
 			<template v-if="activeService === 'transport'">
-				<text class="section-title">03 填写接送路线与时间</text>
+				<text class="section-title">03 接送路线与时间</text>
 				<view class="card transport-card">
-					<view class="field-row">
+					<view class="field-item">
 						<text class="field-label">团购套餐</text>
 						<input class="field-input" v-model="formData.packageName" placeholder="请输入套餐名称(选填)" />
 					</view>
-					<text class="form-item-label">接送模式</text>
+					
+					<text class="form-item-label require">接送模式</text>
 					<view class="mode-select">
 						<view v-for="mode in transportModes" :key="mode.value"
 							:class="['mode-btn', { active: formData.transportMode === mode.value }]"
@@ -60,156 +62,124 @@
 						</view>
 					</view>
 
-					<!-- 接宠路线 (起点=用户家, 终点=门店) @Author: Antigravity -->
+					<!-- 接宠路线 @Author: Antigravity -->
 					<view class="route-box" v-if="formData.transportMode !== 'return_home'">
-						<view class="route-icon pick"><text></text></view>
+						<view class="route-icon pick">接</view>
 						<view class="route-fields">
-							<text class="addr-label">起点</text>
-							<uni-data-picker :localdata="regionTree" v-model="formData.pickArea"
-								:map="{ text: 'name', value: 'code' }" @change="onRegionChange('pick', $event)">
-								<view class="premium-cascader-display">
-									<text :class="['display-text', !formData.pickArea ? 'placeholder' : '']">
-										{{ pickAreaLabel || '选择省/市/区' }}
-									</text>
-									<uni-icons type="right" size="12" color="#ccc"></uni-icons>
-								</view>
-							</uni-data-picker>
-							<input class="route-input" v-model="formData.pickAddress" placeholder="详细地址 (街道/门牌号)" />
-							<text class="addr-label">终点</text>
-							<uni-data-picker :localdata="regionTree" v-model="formData.pickEndArea"
-								:map="{ text: 'name', value: 'code' }" @change="onRegionChange('pickEnd', $event)">
-								<view class="premium-cascader-display">
-									<text :class="['display-text', !formData.pickEndArea ? 'placeholder' : '']">
-										{{ pickEndAreaLabel || '选择省/市/区' }}
-									</text>
-									<uni-icons type="right" size="12" color="#ccc"></uni-icons>
-								</view>
-							</uni-data-picker>
-							<input class="route-input" v-model="formData.pickEndAddress" placeholder="详细地址 (街道/门牌号)" />
+							<text class="addr-label require">起点 (用户家)</text>
+							<view class="route-picker-trigger" @click="openRegionSelect('pick')">
+								<text :class="['display-text', !formData.pickArea ? 'placeholder' : '']">{{ pickAreaLabel || '选择省/市/区' }}</text>
+								<view class="right-arrow"></view>
+							</view>
+							<input class="route-input" v-model="formData.pickAddress" placeholder="详细地址" />
+							
+							<text class="addr-label require">终点 (门店)</text>
+							<view class="route-picker-trigger" @click="openRegionSelect('pickEnd')">
+								<text :class="['display-text', !formData.pickEndArea ? 'placeholder' : '']">{{ pickEndAreaLabel || '选择省/市/区' }}</text>
+								<view class="right-arrow"></view>
+							</view>
+							<input class="route-input" v-model="formData.pickEndAddress" placeholder="详细地址" />
+							
 							<view class="contact-row">
 								<input class="route-input half" v-model="formData.pickContact" placeholder="联系人" />
-								<input class="route-input half" v-model="formData.pickPhone" placeholder="电话"
-									type="tel" />
+								<input class="route-input half" v-model="formData.pickPhone" placeholder="电话" type="tel" />
 							</view>
-							<view class="time-picker-row">
-								<uni-datetime-picker type="datetime" v-model="formData.pickTime" placeholder="选择接宠时间"
-									:border="false" :hide-second="true">
-								</uni-datetime-picker>
+							<view class="route-time-trigger" @click="openTimeModal('pick')">
+								<text :class="!formData.pickTime ? 'placeholder' : ''">{{ pickTimeDisplay || '设置接宠时间' }}</text>
 							</view>
 						</view>
 					</view>
 
-					<!-- 送宠路线 (起点=门店, 终点=用户家) @Author: Antigravity -->
+					<!-- 送宠路线 @Author: Antigravity -->
 					<view class="route-box" v-if="formData.transportMode !== 'pick_up'">
-						<view class="route-icon send"><text></text></view>
+						<view class="route-icon send">送</view>
 						<view class="route-fields">
-							<text class="addr-label">起点</text>
-							<uni-data-picker :localdata="regionTree" v-model="formData.sendStartArea"
-								:map="{ text: 'name', value: 'code' }" @change="onRegionChange('sendStart', $event)">
-								<view class="premium-cascader-display">
-									<text :class="['display-text', !formData.sendStartArea ? 'placeholder' : '']">
-										{{ sendStartAreaLabel || '选择省/市/区' }}
-									</text>
-									<uni-icons type="right" size="12" color="#ccc"></uni-icons>
-								</view>
-							</uni-data-picker>
-							<input class="route-input" v-model="formData.sendStartAddress"
-								placeholder="详细地址 (街道/门牌号)" />
-							<text class="addr-label">终点</text>
-							<uni-data-picker :localdata="regionTree" v-model="formData.sendArea"
-								:map="{ text: 'name', value: 'code' }" @change="onRegionChange('send', $event)">
-								<view class="premium-cascader-display">
-									<text :class="['display-text', !formData.sendArea ? 'placeholder' : '']">
-										{{ sendAreaLabel || '选择省/市/区' }}
-									</text>
-									<uni-icons type="right" size="12" color="#ccc"></uni-icons>
-								</view>
-							</uni-data-picker>
-							<input class="route-input" v-model="formData.sendAddress" placeholder="详细地址 (街道/门牌号)" />
+							<text class="addr-label require">起点 (门店)</text>
+							<view class="route-picker-trigger" @click="openRegionSelect('sendStart')">
+								<text :class="['display-text', !formData.sendStartArea ? 'placeholder' : '']">{{ sendStartAreaLabel || '选择省/市/区' }}</text>
+								<view class="right-arrow"></view>
+							</view>
+							<input class="route-input" v-model="formData.sendStartAddress" placeholder="详细地址" />
+							
+							<text class="addr-label require">终点 (用户家)</text>
+							<view class="route-picker-trigger" @click="openRegionSelect('send')">
+								<text :class="['display-text', !formData.sendArea ? 'placeholder' : '']">{{ sendAreaLabel || '选择省/市/区' }}</text>
+								<view class="right-arrow"></view>
+							</view>
+							<input class="route-input" v-model="formData.sendAddress" placeholder="详细地址" />
+							
 							<view class="contact-row">
 								<input class="route-input half" v-model="formData.sendContact" placeholder="联系人" />
-								<input class="route-input half" v-model="formData.sendPhone" placeholder="电话"
-									type="tel" />
+								<input class="route-input half" v-model="formData.sendPhone" placeholder="电话" type="tel" />
 							</view>
-							<view class="time-picker-row">
-								<uni-datetime-picker type="datetime" v-model="formData.sendTime"
-									placeholder="预计送还时间(可选)" :border="false" :hide-second="true">
-								</uni-datetime-picker>
+							<view class="route-time-trigger" @click="openTimeModal('send')">
+								<text :class="!formData.sendTime ? 'placeholder' : ''">{{ sendTimeDisplay || '设置送宠时间' }}</text>
 							</view>
 						</view>
 					</view>
 				</view>
 			</template>
 
-			<!-- 03 业务表单 - 上门喂遛/洗护 -->
+			<!-- 03 业务表单 - 上门 -->
 			<template v-else>
-				<text class="section-title">03 选择套餐与服务细则</text>
+				<text class="section-title">03 服务细则</text>
 				<view class="card feed-card">
-					<view class="field-row">
+					<view class="field-item">
 						<text class="field-label">团购套餐</text>
 						<input class="field-input" v-model="formData.packageName" placeholder="请输入套餐名称(选填)" />
 					</view>
-					<text class="address-title">上门服务地址</text>
-					<uni-data-picker :localdata="regionTree" v-model="formData.serviceArea"
-						:map="{ text: 'name', value: 'code' }" @change="onRegionChange('service', $event)">
-						<view class="premium-full-picker">
-							<text :class="['display-text', !formData.serviceArea ? 'placeholder' : '']">
-								{{ serviceAreaLabel || '请选择省/市/区' }}
-							</text>
-							<uni-icons type="right" size="14" color="#ccc"></uni-icons>
+					<view class="route-box">
+						<view class="route-icon service">服</view>
+						<view class="route-fields">
+							<text class="addr-label require">上门服务地址</text>
+							<view class="route-picker-trigger" @click="openRegionSelect('service')">
+								<text :class="['display-text', !formData.serviceArea ? 'placeholder' : '']">{{ serviceAreaLabel || '请选择省/市/区' }}</text>
+								<view class="right-arrow"></view>
+							</view>
+							<input class="route-input" v-model="formData.serviceAddress" placeholder="详细地址 (街道/路名/门牌号)" />
 						</view>
-					</uni-data-picker>
-					<input class="full-input" v-model="formData.serviceAddress" placeholder="详细地址 (街道/路名/门牌号)" />
+					</view>
 
 					<view class="booking-section">
 						<view class="booking-header">
-							<text class="label">预约服务时间</text>
-							<text class="count-tag">共 {{ formData.feedTimes.length }} 次</text>
+							<text class="label require">预约服务时间</text>
+							<view class="count-tag">共 {{ formData.feedTimes.length }} 次</view>
 						</view>
 						<view class="time-item-row" v-for="(time, index) in formData.feedTimes" :key="index">
 							<text class="index">{{ index + 1 }}.</text>
-							<view class="flex-picker-box">
-								<uni-datetime-picker type="datetime" v-model="time.start" placeholder="开始"
-									:border="false" class="inline-picker" :hide-second="true"></uni-datetime-picker>
+							<view class="flex-time-box" @click="openTimeModal('feed', index, 'start')">
+								<text :class="['time-text', !time.start ? 'placeholder' : '']">{{ truncateTime(time.start) || '开始' }}</text>
 							</view>
 							<text class="to-line">~</text>
-							<view class="flex-picker-box">
-								<uni-datetime-picker type="datetime" v-model="time.end" placeholder="结束" :border="false"
-									class="inline-picker" :hide-second="true"></uni-datetime-picker>
+							<view class="flex-time-box" @click="openTimeModal('feed', index, 'end')">
+								<text :class="['time-text', !time.end ? 'placeholder' : '']">{{ truncateTime(time.end) || '结束' }}</text>
 							</view>
 							<view class="action-buttons">
-								<view class="circle-btn add" v-if="index === formData.feedTimes.length - 1"
-									@click="addFeedTime">
-									<text>+</text>
-								</view>
-								<view class="circle-btn remove" v-if="formData.feedTimes.length > 1"
-									@click="removeFeedTime(index)">
-									<text>-</text>
-								</view>
+								<view class="circle-btn add" v-if="index === formData.feedTimes.length - 1" @click="addFeedTime">+</view>
+								<view class="circle-btn remove" v-if="formData.feedTimes.length > 1" @click="removeFeedTime(index)">-</view>
 							</view>
 						</view>
 					</view>
-
 					<text class="remarks-title">备注信息</text>
-					<textarea class="remarks-textarea" v-model="formData.otherNote" placeholder="其他注意事项"></textarea>
+					<textarea class="remarks-textarea" v-model="formData.otherNote" placeholder="如有其他注意事项请备注"></textarea>
 				</view>
 			</template>
 
 			<!-- 04 报价信息 -->
 			<text class="section-title">04 报价信息</text>
 			<view class="card quote-card">
-				<view class="field-row">
-					<text class="field-label">报价金额</text>
-					<input class="field-input" v-model="formData.quoteAmount" type="digit" placeholder="请输入报价金额" />
+				<view class="field-item">
+					<text class="field-label require">报价金额</text>
+					<input class="field-input quote-input" v-model="formData.quoteAmount" type="digit" placeholder="填入数字" />
 					<text class="unit-text">元</text>
 				</view>
-				<text class="quote-tips">注:此报价为预估费用,最终费用以实际结算为准。</text>
+				<text class="quote-tips">注:此价格将作为订单最终结算金额。</text>
 			</view>
 		</view>
 
 		<!-- 底部操作栏 -->
 		<view class="footer-bar safe-bottom">
-			<view class="quotation-fulfillmentCommission-box">
+			<view class="quotation-box">
 				<text class="p-label">总计报价:</text>
 				<text class="p-symbol">¥</text>
 				<text class="p-amount">{{ totalFulfillmentCommission }}</text>
@@ -217,436 +187,331 @@
 			<button class="submit-btn" @click="onSubmit">立即下单</button>
 		</view>
 
-		<!-- 门店选择弹窗 -->
-		<view class="popup-mask" v-if="showShopPicker" @click="showShopPicker = false">
-			<view class="popup-content" @click.stop>
-				<text class="popup-title">选择服务门店</text>
-				<scroll-view scroll-y="true" class="popup-scroll">
-					<view class="popup-item" v-for="shop in shopList" :key="shop.id" @click="onShopSelect(shop)">
-						<text>{{ shop.name }}</text>
+		<!-- 居中联动选择弹窗群 @Author: Antigravity -->
+		
+		<!-- 宠主搜索弹窗 -->
+		<view class="center-modal-mask" v-if="showUserSelect" @click="showUserSelect = false">
+			<view class="center-modal-content user-search-modal" @click.stop>
+				<view class="modal-header">
+					<view class="search-box">
+						<view class="search-icon"></view>
+						<input class="search-input" v-model="userSearchKey" placeholder="搜索宠主姓名/手机号" @confirm="fetchUsers" confirm-type="search" />
+						<view class="search-btn" @click="fetchUsers">查询</view>
+					</view>
+				</view>
+				<scroll-view scroll-y class="modal-list-scroll">
+					<view class="list-item" v-for="user in userList" :key="user.id" @click="onUserSelect(user)">
+						<view class="user-info">
+							<text class="name">{{ user.name }}</text>
+							<text class="phone">{{ user.phone || user.phoneNumber }}</text>
+						</view>
+						<view class="checkmark" v-if="formData.customerId === user.id"></view>
 					</view>
+					<view class="empty-tip" v-if="userList.length === 0">未找到相关宠主</view>
 				</scroll-view>
 			</view>
 		</view>
-
-		<!-- 用户选择弹窗 -->
-		<view class="popup-mask" v-if="showUserPopup" @click="showUserPopup = false">
-			<view class="popup-content user-popup" @click.stop>
-				<text class="popup-title">选择宠主</text>
-				<view class="search-bar">
-					<input class="search-input" v-model="userSearchKey" placeholder="输入姓名或手机号搜索"
-						@confirm="fetchUsers" />
-					<uni-icons type="search" size="18" color="#999" @click="fetchUsers"></uni-icons>
+		
+		<!-- 区域选择器 (Cascader) @Author: Antigravity -->
+		<view class="center-modal-mask" v-if="showRegionModal" @click="showRegionModal = false">
+			<view class="center-modal-content region-modal" @click.stop>
+				<view class="modal-header"><text class="modal-title">选择区域</text><view class="close-btn" @click="showRegionModal = false"></view></view>
+				<view class="cascade-indicator">
+					<text v-for="(node, idx) in regionPath" :key="idx" class="path-node" @click="backToLevel(idx)">{{ node.name }}</text>
+					<text class="path-node active" v-if="regionPath.length < 3">请选择</text>
 				</view>
-				<scroll-view scroll-y="true" class="popup-scroll">
-					<view class="popup-item" v-for="user in userList" :key="user.id" @click="onUserSelect(user)">
-						<text class="user-item-name">{{ user.name }}</text>
-						<text class="user-item-phone">{{ user.phone || user.phoneNumber }}</text>
+				<scroll-view scroll-y class="modal-list-scroll">
+					<view class="list-item" v-for="item in currentRegionList" :key="item.code" @click="onRegionStepSelect(item)">
+						<text class="item-text">{{ item.name }}</text>
+						<view class="checkmark" v-if="isRegionSelected(item)"></view>
 					</view>
 				</scroll-view>
 			</view>
 		</view>
 
-		<!-- 宠物选择弹窗 -->
-		<view class="popup-mask" v-if="showPetPopup" @click="showPetPopup = false">
-			<view class="popup-content" @click.stop>
-				<text class="popup-title">选择宠物</text>
-				<scroll-view scroll-y="true" class="popup-scroll">
-					<view class="popup-item" v-for="pet in petList" :key="pet.id" @click="onPetSelect(pet)">
-						<view class="pet-item-cell">
-							<image :src="pet.avatar" class="pet-avatar-mini" mode="aspectFill"></image>
-							<text>{{ pet.name }} ({{ pet.breed }})</text>
-						</view>
-					</view>
-					<view v-if="petList.length === 0" class="empty-tips">该用户下暂无宠物档案</view>
-				</scroll-view>
+		<!-- 门店选择 -->
+		<center-select v-model="showShopSelect" title="选择服务门店" :options="shopList" labelKey="name" valueKey="id" :value="formData.merchantId" @select="onShopSelect" />
+		
+		<!-- 宠物选择 -->
+		<center-select v-model="showPetPopup" title="选择指定宠物" :options="petOptions" labelKey="_label" valueKey="id" :value="formData.petId" @select="onPetSelect" />
+
+		<!-- 日期时间选择弹窗 @Author: Antigravity -->
+		<view class="center-modal-mask" v-if="showTimeModal" @click="showTimeModal = false">
+			<view class="center-modal-content time-modal" @click.stop>
+				<view class="modal-header"><text class="modal-title">选择预约时间</text></view>
+				<view class="datetime-picker-body">
+					<picker-view class="picker-view" :value="tempTimeIdx" @change="onTempTimeChange">
+						<picker-view-column><view class="picker-item" v-for="d in timeRanges[0]" :key="d">{{ d }}</view></picker-view-column>
+						<picker-view-column><view class="picker-item" v-for="h in timeRanges[1]" :key="h">{{ h }}时</view></picker-view-column>
+						<picker-view-column><view class="picker-item" v-for="m in timeRanges[2]" :key="m">{{ m }}分</view></picker-view-column>
+					</picker-view>
+				</view>
+				<view class="modal-footer">
+					<view class="modal-cancel" @click="showTimeModal = false">取消</view>
+					<view class="modal-confirm" @click="confirmTime">确定</view>
+				</view>
 			</view>
 		</view>
+
 	</view>
 </template>
 
 <script setup>
+/**
+ * @Author: Antigravity
+ */
 import { ref, reactive, computed, watch } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import navBar from '@/components/nav-bar/index.vue'
+import centerSelect from '@/components/center-select/index.vue'
 import { listStoreOnOrder } from '@/api/system/store'
 import { listCustomerOnOrder } from '@/api/archieves/customer'
 import { listPetByUser } from '@/api/archieves/pet'
 import { createOrder } from '@/api/order/order'
 import { listRegionTree } from '@/api/system/region'
 
-const showPetPopup = ref(false)
 const activeService = ref('transport')
-const showShopPicker = ref(false)
-const showUserPopup = ref(false)
-const selectedUser = ref(null)
-const selectedShop = ref(null)
-const selectedPet = ref(null)
+const serviceInfo = ref(null)
 const shopList = ref([])
 const userList = ref([])
 const petList = ref([])
-const userSearchKey = ref('')
-const serviceInfo = ref(null)
 const regionTree = ref([])
+
+// 弹窗控制
+const showShopSelect = ref(false)
+const showUserSelect = ref(false)
+const showPetPopup = ref(false)
+const showRegionModal = ref(false)
+const showTimeModal = ref(false)
+
+const userSearchKey = ref('')
+const selectedUser = ref(null)
+const selectedShop = ref(null)
+
 const pickAreaLabel = ref('')
 const pickEndAreaLabel = ref('')
 const sendStartAreaLabel = ref('')
 const sendAreaLabel = ref('')
 const serviceAreaLabel = ref('')
 
-const currentServiceName = computed(() => {
-	// @Author: Antigravity
-	if (serviceInfo.value) return serviceInfo.value.name
-	const map = { transport: '宠物接送', feed: '上门喂遛', wash: '上门洗护' }
-	return map[activeService.value]
-})
-const serviceIcon = computed(() => {
-	const map = { transport: 'car', feed: 'shop', wash: 'color' }
-	return map[activeService.value]
-})
-const serviceDesc = computed(() => {
-	// @Author: Antigravity
-	if (serviceInfo.value) return serviceInfo.value.remark
-	const map = { transport: '专车接送 · 全程监护', feed: '喂食添水 · 陪玩遛狗', wash: '专业设备 · 深度清洁' }
-	return map[activeService.value]
+const formData = reactive({
+	merchantId: '', shopName: '', customerId: '', customerName: '', petId: '', petName: '',
+	packageName: '', transportMode: 'round_trip',
+	pickArea: '', pickAddress: '', pickEndArea: '', pickEndAddress: '', pickContact: '', pickPhone: '', pickTime: '',
+	sendStartArea: '', sendStartAddress: '', sendArea: '', sendAddress: '', sendContact: '', sendPhone: '', sendTime: '',
+	serviceArea: '', serviceAddress: '', feedTimes: [{ start: '', end: '' }],
+	otherNote: '', quoteAmount: ''
 })
 
+// 时间选择器逻辑 (5分钟一个间隔 @Author: Antigravity)
+const timeRanges = ref([[], [], []])
+const tempTimeIdx = ref([0, 0, 0])
+const timeCtx = reactive({ type: '', index: 0, field: '' })
+
 onLoad((options) => {
 	if (options.service) activeService.value = options.service
-	// @Author: Antigravity
 	const stored = uni.getStorageSync('currentService')
-	if (stored) {
-		serviceInfo.value = stored
+	if (stored) serviceInfo.value = stored
+	initTimeRanges()
+	fetchShops(); fetchUsers(); fetchRegionTree()
+})
+
+const initTimeRanges = () => {
+	const dates = []
+	const now = new Date()
+	for (let i = 0; i < 30; i++) {
+		const d = new Date(now); d.setDate(d.getDate() + i)
+		dates.push(`${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`)
+	}
+	timeRanges.value = [
+		dates,
+		Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')),
+		// 五分钟间隔生成 @Author: Antigravity
+		Array.from({ length: 12 }, (_, i) => String(i * 5).padStart(2, '0'))
+	]
+}
+
+const openTimeModal = (type, index = 0, field = '') => {
+	timeCtx.type = type; timeCtx.index = index; timeCtx.field = field
+	tempTimeIdx.value = [0, 0, 0]
+	showTimeModal.value = true
+}
+
+const onTempTimeChange = (e) => { tempTimeIdx.value = e.detail.value }
+
+const confirmTime = () => {
+	const [di, hi, mi] = tempTimeIdx.value
+	const val = `${new Date().getFullYear()}-${timeRanges.value[0][di]} ${timeRanges.value[1][hi]}:${timeRanges.value[2][mi]}:00`
+	if (timeCtx.type === 'pick') formData.pickTime = val
+	else if (timeCtx.type === 'send') formData.sendTime = val
+	else if (timeCtx.type === 'feed') {
+		if (timeCtx.field === 'start') formData.feedTimes[timeCtx.index].start = val
+		else formData.feedTimes[timeCtx.index].end = val
+	}
+	showTimeModal.value = false
+}
+
+const truncateTime = (t) => t ? t.substring(5, 16) : ''
+const pickTimeDisplay = computed(() => truncateTime(formData.pickTime))
+const sendTimeDisplay = computed(() => truncateTime(formData.sendTime))
+
+// 区域选择逻辑
+const regionPath = ref([])
+const activeRegionType = ref('')
+const currentRegionList = computed(() => {
+	let list = regionTree.value
+	for (let node of regionPath.value) {
+		const found = list.find(l => l.code === node.code)
+		if (found && found.children) list = found.children
+		else list = []
 	}
-	// 初始化获取数据
-	fetchShops()
-	fetchUsers()
-	fetchRegionTree()
+	return list
 })
 
-const fetchRegionTree = () => {
-	listRegionTree().then(res => {
-		console.log('移动端获取到的地区树数据:', res)
-		regionTree.value = res || []
-	}).catch(err => {
-		console.error('获取地区树异常:', err)
-	})
+const openRegionSelect = (type) => { 
+	activeRegionType.value = type
+	regionPath.value = []
+	showRegionModal.value = true 
+}
+
+const backToLevel = (idx) => { regionPath.value = regionPath.value.slice(0, idx) }
+
+const onRegionStepSelect = (item) => {
+	regionPath.value.push({ code: item.code, name: item.name })
+	if (!item.children || item.children.length === 0 || regionPath.value.length >= 3) {
+		const fullLabel = regionPath.value.map(p => p.name).join(' / ')
+		const finalCode = item.code
+		if (activeRegionType.value === 'pick') { formData.pickArea = finalCode; pickAreaLabel.value = fullLabel }
+		else if (activeRegionType.value === 'pickEnd') { formData.pickEndArea = finalCode; pickEndAreaLabel.value = fullLabel }
+		else if (activeRegionType.value === 'sendStart') { formData.sendStartArea = finalCode; sendStartAreaLabel.value = fullLabel }
+		else if (activeRegionType.value === 'send') { formData.sendArea = finalCode; sendAreaLabel.value = fullLabel }
+		else if (activeRegionType.value === 'service') { formData.serviceArea = finalCode; serviceAreaLabel.value = fullLabel }
+		showRegionModal.value = false
+	}
 }
 
-const onRegionChange = (type, e) => {
-	// @Author: Antigravity
-	const text = e.detail.value.map(v => v.text).join(' / ')
-	if (type === 'pick') pickAreaLabel.value = text
-	else if (type === 'pickEnd') pickEndAreaLabel.value = text
-	else if (type === 'sendStart') sendStartAreaLabel.value = text
-	else if (type === 'send') sendAreaLabel.value = text
-	else if (type === 'service') serviceAreaLabel.value = text
-}
+const isRegionSelected = (item) => {
+	const level = regionPath.value.length
+	return regionPath.value[level]?.code === item.code
+}
+
+// 核心回填逻辑修正 @Author: Antigravity
+watch([selectedShop, selectedUser, regionTree], ([shop, user, tree]) => {
+	if (!shop && !user) return
+	
+	// 处理门店信息
+	const storeAreaCode = (shop?.areaCode || '').replace(/,/g, '/')
+	const storeLeaf = storeAreaCode.split('/').pop() || ''
+	const storePath = findRegionLabel(storeAreaCode, tree)
+
+	// 处理用户信息
+	const userAreaCode = (user?.regionCode || '')
+	const userLeaf = userAreaCode.split('/').pop() || ''
+	const userPath = findRegionLabel(userAreaCode, tree)
+	
+	if (shop) {
+		formData.merchantId = shop.id; formData.shopName = shop.name
+		// 接送单终点 = 门店
+		formData.pickEndArea = storeLeaf; formData.pickEndAddress = shop.address || ''
+		pickEndAreaLabel.value = storePath
+		// 接送单起点 = 门店
+		formData.sendStartArea = storeLeaf; formData.sendStartAddress = shop.address || ''
+		sendStartAreaLabel.value = storePath
+	}
+	if (user) {
+		formData.customerId = user.id; formData.customerName = user.name
+		// 接宠单起点 = 宠主家
+		formData.pickArea = userLeaf; formData.pickAddress = user.address || ''
+		pickAreaLabel.value = userPath
+		// 送宠单终点 = 宠主家
+		formData.sendArea = userLeaf; formData.sendAddress = user.address || ''
+		sendAreaLabel.value = userPath
+		// 服务单地址 = 宠主家
+		formData.serviceArea = userLeaf; formData.serviceAddress = user.address || ''
+		serviceAreaLabel.value = userPath
+
+		formData.pickContact = user.name; formData.pickPhone = user.phoneNumber || user.phone || ''
+		formData.sendContact = user.name; formData.sendPhone = formData.pickPhone
+	}
+}, { deep: true })
 
-// 根据 code 递归查找地区名称全路径 @Author: Antigravity
 const findRegionLabel = (code, list) => {
 	if (!code || !list || list.length === 0) return ''
-	// 如果是路径格式,取最后一位
-	const targetCode = code.includes('/') ? code.split('/').pop() : code
-
-	const find = (nodes, target) => {
-		for (let item of nodes) {
-			if (item.code === target) return item.name
-			if (item.children && item.children.length > 0) {
-				const childMatch = find(item.children, target)
-				if (childMatch) return item.name + ' / ' + childMatch
+	const target = code.split('/').pop()
+	const find = (nodes, t) => {
+		for (let n of nodes) {
+			if (n.code === t) return n.name
+			if (n.children) {
+				const res = find(n.children, t)
+				if (res) return n.name + ' / ' + res
 			}
 		}
 		return null
 	}
-	return find(list, targetCode) || ''
-}
-
-const formData = reactive({
-	merchantId: '', shopName: '',
-	customerId: '', customerName: '',
-	petId: '', petName: '',
-	packageName: '',
-	transportMode: 'round_trip',
-	pickArea: '', pickAddress: '', pickEndArea: '', pickEndAddress: '', pickContact: '', pickPhone: '', pickTime: '',
-	sendStartArea: '', sendStartAddress: '', sendArea: '', sendAddress: '', sendContact: '', sendPhone: '', sendTime: '',
-	serviceArea: '', serviceAddress: '',
-	feedTimes: [{ start: '', end: '' }],
-	otherNote: '',
-	quoteAmount: ''
-})
-
-const fetchShops = () => {
-	// @Author: Antigravity
-	const query = { pageNum: 1, pageSize: 50 }
-	if (serviceInfo.value && serviceInfo.value.id) {
-		query.serviceId = serviceInfo.value.id
-	}
-	listStoreOnOrder(query).then(res => {
-		shopList.value = res.rows || []
-	})
+	return find(list, target) || ''
 }
 
-const fetchUsers = () => {
-	listCustomerOnOrder({ pageNum: 1, pageSize: 20, content: userSearchKey.value }).then(res => {
-		userList.value = res.rows || []
-	})
-}
-
-const fetchPets = (userId) => {
-	listPetByUser(userId).then(res => {
-		// @Author: Antigravity
-		// 移动端 request.js 自动解构 data,res 即为宠物列表数组
-		petList.value = Array.isArray(res) ? res : (res.rows || [])
-	})
-}
-
-const onShopSelect = (shop) => {
-	// @Author: Antigravity
-	selectedShop.value = shop
-	formData.merchantId = shop.id
-	formData.shopName = shop.name
-	showShopPicker.value = false
-
-}
+const fetchShops = () => listStoreOnOrder({ pageNum: 1, pageSize: 50, serviceId: serviceInfo.value?.id }).then(res => { shopList.value = res.rows || [] })
+const fetchUsers = () => listCustomerOnOrder({ pageNum: 1, pageSize: 20, content: userSearchKey.value }).then(res => { userList.value = res.rows || [] })
+const fetchPets = (uid) => listPetByUser(uid).then(res => { petList.value = Array.isArray(res) ? res : (res.rows || []) })
+const fetchRegionTree = () => listRegionTree().then(res => { regionTree.value = res || [] })
 
+const onShopSelect = (shop) => { selectedShop.value = shop; showShopSelect.value = false }
 const onUserSelect = (user) => {
-	// @Author: Antigravity
-	selectedUser.value = user
-	formData.customerId = user.id
-	formData.customerName = user.name
-
-	// 重置宠物
-	formData.petId = ''
-	formData.petName = ''
-	selectedPet.value = null
-	fetchPets(user.id)
-	showUserPopup.value = false
-}
-
-// 核心回填逻辑:watch 响应门店/用户/地区树任一变化,对齐 Web 端 watch([store, user]) @Author: Antigravity
-watch(
-	[selectedShop, selectedUser, regionTree],
-	([shop, user, tree]) => {
-		// 门店区域码与地址 (areaCode 为逗号分隔) @Author: Antigravity
-		const storeArea = shop?.areaCode || ''
-		const storeAddr = shop?.address || ''
-		const storeLeaf = storeArea.includes(',') ? storeArea.split(',').pop() : storeArea
-
-		// 用户区域码与地址 (regionCode 为斜杠分隔) @Author: Antigravity
-		const userArea = user?.regionCode || ''
-		const userAddr = user?.address || ''
-		const userPhone = user?.phoneNumber || user?.phone || ''
-		const userLeaf = userArea.includes('/') ? userArea.split('/').pop() : userArea
-
-		// 回填接宠路线:起点=用户家,终点=门店 @Author: Antigravity
-		formData.pickArea = userLeaf
-		formData.pickAddress = userAddr
-		formData.pickEndArea = storeLeaf
-		formData.pickEndAddress = storeAddr
-		formData.pickContact = user?.name || ''
-		formData.pickPhone = userPhone
-		pickAreaLabel.value = findRegionLabel(userArea, tree)
-		pickEndAreaLabel.value = findRegionLabel(storeArea.replace(/,/g, '/'), tree)
-
-		// 回填送宠路线:起点=门店,终点=用户家 @Author: Antigravity
-		formData.sendStartArea = storeLeaf
-		formData.sendStartAddress = storeAddr
-		formData.sendArea = userLeaf
-		formData.sendAddress = userAddr
-		formData.sendContact = user?.name || ''
-		formData.sendPhone = userPhone
-		sendStartAreaLabel.value = findRegionLabel(storeArea.replace(/,/g, '/'), tree)
-		sendAreaLabel.value = findRegionLabel(userArea, tree)
-
-		// 回填上门服务地址 @Author: Antigravity
-		formData.serviceArea = userLeaf
-		formData.serviceAddress = userAddr
-		serviceAreaLabel.value = findRegionLabel(userArea, tree)
-	},
-	{ deep: true }
-)
-
-const openPetPicker = () => {
-	if (!formData.customerId) {
-		uni.showToast({ title: '请先选择宠主', icon: 'none' })
-		return
-	}
-	showPetPopup.value = true
-}
-
-const onPetSelect = (pet) => {
-	// @Author: Antigravity
-	selectedPet.value = pet
-	formData.petId = pet.id
-	formData.petName = pet.name
-	showPetPopup.value = false
-}
-
-const totalFulfillmentCommission = computed(() => {
-	if (formData.quoteAmount && !isNaN(parseFloat(formData.quoteAmount))) return parseFloat(formData.quoteAmount).toFixed(2)
-	return '0.00'
-})
-
-const transportModes = [
-	{ label: '往返接送', value: 'round_trip' },
-	{ label: '单程接', value: 'pick_up' },
-	{ label: '单程送', value: 'return_home' }
-]
-
-const addFeedTime = () => { formData.feedTimes.push({ start: '', end: '' }) }
-const removeFeedTime = (index) => { formData.feedTimes.splice(index, 1) }
+	selectedUser.value = user; formData.customerId = user.id; 
+	formData.petId = ''; formData.petName = ''; petList.value = []; fetchPets(user.id)
+	showUserSelect.value = false
+}
+const openPetPicker = () => { if (!formData.customerId) return uni.showToast({ title: '先选择宠主', icon: 'none' }); showPetPopup.value = true }
+const petOptions = computed(() => petList.value.map(p => ({ ...p, _label: `${p.name} (${p.breed || '未知'})` })))
+const onPetSelect = (pet) => { formData.petId = pet.id; formData.petName = pet.name; showPetPopup.value = false }
+
+const currentServiceName = computed(() => serviceInfo.value?.name || (activeService.value === 'transport' ? '宠物接送' : '上门喂遛'))
+const serviceIconClass = computed(() => activeService.value)
+const serviceDesc = computed(() => serviceInfo.value?.remark || '专人专项 · 贴心呵护')
+const transportModes = [{ label: '往返', value: 'round_trip' }, { label: '单程接', value: 'pick_up' }, { label: '单程送', value: 'return_home' }]
+const addFeedTime = () => formData.feedTimes.push({ start: '', end: '' })
+const removeFeedTime = (idx) => formData.feedTimes.splice(idx, 1)
+const totalFulfillmentCommission = computed(() => formData.quoteAmount ? parseFloat(formData.quoteAmount).toFixed(2) : '0.00')
 
 const onSubmit = async () => {
-	// @Author: Antigravity
-	if (!formData.merchantId) { uni.showToast({ title: '请选择门店', icon: 'none' }); return }
-	if (!formData.customerId) { uni.showToast({ title: '请选择宠主', icon: 'none' }); return }
-	if (!formData.petId) { uni.showToast({ title: '请选择宠物', icon: 'none' }); return }
-	if (!formData.quoteAmount) { uni.showToast({ title: '请输入报价金额', icon: 'none' }); return }
-
+	if (!formData.merchantId || !formData.customerId || !formData.petId || !formData.quoteAmount) return uni.showToast({ title: '请完善红星必填项', icon: 'none' })
 	uni.showLoading({ title: '提交中...', mask: true })
-
 	try {
 		const subOrders = []
 		const baseMode = serviceInfo.value?.mode || 0
-		const defaultContact = selectedUser.value?.name || ''
-		const defaultPhone = selectedUser.value?.phone || selectedUser.value?.phoneNumber || ''
-
+		const defC = selectedUser.value?.name; const defP = selectedUser.value?.phone || selectedUser.value?.phoneNumber
 		if (activeService.value === 'transport') {
-			// 接送逻辑:使用表单中回填/编辑后的起终点地址 @Author: Antigravity
-			if (formData.transportMode === 'round_trip' || formData.transportMode === 'pick_up') {
-				subOrders.push({
-					mode: baseMode,
-					type: formData.transportMode === 'round_trip' ? 0 : 2,
-					contact: formData.pickContact || defaultContact,
-					contactPhoneNumber: formData.pickPhone || defaultPhone,
-					serviceTime: formData.pickTime || '',
-					endServiceTime: formData.pickTime || '',
-					fromCode: formData.pickArea || '',
-					fromAddress: formData.pickAddress || '',
-					toCode: formData.pickEndArea || '',
-					toAddress: formData.pickEndAddress || ''
-				})
-			}
-			if (formData.transportMode === 'round_trip' || formData.transportMode === 'return_home') {
-				subOrders.push({
-					mode: baseMode,
-					type: formData.transportMode === 'round_trip' ? 1 : 3,
-					contact: formData.sendContact || defaultContact,
-					contactPhoneNumber: formData.sendPhone || defaultPhone,
-					serviceTime: formData.sendTime || '',
-					endServiceTime: formData.sendTime || '',
-					fromCode: formData.sendStartArea || '',
-					fromAddress: formData.sendStartAddress || '',
-					toCode: formData.sendArea || '',
-					toAddress: formData.sendAddress || ''
-				})
-			}
+			if (formData.transportMode !== 'return_home') subOrders.push({ mode: baseMode, type: formData.transportMode === 'round_trip' ? 0 : 2, contact: formData.pickContact || defC, contactPhoneNumber: formData.pickPhone || defP, serviceTime: formData.pickTime, endServiceTime: formData.pickTime, fromCode: formData.pickArea, fromAddress: formData.pickAddress, toCode: formData.pickEndArea, toAddress: formData.pickEndAddress })
+			if (formData.transportMode !== 'pick_up') subOrders.push({ mode: baseMode, type: formData.transportMode === 'round_trip' ? 1 : 3, contact: formData.sendContact || defC, contactPhoneNumber: formData.sendPhone || defP, serviceTime: formData.sendTime, endServiceTime: formData.sendTime, fromCode: formData.sendStartArea, fromAddress: formData.sendStartAddress, toCode: formData.sendArea, toAddress: formData.sendAddress })
 		} else {
-			// 上门喂遛或洗护逻辑,补全 fromCode/toCode/toAddress @Author: Antigravity
-			formData.feedTimes.forEach(time => {
-				subOrders.push({
-					mode: baseMode,
-					contact: defaultContact,
-					contactPhoneNumber: defaultPhone,
-					serviceTime: time.start,
-					endServiceTime: time.end || time.start,
-					fromCode: formData.serviceArea || '',
-					fromAddress: formData.serviceAddress,
-					toCode: formData.serviceArea || '',
-					toAddress: formData.serviceAddress
-				})
-			})
+			formData.feedTimes.forEach(t => subOrders.push({ mode: baseMode, contact: defC, contactPhoneNumber: defP, serviceTime: t.start, endServiceTime: t.end || t.start, fromCode: formData.serviceArea, fromAddress: formData.serviceAddress, toCode: formData.serviceArea, toAddress: formData.serviceAddress }))
 		}
-
-		const payload = {
-			store: formData.merchantId,
-			storeSite: selectedShop.value?.site,
-			customer: formData.customerId,
-			pet: formData.petId,
-			groupPurchasePackageName: formData.packageName || '',
-			service: serviceInfo.value?.id,
-			orderCommission: Math.round(Number(formData.quoteAmount) * 100),
-			remark: formData.otherNote,
-			tenantId: selectedShop.value?.tenantId,
-			subOrders: subOrders
-		}
-
+		const payload = { store: formData.merchantId, storeSite: selectedShop.value?.site, customer: formData.customerId, pet: formData.petId, groupPurchasePackageName: formData.packageName, service: serviceInfo.value?.id, orderCommission: Math.round(Number(formData.quoteAmount) * 100), remark: formData.otherNote, tenantId: selectedShop.value?.tenantId, subOrders }
 		await createOrder(payload)
-		uni.hideLoading()
-		uni.showToast({ title: '下单成功', icon: 'success' })
-		setTimeout(() => {
-			uni.reLaunch({ url: '/pages/order/list/index' })
-		}, 1500)
-	} catch (error) {
-		uni.hideLoading()
-		console.error('下单失败:', error)
-	}
+		uni.showToast({ title: '成功', icon: 'success' })
+		setTimeout(() => uni.reLaunch({ url: '/pages/order/list/index' }), 1000)
+	} catch (e) { } finally { uni.hideLoading() }
 }
 </script>
 
 <style lang="scss" scoped>
-.order-apply-page {
-	background: #f7f8fa;
-	min-height: 100vh;
-	padding-bottom: 200rpx;
-}
-
-.apply-content {
-	padding: 0 32rpx;
-}
-
-.section-title {
-	display: flex;
-	align-items: center;
-	font-size: 30rpx;
-	font-weight: bold;
-	color: #333;
-	margin: 32rpx 0 20rpx;
-}
-
-.section-title::before {
-	content: '';
-	width: 8rpx;
-	height: 28rpx;
-	background: #f7ca3e;
-	margin-right: 16rpx;
-	border-radius: 4rpx;
-}
-
-.card {
-	background: #fff;
-	border-radius: 24rpx;
-	padding: 24rpx;
-	margin-bottom: 24rpx;
-}
-
-.service-type-display {
-	display: flex;
-	align-items: center;
-	gap: 24rpx;
-}
-
-.service-icon-box {
-	width: 88rpx;
-	height: 88rpx;
-	border-radius: 20rpx;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-}
-
-.service-icon-box.transport {
-	background: linear-gradient(135deg, #64b5f6, #2196f3);
-}
-
-.service-icon-box.feed {
-	background: linear-gradient(135deg, #ffb74d, #ff9800);
-}
-
-.service-icon-box.wash {
-	background: linear-gradient(135deg, #81c784, #4caf50);
-}
+/* 统一页面字体栈 @Author: Antigravity */
+.order-apply-page { 
+	background: #f7f8fa; 
+	min-height: 100vh; 
+	padding-bottom: 220rpx; 
+	font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'STHeitiSTXihei', 'Microsoft YaHei', Arial, sans-serif;
+}
+.apply-content { padding: 0 28rpx; }
+.section-title { display: flex; align-items: center; font-size: 28rpx; font-weight: bold; color: #333; margin: 32rpx 0 20rpx; &::before { content: ''; width: 8rpx; height: 26rpx; background: #f7ca3e; margin-right: 16rpx; border-radius: 4rpx; } }
+.card { background: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 24rpx; }
+
+.service-type-display { display: flex; align-items: center; gap: 24rpx; }
+.service-icon-box { width: 88rpx; height: 88rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
+.service-icon-box.transport { background: linear-gradient(135deg, #64b5f6, #2196f3); }
+.service-icon-box.feed { background: linear-gradient(135deg, #ffb74d, #ff9800); }
+.service-icon-box.wash { background: linear-gradient(135deg, #81c784, #4caf50); }
 
 .main-name {
 	display: block;
@@ -662,482 +527,70 @@ const onSubmit = async () => {
 	margin-top: 4rpx;
 }
 
-.field-row {
-	display: flex;
-	align-items: center;
-	padding: 24rpx 0;
-	border-bottom: 1rpx solid #f5f5f5;
-}
-
-.field-row:last-child {
-	border-bottom: none;
-}
-
-.field-label {
-	width: 160rpx;
-	font-size: 28rpx;
-	color: #333;
-	flex-shrink: 0;
-}
-
-.field-value {
-	flex: 1;
-	font-size: 28rpx;
-	color: #333;
-	text-align: right;
-}
-
-.field-value.placeholder {
-	color: #ccc;
-}
-
-.field-value-wrap {
-	flex: 1;
-	display: flex;
-	align-items: center;
-	justify-content: flex-end;
-	gap: 12rpx;
-}
-
-.selected-name {
-	font-size: 28rpx;
-	font-weight: bold;
-	color: #333;
-}
-
-.selected-phone {
-	font-size: 24rpx;
-	color: #666;
-}
-
-.placeholder {
-	color: #ccc;
-	font-size: 28rpx;
-}
-
-.field-input {
-	flex: 1;
-	font-size: 28rpx;
-	color: #333;
-	text-align: right;
-}
-
-.unit-text {
-	font-size: 28rpx;
-	color: #999;
-	margin-left: 8rpx;
-}
-
-.form-item-label {
-	display: block;
-	font-size: 28rpx;
-	color: #333;
-	margin: 24rpx 0 16rpx;
-	font-weight: 500;
-}
-
-.mode-select {
-	display: flex;
-	gap: 16rpx;
-	margin-bottom: 32rpx;
-}
-
-.mode-btn {
-	flex: 1;
-	height: 64rpx;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	border: 1rpx solid #ddd;
-	border-radius: 12rpx;
-	font-size: 24rpx;
-	color: #666;
-}
-
-.mode-btn.active {
-	background: #fef8e5;
-	border-color: #f7ca3e;
-	color: #f7ca3e;
-	font-weight: bold;
-}
-
-.route-box {
-	display: flex;
-	gap: 24rpx;
-	margin-bottom: 24rpx;
-}
-
-.route-icon {
-	width: 48rpx;
-	height: 48rpx;
-	border-radius: 8rpx;
-	color: #fff;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	font-size: 24rpx;
-	font-weight: bold;
-	flex-shrink: 0;
-	margin-top: 16rpx;
-}
-
-.route-icon.pick {
-	background: #5bb7ff;
-}
-
-.route-icon.send {
-	background: #64cf5c;
-}
-
-.route-fields {
-	flex: 1;
-	display: flex;
-	flex-direction: column;
-	gap: 8rpx;
-}
-
-.route-input {
-	width: 100%;
-	height: 72rpx;
-	font-size: 26rpx;
-	color: #333;
-	border-bottom: 1rpx solid #f0f0f0;
-	padding: 0 8rpx;
-}
-
-.contact-row {
-	display: flex;
-	gap: 16rpx;
-}
-
-.route-input.half {
-	flex: 1;
-}
-
-/* 起点/终点标签 @Author: Antigravity */
-.addr-label {
-	font-size: 24rpx;
-	color: #606266;
-	font-weight: bold;
-	margin-top: 16rpx;
-	margin-bottom: 4rpx;
-}
-
-.address-title {
-	display: block;
-	font-size: 28rpx;
-	color: #333;
-	font-weight: 500;
-	margin: 24rpx 0 16rpx;
-}
-
-.full-input {
-	width: 100%;
-	height: 72rpx;
-	font-size: 26rpx;
-	color: #333;
-	border-bottom: 1rpx solid #f0f0f0;
-	padding: 0 8rpx;
-	margin-bottom: 8rpx;
-}
-
-.premium-cascader-display {
-	display: flex;
-	align-items: center;
-	padding: 16rpx 8rpx;
-	border-bottom: 1rpx solid #f0f0f0;
-
-	.display-text {
-		flex: 1;
-		font-size: 26rpx;
-		color: #333;
-
-		&.placeholder {
-			color: #ccc;
-		}
-	}
-}
-
-.premium-full-picker {
-	display: flex;
-	align-items: center;
-	padding: 24rpx 12rpx;
-	background: #f9f9f9;
-	border-radius: 12rpx;
-	margin-bottom: 16rpx;
-
-	.display-text {
-		flex: 1;
-		font-size: 28rpx;
-		color: #333;
-
-		&.placeholder {
-			color: #ccc;
-		}
-	}
-}
-
-.booking-section {
-	margin-top: 24rpx;
-}
-
-.time-picker-row {
-	border-bottom: 1rpx solid #f0f0f0;
-	padding: 4rpx 0;
-}
-
-.flex-picker-box {
-	flex: 1;
-	background: #fcfcfc;
-	border-radius: 12rpx;
-	border: 1rpx solid #f0f0f0;
-	height: 64rpx;
-	display: flex;
-	align-items: center;
-
-	.inline-picker {
-		width: 100%;
-		height: 100%;
-		display: flex;
-		align-items: center;
-	}
-}
-
-.booking-header {
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
-	margin-bottom: 16rpx;
-}
-
-.booking-header .label {
-	font-size: 28rpx;
-	color: #333;
-	font-weight: 500;
-}
-
-.count-tag {
-	font-size: 22rpx;
-	color: #ff9500;
-	background: #fff3e0;
-	padding: 4rpx 16rpx;
-	border-radius: 8rpx;
-}
-
-.time-item-row {
-	display: flex;
-	align-items: center;
-	gap: 12rpx;
-	margin-bottom: 16rpx;
-}
-
-.index {
-	font-size: 26rpx;
-	color: #999;
-	width: 40rpx;
-}
-
-.time-input {
-	flex: 1;
-	height: 64rpx;
-	font-size: 24rpx;
-	border: 1rpx solid #f0f0f0;
-	border-radius: 12rpx;
-	padding: 0 16rpx;
-}
-
-.to-line {
-	color: #999;
-}
-
-.action-buttons {
-	display: flex;
-	gap: 8rpx;
-}
-
-.circle-btn {
-	width: 48rpx;
-	height: 48rpx;
-	border-radius: 50%;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	font-size: 28rpx;
-	font-weight: bold;
-}
-
-.circle-btn.add {
-	background: #e3f2fd;
-	color: #2196f3;
-}
-
-.circle-btn.remove {
-	background: #fde2e2;
-	color: #f56c6c;
-}
-
-.remarks-title {
-	display: block;
-	font-size: 28rpx;
-	color: #333;
-	font-weight: 500;
-	margin: 24rpx 0 16rpx;
-}
-
-.remarks-textarea {
-	width: 100%;
-	height: 160rpx;
-	font-size: 26rpx;
-	color: #333;
-	background: #f9f9f9;
-	border-radius: 16rpx;
-	padding: 20rpx;
-}
-
-.quote-tips {
-	display: block;
-	font-size: 22rpx;
-	color: #999;
-	margin-top: 12rpx;
-}
-
-.footer-bar {
-	position: fixed;
-	bottom: 0;
-	left: 0;
-	right: 0;
-	background: #fff;
-	padding: 20rpx 32rpx;
-	padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
-	display: flex;
-	align-items: center;
-	box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
-	z-index: 10;
-}
-
-.quotation-fulfillmentCommission-box {
-	flex: 1;
-	display: flex;
-	align-items: baseline;
-}
-
-.p-label {
-	font-size: 26rpx;
-	color: #333;
-}
-
-.p-symbol {
-	font-size: 28rpx;
-	color: #f44336;
-	font-weight: bold;
-	margin-left: 8rpx;
-}
-
-.p-amount {
-	font-size: 44rpx;
-	font-weight: 900;
-	color: #f44336;
-}
-
-.submit-btn {
-	width: 280rpx;
-	height: 88rpx;
-	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
-	border: none;
-	border-radius: 44rpx;
-	font-size: 30rpx;
-	font-weight: bold;
-	line-height: 88rpx;
-}
-
-.popup-mask {
-	position: fixed;
-	top: 0;
-	left: 0;
-	right: 0;
-	bottom: 0;
-	background: rgba(0, 0, 0, 0.5);
-	z-index: 999;
-	display: flex;
-	align-items: flex-end;
-}
-
-.popup-content {
-	width: 100%;
-	background: #fff;
-	border-radius: 32rpx 32rpx 0 0;
-	padding: 40rpx 32rpx;
-	max-height: 70vh;
-}
-
-.popup-title {
-	display: block;
-	font-size: 32rpx;
-	font-weight: bold;
-	color: #333;
-	margin-bottom: 24rpx;
-	text-align: center;
-}
-
-.popup-item {
-	padding: 28rpx 0;
-	border-bottom: 1rpx solid #f5f5f5;
-	font-size: 28rpx;
-	color: #333;
-}
-
-.user-popup .popup-item {
-	display: flex;
-	justify-content: space-between;
-}
-
-.user-item-name {
-	font-weight: bold;
-}
-
-.user-item-phone {
-	color: #999;
-}
-
-.popup-scroll {
-	max-height: 600rpx;
-	margin-top: 20rpx;
-}
-
-.search-bar {
-	display: flex;
-	align-items: center;
-	background: #f5f5f5;
-	border-radius: 40rpx;
-	padding: 0 30rpx;
-	margin-bottom: 20rpx;
-	height: 72rpx;
-}
-
-.search-input {
-	flex: 1;
-	font-size: 26rpx;
-	color: #333;
-}
-
-.pet-item-cell {
-	display: flex;
-	align-items: center;
-	gap: 20rpx;
-}
-
-.pet-avatar-mini {
-	width: 60rpx;
-	height: 60rpx;
-	border-radius: 10rpx;
-	background: #f0f0f0;
-}
-
-.empty-tips {
-	text-align: center;
-	color: #999;
-	font-size: 24rpx;
-	padding: 40rpx 0;
-}
+/* CSS 手绘图标 @Author: Antigravity */
+.pure-css-icon { width: 40rpx; height: 40rpx; border: 4rpx solid #fff; border-radius: 8rpx; position: relative; &::after { content: ''; position: absolute; top: 10rpx; left: 10rpx; width: 12rpx; height: 12rpx; background: #fff; border-radius: 50%; } }
+
+.field-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 2rpx solid #f5f5f5; height: 44rpx; &:last-child { border-bottom: none; } }
+.field-label { width: 180rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
+.require::before { content: '*'; color: #f56c6c; margin-right: 4rpx; }
+.field-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; margin-right: 16rpx; }
+.field-value.placeholder { color: #ccc; }
+.field-value-wrap { flex: 1; display: flex; flex-direction: column; align-items: flex-end; margin-right: 16rpx; .selected-name { font-size: 28rpx; font-weight: bold; color: #333; } .selected-phone { font-size: 22rpx; color: #999; } }
+.placeholder { color: #ccc; font-size: 28rpx; }
+
+.mode-select { display: flex; gap: 16rpx; margin: 20rpx 0 32rpx; }
+.mode-btn { flex: 1; height: 60rpx; display: flex; align-items: center; justify-content: center; border: 2rpx solid #f0f0f0; border-radius: 30rpx; font-size: 24rpx; color: #666; &.active { background: #fef8e5; border-color: #f7ca3e; color: #f7ca3e; font-weight: bold; } }
+
+.route-box { display: flex; gap: 20rpx; margin-bottom: 30rpx; }
+.route-icon { width: 44rpx; height: 44rpx; border-radius: 8rpx; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 22rpx; font-weight: bold; flex-shrink: 0; margin-top: 10rpx; }
+.route-icon.pick { background: #5bb7ff; }
+.route-icon.send { background: #64cf5c; }
+.route-icon.service { background: #ff9500; }
+.route-fields { flex: 1; display: flex; flex-direction: column; gap: 6rpx; }
+.addr-label { font-size: 22rpx; color: #999; margin-top: 10rpx; }
+.route-picker-trigger { height: 64rpx; border-bottom: 2rpx solid #f5f5f5; display: flex; align-items: center; justify-content: space-between; .display-text { font-size: 26rpx; color: #333; &.placeholder { color: #ccc; } } }
+.route-input { height: 72rpx; font-size: 26rpx; border-bottom: 2rpx solid #f5f5f5; &.half { flex: 1; } }
+.contact-row { display: flex; gap: 16rpx; }
+.route-time-trigger { height: 72rpx; background: #f9f9f9; border-radius: 12rpx; display: flex; align-items: center; padding: 0 20rpx; font-size: 26rpx; color: #333; margin-top: 10rpx; .placeholder { color: #ccc; } }
+
+.address-title, .form-item-label, .booking-header .label, .remarks-title { display: block; font-size: 26rpx; color: #666; margin: 20rpx 0 10rpx; }
+.booking-section { margin-top: 24rpx; }
+.count-tag { font-size: 20rpx; color: #ff9500; background: #fff3e0; padding: 4rpx 12rpx; border-radius: 6rpx; }
+.time-item-row { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; }
+.index { font-size: 24rpx; color: #999; width: 30rpx; }
+.flex-time-box { flex: 1; height: 64rpx; background: #fcfcfc; border: 2rpx solid #eee; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; .time-text { font-size: 24rpx; color: #333; &.placeholder { color: #ccc; } } }
+.action-buttons { display: flex; gap: 12rpx; margin-left: 8rpx; }
+.circle-btn { width: 44rpx; height: 44rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: bold; &.add { background: #e3f2fd; color: #2196f3; } &.remove { background: #fde2e2; color: #f56c6c; } }
+.remarks-textarea { width: 100%; height: 140rpx; font-size: 26rpx; background: #f9f9f9; border-radius: 16rpx; padding: 16rpx; box-sizing: border-box; }
+
+.quote-input { flex: 1; font-size: 36rpx; color: #f44336; font-weight: bold; text-align: right; }
+.unit-text { font-size: 28rpx; color: #333; margin-left: 8rpx; }
+.quote-tips { display: block; font-size: 22rpx; color: #999; margin-top: 20rpx; }
+
+.footer-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 24rpx 32rpx; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05); z-index: 100; }
+.quotation-box { display: flex; align-items: baseline; .p-label { font-size: 24rpx; color: #333; } .p-symbol { font-size: 28rpx; color: #f44336; font-weight: bold; margin-left: 8rpx; } .p-amount { font-size: 40rpx; font-weight: 900; color: #f44336; } }
+.submit-btn { width: 280rpx; height: 84rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #fff; border-radius: 42rpx; font-size: 28rpx; font-weight: bold; line-height: 84rpx; &::after { border: none; } }
+
+/* CSS Common Modal UI @Author: Antigravity */
+.center-modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4rpx); }
+.center-modal-content { width: 620rpx; background: #fff; border-radius: 32rpx; display: flex; flex-direction: column; overflow: hidden; animation: popIn 0.3s ease-out; }
+@keyframes popIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }
+
+.modal-header { padding: 32rpx; border-bottom: 2rpx solid #f2f2f2; position: relative; text-align: center; .modal-title { font-size: 30rpx; font-weight: bold; color: #333; } }
+.close-btn { position: absolute; right: 24rpx; top: 24rpx; width: 44rpx; height: 44rpx; &::before, &::after { content: ''; position: absolute; top: 20rpx; left: 8rpx; width: 28rpx; height: 4rpx; background: #999; transform: rotate(45deg); border-radius: 4rpx; } &::after { transform: rotate(-45deg); } }
+
+.search-box { display: flex; align-items: center; background: #f5f5f5; border-radius: 36rpx; padding: 0 24rpx; height: 72rpx; margin: 0 4rpx; .search-icon { width: 20rpx; height: 20rpx; border: 3rpx solid #999; border-radius: 50%; margin-right: 12rpx; position: relative; &::after { content: ''; width: 10rpx; height: 3rpx; background: #999; position: absolute; bottom: -4rpx; right: -4rpx; transform: rotate(45deg); } } .search-input { flex: 1; font-size: 26rpx; } .search-btn { font-size: 26rpx; color: #ff9500; font-weight: bold; margin-left: 20rpx; } }
+.modal-list-scroll { flex: 1; max-height: 55vh; padding: 0 32rpx; }
+.list-item { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 0; border-bottom: 2rpx solid #f9f9f9; .user-info { display: flex; flex-direction: column; .name { font-size: 28rpx; font-weight: bold; color: #333; } .phone { font-size: 22rpx; color: #999; margin-top: 4rpx; } } }
+.checkmark { width: 12rpx; height: 22rpx; border-right: 4rpx solid #ff9500; border-bottom: 4rpx solid #ff9500; transform: rotate(45deg); }
+
+.cascade-indicator { display: flex; padding: 20rpx 32rpx; background: #fafafa; border-bottom: 2rpx solid #f2f2f2; flex-wrap: wrap; gap: 12rpx; .path-node { font-size: 24rpx; color: #666; &.active { color: #ff9500; font-weight: bold; } } }
+
+.datetime-picker-body { height: 400rpx; padding: 20rpx 0; }
+.picker-view { width: 100%; height: 100%; }
+.picker-item { line-height: 80rpx; text-align: center; font-size: 28rpx; }
+.modal-footer { display: flex; border-top: 2rpx solid #f2f2f2; .modal-cancel, .modal-confirm { flex: 1; height: 96rpx; line-height: 96rpx; text-align: center; font-size: 28rpx; } .modal-confirm { color: #ff9500; font-weight: bold; border-left: 2rpx solid #f2f2f2; } }
+
+.right-arrow { width: 12rpx; height: 12rpx; border-right: 3rpx solid #ccc; border-top: 3rpx solid #ccc; transform: rotate(45deg); flex-shrink: 0; }
+.empty-tip { padding: 80rpx 0; text-align: center; color: #ccc; font-size: 24rpx; }
 </style>

+ 46 - 45
pages/order/detail/index.vue

@@ -44,7 +44,10 @@
 			<view class="info-card pet-card">
 				<text class="card-label">宠物档案</text>
 				<view class="pet-header">
-					<view class="pet-avatar"><text>{{ (order.petName || '宠')[0] }}</text></view>
+					<view class="pet-avatar">
+						<image v-if="order.petAvatarUrl" :src="order.petAvatarUrl" mode="aspectFill" class="avatar-img"></image>
+						<text v-else>{{ (order.petName || '宠')[0] }}</text>
+					</view>
 					<view class="pet-basic">
 						<text class="pet-name">
 							{{ order.petName || '-' }} 
@@ -78,7 +81,8 @@
 				<text class="card-label">用户信息</text>
 				<view class="user-header">
 					<view class="user-avatar">
-						<uni-icons type="person" size="26" color="#aaa"></uni-icons>
+						<image v-if="order.userAvatarUrl" :src="order.userAvatarUrl" mode="aspectFill" class="avatar-img"></image>
+						<uni-icons v-else type="person" size="26" color="#aaa"></uni-icons>
 					</view>
 					<view class="user-basic">
 						<text class="user-name-text">{{ order.userName }}</text>
@@ -157,7 +161,8 @@
 				<view class="assignee-card" v-else>
 					<view class="assignee-header">
 						<view class="assignee-avatar">
-							<uni-icons type="person" size="30" color="#aaa"></uni-icons>
+							<image v-if="order.assigneeAvatarUrl" :src="order.assigneeAvatarUrl" mode="aspectFill" class="avatar-img"></image>
+							<uni-icons v-else type="person" size="30" color="#aaa"></uni-icons>
 						</view>
 						<view class="assignee-info">
 							<text class="assignee-name">{{ order.assigneeName }}</text>
@@ -316,7 +321,10 @@ const order = reactive({
 	fulfiller: '',
 	fulfillerName: '',
 	assigneePhone: '-',
-	assigneeZone: '-'
+	assigneeZone: '-',
+	petAvatarUrl: '',
+	userAvatarUrl: '',
+	assigneeAvatarUrl: ''
 })
 
 const orderLogsData = ref([])
@@ -377,14 +385,17 @@ const loadPetInfo = async (petId) => {
 	try {
 		const res = await getPet(petId)
 		if (res) {
-			order.petName = res.name || '-'
-			order.petBreed = res.breed || '-'
-			order.petAge = res.age ? `${res.age}岁` : '-'
-			order.petWeight = res.weight ? `${res.weight}kg` : '-'
-			order.petGender = res.gender ?? '-'
-			order.petVaccine = res.vaccineStatus || '-'
-			order.petCharacter = res.personality || '-'
-			order.petHealth = res.healthStatus || '-'
+			const data = res.data || res
+			console.log('宠物详情 res:', data)
+			order.petName = data.name || '-'
+			order.petBreed = data.breed || '-'
+			order.petAge = data.age ? `${data.age}岁` : '-'
+			order.petWeight = data.weight ? `${data.weight}kg` : '-'
+			order.petGender = data.gender ?? '-'
+			order.petVaccine = data.vaccineStatus || '-'
+			order.petCharacter = data.personality || '-'
+			order.petHealth = data.healthStatus || '-'
+			order.petAvatarUrl = data.avatarUrl || ''
 		}
 	} catch (error) {
 		console.error('加载宠物信息失败:', error)
@@ -395,9 +406,12 @@ const loadCustomerInfo = async (customerId) => {
 	try {
 		const res = await getCustomer(customerId)
 		if (res) {
-			order.userName = res.name || '-'
-			order.userPhone = res.phone || '-'
-			order.address = res.address || '-'
+			const data = res.data || res
+			console.log('客户详情 res:', data)
+			order.userName = data.name || '-'
+			order.userPhone = data.phone || '-'
+			order.address = data.address || '-'
+			order.userAvatarUrl = data.avatarUrl || ''
 		}
 	} catch (error) {
 		console.error('加载客户信息失败:', error)
@@ -408,9 +422,12 @@ const loadFulfillerInfo = async (fulfillerId) => {
 	try {
 		const res = await getFulfiller(fulfillerId)
 		if (res) {
-			order.assigneeName = res.name || order.assigneeName
-			order.assigneePhone = res.phone || '-'
-			order.assigneeZone = res.stationName || '-'
+			const data = res.data || res
+			console.log('履约者详情 res:', data)
+			order.assigneeName = data.name || order.assigneeName
+			order.assigneePhone = data.phone || '-'
+			order.assigneeZone = data.stationName || '-'
+			order.assigneeAvatarUrl = data.avatarUrl || data.avatar || ''
 		}
 	} catch (error) {
 		console.error('加载履约者信息失败:', error)
@@ -500,7 +517,7 @@ const progressSteps = computed(() => {
 		return log ? (log.createTime || log.time) : ''
 	}
 	const fulfillerCustomLogTime = () => {
-		const log = (fulfillerLogsData.value || []).reverse().find(l => {
+		const log = [...(fulfillerLogsData.value || [])].reverse().find(l => {
 			const s = parseInt(l.step)
 			return s >= 1 && s <= 98
 		})
@@ -830,17 +847,10 @@ const confirmCancelOrder = async () => {
 	margin-bottom: 20rpx;
 }
 
-.pet-avatar {
-	width: 72rpx;
-	height: 72rpx;
-	border-radius: 20rpx;
-	background: #e3f2fd;
-	color: #2196f3;
-	font-size: 36rpx;
-	font-weight: bold;
-	display: flex;
-	align-items: center;
-	justify-content: center;
+.pet-avatar, .user-avatar, .assignee-avatar {
+	width: 80rpx; height: 80rpx; border-radius: 50%; background: #f0f2f5;
+	display: flex; align-items: center; justify-content: center; overflow: hidden; flex-shrink: 0;
+	.avatar-img { width: 100%; height: 100%; }
 }
 
 .pet-basic {
@@ -918,16 +928,6 @@ const confirmCancelOrder = async () => {
 	margin-bottom: 20rpx;
 }
 
-.user-avatar {
-	width: 80rpx;
-	height: 80rpx;
-	border-radius: 50%;
-	background: #f5f5f5;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-}
-
 .user-name-text {
 	display: block;
 	font-size: 28rpx;
@@ -970,7 +970,7 @@ const confirmCancelOrder = async () => {
 
 .tab-nav {
 	display: flex;
-	border-bottom: 1rpx solid #f0f0f0;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .tab-nav-item {
@@ -1054,7 +1054,7 @@ const confirmCancelOrder = async () => {
 .task-card {
 	padding: 24rpx;
 	background: #fff;
-	border: 1rpx solid #f0f0f0;
+	border: 2rpx solid #EEEEEE;
 	border-radius: 12rpx;
 	margin-bottom: 20rpx;
 	box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.02);
@@ -1283,7 +1283,8 @@ const confirmCancelOrder = async () => {
 	color: #f44336;
 	background: transparent;
 	border-radius: 48rpx;
-	line-height: 96rpx;
+	line-height: 92rpx;
+	&::after { border: none; }
 }
 
 /* 骨架屏动画与样式 */
@@ -1462,7 +1463,7 @@ const confirmCancelOrder = async () => {
 }
 .modal-footer {
 	display: flex;
-	border-top: 1rpx solid #eee;
+	border-top: 2rpx solid #EEEEEE;
 }
 .modal-btn {
 	flex: 1;
@@ -1474,7 +1475,7 @@ const confirmCancelOrder = async () => {
 }
 .btn-cancel {
 	color: #666;
-	border-right: 1rpx solid #eee;
+	border-right: 2rpx solid #EEEEEE;
 }
 .btn-confirm {
 	color: #2196f3;

+ 50 - 41
pages/order/list/index.vue

@@ -72,11 +72,9 @@
 							<text>预约: {{ order.bookTime }}</text>
 						</view>
 					</view>
-				</view>
 
-				<!-- 履约与操作栏 -->
-				<view class="order-foot">
-					<view class="foot-left">
+					<!-- 时间与履约信息 -->
+					<view class="time-info-block">
 						<text class="create-time">下单: {{ order.createTime }}</text>
 						<view class="assign-info">
 							<text class="assign-label">履约信息:</text>
@@ -86,23 +84,26 @@
 						<text class="cancel-time" v-if="order.statusText === '已取消' && order.cancelTime">取消: {{
 							order.cancelTime }}</text>
 					</view>
+				</view>
 
+				<!-- 操作栏 (带分割线) -->
+				<view class="order-foot">
 					<view class="actions">
 						<template v-if="order.statusText === '待派单' || order.statusText === '待接单'">
 							<button size="mini" class="action-btn btn-cancel"
-								@click.stop="onCancelOrder(order)">取消</button>
+								@click.stop="onCancelOrder(order)">取消订单</button>
 							<button size="mini" class="action-btn btn-primary"
-								@click.stop="goToDetail(order)">详情</button>
+								@click.stop="goToDetail(order)">查看详情</button>
 						</template>
 						<template v-else-if="['服务中', '待商家确认', '已完成'].includes(order.statusText)">
 							<button v-if="['服务中', '已完成'].includes(order.statusText)" size="mini"
-								class="action-btn btn-cancel" @click.stop="onComplaint(order)">投诉</button>
+								class="action-btn btn-cancel" @click.stop="onComplaint(order)">投诉订单</button>
 							<button size="mini" class="action-btn btn-primary"
-								@click.stop="goToDetail(order)">详情</button>
+								@click.stop="goToDetail(order)">查看详情</button>
 						</template>
 						<template v-else>
 							<button size="mini" class="action-btn btn-primary"
-								@click.stop="goToDetail(order)">详情</button>
+								@click.stop="goToDetail(order)">查看详情</button>
 						</template>
 					</view>
 				</view>
@@ -426,7 +427,7 @@ const onComplaint = (order) => {
 
 .tabs-scroll {
 	white-space: nowrap;
-	border-bottom: 1rpx solid #f5f5f5;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .tabs-row {
@@ -435,8 +436,8 @@ const onComplaint = (order) => {
 }
 
 .tab-item {
-	padding: 24rpx 24rpx;
-	font-size: 28rpx;
+	padding: 32rpx 24rpx;
+	font-size: 30rpx;
 	color: #666;
 	position: relative;
 	flex-shrink: 0;
@@ -464,7 +465,7 @@ const onComplaint = (order) => {
 	align-items: center;
 	padding: 12rpx 16rpx;
 	background-color: #fff;
-	border-top: 1rpx solid #f9f9f9;
+	border-top: 2rpx solid #EEEEEE;
 	gap: 16rpx;
 }
 
@@ -473,9 +474,9 @@ const onComplaint = (order) => {
 	align-items: center;
 	gap: 8rpx;
 	background: #f5f5f5;
-	border-radius: 34rpx;
-	padding: 12rpx 24rpx;
-	font-size: 26rpx;
+	border-radius: 40rpx;
+	padding: 24rpx 28rpx;
+	font-size: 28rpx;
 	color: #333;
 }
 
@@ -484,19 +485,19 @@ const onComplaint = (order) => {
 	display: flex;
 	align-items: center;
 	background: #f5f5f5;
-	border-radius: 34rpx;
-	padding: 12rpx 20rpx;
+	border-radius: 40rpx;
+	padding: 24rpx 24rpx;
 	gap: 12rpx;
 }
 
 .search-input {
 	flex: 1;
-	font-size: 24rpx;
+	font-size: 26rpx;
 }
 
 .placeholder-style {
 	color: #999;
-	font-size: 24rpx;
+	font-size: 26rpx;
 }
 
 .list-container {
@@ -515,18 +516,18 @@ const onComplaint = (order) => {
 	display: flex;
 	justify-content: space-between;
 	align-items: center;
-	border-bottom: 1rpx solid #f5f5f5;
+	border-bottom: 2rpx solid #EEEEEE;
 	padding-bottom: 20rpx;
 	margin-bottom: 20rpx;
 }
 
 .order-no {
-	font-size: 26rpx;
-	color: #666;
+	font-size: 28rpx;
+	color: #333333;
 }
 
 .status-text {
-	font-size: 26rpx;
+	font-size: 28rpx;
 	font-weight: bold;
 }
 
@@ -619,18 +620,18 @@ const onComplaint = (order) => {
 }
 
 .pet-desc .bold {
-	font-size: 28rpx;
+	font-size: 30rpx;
 	font-weight: bold;
 	color: #333;
 }
 
 .pet-desc .sub {
-	font-size: 24rpx;
+	font-size: 26rpx;
 	color: #666;
 }
 
 .user-desc {
-	font-size: 26rpx;
+	font-size: 28rpx;
 	color: #333;
 }
 
@@ -643,18 +644,25 @@ const onComplaint = (order) => {
 .info-item {
 	display: flex;
 	align-items: center;
-	font-size: 24rpx;
+	font-size: 26rpx;
 	color: #666;
 	gap: 12rpx;
 }
 
+.time-info-block {
+	margin-top: 24rpx;
+	display: flex;
+	flex-direction: column;
+	gap: 12rpx;
+}
+
 .order-foot {
 	display: flex;
-	justify-content: space-between;
-	align-items: flex-end;
+	justify-content: flex-end;
+	align-items: center;
 	margin-top: 24rpx;
-	padding-top: 20rpx;
-	border-top: 1rpx solid #f5f5f5;
+	padding-top: 24rpx;
+	border-top: 2rpx solid #EEEEEE;
 }
 
 .foot-left {
@@ -665,7 +673,7 @@ const onComplaint = (order) => {
 
 .create-time,
 .cancel-time {
-	font-size: 22rpx;
+	font-size: 24rpx;
 	color: #999;
 }
 
@@ -695,20 +703,21 @@ const onComplaint = (order) => {
 }
 
 .action-btn {
-	height: 56rpx;
-	line-height: 56rpx;
-	min-width: 120rpx;
-	font-size: 24rpx;
+	height: 60rpx;
+	line-height: 60rpx;
+	min-width: 140rpx;
+	font-size: 26rpx;
 	font-weight: 600;
-	padding: 0 28rpx;
-	border-radius: 28rpx;
+	padding: 0 32rpx;
+	border-radius: 30rpx;
 	display: inline-flex;
 	align-items: center;
 	justify-content: center;
+	&::after { border: none; }
 }
 
 .btn-cancel {
-	border: 1rpx solid #ddd;
+	border: 2rpx solid #EEEEEE;
 	color: #666;
 	background: transparent;
 }
@@ -793,7 +802,7 @@ const onComplaint = (order) => {
 }
 .modal-footer {
 	display: flex;
-	border-top: 1rpx solid #eee;
+	border-top: 1rpx solid #CCCCCC;
 }
 .modal-btn {
 	flex: 1;

+ 50 - 3
pages/service/all/index.vue

@@ -47,15 +47,61 @@ import navBar from '@/components/nav-bar/index.vue'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 import { listAll as getClassifications } from '@/api/service/classification'
 import { listAll as getServices } from '@/api/service/list'
+import { listMyServices } from '@/api/system/store'
 
 const searchValue = ref('')
 const activeSidebar = ref(0)
 const allData = ref([])
+/** 当前用户可用的服务ID列表 */
+const myServiceIds = ref([])
+
+/** 前端搜索 + 权限过滤 */
+const filteredData = computed(() => {
+	let data = allData.value
+
+	// 先按权限过滤:只展示用户有权限的服务类型
+	if (myServiceIds.value.length > 0) {
+		const idSet = new Set(myServiceIds.value)
+		data = data.map(group => ({
+			...group,
+			categories: group.categories.map(cat => ({
+				...cat,
+				items: cat.items.filter(s => idSet.has(s.id))
+			})).filter(cat => cat.items.length > 0)
+		})).filter(group => group.categories.some(cat => cat.items.length > 0))
+	}
+
+	// 再按搜索关键词过滤
+	const keyword = searchValue.value.trim().toLowerCase()
+	if (!keyword) return data
+	return data.map(group => ({
+			...group,
+			categories: group.categories.map(cat => ({
+				...cat,
+				items: cat.items.filter(s => s.name.toLowerCase().includes(keyword))
+			})).filter(cat => cat.items.length > 0)
+		})).filter(group => group.categories.some(cat => cat.items.length > 0))
+})
+
 const currentCategories = computed(() => {
-	if (allData.value.length === 0 || !allData.value[activeSidebar.value]) return []
-	return allData.value[activeSidebar.value].categories
+	if (filteredData.value.length === 0 || !filteredData.value[activeSidebar.value]) return []
+	return filteredData.value[activeSidebar.value].categories
 })
 
+/** 获取当前用户可下单的服务ID列表 */
+const fetchMyServiceIds = async () => {
+	try {
+		const res = await listMyServices()
+		// 兼容多种响应格式,确保取到数组并保持字符串类型
+		const ids = res?.data ?? res?.rows ?? res ?? []
+		myServiceIds.value = Array.isArray(ids) ? ids.map(String) : []
+		console.log('我的可用服务ID:', myServiceIds.value)
+	} catch (error) {
+		console.error('获取可用服务列表失败,将展示全部服务', error)
+		myServiceIds.value = []
+	}
+}
+
 const loadData = async () => {
 	try {
 		const [classifications, services] = await Promise.all([
@@ -95,6 +141,7 @@ const loadData = async () => {
 
 onMounted(() => {
 	loadData()
+	fetchMyServiceIds()
 })
 
 const onServiceClick = (service) => {
@@ -118,7 +165,7 @@ const onServiceClick = (service) => {
 .header-search {
 	padding: 16rpx 32rpx;
 	background-color: #fff;
-	border-bottom: 1rpx solid #f2f2f2;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .search-input-wrap {

+ 2 - 2
pages/service/detail/index.vue

@@ -251,7 +251,7 @@ const goToOrderApply = () => {
 
 .tab-header {
 	display: flex;
-	border-bottom: 1rpx solid #f0f0f0;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .tab-btn {
@@ -339,7 +339,7 @@ const goToOrderApply = () => {
 	height: 92rpx;
 	font-size: 32rpx;
 	font-weight: bold;
-	color: #333;
+	color: #fff;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
 	border: none;
 	border-radius: 46rpx;

+ 2 - 2
pages/store/apply/index.vue

@@ -77,7 +77,7 @@ const onSubmit = () => {
 	display: flex;
 	align-items: center;
 	padding: 28rpx 0;
-	border-bottom: 1rpx solid #f5f5f5;
+	border-bottom: 2rpx solid #EEEEEE;
 }
 
 .form-item.column {
@@ -124,7 +124,7 @@ const onSubmit = () => {
 	width: calc(100% - 64rpx);
 	height: 96rpx;
 	background: linear-gradient(90deg, #ffd53f, #ff9500);
-	color: #333;
+	color: #fff;
 	border: none;
 	border-radius: 48rpx;
 	font-size: 32rpx;

+ 1 - 0
static/icon/cancel.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776243871705" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14367" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M358.524518 693.727598a26.991105 26.991105 0 0 1-19.099103-46.090208l309.577532-309.577532a27.014047 27.014047 0 0 1 38.198206 38.198206L377.623621 685.835596a26.979634 26.979634 0 0 1-19.099103 7.892002z m0 0" fill="#E6A23F" p-id="14368"></path><path d="M666.163463 693.727598a27.014047 27.014047 0 0 1-19.099103-7.892002L337.486828 376.372773a27.014047 27.014047 0 0 1 38.198205-38.198206l309.54312 309.462823a26.991105 26.991105 0 0 1-19.099103 46.090208z m0 0M138.385848 847.885462a26.796099 26.796099 0 0 1-20.819742-9.84206 507.944371 507.944371 0 0 1-42.54569-59.64885A27.064519 27.064519 0 0 1 121.225333 750.198999a449.132899 449.132899 0 0 0 38.083497 53.408663 27.151698 27.151698 0 0 1-3.544519 38.083497 28.103785 28.103785 0 0 1-17.378463 6.171362z m0 0" fill="#E6A23F" p-id="14369"></path><path d="M511.879418 1023.998692a508.426151 508.426151 0 0 1-295.043861-93.545456 27.002576 27.002576 0 0 1 31.10917-44.140149 455.178081 455.178081 0 0 0 263.934691 83.600158c252.521113 0 458.034343-205.51323 458.034343-458.022873S764.377588 53.913384 511.879418 53.913384 53.982726 259.49544 53.982726 512.005082a456.359587 456.359587 0 0 0 18.353492 128.77269 27.049606 27.049606 0 0 1-51.917442 15.210457A512.750692 512.750692 0 0 1 0.000516 512.005082C0.000516 229.648073 229.637118 0 512.017069 0s511.98214 229.648073 511.98214 512.005082-229.797195 511.993611-512.119791 511.99361z m0 0" fill="#E6A23F" p-id="14370"></path></svg>

+ 1 - 0
static/icon/finished.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776243832053" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12796" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M958.2 324.7c-24.3-57.5-59.2-109.2-103.5-153.6-44.4-44.4-96-79.2-153.6-103.5-59.6-25.2-122.9-38-188.1-38s-128.5 12.8-188.1 38c-57.5 24.3-109.2 59.2-153.6 103.5-44.4 44.4-79.2 96-103.5 153.6-25.2 59.6-38 122.9-38 188.1s12.8 128.5 38 188.1c24.3 57.5 59.2 109.2 103.5 153.6 44.4 44.4 96 79.2 153.6 103.5 59.6 25.2 122.9 38 188.1 38s128.5-12.8 188.1-38c57.5-24.3 109.2-59.2 153.6-103.5s79.2-96 103.5-153.6c25.2-59.6 38-122.9 38-188.1s-12.8-128.5-38-188.1zM513 924c-226.7 0-411.2-184.5-411.2-411.2S286.3 101.6 513 101.6s411.2 184.5 411.2 411.2S739.7 924 513 924z" p-id="12797" fill="#E6A23F"></path><path d="M694.9 624.2c-18.9-6.2-39.2 4.1-45.4 23-19.3 59.1-74.1 98.8-136.4 98.8-59.3 0-113.2-37.2-134.2-92.7-7-18.6-27.8-28-46.4-20.9-18.6 7-28 27.8-20.9 46.4 15.3 40.5 42.2 75.1 77.7 100.1 36.4 25.6 79.2 39.1 123.8 39.1 46.3 0 90.5-14.5 127.7-41.9 36.4-26.9 63.1-63.7 77.1-106.5 6.2-18.9-4.1-39.2-23-45.4zM350 554.1c19.9 0 36-16.1 36-36V334.4c0-19.9-16.1-36-36-36s-36 16.1-36 36v183.7c0 19.9 16.1 36 36 36zM674.1 554.1c19.9 0 36-16.1 36-36V334.4c0-19.9-16.1-36-36-36s-36 16.1-36 36v183.7c0 19.9 16.1 36 36 36z" p-id="12798" fill="#E6A23F"></path></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/in-service.svg


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/male.svg


+ 1 - 0
static/icon/my/agreement.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776303585325" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11932" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M744.96 1000.96L179.2 998.4c-69.12 0-128-56.32-128-128V153.6c0-69.12 56.32-128 128-128h550.4c69.12 0 128 56.32 128 128v240.64c0 23.04-17.92 43.52-43.52 43.52-23.04 0-43.52-17.92-43.52-43.52V153.6c0-23.04-17.92-43.52-43.52-43.52H179.2c-23.04 0-43.52 17.92-43.52 43.52v719.36c0 23.04 17.92 43.52 43.52 43.52h565.76c46.08 0 69.12 15.36 69.12 43.52 0 25.6-23.04 40.96-69.12 40.96z m-481.28-235.52c-2.56 0-7.68 0-10.24-2.56-10.24-5.12-12.8-17.92-7.68-28.16 89.6-161.28 125.44-156.16 138.24-153.6 7.68 2.56 23.04 7.68 28.16 33.28 5.12 20.48 5.12 43.52 7.68 61.44v12.8l15.36-17.92c30.72-35.84 51.2-56.32 74.24-51.2 20.48 5.12 28.16 28.16 28.16 35.84 2.56 12.8 2.56 23.04 2.56 33.28 2.56-2.56 7.68-2.56 10.24-5.12 10.24-5.12 23.04 0 28.16 10.24 5.12 10.24 0 23.04-10.24 28.16-33.28 15.36-51.2 23.04-66.56 10.24-12.8-10.24-10.24-25.6-7.68-35.84 2.56-10.24 5.12-20.48 2.56-30.72-7.68 7.68-20.48 20.48-30.72 33.28l-17.92 17.92c-10.24 10.24-28.16 30.72-48.64 23.04-20.48-7.68-23.04-30.72-23.04-61.44 0-15.36-2.56-30.72-5.12-48.64-20.48 17.92-56.32 69.12-89.6 128-5.12 5.12-10.24 7.68-17.92 7.68z m-43.52-486.4h465.92v43.52H220.16v-43.52z m0 84.48h465.92v43.52H220.16v-43.52z m0 84.48h381.44v43.52H220.16v-43.52z m744.96 0l20.48 15.36c23.04 15.36 28.16 48.64 10.24 71.68l-199.68 276.48c-7.68 10.24-15.36 15.36-28.16 17.92l-81.92 23.04-2.56-84.48c0-10.24 2.56-23.04 10.24-30.72L893.44 460.8c17.92-23.04 48.64-28.16 71.68-12.8z" fill="#E6A23F" p-id="11933"></path></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/my/complaint-management.svg


BIN
static/icon/my/customerservice/online.png


BIN
static/icon/my/customerservice/phone.png


+ 1 - 0
static/icon/my/fee-statistic.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776303503627" class="icon" viewBox="0 0 1092 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8860" xmlns:xlink="http://www.w3.org/1999/xlink" width="1092" height="1024"><path d="M682.666667 819.2a204.8 204.8 0 0 1 204.8-204.8 204.8 204.8 0 0 1 204.8 204.8 204.8 204.8 0 0 1-204.8 204.8 204.8 204.8 0 0 1-204.8-204.8z m112.708266 4.369067l96.529067 96.597333 96.529067-96.597333-96.529067-96.529067zM0 682.666667a189.576533 189.576533 0 0 1 12.9024-68.266667C59.460267 734.958933 222.549333 819.2 409.6 819.2a577.262933 577.262933 0 0 0 204.8-36.522667v2.321067a238.455467 238.455467 0 0 0 32.3584 120.149333A555.6224 555.6224 0 0 1 409.6 955.733333c-225.826133 0-409.6-122.538667-409.6-273.066666z m0-204.8a189.6448 189.6448 0 0 1 12.9024-68.266667C59.460267 530.2272 222.549333 614.4 409.6 614.4s350.139733-84.241067 396.629333-204.8a189.166933 189.166933 0 0 1 12.9024 68.266667 188.893867 188.893867 0 0 1-15.018666 73.3184 239.547733 239.547733 0 0 0-177.493334 158.242133A567.022933 567.022933 0 0 1 409.6 750.933333c-225.826133 0-409.6-122.538667-409.6-273.066666z m0-204.8c0-150.596267 183.773867-273.066667 409.6-273.066667s409.6 122.4704 409.6 273.066667-183.773867 273.066667-409.6 273.066666-409.6-122.4704-409.6-273.066666z" fill="#E6A23F" p-id="8861"></path></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/my/pet-archieves.svg


+ 1 - 0
static/icon/my/service-customer.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776303535997" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9950" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M966.5 345.4c-30.3-91.7-89.1-173.9-166.6-232.4-83.5-63-183-96.3-287.9-96.3S307.6 50 224.1 113C146.6 171.4 87.8 253.6 57.5 345.4c-34 13-57.5 46-57.5 83.1v133.6c0 41.7 29.6 78.3 70.4 87 6.2 1.3 12.4 2 18.6 2 49.1 0 89-39.9 89-89V428.5c0-43.2-31-79.3-71.9-87.3 63.3-166.2 226-280 405.8-280s342.5 113.7 405.8 280c-40.9 8-71.9 44.1-71.9 87.3v133.6c0 39 25.2 72.1 60.2 84.1C847.8 772.1 732.3 863 596.3 889.8c-11.8-35.5-45.1-60.7-84.3-60.7-49.1 0-89 39.9-89 89s39.9 89 89 89c43.5 0 79.7-31.4 87.5-72.7 158.1-29.2 291.6-136.8 353.9-285.5h0.2c40.8-8.8 70.4-45.4 70.4-87V428.5c0-37.1-23.5-70.1-57.5-83.1z m-832.9 83.1v133.6c0 24.6-20 44.5-44.5 44.5-3.1 0-6.2-0.3-9.3-1-20.4-4.4-35.2-22.7-35.2-43.5V428.5c0-20.8 14.8-39.1 35.2-43.5 3.1-0.7 6.2-1 9.3-1 24.5 0 44.5 20 44.5 44.5zM512 962.8c-24.5 0-44.5-20-44.5-44.5s20-44.5 44.5-44.5c23.9 0 43.4 18.8 44.4 42.7 0 0.6 0.1 1.1 0.1 1.8 0 24.5-20 44.5-44.5 44.5z m467.5-400.7c0 20.8-14.8 39.1-35.2 43.5-2.2 0.5-4.6 0.8-7.5 0.9-0.6 0-1.2 0.1-1.8 0.1-24.5 0-44.5-20-44.5-44.5V428.5c0-24.5 20-44.5 44.5-44.5 3.1 0 6.2 0.3 9.3 1 20.4 4.4 35.2 22.7 35.2 43.5v133.6z" p-id="9951" fill="#E6A23F"></path><path d="M682.7 656.6c9.2-8.2 9.9-22.3 1.7-31.4-8.2-9.2-22.3-9.9-31.4-1.7-149.1 133.5-275.2 6.9-280.7 1.2-8.5-8.9-22.6-9.2-31.5-0.7-8.9 8.5-9.2 22.6-0.7 31.5 1.1 1.1 72.2 73.6 173.3 73.6 50.6-0.1 108.7-18.3 169.3-72.5z" p-id="9952" fill="#E6A23F"></path></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/my/system-settings.svg


+ 1 - 0
static/icon/my/user-management.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776303409118" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6681" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M722.995 541.05c83.58-43.31 140.734-130.544 140.734-231.177 0-143.755-116.537-260.28-260.28-260.28-143.75 0-260.286 116.53-260.286 260.28 0 99.425 55.772 185.8 137.718 229.632-174.065 52.06-303.135 214.753-298.814 399.728 0 36.46 49.29 31.447 49.29 1.808 0-204.831 166.052-370.883 370.888-370.883 204.83 0 370.883 166.057 370.883 370.883 0 24.852 43.269 21.1 43.269-1.808 4.08-183.219-122.122-344.448-293.402-398.182zM599.24 522.06c-5.028 0-10.015 0.206-15.007 0.39-108.754-9.15-194.181-100.26-194.181-211.38 0-117.191 95.001-212.187 212.193-212.187 117.192 0 217.487 95.001 217.487 212.188 0 111.641-91.033 203.08-200.975 211.497a411.674 411.674 0 0 0-19.517-0.507zM336.43 552.05c0.338 0 0.67 0.025 1.004 0.025 30.56 0 31.221-49.643-1.004-49.643-0.824 0-1.638-0.046-2.463-0.057l-0.046-0.937c-4.29 0-8.53 0.18-12.764 0.343-91.105-7.214-167.004-78.284-167.004-170.741 0-97.587 84.536-176.691 182.277-176.691 32.277 0 23.772-41.048-9.595-41.048-119.9 0-217.093 97.04-217.093 216.735 0 79.55 47.631 151.383 114.76 190.659C91.397 569.42 3.676 704.722 7.168 854.103c0 30.372 46.403 26.189 46.403 1.505 0-170.562 112.015-303.534 282.86-303.534v-0.025z" fill="#E6A23F" p-id="6682"></path></svg>

+ 1 - 0
static/icon/pending-dispatch.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776306559380" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10834" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M512 0C229.229 0 0 229.23 0 512s229.229 512 512 512c282.771 0 512-229.23 512-512S794.771 0 512 0z m0 972.801C257.505 972.801 51.199 766.496 51.199 512 51.199 257.508 257.505 51.199 512 51.199c254.497 0 460.801 206.309 460.801 460.801 0 254.496-206.304 460.801-460.801 460.801z" fill="#E6A23F" p-id="10835"></path><path d="M699.757714 541.568a118.857143 118.857143 0 0 0-118.034285 205.476571H347.428571a36.571429 36.571429 0 0 1-36.571428-36.571428V292.571429a36.571429 36.571429 0 0 1 36.571428-36.571429h315.757715a36.571429 36.571429 0 0 1 36.571428 36.571429V541.568z m-322.651428-150.180571h222.244571a18.267429 18.267429 0 0 0 18.066286-18.468572 18.267429 18.267429 0 0 0-18.066286-18.468571H377.106286a18.267429 18.267429 0 0 0-18.066286 18.468571c0 10.203429 8.082286 18.468571 18.066286 18.468572z m-0.018286 82.870857h173.056a18.267429 18.267429 0 0 0 18.066286-18.468572 18.267429 18.267429 0 0 0-18.066286-18.450285h-173.056a18.267429 18.267429 0 0 0-18.066286 18.468571c0 10.185143 8.082286 18.468571 18.066286 18.468571z m0.018286 89.417143h131.84a18.267429 18.267429 0 0 0 18.048-18.450286 18.267429 18.267429 0 0 0-18.066286-18.468572h-131.821714a18.267429 18.267429 0 0 0-18.066286 18.468572c0 10.203429 8.082286 18.468571 18.066286 18.468571z" fill="#E6A23F" p-id="10836"></path><path d="M649.142857 749.714286a100.571429 100.571429 0 1 1 0-201.142857 100.571429 100.571429 0 0 1 0 201.142857z m35.657143-61.988572l-35.657143-32.201143V603.428571a9.142857 9.142857 0 1 0-18.285714 0v56.155429a9.142857 9.142857 0 0 0 3.017143 6.784l38.692571 34.925714a9.142857 9.142857 0 0 0 12.251429-13.568z" fill="#E6A23F" p-id="10837"></path></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/pet.svg


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icon/remale.svg


+ 1 - 0
static/icon/waiting-accept.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776306433454" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9695" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M512 0C229.229 0 0 229.23 0 512s229.229 512 512 512c282.771 0 512-229.23 512-512S794.771 0 512 0z m0 972.801C257.505 972.801 51.199 766.496 51.199 512 51.199 257.508 257.505 51.199 512 51.199c254.497 0 460.801 206.309 460.801 460.801 0 254.496-206.304 460.801-460.801 460.801zM768 331.52c0 14.141-11.458 25.602-25.602 25.602H281.6c-14.137 0-25.6-11.461-25.6-25.602s11.462-25.6 25.6-25.6h460.799c14.143 0 25.601 11.459 25.601 25.6z m0 174.08c0 14.141-11.458 25.6-25.602 25.6H281.6c-14.137 0-25.6-11.459-25.6-25.6 0-14.139 11.462-25.6 25.6-25.6h460.799C756.542 480 768 491.461 768 505.6z m0 174.08c0 14.141-11.458 25.602-25.602 25.602H281.6c-14.137 0-25.6-11.461-25.6-25.602s11.462-25.6 25.6-25.6h460.799c14.143 0 25.601 11.459 25.601 25.6z" fill="#E6A23F" p-id="9696"></path></svg>

+ 1 - 0
static/icon/waiting-service.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1776244200746" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15459" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M782.58 187.647c-15.43 15.43-39.672 17.649-57.646 5.278-9.362-6.443-17.324-11.525-23.889-15.245a382.068 382.068 0 0 0-59.823-27.395C600.842 135.857 557.338 128 512 128c-212.077 0-384 171.923-384 384s171.923 384 384 384 384-171.923 384-384c0-45.205-7.811-88.585-22.157-128.865a382.05 382.05 0 0 0-27.244-59.686c-3.688-6.53-8.721-14.448-15.1-23.754-12.343-18.008-10.1-42.256 5.338-57.693 13.062-13.062 34.239-13.062 47.3 0a33.446 33.446 0 0 1 3.48 4.087c9.942 13.79 17.607 25.313 22.992 34.573a458.646 458.646 0 0 1 34.279 73.034C962.946 403.062 972.8 456.38 972.8 512c0 254.493-206.307 460.8-460.8 460.8-254.493 0-460.8-206.307-460.8-460.8C51.2 257.507 257.507 51.2 512 51.2c55.753 0 109.194 9.902 158.66 28.042a458.664 458.664 0 0 1 73.167 34.434c9.293 5.42 20.86 13.137 34.702 23.152 14.987 10.843 18.346 31.781 7.504 46.768a33.493 33.493 0 0 1-3.453 4.051zM512 378.489l28.583-28.303c57.88-57.315 151.725-57.315 209.606 0s57.881 150.24 0 207.556L548.025 757.927c-19.953 19.758-52.097 19.758-72.05 0L273.81 557.742c-57.881-57.315-57.881-150.241 0-207.556s151.725-57.315 209.606 0L512 378.49z m184.15 124.68c13.723-13.587 20.65-31.38 20.65-49.205 0-17.826-6.927-35.618-20.65-49.206C682.134 390.878 663.744 384 645.387 384c-18.357 0-36.747 6.877-50.765 20.758l-64.608 63.976c-9.977 9.88-26.049 9.88-36.026 0l-64.608-63.976C415.361 390.878 396.971 384 378.614 384s-36.747 6.877-50.765 20.758c-13.722 13.588-20.649 31.38-20.649 49.206 0 17.826 6.927 35.618 20.65 49.205L512 685.52l184.15-182.35z" fill="#E6A23F" p-id="15460"></path></svg>

BIN
static/images/feed-walk.png


BIN
static/images/index-header.png


BIN
static/images/index-notice.png


BIN
static/images/laundry-clean.png


BIN
static/images/my-agreement.png


BIN
static/images/my-cancel.png


BIN
static/images/my-complaint.png


BIN
static/images/my-customer.png


BIN
static/images/my-customerservice.png


BIN
static/images/my-fee.png


BIN
static/images/my-finished.png


BIN
static/images/my-header.png


BIN
static/images/my-inservice.png


BIN
static/images/my-pendingaccept.png


BIN
static/images/my-pendingdispatch.png


BIN
static/images/my-pendingservice.png


BIN
static/images/my-pet.png


BIN
static/images/my-systemsetting.png


BIN
static/images/pickup-dropoff.png


+ 13 - 4
uni.promisify.adaptor.js

@@ -1,4 +1,13 @@
-/* uni.promisify.adaptor - 将uni API转换为Promise风格 */
-if (uni.restoreGlobal) {
-	uni.restoreGlobal(uni, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval)
-}
+uni.addInterceptor({
+  returnValue (res) {
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+      return res;
+    }
+    return new Promise((resolve, reject) => {
+      res.then((res) => {
+        if (!res) return resolve(res) 
+        return res[0] ? reject(res[0]) : resolve(res[1])
+      });
+    });
+  },
+});

BIN
unpackage/cache/apk/__UNI__F19BBAD_cm.apk


+ 1 - 1
unpackage/cache/apk/apkurl

@@ -1 +1 @@
-https://app.liuyingyong.cn/build/download/85f4f0a0-380a-11f1-8001-47d9cfd6ab79
+https://app.liuyingyong.cn/build/download/d728f690-3946-11f1-9556-d37c8c4b9525

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/apk/cmManifestCache.json


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
unpackage/cache/wgt/__UNI__F19BBAD/app-config-service.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/app-service.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/manifest.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/index/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/login/index.css


+ 1 - 1
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/agreement/detail/index.css

@@ -1 +1 @@
-.nav-bar[data-v-ee6d93a8]{position:-webkit-sticky;position:sticky;top:0;z-index:999;width:100%}.nav-content[data-v-ee6d93a8]{height:44px;display:flex;align-items:center;padding:0 1rem;position:relative}.left-box[data-v-ee6d93a8]{position:absolute;left:1rem;z-index:10}.title-box[data-v-ee6d93a8]{flex:1;display:flex;justify-content:center;align-items:center}.title-text[data-v-ee6d93a8]{font-size:1rem;font-weight:700}.right-box[data-v-ee6d93a8]{position:absolute;right:1rem}.agreement-detail-page[data-v-6dfd6b26]{min-height:100vh;background:#f7f8fa;padding-bottom:calc(1.25rem + env(safe-area-inset-bottom))}.content-card[data-v-6dfd6b26]{background:#fff;margin:.75rem;border-radius:.75rem;padding:1.25rem 1rem;box-shadow:0 .125rem .375rem rgba(0,0,0,.03)}.title[data-v-6dfd6b26]{display:block;font-size:1.125rem;font-weight:800;color:#333;margin-bottom:1rem;position:relative;padding-bottom:.75rem;border-bottom:.03125rem solid #f5f5f5}.rich-content[data-v-6dfd6b26]{display:block;font-size:.875rem;color:#555;line-height:1.8;word-break:break-all}.loading-state[data-v-6dfd6b26]{text-align:center;padding:3.125rem 0;color:#999;font-size:.875rem}
+.nav-bar[data-v-ad1d7ee4]{position:-webkit-sticky;position:sticky;top:0;z-index:999;width:100%}.nav-content[data-v-ad1d7ee4]{height:44px;display:flex;align-items:center;padding:0 1rem;position:relative}.left-box[data-v-ad1d7ee4]{position:absolute;left:1rem;z-index:10;padding:.625rem 1.25rem .625rem 0}.back-arrow[data-v-ad1d7ee4]{width:.6875rem;height:.6875rem;border-left:.125rem solid;border-top:.125rem solid;transform:rotate(-45deg);background:transparent}.title-box[data-v-ad1d7ee4]{flex:1;display:flex;justify-content:center;align-items:center}.title-text[data-v-ad1d7ee4]{font-size:1rem;font-weight:700}.right-box[data-v-ad1d7ee4]{position:absolute;right:1rem}.agreement-detail-page[data-v-dcb64685]{min-height:100vh;background:#f7f8fa;padding-bottom:calc(1.25rem + env(safe-area-inset-bottom))}.content-card[data-v-dcb64685]{background:#fff;margin:.75rem;border-radius:.75rem;padding:1.25rem 1rem;box-shadow:0 .125rem .375rem rgba(0,0,0,.03)}.title[data-v-dcb64685]{display:block;font-size:1.125rem;font-weight:800;color:#333;margin-bottom:1rem;position:relative;padding-bottom:.75rem;border-bottom:.0625rem solid #EEEEEE}.rich-content[data-v-dcb64685]{display:block;font-size:.875rem;color:#555;line-height:1.8;word-break:break-all}.loading-state[data-v-dcb64685]{text-align:center;padding:3.125rem 0;color:#999;font-size:.875rem}

+ 1 - 1
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/agreement/list/index.css

@@ -1 +1 @@
-.nav-bar[data-v-ee6d93a8]{position:-webkit-sticky;position:sticky;top:0;z-index:999;width:100%}.nav-content[data-v-ee6d93a8]{height:44px;display:flex;align-items:center;padding:0 1rem;position:relative}.left-box[data-v-ee6d93a8]{position:absolute;left:1rem;z-index:10}.title-box[data-v-ee6d93a8]{flex:1;display:flex;justify-content:center;align-items:center}.title-text[data-v-ee6d93a8]{font-size:1rem;font-weight:700}.right-box[data-v-ee6d93a8]{position:absolute;right:1rem}.agreement-list-page[data-v-71f3f0af]{min-height:100vh;background:#f7f8fa}.list-container[data-v-71f3f0af]{padding:.75rem}.agreement-item[data-v-71f3f0af]{display:flex;align-items:center;background:#fff;border-radius:.625rem;padding:1rem;margin-bottom:.625rem;box-shadow:0 .125rem .375rem rgba(0,0,0,.03)}.item-info[data-v-71f3f0af]{flex:1}.item-title[data-v-71f3f0af]{display:block;font-size:.9375rem;color:#333;font-weight:500}
+.nav-bar[data-v-ad1d7ee4]{position:-webkit-sticky;position:sticky;top:0;z-index:999;width:100%}.nav-content[data-v-ad1d7ee4]{height:44px;display:flex;align-items:center;padding:0 1rem;position:relative}.left-box[data-v-ad1d7ee4]{position:absolute;left:1rem;z-index:10;padding:.625rem 1.25rem .625rem 0}.back-arrow[data-v-ad1d7ee4]{width:.6875rem;height:.6875rem;border-left:.125rem solid;border-top:.125rem solid;transform:rotate(-45deg);background:transparent}.title-box[data-v-ad1d7ee4]{flex:1;display:flex;justify-content:center;align-items:center}.title-text[data-v-ad1d7ee4]{font-size:1rem;font-weight:700}.right-box[data-v-ad1d7ee4]{position:absolute;right:1rem}.agreement-list-page[data-v-71f3f0af]{min-height:100vh;background:#f7f8fa}.list-container[data-v-71f3f0af]{padding:.75rem}.agreement-item[data-v-71f3f0af]{display:flex;align-items:center;background:#fff;border-radius:.625rem;padding:1rem;margin-bottom:.625rem;box-shadow:0 .125rem .375rem rgba(0,0,0,.03)}.item-info[data-v-71f3f0af]{flex:1}.item-title[data-v-71f3f0af]{display:block;font-size:.9375rem;color:#333;font-weight:500}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/complaint/list/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/complaint/submit/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/fee/statistics/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/add/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/detail/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/edit/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/pet/list/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/account-delete/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/change-password/index.css


+ 1 - 1
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/index.css

@@ -1 +1 @@
-.nav-bar[data-v-ee6d93a8]{position:-webkit-sticky;position:sticky;top:0;z-index:999;width:100%}.nav-content[data-v-ee6d93a8]{height:44px;display:flex;align-items:center;padding:0 1rem;position:relative}.left-box[data-v-ee6d93a8]{position:absolute;left:1rem;z-index:10}.title-box[data-v-ee6d93a8]{flex:1;display:flex;justify-content:center;align-items:center}.title-text[data-v-ee6d93a8]{font-size:1rem;font-weight:700}.right-box[data-v-ee6d93a8]{position:absolute;right:1rem}.settings-page[data-v-e65bcf38]{min-height:100vh;background:#f7f8fa}.menu-list[data-v-e65bcf38]{padding-top:.75rem}.cell-group[data-v-e65bcf38]{background:#fff;margin:0 .75rem .75rem;border-radius:.75rem;overflow:hidden}.cell-item[data-v-e65bcf38]{display:flex;align-items:center;padding:1rem;border-bottom:.03125rem solid #f5f5f5}.cell-item[data-v-e65bcf38]:last-child{border-bottom:none}.cell-title[data-v-e65bcf38]{flex:1;font-size:.875rem;color:#333}.cell-title.danger[data-v-e65bcf38]{color:#ee0a24}.cell-value[data-v-e65bcf38]{font-size:.8125rem;color:#999;margin-right:.25rem}.danger-group[data-v-e65bcf38]{margin-top:1.25rem}.logout-btn-wrapper[data-v-e65bcf38]{padding:2.5rem 1rem}.logout-btn[data-v-e65bcf38]{width:100%;height:2.75rem;background:#fff;color:#333;border:none;border-radius:1.375rem;font-size:.9375rem;line-height:2.75rem}
+.nav-bar[data-v-ad1d7ee4]{position:-webkit-sticky;position:sticky;top:0;z-index:999;width:100%}.nav-content[data-v-ad1d7ee4]{height:44px;display:flex;align-items:center;padding:0 1rem;position:relative}.left-box[data-v-ad1d7ee4]{position:absolute;left:1rem;z-index:10;padding:.625rem 1.25rem .625rem 0}.back-arrow[data-v-ad1d7ee4]{width:.6875rem;height:.6875rem;border-left:.125rem solid;border-top:.125rem solid;transform:rotate(-45deg);background:transparent}.title-box[data-v-ad1d7ee4]{flex:1;display:flex;justify-content:center;align-items:center}.title-text[data-v-ad1d7ee4]{font-size:1rem;font-weight:700}.right-box[data-v-ad1d7ee4]{position:absolute;right:1rem}.settings-page[data-v-68120730]{min-height:100vh;background:#f7f8fa}.menu-list[data-v-68120730]{padding-top:.75rem}.cell-group[data-v-68120730]{background:#fff;margin:0 .75rem .75rem;border-radius:.75rem;overflow:hidden}.cell-item[data-v-68120730]{display:flex;align-items:center;padding:1rem;border-bottom:.0625rem solid #eee}.cell-item[data-v-68120730]:last-child{border-bottom:none}.cell-title[data-v-68120730]{flex:1;font-size:.875rem;color:#333}.cell-title.danger[data-v-68120730]{color:#ee0a24}.cell-value[data-v-68120730]{font-size:.8125rem;color:#999;margin-right:.25rem}.arrow[data-v-68120730]{font-size:1.125rem;color:#ccc;line-height:1}.danger-group[data-v-68120730]{margin-top:1.25rem}.logout-btn-wrapper[data-v-68120730]{padding:2.5rem 1rem}.logout-btn[data-v-68120730]{width:100%;height:2.75rem;background:#fff;color:#ee0a24;border:.0625rem solid #ee0a24;border-radius:1.375rem;font-size:.9375rem;font-weight:500;line-height:2.625rem}.logout-btn[data-v-68120730]:after{border:none}

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/settings/profile/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/add/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/detail/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/edit/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/my/user/list/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/order/apply/index.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
unpackage/cache/wgt/__UNI__F19BBAD/pages/order/detail/index.css


Некоторые файлы не были показаны из-за большого количества измененных файлов