index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. <template>
  2. <view class="detail-container">
  3. <!-- 加载动画 -->
  4. <view class="loading-container" v-if="pageLoading">
  5. <view class="skeleton-header">
  6. <view class="skeleton-line skeleton-ani" style="width: 30%; height: 36rpx;"></view>
  7. <view class="skeleton-line skeleton-ani" style="width: 20%; height: 36rpx;"></view>
  8. </view>
  9. <view class="skeleton-card" v-for="j in 3" :key="'c' + j">
  10. <view class="skeleton-line skeleton-ani" style="width: 60%; height: 28rpx; margin-bottom: 20rpx;"></view>
  11. <view class="skeleton-line skeleton-ani" style="width: 90%; height: 24rpx; margin-bottom: 14rpx;"></view>
  12. <view class="skeleton-line skeleton-ani" style="width: 75%; height: 24rpx;"></view>
  13. </view>
  14. </view>
  15. <template v-else>
  16. <!-- 顶部状态区 -->
  17. <view class="detail-header pre-accept">
  18. <view class="status-row">
  19. <text class="status-title">待接单</text>
  20. <text class="status-price">¥{{ orderDetail.fulfillmentCommission }}</text>
  21. </view>
  22. <view class="status-desc">待接单订单,接单后可查看完整联系方式</view>
  23. </view>
  24. <scroll-view scroll-y class="detail-content">
  25. <!-- 宠物档案卡片 -->
  26. <view class="white-card pet-bar">
  27. <image class="pb-avatar" :src="orderDetail.petAvatar" mode="aspectFill"></image>
  28. <view class="pb-info">
  29. <view class="pb-name-row">
  30. <text class="pb-name">{{ orderDetail.petName }}</text>
  31. <text class="pb-breed">{{ orderDetail.petBreed }}</text>
  32. </view>
  33. <view class="pb-tags">
  34. <text class="pb-tag">{{ orderDetail.serviceName }}</text>
  35. </view>
  36. </view>
  37. <view class="pb-actions">
  38. <view class="view-profile-btn" @click="showPetProfile">
  39. <text>宠物档案</text>
  40. <text class="arrow">></text>
  41. </view>
  42. </view>
  43. </view>
  44. <!-- 用户档案卡片(待接单状态脱敏) -->
  45. <view class="white-card user-profile-card">
  46. <view class="tl-title-row">
  47. <view class="orange-bar"></view>
  48. <text class="tl-title">用户档案</text>
  49. <text class="tl-hint">接单后可查看完整信息</text>
  50. </view>
  51. <view class="bi-row">
  52. <image class="bi-icon" src="/static/icons/user.svg"></image>
  53. <view class="bi-content">
  54. <text class="bi-label">联系人</text>
  55. <text class="bi-val bi-blur">接单后可见</text>
  56. </view>
  57. </view>
  58. <view class="bi-row">
  59. <image class="bi-icon" src="/static/icons/phone.svg"></image>
  60. <view class="bi-content">
  61. <text class="bi-label">联系电话</text>
  62. <text class="bi-val bi-blur">接单后可见</text>
  63. </view>
  64. </view>
  65. <view class="bi-row no-border">
  66. <image class="bi-icon" src="/static/icons/location.svg"></image>
  67. <view class="bi-content">
  68. <text class="bi-label">详细地址</text>
  69. <text class="bi-val bi-blur">接单后可见</text>
  70. </view>
  71. </view>
  72. </view>
  73. <!-- 路线及服务信息 -->
  74. <view class="white-card service-info-card">
  75. <view class="tl-title-row">
  76. <view class="orange-bar"></view>
  77. <text class="tl-title">服务详情</text>
  78. </view>
  79. <view class="si-row time-row">
  80. <image class="si-icon outline" src="/static/icons/clock.svg"></image>
  81. <view class="si-content">
  82. <text class="si-label">服务时间</text>
  83. <text class="si-val">{{ orderDetail.time }}</text>
  84. </view>
  85. </view>
  86. <!-- 接送类型的地址展现 -->
  87. <template v-if="orderDetail.type === 1">
  88. <view class="si-row addr-row start-addr">
  89. <view class="icon-circle start">起</view>
  90. <view class="route-line-vertical"></view>
  91. <view class="si-content">
  92. <text class="si-addr-title">{{ orderDetail.startLocation }}</text>
  93. <text class="si-addr-desc">{{ orderDetail.startAddress }}</text>
  94. </view>
  95. <view class="nav-btn-circle" @click="openNavigation('start')">
  96. <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
  97. </view>
  98. </view>
  99. <view class="si-row addr-row end-addr">
  100. <view class="icon-circle end">终</view>
  101. <view class="si-content">
  102. <text class="si-addr-title">{{ orderDetail.endLocation }}</text>
  103. <text class="si-addr-desc">{{ orderDetail.endAddress }}</text>
  104. </view>
  105. <view class="nav-btn-circle" @click="openNavigation('end')">
  106. <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
  107. </view>
  108. </view>
  109. </template>
  110. <!-- 喂遛/洗护类型的地址展现 -->
  111. <template v-else>
  112. <view class="si-row addr-row end-addr">
  113. <view class="icon-circle service">服</view>
  114. <view class="si-content">
  115. <text class="si-addr-title">{{ orderDetail.endLocation }}</text>
  116. <text class="si-addr-desc">{{ orderDetail.endAddress }}</text>
  117. </view>
  118. <view class="nav-btn-circle" @click="openNavigation('end')">
  119. <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
  120. </view>
  121. </view>
  122. </template>
  123. <view class="si-row no-border">
  124. <image class="si-icon outline custom-icon-file" src="/static/icons/file.svg"></image>
  125. <view class="si-content">
  126. <text class="si-label">订单备注</text>
  127. <text class="si-val">{{ orderDetail.remark || '-' }}</text>
  128. </view>
  129. </view>
  130. </view>
  131. <!-- 订单日志 -->
  132. <view class="white-card logs-card">
  133. <view class="tl-title-row">
  134. <view class="orange-bar"></view>
  135. <text class="tl-title">订单日志</text>
  136. </view>
  137. <view class="tl-list">
  138. <view class="tl-item" v-for="(log, index) in orderLogs" :key="index">
  139. <view class="tl-marker" :class="{ 'active': index === 0 }">
  140. <view class="tl-dot-inner" v-if="index === 0"></view>
  141. </view>
  142. <view class="tl-content-row">
  143. <view class="tl-header">
  144. <text class="tl-status">{{ log.title || '系统记录' }}</text>
  145. <text class="tl-time">{{ log.time }}</text>
  146. </view>
  147. <text class="tl-remark" v-if="log.content">{{ log.content }}</text>
  148. </view>
  149. </view>
  150. </view>
  151. </view>
  152. <view style="height: 60rpx;"></view>
  153. </scroll-view>
  154. </template>
  155. <!-- 宠物档案弹窗 (引用首页逻辑) -->
  156. <view class="pet-modal-mask" v-if="showPetModal" @click="closePetProfile">
  157. <view class="pet-modal-content" @click.stop>
  158. <view class="pet-modal-header">
  159. <text class="pet-modal-title">宠物档案</text>
  160. <view class="close-icon-btn" @click="closePetProfile">×</view>
  161. </view>
  162. <scroll-view scroll-y class="pet-modal-scroll">
  163. <view class="pet-base-info">
  164. <image class="pm-avatar" :src="orderDetail.petAvatar" mode="aspectFill"></image>
  165. <view class="pm-info-text">
  166. <view class="pm-name-row">
  167. <text class="pm-name">{{ orderDetail.petName }}</text>
  168. <view class="pm-gender" v-if="orderDetail.petGender === 'M'">
  169. <text class="gender-icon">♂</text><text>公</text>
  170. </view>
  171. <view class="pm-gender female" v-else-if="orderDetail.petGender === 'F'">
  172. <text class="gender-icon">♀</text><text>母</text>
  173. </view>
  174. </view>
  175. <text class="pm-breed">品种:{{ orderDetail.petBreed }}</text>
  176. </view>
  177. </view>
  178. <view class="pm-detail-grid">
  179. <view class="pm-grid-item half">
  180. <text class="pm-label">年龄</text>
  181. <text class="pm-val">{{ orderDetail.petAge || '未知' }}</text>
  182. </view>
  183. <view class="pm-grid-item half">
  184. <text class="pm-label">体重</text>
  185. <text class="pm-val">{{ orderDetail.petWeight || '未知' }}</text>
  186. </view>
  187. <view class="pm-grid-item full">
  188. <text class="pm-label">性格</text>
  189. <text class="pm-val">{{ orderDetail.petPersonality || '暂无' }}</text>
  190. </view>
  191. <view class="pm-grid-item full">
  192. <text class="pm-label">备注/禁忌</text>
  193. <text class="pm-val">{{ orderDetail.petNotes || '暂无说明' }}</text>
  194. </view>
  195. </view>
  196. <view style="height: 40rpx;"></view>
  197. <button class="pm-bottom-close" @click="closePetProfile">关闭</button>
  198. <view style="height: 20rpx;"></view>
  199. </scroll-view>
  200. </view>
  201. </view>
  202. <!-- 导航地图选择 -->
  203. <view class="nav-modal-mask" v-if="showNavModal" @click="closeNavModal">
  204. <view class="nav-action-sheet" @click.stop>
  205. <view class="nav-sheet-title">选择地图进行导航</view>
  206. <view class="nav-sheet-item" @click="chooseMap('高德')">高德地图</view>
  207. <view class="nav-sheet-item" @click="chooseMap('腾讯')">腾讯地图</view>
  208. <view class="nav-sheet-item" @click="chooseMap('百度')">百度地图</view>
  209. <view class="nav-sheet-gap"></view>
  210. <view class="nav-sheet-item cancel" @click="closeNavModal">取消</view>
  211. </view>
  212. </view>
  213. </view>
  214. </template>
  215. <script>
  216. import { getOrderInfo } from '@/api/order/subOrder'
  217. import { getOrderLogs } from '@/api/order/subOrderLog'
  218. import { listAllService } from '@/api/service/list'
  219. import { getPetDetail } from '@/api/archieves/pet'
  220. import { reportGps } from '@/utils/gps'
  221. /**
  222. * 订单大厅详情页 (数据修正版)
  223. * @Author: Antigravity
  224. */
  225. export default {
  226. data() {
  227. return {
  228. orderId: null,
  229. pageLoading: true,
  230. orderDetail: {
  231. type: 1, fulfillmentCommission: '0.00', time: '',
  232. petAvatar: '/static/dog.png', petName: '', petBreed: '',
  233. serviceName: '', startLocation: '', startAddress: '', endLocation: '', endAddress: '',
  234. remark: '', orderNo: '', createTime: '', fromLat: null, fromLng: null, toLat: null, toLng: null,
  235. ownerName: '', ownerPhone: '', petAge: '', petWeight: '', petGender: 'M', petPersonality: '', petNotes: ''
  236. },
  237. orderLogs: [],
  238. serviceList: [],
  239. showNavModal: false,
  240. navTargetPointType: '',
  241. showPetModal: false
  242. }
  243. },
  244. async onLoad(options) {
  245. if (options.id) this.orderId = options.id
  246. this.pageLoading = true
  247. try {
  248. await this.loadServiceList()
  249. await this.loadOrderDetail()
  250. } finally {
  251. this.pageLoading = false
  252. }
  253. },
  254. methods: {
  255. async loadServiceList() {
  256. try {
  257. const res = await listAllService()
  258. this.serviceList = res.data || []
  259. } catch (err) { console.error('获取服务类型失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
  260. },
  261. async loadOrderDetail() {
  262. if (!this.orderId) return
  263. try {
  264. const res = await getOrderInfo(this.orderId)
  265. const order = res.data
  266. if (!order) return
  267. const serviceInfo = this.serviceList.find(s => s.id === order.service)
  268. const mode = serviceInfo?.mode || 0
  269. this.orderDetail = {
  270. id: order.id,
  271. type: mode === 1 ? 1 : 2,
  272. fulfillmentCommission: (order.fulfillmentCommission / 100).toFixed(2),
  273. time: order.serviceTime || '',
  274. petAvatar: order.petAvatar || '/static/dog.png',
  275. petName: order.petName || '宠物',
  276. petBreed: order.breed || '未知品种',
  277. serviceName: serviceInfo?.name || '未知服务',
  278. startLocation: order.fromAddress || '暂无起点',
  279. startAddress: order.fromAddress || '',
  280. fromLat: order.fromLat, fromLng: order.fromLng,
  281. endLocation: order.toAddress || '查看详情可见',
  282. endAddress: order.toAddress || '',
  283. toLat: order.toLat, toLng: order.toLng,
  284. remark: order.remark || '',
  285. orderNo: order.code || 'T' + order.id,
  286. createTime: order.createDateTime || order.serviceTime,
  287. ownerName: order.contact || '匿名用户',
  288. ownerPhone: order.contactPhoneNumber || ''
  289. }
  290. // 1. 加载宠物详情
  291. if (order.usrPet) {
  292. this.loadPetInfo(order.usrPet)
  293. }
  294. // 2. 加载时间轴日志
  295. this.loadLogs()
  296. } catch (err) { console.error('获取详情失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
  297. },
  298. async loadPetInfo(petId) {
  299. try {
  300. const res = await getPetDetail(petId)
  301. const pet = res.data
  302. if (pet) {
  303. this.orderDetail.petAge = pet.age || '未知'
  304. this.orderDetail.petWeight = pet.weight || '未知'
  305. this.orderDetail.petGender = pet.sex || 'M'
  306. this.orderDetail.petPersonality = pet.personality || '暂无标签'
  307. this.orderDetail.petNotes = pet.remark || '暂无说明'
  308. }
  309. } catch (err) { console.error('获取宠物详情失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
  310. },
  311. async loadLogs() {
  312. try {
  313. const res = await getOrderLogs(this.orderId)
  314. const list = Array.isArray(res.data) ? res.data : (res.rows || [])
  315. this.orderLogs = list.map(item => ({
  316. title: item.title || '状态更新',
  317. time: item.createTime || '',
  318. content: item.content || ''
  319. }))
  320. } catch (err) { console.error('获取日志失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
  321. },
  322. showPetProfile() { this.showPetModal = true },
  323. closePetProfile() { this.showPetModal = false },
  324. openNavigation(type) { this.navTargetPointType = type; this.showNavModal = true },
  325. closeNavModal() { this.showNavModal = false },
  326. chooseMap(mapType) {
  327. let pointType = this.navTargetPointType;
  328. let name = pointType === 'start' ? (this.orderDetail.startAddress || '起点') : (this.orderDetail.endAddress || '终点');
  329. let address = name;
  330. let latitude = pointType === 'start' ? Number(this.orderDetail.fromLat) : Number(this.orderDetail.toLat);
  331. let longitude = pointType === 'start' ? Number(this.orderDetail.fromLng) : Number(this.orderDetail.toLng);
  332. this.showNavModal = false;
  333. const navigateTo = (lat, lng) => {
  334. uni.openLocation({ latitude: lat, longitude: lng, name: name, address: address });
  335. };
  336. if (latitude && longitude && !isNaN(latitude)) {
  337. navigateTo(latitude, longitude);
  338. } else {
  339. uni.showLoading({ title: '定位中...' });
  340. reportGps(true).then(res => {
  341. uni.hideLoading(); navigateTo(res.latitude, res.longitude);
  342. }).catch(() => uni.hideLoading());
  343. }
  344. }
  345. }
  346. }
  347. </script>
  348. <style lang="scss">
  349. .detail-container {
  350. min-height: 100vh;
  351. background-color: #f8f8f8;
  352. display: flex;
  353. flex-direction: column;
  354. }
  355. .detail-header {
  356. padding: 40rpx 40rpx 80rpx;
  357. background: linear-gradient(135deg, #FF9800 0%, #FFB74D 100%);
  358. color: #fff;
  359. .status-row {
  360. display: flex;
  361. justify-content: space-between;
  362. align-items: center;
  363. margin-bottom: 20rpx;
  364. .status-title { font-size: 44rpx; font-weight: bold; }
  365. .status-price { font-size: 40rpx; font-weight: bold; }
  366. }
  367. .status-desc { font-size: 26rpx; opacity: 0.9; }
  368. }
  369. .detail-content {
  370. flex: 1;
  371. padding: 20rpx;
  372. margin-top: -40rpx;
  373. }
  374. .white-card {
  375. background: #fff;
  376. border-radius: 24rpx;
  377. padding: 30rpx;
  378. margin-bottom: 24rpx;
  379. box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
  380. }
  381. /* 宠物栏 */
  382. .pet-bar {
  383. display: flex;
  384. align-items: center;
  385. .pb-avatar { width: 110rpx; height: 110rpx; border-radius: 50%; margin-right: 24rpx; background: #f0f0f0; border: 4rpx solid #fff; box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.05); }
  386. .pb-info {
  387. flex: 1;
  388. .pb-name { font-size: 34rpx; font-weight: bold; color: #333; margin-right: 12rpx; }
  389. .pb-breed { font-size: 24rpx; color: #999; }
  390. .pb-tags { margin-top: 10rpx; .pb-tag { font-size: 22rpx; color: #FF9800; background: rgba(255,152,0,0.1); padding: 4rpx 16rpx; border-radius: 6rpx; } }
  391. }
  392. .view-profile-btn {
  393. display: flex; align-items: center; background: #FFF3E0; padding: 10rpx 20rpx; border-radius: 30rpx;
  394. text { font-size: 24rpx; color: #FF9800; font-weight: bold; }
  395. .arrow { margin-left: 6rpx; font-size: 20rpx; }
  396. }
  397. }
  398. /* 用户档案 */
  399. .user-profile-card {
  400. .bi-row {
  401. display: flex; align-items: flex-start; padding: 20rpx 0; border-bottom: 1rpx solid #f5f5f5;
  402. &.no-border { border-bottom: none; }
  403. .bi-icon { width: 32rpx; height: 32rpx; margin-right: 20rpx; margin-top: 6rpx; opacity: 0.6; }
  404. .bi-content {
  405. flex: 1;
  406. .bi-label { font-size: 24rpx; color: #999; margin-bottom: 4rpx; display: block; }
  407. .bi-val { font-size: 28rpx; color: #333; font-weight: 500; }
  408. &.bi-blur { color: #bbb; font-weight: normal; }
  409. }
  410. }
  411. }
  412. /* 服务详情 */
  413. .service-info-card {
  414. .si-row {
  415. display: flex; padding: 24rpx 0; border-bottom: 1rpx solid #f5f5f5;
  416. &.no-border { border-bottom: none; }
  417. .si-icon { width: 36rpx; height: 36rpx; margin-right: 20rpx; margin-top: 4rpx; }
  418. .si-content {
  419. flex: 1;
  420. .si-label { font-size: 24rpx; color: #999; display: block; margin-bottom: 6rpx; }
  421. .si-val { font-size: 28rpx; color: #333; }
  422. .si-addr-title { font-size: 30rpx; font-weight: bold; color: #333; display: block; margin-bottom: 8rpx; }
  423. .si-addr-desc { font-size: 24rpx; color: #666; }
  424. }
  425. }
  426. }
  427. .addr-row {
  428. position: relative;
  429. .icon-circle {
  430. width: 44rpx; height: 44rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  431. font-size: 22rpx; color: #fff; margin-right: 20rpx; flex-shrink: 0; margin-top: 6rpx;
  432. &.start { background: #FF9800; }
  433. &.end { background: #4CAF50; }
  434. &.service { background: #2196F3; }
  435. }
  436. .route-line-vertical { position: absolute; left: 22rpx; top: 56rpx; bottom: -10rpx; width: 2rpx; border-left: 2rpx dashed #ddd; }
  437. }
  438. .nav-btn-circle { width: 56rpx; height: 56rpx; border-radius: 50%; background: #FFF3E0; display: flex; align-items: center; justify-content: center; margin-left: 20rpx; .nav-arrow { width: 28rpx; height: 28rpx; } }
  439. /* 日志板块 */
  440. .tl-title-row { display: flex; align-items: center; margin-bottom: 30rpx; }
  441. .orange-bar { width: 8rpx; height: 32rpx; background-color: #FF9800; margin-right: 16rpx; border-radius: 4rpx; }
  442. .tl-title { font-size: 30rpx; font-weight: bold; color: #333; }
  443. .tl-hint { font-size: 22rpx; color: #FF9800; margin-left: auto; }
  444. .tl-list { padding-left: 10rpx; }
  445. .tl-item { display: flex; position: relative; padding-bottom: 40rpx; }
  446. .tl-item:last-child { padding-bottom: 0; }
  447. .tl-marker { width: 16rpx; height: 16rpx; border-radius: 50%; background-color: #E0E0E0; position: absolute; left: 0; top: 12rpx; z-index: 2; display: flex; justify-content: center; align-items: center; }
  448. .tl-marker.active { background-color: #fff; border: 3rpx solid #FF9800; width: 18rpx; height: 18rpx; left: -1rpx; }
  449. .tl-dot-inner { width: 10rpx; height: 10rpx; border-radius: 50%; background-color: #FF9800; }
  450. .tl-item:not(:last-child)::after { content: ''; position: absolute; left: 7rpx; top: 32rpx; bottom: -10rpx; width: 2rpx; background-color: #FFE0B2; z-index: 1; }
  451. .tl-content-row { margin-left: 44rpx; display: flex; flex-direction: column; width: 100%; }
  452. .tl-header { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 12rpx; }
  453. .tl-status { font-size: 28rpx; color: #333; font-weight: bold; }
  454. .tl-time { font-size: 24rpx; color: #999; }
  455. .tl-remark { font-size: 24rpx; color: #666; background-color: #F9F9F9; padding: 16rpx; border-radius: 12rpx; line-height: 1.5; }
  456. /* 弹窗通用 */
  457. .pet-modal-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 999; display: flex; align-items: center; justify-content: center; }
  458. .pet-modal-content { width: 680rpx; background: #fff; border-radius: 24rpx; overflow: hidden; }
  459. .pet-modal-header { padding: 30rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #f5f5f5; .pet-modal-title { font-size: 34rpx; font-weight: bold; } .close-icon-btn { font-size: 40rpx; color: #999; } }
  460. .pet-modal-scroll { padding: 30rpx; height: 75vh; box-sizing: border-box; }
  461. .pet-base-info { display: flex; align-items: center; margin-bottom: 40rpx; .pm-avatar { width: 120rpx; height: 120rpx; border-radius: 50%; margin-right: 30rpx; } }
  462. .pm-name-row { display: flex; align-items: center; margin-bottom: 12rpx; .pm-name { font-size: 36rpx; font-weight: bold; margin-right: 20rpx; } .pm-gender { background: #E3F2FD; padding: 4rpx 16rpx; border-radius: 20rpx; text { font-size: 22rpx; color: #1E88E5; } &.female { background: #FCE4EC; text { color: #D81B60; } } } }
  463. .pm-breed { font-size: 26rpx; color: #999; }
  464. .pm-detail-grid { display: flex; flex-wrap: wrap; gap: 20rpx; .pm-grid-item { background: #f8f8f8; padding: 24rpx; border-radius: 16rpx; display: flex; flex-direction: column; &.half { width: calc(50% - 10rpx); box-sizing: border-box; } &.full { width: 100%; box-sizing: border-box; } .pm-label { font-size: 24rpx; color: #999; margin-bottom: 8rpx; } .pm-val { font-size: 28rpx; color: #333; font-weight: 500; } } }
  465. .pm-bottom-close { margin-top: 40rpx; background: #f5f5f5; color: #666; border-radius: 40rpx; font-size: 30rpx; }
  466. .nav-modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.4); z-index: 1000; }
  467. .nav-action-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 24rpx 24rpx 0 0; padding-bottom: env(safe-area-inset-bottom);
  468. .nav-sheet-title { padding: 30rpx; text-align: center; font-size: 28rpx; color: #999; border-bottom: 1rpx solid #eee; }
  469. .nav-sheet-item { padding: 30rpx; text-align: center; font-size: 32rpx; color: #333; border-bottom: 1rpx solid #eee; &.cancel { color: #f26d6d; border-bottom: none; } }
  470. .nav-sheet-gap { height: 12rpx; background: #f5f5f5; }
  471. }
  472. .loading-container { padding: 100rpx 40rpx; .skeleton-header { display: flex; justify-content: space-between; margin-bottom: 60rpx; } .skeleton-card { background: #fff; border-radius: 20rpx; padding: 30rpx; margin-bottom: 30rpx; } .skeleton-line { background: #f0f0f0; border-radius: 4rpx; } .skeleton-ani { animation: skeleton-loading 1.4s ease infinite; } }
  473. @keyframes skeleton-loading { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
  474. </style>