Quellcode durchsuchen

基本框架已初步搭建完成

Huanyi vor 2 Wochen
Ursprung
Commit
fb6cd81586

+ 9 - 0
api/auth.js

@@ -0,0 +1,9 @@
+import { request } from '@/utils/request'
+
+export function login(data) {
+  return request({
+    url: '/auth/login',
+    method: 'post',
+    data
+  })
+}

+ 8 - 0
api/service/list.js

@@ -0,0 +1,8 @@
+import { request } from '@/utils/request'
+
+export function listAll() {
+  return request({
+    url: '/service/list/listAll',
+    method: 'get'
+  })
+}

+ 8 - 0
api/system/agreement.js

@@ -0,0 +1,8 @@
+import { request } from '@/utils/request'
+
+export function getAgreement(id) {
+  return request({
+    url: `/system/agreement/${id}`,
+    method: 'get'
+  })
+}

+ 8 - 0
api/system/user.js

@@ -0,0 +1,8 @@
+import { request } from '@/utils/request'
+
+export function getInfo() {
+  return request({
+    url: '/system/user/getInfo',
+    method: 'get'
+  })
+}

+ 90 - 0
components/nav-bar/index.vue

@@ -0,0 +1,90 @@
+<template>
+	<view class="nav-bar" :style="{ backgroundColor: bgColor }">
+		<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>
+			</view>
+			<view class="title-box">
+				<text class="title-text" :style="{ color: color }">{{ title }}</text>
+			</view>
+			<view class="right-box">
+				<slot name="right"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+
+const props = defineProps({
+	title: {
+		type: String,
+		default: ''
+	},
+	showBack: {
+		type: Boolean,
+		default: true
+	},
+	bgColor: {
+		type: String,
+		default: '#ffffff'
+	},
+	color: {
+		type: String,
+		default: '#000000'
+	}
+})
+
+const statusBarHeight = ref(44)
+
+onMounted(() => {
+	const sysInfo = uni.getSystemInfoSync()
+	statusBarHeight.value = sysInfo.statusBarHeight || 44
+})
+
+const goBack = () => {
+	uni.navigateBack()
+}
+</script>
+
+<style lang="scss" scoped>
+.nav-bar {
+	position: sticky;
+	top: 0;
+	z-index: 999;
+	width: 100%;
+}
+
+.nav-content {
+	height: 44px;
+	display: flex;
+	align-items: center;
+	padding: 0 32rpx;
+	position: relative;
+}
+
+.left-box {
+	position: absolute;
+	left: 32rpx;
+	z-index: 10;
+}
+
+.title-box {
+	flex: 1;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.title-text {
+	font-size: 32rpx;
+	font-weight: bold;
+}
+
+.right-box {
+	position: absolute;
+	right: 32rpx;
+}
+</style>

+ 138 - 0
components/policy-dialog/index.vue

@@ -0,0 +1,138 @@
+<template>
+	<view class="policy-dialog" v-if="visible" @click="onMaskClick">
+		<view class="dialog-content" @click.stop>
+			<view class="dialog-header">
+				<text class="dialog-title">{{ title }}</text>
+				<view class="close-btn" @click="onClose">
+					<uni-icons type="closeempty" size="20" color="#999"></uni-icons>
+				</view>
+			</view>
+
+			<scroll-view scroll-y class="dialog-body">
+				<view class="policy-content">
+					<text class="policy-text">{{ content }}</text>
+				</view>
+			</scroll-view>
+
+			<view class="dialog-footer">
+				<button class="confirm-btn" @click="onClose">我知道了</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+
+const props = defineProps({
+	visible: {
+		type: Boolean,
+		default: false
+	},
+	title: {
+		type: String,
+		default: ''
+	},
+	content: {
+		type: String,
+		default: ''
+	},
+	maskClosable: {
+		type: Boolean,
+		default: true
+	}
+})
+
+const emit = defineEmits(['update:visible', 'close'])
+
+const onClose = () => {
+	emit('update:visible', false)
+	emit('close')
+}
+
+const onMaskClick = () => {
+	if (props.maskClosable) {
+		onClose()
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.policy-dialog {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.5);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 9999;
+	padding: 40rpx;
+}
+
+.dialog-content {
+	width: 100%;
+	max-height: 80vh;
+	background: #fff;
+	border-radius: 32rpx;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+}
+
+.dialog-header {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	position: relative;
+	padding: 32rpx;
+	border-bottom: 1rpx solid #f5f5f5;
+}
+
+.dialog-title {
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+.close-btn {
+	position: absolute;
+	right: 32rpx;
+	top: 50%;
+	transform: translateY(-50%);
+}
+
+.dialog-body {
+	flex: 1;
+	padding: 32rpx;
+	overflow-y: auto;
+}
+
+.policy-content {
+	line-height: 1.8;
+}
+
+.policy-text {
+	font-size: 28rpx;
+	color: #666;
+	white-space: pre-wrap;
+}
+
+.dialog-footer {
+	padding: 24rpx 32rpx;
+	border-top: 1rpx solid #f5f5f5;
+}
+
+.confirm-btn {
+	width: 100%;
+	height: 88rpx;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	color: #333;
+	border: none;
+	border-radius: 44rpx;
+	font-size: 30rpx;
+	font-weight: bold;
+}
+</style>

+ 11 - 0
enums/agreementType.js

@@ -0,0 +1,11 @@
+/**
+ * 协议类型枚举
+ */
+export const AgreementType = {
+	USER_AGREEMENT: 1,
+	PRIVACY_POLICY: 2,
+	PERFORMER_GUIDE: 3,
+	SHIPPING_AGREEMENT: 4
+}
+
+export default AgreementType

+ 6 - 6
json/orderStatus.json

@@ -1,8 +1,8 @@
 [
-	{ "value": "all", "label": "全部订单" },
-	{ "value": "wait_dispatch", "label": "待派单" },
-	{ "value": "wait_accept", "label": "待接单" },
-	{ "value": "serving", "label": "服务中" },
-	{ "value": "done", "label": "已完成" },
-	{ "value": "cancel", "label": "已取消" }
+	{ "value": 0, "label": "待派单", "color": "#f56c6c" },
+	{ "value": 1, "label": "待接单", "color": "#e6a23c" },
+	{ "value": 2, "label": "待服务", "color": "#49a3ff" },
+	{ "value": 3, "label": "服务中", "color": "#49a3ff" },
+	{ "value": 4, "label": "已完成", "color": "#67c23a" },
+	{ "value": 5, "label": "已取消", "color": "#909399" }
 ]

+ 4 - 0
json/serviceMode.json

@@ -0,0 +1,4 @@
+{
+	"0": "服务订单",
+	"1": "接送订单"
+}

+ 3 - 1
manifest.json

@@ -15,7 +15,9 @@
             "autoclose" : true,
             "delay" : 0
         },
-        "modules" : {},
+        "modules" : {
+            "VideoPlayer" : {}
+        },
         "distribute" : {
             "android" : {
                 "permissions" : [

+ 50 - 25
pages.json

@@ -16,13 +16,15 @@
 		{
 			"path": "pages/service/all/index",
 			"style": {
-				"navigationBarTitleText": "全部服务"
+				"navigationBarTitleText": "全部服务",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/order/list/index",
 			"style": {
-				"navigationBarTitleText": "订单列表"
+				"navigationBarTitleText": "订单列表",
+				"navigationStyle": "custom"
 			}
 		},
 		{
@@ -42,140 +44,163 @@
 		{
 			"path": "pages/service/detail/index",
 			"style": {
-				"navigationBarTitleText": "服务详情"
+				"navigationBarTitleText": "服务详情",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/service/review/index",
 			"style": {
-				"navigationBarTitleText": "全部评价"
+				"navigationBarTitleText": "全部评价",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/order/apply/index",
 			"style": {
-				"navigationBarTitleText": "订单申请"
+				"navigationBarTitleText": "订单申请",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/order/detail/index",
 			"style": {
-				"navigationBarTitleText": "订单详情"
+				"navigationBarTitleText": "订单详情",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/store/apply/index",
 			"style": {
-				"navigationBarTitleText": "商家入驻"
+				"navigationBarTitleText": "商家入驻",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/pet/list/index",
 			"style": {
-				"navigationBarTitleText": "宠物档案"
+				"navigationBarTitleText": "宠物档案",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/pet/edit/index",
 			"style": {
-				"navigationBarTitleText": "编辑宠物"
+				"navigationBarTitleText": "编辑宠物",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/pet/add/index",
 			"style": {
-				"navigationBarTitleText": "新增宠物"
+				"navigationBarTitleText": "新增宠物",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/pet/detail/index",
 			"style": {
-				"navigationBarTitleText": "宠物详情"
+				"navigationBarTitleText": "宠物详情",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/user/list/index",
 			"style": {
-				"navigationBarTitleText": "用户管理"
+				"navigationBarTitleText": "用户管理",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/user/edit/index",
 			"style": {
-				"navigationBarTitleText": "编辑用户"
+				"navigationBarTitleText": "编辑用户",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/user/add/index",
 			"style": {
-				"navigationBarTitleText": "新增用户"
+				"navigationBarTitleText": "新增用户",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/user/detail/index",
 			"style": {
-				"navigationBarTitleText": "用户详情"
+				"navigationBarTitleText": "用户详情",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/review/list/index",
 			"style": {
-				"navigationBarTitleText": "我的评价"
+				"navigationBarTitleText": "我的评价",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/agreement/list/index",
 			"style": {
-				"navigationBarTitleText": "协议中心"
+				"navigationBarTitleText": "协议中心",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/agreement/detail/index",
 			"style": {
-				"navigationBarTitleText": "协议详情"
+				"navigationBarTitleText": "协议详情",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/settings/index",
 			"style": {
-				"navigationBarTitleText": "设置"
+				"navigationBarTitleText": "设置",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/settings/account-delete/index",
 			"style": {
-				"navigationBarTitleText": "账号注销"
+				"navigationBarTitleText": "账号注销",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/settings/change-password/index",
 			"style": {
-				"navigationBarTitleText": "修改密码"
+				"navigationBarTitleText": "修改密码",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/complaint/list/index",
 			"style": {
-				"navigationBarTitleText": "投诉管理"
+				"navigationBarTitleText": "投诉管理",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/complaint/submit/index",
 			"style": {
-				"navigationBarTitleText": "提交投诉"
+				"navigationBarTitleText": "提交投诉",
+				"navigationStyle": "custom"
 			}
 		},
 		{
 			"path": "pages/my/fee/statistics/index",
 			"style": {
-				"navigationBarTitleText": "服务费统计"
+				"navigationBarTitleText": "服务费统计",
+				"navigationStyle": "custom"
 			}
 		}
 	],
 	"globalStyle": {
+		"navigationStyle": "custom",
 		"navigationBarTextStyle": "black",
 		"navigationBarTitleText": "宠物服务商家端",
-		"navigationBarBackgroundColor": "#f7ca3e",
+		"navigationBarBackgroundColor": "#ffffff",
 		"backgroundColor": "#f2f2f2",
 		"app-plus": {
 			"background": "#f2f2f2"

+ 23 - 8
pages/index/index.vue

@@ -72,16 +72,15 @@
 		</view>
 
 		<view class="recommend-list">
-			<view class="recommend-card" v-for="(item, index) in services" :key="index" @click="goToDetail(item.title)">
-				<image :src="item.image" class="item-img" mode="aspectFill"></image>
+			<view class="recommend-card" v-for="(item, index) in services" :key="index" @click="goToDetail(item.name)">
+				<image :src="item.iconUrl" class="item-img" mode="aspectFill"></image>
 				<view class="item-info">
 					<view class="item-header">
-						<text class="item-title">{{ item.title }}</text>
-						<text class="booked">{{ item.booked }}</text>
+						<text class="item-title">{{ item.name }}</text>
 					</view>
-					<text class="item-desc">{{ item.desc }}</text>
+					<text class="item-desc">{{ item.remark }}</text>
 					<view class="item-bottom">
-						<text class="tag" v-for="tag in item.tags" :key="tag">{{ tag }}</text>
+						<text class="tag" v-if="serviceModeEnum[item.mode]">{{ serviceModeEnum[item.mode] }}</text>
 					</view>
 				</view>
 			</view>
@@ -93,11 +92,27 @@
 
 <script setup>
 import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 import mockData from '@/mock/index.json'
+import { listAll } from '@/api/service/list'
+import serviceModeEnum from '@/json/serviceMode.json'
 
 const bannerImages = mockData.bannerImages
-const services = ref(mockData.services)
+const services = ref([])
+
+const fetchServices = async () => {
+	try {
+		const res = await listAll()
+		services.value = res || []
+	} catch (error) {
+		console.error("Failed to fetch services", error)
+	}
+}
+
+onShow(() => {
+	fetchServices()
+})
 
 const onLocationClick = () => {
 	uni.showToast({ title: '城市选择功能即将上线', icon: 'none' })
@@ -124,7 +139,7 @@ const goToDetail = (type) => {
 
 <style lang="scss" scoped>
 .home-page {
-	background-image: linear-gradient(to bottom, #f7ca3e 0%, #f7ca3e 440rpx, #f2f2f2 440rpx, #f2f2f2 100%);
+	background-image: linear-gradient(to bottom, #FFD93D 0%, #FFD93D 440rpx, #f2f2f2 440rpx, #f2f2f2 100%);
 	min-height: 100vh;
 	padding-bottom: 120rpx;
 }

+ 250 - 35
pages/login/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="login-page">
+		<nav-bar title="登录" bgColor="transparent" color="#fff"></nav-bar>
 		<!-- 顶部渐变装饰区 -->
 		<view class="hero-bg">
 			<view class="deco-circle c1"></view>
@@ -30,7 +31,8 @@
 				<view class="input-icon-wrap">
 					<uni-icons type="person" size="18" color="#ffc837"></uni-icons>
 				</view>
-				<input class="custom-input" v-model="username" placeholder="请输入登录账号" placeholder-class="input-placeholder" />
+				<input class="custom-input" v-model="username" placeholder="请输入登录账号"
+					placeholder-class="input-placeholder" />
 			</view>
 
 			<!-- 密码输入框 -->
@@ -38,7 +40,8 @@
 				<view class="input-icon-wrap">
 					<uni-icons type="locked" size="18" color="#ffc837"></uni-icons>
 				</view>
-				<input class="custom-input" v-model="password" type="password" placeholder="请输入密码" placeholder-class="input-placeholder" />
+				<input class="custom-input" v-model="password" type="password" placeholder="请输入密码"
+					placeholder-class="input-placeholder" />
 			</view>
 
 			<!-- 提示信息 -->
@@ -53,9 +56,9 @@
 					<label class="agree-label">
 						<checkbox :checked="checked" color="#ffc837" style="transform: scale(0.7);" />
 						<text class="agree-text">我已阅读并同意</text>
-						<text class="text-link" @click.stop="showAgreement('隐私政策')">《隐私政策》</text>
+						<text class="text-link" @click.stop="showAgreement(2)">《隐私政策》</text>
 						<text class="agree-text">和</text>
-						<text class="text-link" @click.stop="showAgreement('商家托运协议')">《托运协议》</text>
+						<text class="text-link" @click.stop="showAgreement(4)">《托运协议》</text>
 					</label>
 				</checkbox-group>
 			</view>
@@ -68,15 +71,27 @@
 		<view class="footer-hint">
 			<text>安全加密 · 保护您的账号信息</text>
 		</view>
+
+		<!-- 隐私政策/托运协议弹窗 -->
+		<policy-dialog v-model:visible="dialogVisible" :title="dialogTitle" :content="dialogContent"></policy-dialog>
 	</view>
 </template>
 
 <script setup>
 import { ref } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
+import policyDialog from '@/components/policy-dialog/index.vue'
+import { login } from '@/api/auth'
+import { getAgreement } from '@/api/system/agreement'
+import { AgreementType } from '@/enums/agreementType'
+import { DEFAULT_HEADERS } from '@/utils/config'
 
 const username = ref('')
 const password = ref('')
 const checked = ref(false)
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const dialogContent = ref('')
 
 const onClickLeft = () => uni.navigateBack()
 
@@ -84,7 +99,7 @@ const onCheckChange = () => {
 	checked.value = !checked.value
 }
 
-const onSubmit = () => {
+const onSubmit = async () => {
 	if (!username.value) {
 		uni.showToast({ title: '请填写账号', icon: 'none' })
 		return
@@ -97,40 +112,240 @@ const onSubmit = () => {
 		uni.showToast({ title: '请先阅读并勾选协议', icon: 'none' })
 		return
 	}
-	uni.showToast({ title: '登录成功 (模拟)', icon: 'success' })
-	setTimeout(() => {
-		uni.reLaunch({ url: '/pages/index/index' })
-	}, 1000)
+
+	try {
+		uni.showLoading({ title: '登录中...' })
+		const res = await login({
+			userSource: 0,
+			username: username.value,
+			password: password.value,
+			clientId: DEFAULT_HEADERS.clientid,
+			grantType: 'password',
+			source: 1
+		})
+
+		if (res.access_token) {
+			uni.setStorageSync('token', res.access_token)
+			uni.showToast({ title: '登录成功', icon: 'success' })
+			setTimeout(() => {
+				uni.reLaunch({ url: '/pages/index/index' })
+			}, 1000)
+		} else {
+			uni.showToast({ title: '登录异常:未获取到Token', icon: 'none' })
+		}
+	} catch (error) {
+		console.error('Login error:', error)
+		// 注意这里的提示也可以通过request.js自带去提示,这里静默或者输出
+	} finally {
+		uni.hideLoading()
+	}
 }
 
-const showAgreement = (name) => {
-	uni.showToast({ title: `查看 ${name} 详情 (演示)`, icon: 'none' })
+const showAgreement = async (agreementId) => {
+	try {
+		uni.showLoading({ title: '加载中...' })
+		const res = await getAgreement(agreementId)
+
+		if (res && res.title) {
+			dialogTitle.value = res.title || '协议详情'
+			dialogContent.value = res.content || '暂无内容'
+			dialogVisible.value = true
+		} else {
+			console.warn('接口返回数据格式异常:', res)
+			uni.showToast({ title: '数据格式异常', icon: 'none' })
+		}
+	} catch (error) {
+		console.error('获取协议失败:', error)
+		uni.showToast({ title: '加载失败,请稍后重试', icon: 'none' })
+	} finally {
+		uni.hideLoading()
+	}
 }
 </script>
 
 <style lang="scss" scoped>
-.login-page { min-height: 100vh; background: #f2f3f7; display: flex; flex-direction: column; }
-.hero-bg { background: linear-gradient(150deg, #ffd53f 0%, #ff9500 100%); padding: 0 40rpx 140rpx; position: relative; overflow: hidden; min-height: 560rpx; display: flex; flex-direction: column; justify-content: flex-end; }
-.deco-circle { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.12); }
-.c1 { width: 400rpx; height: 400rpx; top: -160rpx; right: -120rpx; }
-.c2 { width: 260rpx; height: 260rpx; top: 80rpx; left: -100rpx; }
-.c3 { width: 160rpx; height: 160rpx; bottom: 80rpx; right: 80rpx; }
-.back-btn { position: absolute; top: calc(var(--status-bar-height, 44px) + 20rpx); left: 32rpx; width: 72rpx; height: 72rpx; border-radius: 50%; background: rgba(255, 255, 255, 0.25); display: flex; align-items: center; justify-content: center; }
-.hero-content { position: relative; z-index: 2; }
-.logo-wrap { width: 140rpx; height: 140rpx; background: rgba(255, 255, 255, 0.25); border-radius: 44rpx; display: flex; align-items: center; justify-content: center; margin-bottom: 28rpx; }
-.brand-name { display: block; font-size: 52rpx; font-weight: 800; color: #fff; margin-bottom: 8rpx; }
-.brand-desc { display: block; font-size: 28rpx; color: rgba(255, 255, 255, 0.8); letter-spacing: 4rpx; }
-.form-card { background: #fff; border-radius: 56rpx 56rpx 0 0; margin-top: -60rpx; padding: 60rpx 48rpx 40rpx; flex: 1; position: relative; z-index: 10; }
-.form-title { display: block; font-size: 38rpx; font-weight: 700; color: #1a1a1a; margin-bottom: 48rpx; }
-.input-group { display: flex; align-items: center; background: #f8f8f8; border-radius: 28rpx; padding: 8rpx 24rpx; margin-bottom: 28rpx; border: 3rpx solid transparent; }
-.input-icon-wrap { margin-right: 16rpx; flex-shrink: 0; }
-.custom-input { flex: 1; height: 88rpx; font-size: 30rpx; color: #222; background: transparent; }
-.input-placeholder { color: #c0c0c0; }
-.tip-row { display: flex; align-items: center; gap: 10rpx; font-size: 24rpx; color: #aaa; padding: 8rpx 8rpx 24rpx; }
-.agreement-row { margin-bottom: 48rpx; padding: 0 4rpx; }
-.agree-label { display: flex; align-items: center; flex-wrap: wrap; }
-.agree-text { font-size: 24rpx; color: #999; }
-.text-link { font-size: 24rpx; color: #ff9500; }
-.login-btn { width: 100%; height: 104rpx; font-size: 34rpx; font-weight: 700; color: #333; background: linear-gradient(90deg, #ffd53f, #ff9500); border: none; border-radius: 52rpx; letter-spacing: 4rpx; }
-.footer-hint { text-align: center; font-size: 22rpx; color: #bbb; padding: 32rpx 0 60rpx; background: #fff; }
+.login-page {
+	min-height: 100vh;
+	background: #f2f3f7;
+	display: flex;
+	flex-direction: column;
+}
+
+.hero-bg {
+	background: linear-gradient(150deg, #ffd53f 0%, #ff9500 100%);
+	padding: 0 40rpx 140rpx;
+	position: relative;
+	overflow: hidden;
+	min-height: 560rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: flex-end;
+	margin-top: calc(-44px - var(--status-bar-height, 44px));
+}
+
+.deco-circle {
+	position: absolute;
+	border-radius: 50%;
+	background: rgba(255, 255, 255, 0.12);
+}
+
+.c1 {
+	width: 400rpx;
+	height: 400rpx;
+	top: -160rpx;
+	right: -120rpx;
+}
+
+.c2 {
+	width: 260rpx;
+	height: 260rpx;
+	top: 80rpx;
+	left: -100rpx;
+}
+
+.c3 {
+	width: 160rpx;
+	height: 160rpx;
+	bottom: 80rpx;
+	right: 80rpx;
+}
+
+.back-btn {
+	position: absolute;
+	top: calc(var(--status-bar-height, 44px) + 20rpx);
+	left: 32rpx;
+	width: 72rpx;
+	height: 72rpx;
+	border-radius: 50%;
+	background: rgba(255, 255, 255, 0.25);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.hero-content {
+	position: relative;
+	z-index: 2;
+}
+
+.logo-wrap {
+	width: 140rpx;
+	height: 140rpx;
+	background: rgba(255, 255, 255, 0.25);
+	border-radius: 44rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	margin-bottom: 28rpx;
+}
+
+.brand-name {
+	display: block;
+	font-size: 52rpx;
+	font-weight: 800;
+	color: #fff;
+	margin-bottom: 8rpx;
+}
+
+.brand-desc {
+	display: block;
+	font-size: 28rpx;
+	color: rgba(255, 255, 255, 0.8);
+	letter-spacing: 4rpx;
+}
+
+.form-card {
+	background: #fff;
+	border-radius: 56rpx 56rpx 0 0;
+	margin-top: -60rpx;
+	padding: 60rpx 48rpx 40rpx;
+	flex: 1;
+	position: relative;
+	z-index: 10;
+}
+
+.form-title {
+	display: block;
+	font-size: 38rpx;
+	font-weight: 700;
+	color: #1a1a1a;
+	margin-bottom: 48rpx;
+}
+
+.input-group {
+	display: flex;
+	align-items: center;
+	background: #f8f8f8;
+	border-radius: 28rpx;
+	padding: 8rpx 24rpx;
+	margin-bottom: 28rpx;
+	border: 3rpx solid transparent;
+}
+
+.input-icon-wrap {
+	margin-right: 16rpx;
+	flex-shrink: 0;
+}
+
+.custom-input {
+	flex: 1;
+	height: 88rpx;
+	font-size: 30rpx;
+	color: #222;
+	background: transparent;
+}
+
+.input-placeholder {
+	color: #c0c0c0;
+}
+
+.tip-row {
+	display: flex;
+	align-items: center;
+	gap: 10rpx;
+	font-size: 24rpx;
+	color: #aaa;
+	padding: 8rpx 8rpx 24rpx;
+}
+
+.agreement-row {
+	margin-bottom: 48rpx;
+	padding: 0 4rpx;
+}
+
+.agree-label {
+	display: flex;
+	align-items: center;
+	flex-wrap: wrap;
+}
+
+.agree-text {
+	font-size: 24rpx;
+	color: #999;
+}
+
+.text-link {
+	font-size: 24rpx;
+	color: #ff9500;
+}
+
+.login-btn {
+	width: 100%;
+	height: 104rpx;
+	font-size: 34rpx;
+	font-weight: 700;
+	color: #333;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	border: none;
+	border-radius: 52rpx;
+	letter-spacing: 4rpx;
+}
+
+.footer-hint {
+	text-align: center;
+	font-size: 22rpx;
+	color: #bbb;
+	padding: 32rpx 0 60rpx;
+	background: #fff;
+}
 </style>

+ 37 - 6
pages/my/agreement/list/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="agreement-list-page">
+		<nav-bar title="协议列表"></nav-bar>
 		<view class="list-container">
 			<view class="agreement-item" v-for="item in agreements" :key="item.id" @click="goToDetail(item)">
 				<view class="item-info">
@@ -14,6 +15,7 @@
 
 <script setup>
 import { ref } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
 const agreements = ref([
 	{ id: 1, title: '隐私政策', updateTime: '2024-01-15' },
 	{ id: 2, title: '用户服务协议', updateTime: '2024-01-15' },
@@ -24,10 +26,39 @@ const goToDetail = (item) => uni.navigateTo({ url: `/pages/my/agreement/detail/i
 </script>
 
 <style lang="scss" scoped>
-.agreement-list-page { min-height: 100vh; background: #f7f8fa; }
-.list-container { padding: 24rpx; }
-.agreement-item { display: flex; align-items: center; background: #fff; border-radius: 20rpx; padding: 32rpx; margin-bottom: 16rpx; }
-.item-info { flex: 1; }
-.item-title { display: block; font-size: 30rpx; color: #333; font-weight: 600; margin-bottom: 8rpx; }
-.item-date { display: block; font-size: 24rpx; color: #999; }
+.agreement-list-page {
+	min-height: 100vh;
+	background: #f7f8fa;
+}
+
+.list-container {
+	padding: 24rpx;
+}
+
+.agreement-item {
+	display: flex;
+	align-items: center;
+	background: #fff;
+	border-radius: 20rpx;
+	padding: 32rpx;
+	margin-bottom: 16rpx;
+}
+
+.item-info {
+	flex: 1;
+}
+
+.item-title {
+	display: block;
+	font-size: 30rpx;
+	color: #333;
+	font-weight: 600;
+	margin-bottom: 8rpx;
+}
+
+.item-date {
+	display: block;
+	font-size: 24rpx;
+	color: #999;
+}
 </style>

+ 103 - 19
pages/my/complaint/list/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="complaint-list-page">
+		<nav-bar title="我的评价"></nav-bar>
 		<view class="list-container">
 			<view class="empty-state" v-if="historyList.length === 0">
 				<text>暂无投诉记录</text>
@@ -7,7 +8,8 @@
 			<view v-else class="history-card" v-for="item in historyList" :key="item.id">
 				<view class="card-header">
 					<text class="order-no">订单号:{{ item.orderNo }}</text>
-					<text :class="['status-text', item.status === '已处理' ? 'text-green' : 'text-orange']">{{ item.status }}</text>
+					<text :class="['status-text', item.status === '已处理' ? 'text-green' : 'text-orange']">{{ item.status
+						}}</text>
 				</view>
 				<view class="card-body">
 					<view class="rate-row">
@@ -16,7 +18,8 @@
 					</view>
 					<text class="content-text">{{ item.content }}</text>
 					<view class="images-preview" v-if="item.images && item.images.length">
-						<image v-for="(img, idx) in item.images" :key="idx" :src="img" class="preview-img" mode="aspectFill"></image>
+						<image v-for="(img, idx) in item.images" :key="idx" :src="img" class="preview-img"
+							mode="aspectFill"></image>
 					</view>
 				</view>
 				<view class="card-footer">
@@ -29,27 +32,108 @@
 
 <script setup>
 import { ref } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
 import complaintMockData from '@/mock/complaint.json'
 
 const historyList = ref(complaintMockData)
 </script>
 
 <style lang="scss" scoped>
-.complaint-list-page { min-height: 100vh; background: #f7f8fa; padding-bottom: 40rpx; }
-.list-container { padding: 24rpx; }
-.empty-state { text-align: center; padding: 100rpx 0; color: #999; font-size: 28rpx; }
-.history-card { background: #fff; border-radius: 24rpx; padding: 32rpx; margin-bottom: 24rpx; }
-.card-header { display: flex; justify-content: space-between; border-bottom: 1rpx solid #f5f5f5; padding-bottom: 20rpx; margin-bottom: 24rpx; font-size: 26rpx; }
-.order-no { color: #666; }
-.status-text { font-weight: bold; }
-.text-green { color: #4caf50; }
-.text-orange { color: #ff9800; }
-.rate-row { display: flex; align-items: center; margin-bottom: 16rpx; }
-.label { font-size: 26rpx; color: #333; }
-.stars { color: #f7ca3e; font-size: 28rpx; margin-left: 8rpx; }
-.content-text { font-size: 28rpx; color: #333; line-height: 1.5; margin-bottom: 20rpx; }
-.images-preview { display: flex; gap: 16rpx; flex-wrap: wrap; }
-.preview-img { width: 120rpx; height: 120rpx; border-radius: 8rpx; border: 1rpx solid #eee; }
-.card-footer { display: flex; justify-content: flex-end; border-top: 1rpx solid #f5f5f5; padding-top: 20rpx; }
-.time { font-size: 24rpx; color: #999; }
+.complaint-list-page {
+	min-height: 100vh;
+	background: #f7f8fa;
+	padding-bottom: 40rpx;
+}
+
+.list-container {
+	padding: 24rpx;
+}
+
+.empty-state {
+	text-align: center;
+	padding: 100rpx 0;
+	color: #999;
+	font-size: 28rpx;
+}
+
+.history-card {
+	background: #fff;
+	border-radius: 24rpx;
+	padding: 32rpx;
+	margin-bottom: 24rpx;
+}
+
+.card-header {
+	display: flex;
+	justify-content: space-between;
+	border-bottom: 1rpx solid #f5f5f5;
+	padding-bottom: 20rpx;
+	margin-bottom: 24rpx;
+	font-size: 26rpx;
+}
+
+.order-no {
+	color: #666;
+}
+
+.status-text {
+	font-weight: bold;
+}
+
+.text-green {
+	color: #4caf50;
+}
+
+.text-orange {
+	color: #ff9800;
+}
+
+.rate-row {
+	display: flex;
+	align-items: center;
+	margin-bottom: 16rpx;
+}
+
+.label {
+	font-size: 26rpx;
+	color: #333;
+}
+
+.stars {
+	color: #f7ca3e;
+	font-size: 28rpx;
+	margin-left: 8rpx;
+}
+
+.content-text {
+	font-size: 28rpx;
+	color: #333;
+	line-height: 1.5;
+	margin-bottom: 20rpx;
+}
+
+.images-preview {
+	display: flex;
+	gap: 16rpx;
+	flex-wrap: wrap;
+}
+
+.preview-img {
+	width: 120rpx;
+	height: 120rpx;
+	border-radius: 8rpx;
+	border: 1rpx solid #eee;
+}
+
+.card-footer {
+	display: flex;
+	justify-content: flex-end;
+	border-top: 1rpx solid #f5f5f5;
+	padding-top: 20rpx;
+}
+
+.time {
+	font-size: 24rpx;
+	color: #999;
+}
 </style>

+ 204 - 40
pages/my/fee/statistics/index.vue

@@ -1,30 +1,55 @@
 <template>
 	<view class="fee-statistics-page">
-		<!-- 总览卡片 -->
-		<view class="summary-card">
-			<view class="summary-item">
-				<text class="sum-value">¥12,580</text>
-				<text class="sum-label">本月服务费</text>
-			</view>
-			<view class="divider"></view>
-			<view class="summary-item">
-				<text class="sum-value">¥86,320</text>
-				<text class="sum-label">累计服务费</text>
+		<NavBar title="费用统计" bgColor="#ffffff" color="#000"></NavBar>
+		<!-- 顶部背景 -->
+		<view class="header-bg">
+			<view class="total-title">总服务费 (元)</view>
+			<view class="total-amount">500.00</view>
+			<view class="date-picker-wrap">
+				<picker mode="date" fields="day" @change="onDateChange">
+					<view class="date-range">
+						<text>2026/02/25 - 2026/03/27</text>
+						<uni-icons type="bottom" size="14" color="#fff"></uni-icons>
+					</view>
+				</picker>
 			</view>
 		</view>
 
-		<!-- 月度明细 -->
-		<view class="section-title">
-			<text>月度明细</text>
+		<!-- 统计卡片 -->
+		<view class="stats-card">
+			<view class="stats-item">
+				<text class="stats-value">3</text>
+				<text class="stats-label">单数</text>
+			</view>
+			<view class="stats-divider"></view>
+			<view class="stats-item">
+				<text class="stats-value">280.00</text>
+				<text class="stats-label">最高单笔</text>
+			</view>
+			<view class="stats-divider"></view>
+			<view class="stats-item">
+				<text class="stats-value">166.67</text>
+				<text class="stats-label">平均单笔</text>
+			</view>
 		</view>
-		<view class="detail-list">
-			<view class="detail-card" v-for="item in monthList" :key="item.month">
-				<view class="detail-left">
-					<text class="month-text">{{ item.month }}</text>
-					<text class="count-text">{{ item.orderCount }} 单</text>
-				</view>
-				<view class="detail-right">
-					<text class="amount-text">¥{{ item.amount }}</text>
+
+		<!-- 订单明细 -->
+		<view class="detail-section">
+			<view class="section-header">
+				<view class="header-line"></view>
+				<text class="header-text">订单明细</text>
+			</view>
+
+			<view class="detail-list">
+				<view class="detail-item" v-for="(item, index) in orderDetails" :key="index">
+					<view class="detail-info">
+						<view class="detail-title">{{ item.title }}</view>
+						<view class="detail-time">{{ item.time }}</view>
+					</view>
+					<view class="detail-amount-wrap">
+						<view class="detail-amount">+{{ item.amount }}</view>
+						<view class="detail-no">{{ item.orderNo }}</view>
+					</view>
 				</view>
 			</view>
 		</view>
@@ -33,27 +58,166 @@
 
 <script setup>
 import { ref } from 'vue'
-const monthList = ref([
-	{ month: '2024年02月', orderCount: 45, amount: '12,580' },
-	{ month: '2024年01月', orderCount: 62, amount: '18,420' },
-	{ month: '2023年12月', orderCount: 38, amount: '10,350' },
-	{ month: '2023年11月', orderCount: 55, amount: '15,800' },
-	{ month: '2023年10月', orderCount: 41, amount: '11,200' },
-	{ month: '2023年09月', orderCount: 52, amount: '17,970' }
+import NavBar from '@/components/nav-bar/index.vue'
+
+const orderDetails = ref([
+	{ title: '宠物寄养(1天)', time: '2026-03-19 09:00', amount: '100.00', orderNo: 'ORD20260319011' },
+	{ title: '狗狗全套美容', time: '2026-03-17 10:30', amount: '280.00', orderNo: 'ORD20260317022' },
+	{ title: '猫咪洗澡护肤', time: '2026-03-15 14:00', amount: '120.00', orderNo: 'ORD20260315001' }
 ])
+
+const onDateChange = (e) => {
+	console.log('date change', e.detail.value)
+}
 </script>
 
 <style lang="scss" scoped>
-.fee-statistics-page { min-height: 100vh; background: #f7f8fa; }
-.summary-card { display: flex; background: linear-gradient(135deg, #ffd53f, #ff9500); margin: 24rpx; border-radius: 24rpx; padding: 48rpx 32rpx; }
-.summary-item { flex: 1; text-align: center; }
-.sum-value { display: block; font-size: 44rpx; font-weight: 900; color: #fff; }
-.sum-label { display: block; font-size: 24rpx; color: rgba(255,255,255,0.8); margin-top: 8rpx; }
-.divider { width: 2rpx; background: rgba(255,255,255,0.3); margin: 0 24rpx; }
-.section-title { padding: 32rpx 32rpx 16rpx; font-size: 30rpx; font-weight: bold; color: #333; }
-.detail-list { padding: 0 24rpx; }
-.detail-card { display: flex; justify-content: space-between; align-items: center; background: #fff; border-radius: 20rpx; padding: 28rpx 32rpx; margin-bottom: 16rpx; }
-.month-text { display: block; font-size: 28rpx; color: #333; font-weight: 600; }
-.count-text { display: block; font-size: 24rpx; color: #999; margin-top: 8rpx; }
-.amount-text { font-size: 32rpx; font-weight: 800; color: #ff9500; }
+.fee-statistics-page {
+	min-height: 100vh;
+	background-color: #f8f9fb;
+}
+
+.header-bg {
+	background: linear-gradient(180deg, #ff7b00 0%, #ff9500 100%);
+	padding: 60rpx 40rpx 140rpx;
+	text-align: center;
+	color: #fff;
+}
+
+.total-title {
+	font-size: 28rpx;
+	opacity: 0.9;
+	margin-bottom: 20rpx;
+}
+
+.total-amount {
+	font-size: 80rpx;
+	font-weight: 900;
+	margin-bottom: 40rpx;
+}
+
+.date-picker-wrap {
+	display: inline-block;
+}
+
+.date-range {
+	background: rgba(255, 255, 255, 0.2);
+	padding: 12rpx 32rpx;
+	border-radius: 40rpx;
+	font-size: 24rpx;
+	display: flex;
+	align-items: center;
+	gap: 8rpx;
+}
+
+.stats-card {
+	background: #fff;
+	margin: -80rpx 32rpx 40rpx;
+	padding: 40rpx 0;
+	border-radius: 32rpx;
+	display: flex;
+	align-items: center;
+	box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.05);
+	position: relative;
+	z-index: 2;
+}
+
+.stats-item {
+	flex: 1;
+	text-align: center;
+	display: flex;
+	flex-direction: column;
+	gap: 12rpx;
+}
+
+.stats-value {
+	font-size: 36rpx;
+	font-weight: 800;
+	color: #1a1a1a;
+}
+
+.stats-label {
+	font-size: 22rpx;
+	color: #999;
+}
+
+.stats-divider {
+	width: 2rpx;
+	height: 60rpx;
+	background: #f0f0f0;
+}
+
+.detail-section {
+	padding: 0 32rpx;
+}
+
+.section-header {
+	display: flex;
+	align-items: center;
+	gap: 12rpx;
+	margin-bottom: 24rpx;
+}
+
+.header-line {
+	width: 8rpx;
+	height: 32rpx;
+	background: #ff7b00;
+	border-radius: 4rpx;
+}
+
+.header-text {
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+.detail-list {
+	display: flex;
+	flex-direction: column;
+	gap: 20rpx;
+}
+
+.detail-item {
+	background: #fff;
+	padding: 32rpx;
+	border-radius: 24rpx;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.detail-info {
+	display: flex;
+	flex-direction: column;
+	gap: 12rpx;
+}
+
+.detail-title {
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+.detail-time {
+	font-size: 24rpx;
+	color: #999;
+}
+
+.detail-amount-wrap {
+	text-align: right;
+	display: flex;
+	flex-direction: column;
+	gap: 8rpx;
+}
+
+.detail-amount {
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #ff5252;
+}
+
+.detail-no {
+	font-size: 22rpx;
+	color: #ccc;
+}
 </style>

+ 59 - 17
pages/my/index.vue

@@ -3,13 +3,14 @@
 		<!-- 顶部背景墙 -->
 		<view class="header-curve">
 			<view class="user-block" @click="goToLogin">
-				<image class="user-avatar" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
+				<image class="user-avatar" :src="userInfo?.avatarUrl || '/static/images/default-avatar.png'"
 					mode="aspectFill"></image>
 				<view class="user-info">
-					<text class="user-name">点击登录</text>
-					<text class="user-desc-text">登录后享受更多权益 🐾</text>
+					<text class="user-name">{{ userInfo ? userInfo.nickName : '点击登录' }}</text>
+					<text class="user-desc-text">{{ userInfo ? (userInfo.remark || '这位用户很懒,什么都没写 🐾') : '登录后享受更多权益 🐾'
+					}}</text>
 				</view>
-				<uni-icons type="right" size="16" color="rgba(100, 70, 20, 0.4)"></uni-icons>
+				<uni-icons v-if="!userInfo" type="right" size="16" color="rgba(100, 70, 20, 0.4)"></uni-icons>
 			</view>
 			<view class="wave-shape"></view>
 		</view>
@@ -53,14 +54,46 @@
 
 <script setup>
 import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import { getInfo } from '@/api/system/user'
 import customTabbar from '@/components/custom-tabbar/index.vue'
+import orderStatusData from '@/json/orderStatus.json'
+
+const userInfo = ref(null)
+
+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
+	}
+}
+
+onShow(() => {
+	fetchUserInfo()
+})
 
 const goToLogin = () => {
-	uni.navigateTo({ url: '/pages/login/index' })
+	if (!userInfo.value) {
+		uni.navigateTo({ url: '/pages/login/index' })
+	}
 }
 
-const goToOrder = (status) => {
-	uni.reLaunch({ url: `/pages/order/list/index?status=${status}` })
+const goToOrder = (statusValue) => {
+	// statusValue 可以是 'all' 或枚举值
+	if (statusValue === 'all') {
+		uni.reLaunch({ url: '/pages/order/list/index' })
+	} else {
+		uni.reLaunch({ url: `/pages/order/list/index?status=${statusValue}` })
+	}
 }
 
 const goToMenu = (item) => {
@@ -69,20 +102,28 @@ const goToMenu = (item) => {
 	}
 }
 
-// 订单图标
-const orderItems = [
-	{ key: 'wait_dispatch', label: '待派单', icon: '/static/icon/order-wait.svg' },
-	{ key: 'wait_accept', label: '待接单', icon: '/static/icon/order-accept.svg' },
-	{ key: 'serving', label: '服务中', icon: '/static/icon/order-service.svg' },
-	{ key: 'done', label: '已完成', icon: '/static/icon/order-done.svg' },
-	{ key: 'cancel', label: '已取消', icon: '/static/icon/order-cancel.svg' }
-]
+// 订单图标 - 从 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/review/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' }
@@ -245,7 +286,8 @@ const menuItems = [
 	height: 44rpx;
 }
 
-.nav-label, .menu-text {
+.nav-label,
+.menu-text {
 	font-size: 22rpx;
 	font-weight: 700;
 	color: #6d5b45;

+ 64 - 9
pages/my/pet/edit/index.vue

@@ -50,13 +50,68 @@ const onSave = () => {
 </script>
 
 <style lang="scss" scoped>
-.pet-edit-page { min-height: 100vh; background: #f7f8fa; padding: 24rpx; padding-bottom: 160rpx; }
-.form-card { background: #fff; border-radius: 24rpx; padding: 8rpx 32rpx; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.form-item:last-child { border-bottom: none; }
-.form-label { width: 180rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
-.form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.picker-value { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.form-textarea { flex: 1; font-size: 28rpx; color: #333; height: 160rpx; }
-.save-btn { margin-top: 48rpx; width: 100%; height: 96rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 48rpx; font-size: 32rpx; font-weight: bold; line-height: 96rpx; }
+.pet-edit-page {
+	min-height: 100vh;
+	background: #f7f8fa;
+	padding: 24rpx;
+	padding-bottom: 160rpx;
+}
+
+.form-card {
+	background: #fff;
+	border-radius: 24rpx;
+	padding: 8rpx 32rpx;
+}
+
+.form-item {
+	display: flex;
+	align-items: center;
+	padding: 28rpx 0;
+	border-bottom: 1rpx solid #f5f5f5;
+}
+
+.form-item:last-child {
+	border-bottom: none;
+}
+
+.form-label {
+	width: 180rpx;
+	font-size: 28rpx;
+	color: #333;
+	flex-shrink: 0;
+}
+
+.form-input {
+	flex: 1;
+	font-size: 28rpx;
+	color: #333;
+	text-align: right;
+}
+
+.picker-value {
+	flex: 1;
+	font-size: 28rpx;
+	color: #333;
+	text-align: right;
+}
+
+.form-textarea {
+	flex: 1;
+	font-size: 28rpx;
+	color: #333;
+	height: 160rpx;
+}
+
+.save-btn {
+	margin-top: 48rpx;
+	width: 100%;
+	height: 96rpx;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	color: #333;
+	border: none;
+	border-radius: 48rpx;
+	font-size: 32rpx;
+	font-weight: bold;
+	line-height: 96rpx;
+}
 </style>

+ 22 - 0
pages/my/pet/list/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="pet-list-page">
+		<nav-bar title="宠物档案"></nav-bar>
 		<!-- 顶部操作栏 -->
 		<view class="action-bar">
 			<view class="search-box">
@@ -38,6 +39,7 @@
 
 <script setup>
 import { ref } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
 import petMockData from '@/mock/pet.json'
 
 const goToAdd = () => uni.navigateTo({ url: '/pages/my/pet/add/index' })
@@ -74,6 +76,7 @@ const pets = ref(petMockData.map(p => ({
 	background-color: #f2f2f2;
 	padding-bottom: 40rpx;
 }
+
 .action-bar {
 	display: flex;
 	align-items: center;
@@ -81,6 +84,7 @@ const pets = ref(petMockData.map(p => ({
 	background-color: #fff;
 	gap: 16rpx;
 }
+
 .search-box {
 	flex: 1;
 	display: flex;
@@ -90,11 +94,13 @@ const pets = ref(petMockData.map(p => ({
 	padding: 12rpx 20rpx;
 	gap: 12rpx;
 }
+
 .search-input {
 	flex: 1;
 	font-size: 26rpx;
 	background: transparent;
 }
+
 .add-btn {
 	font-size: 24rpx;
 	font-weight: bold;
@@ -105,9 +111,11 @@ const pets = ref(petMockData.map(p => ({
 	padding: 12rpx 28rpx;
 	white-space: nowrap;
 }
+
 .list-container {
 	padding: 24rpx;
 }
+
 .pet-card {
 	display: flex;
 	background: #fff;
@@ -116,43 +124,51 @@ const pets = ref(petMockData.map(p => ({
 	margin-bottom: 24rpx;
 	box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
 }
+
 .pet-photo {
 	width: 210rpx;
 	height: 280rpx;
 	flex-shrink: 0;
 }
+
 .card-info {
 	flex: 1;
 	padding: 24rpx;
 	display: flex;
 	flex-direction: column;
 }
+
 .info-top {
 	display: flex;
 	justify-content: space-between;
 	align-items: center;
 	margin-bottom: 8rpx;
 }
+
 .pet-name {
 	font-size: 34rpx;
 	font-weight: 800;
 	color: #333;
 }
+
 .owner-name {
 	font-size: 24rpx;
 	color: #999;
 }
+
 .pet-meta {
 	font-size: 26rpx;
 	color: #666;
 	margin-bottom: 16rpx;
 }
+
 .health-overview {
 	display: flex;
 	align-items: center;
 	gap: 16rpx;
 	margin-bottom: 20rpx;
 }
+
 .health-badge {
 	font-size: 22rpx;
 	color: #2e7d32;
@@ -160,6 +176,7 @@ const pets = ref(petMockData.map(p => ({
 	padding: 4rpx 16rpx;
 	border-radius: 8rpx;
 }
+
 .vaccine-info {
 	font-size: 22rpx;
 	color: #795548;
@@ -167,16 +184,19 @@ const pets = ref(petMockData.map(p => ({
 	padding: 4rpx 16rpx;
 	border-radius: 8rpx;
 }
+
 .card-footer {
 	margin-top: auto;
 	border-top: 1rpx solid #f8f8f8;
 	padding-top: 16rpx;
 }
+
 .action-btn-group {
 	display: flex;
 	justify-content: flex-end;
 	gap: 16rpx;
 }
+
 .btn-item {
 	font-size: 24rpx;
 	padding: 8rpx 20rpx;
@@ -184,11 +204,13 @@ const pets = ref(petMockData.map(p => ({
 	border: 1rpx solid #eee;
 	color: #666;
 }
+
 .btn-item.detail {
 	background: #fdf6ec;
 	border-color: #faecd8;
 	color: #e6a23c;
 }
+
 .btn-item.delete {
 	color: #f56c6c;
 	border-color: #fde2e2;

+ 0 - 0
pages/my/settings/account-delete → pages/my/settings/account-delete/index.vue


+ 5 - 1
pages/my/settings/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="settings-page">
+		<nav-bar title="设置"></nav-bar>
 		<view class="menu-list">
 			<view class="cell-group">
 				<view class="cell-item" @click="onTodo">
@@ -34,6 +35,7 @@
 <script setup>
 import { ref } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
+import navBar from '@/components/nav-bar/index.vue'
 
 const cacheSize = ref('0 KB')
 
@@ -71,7 +73,7 @@ const onClearCache = () => {
 					if (token) {
 						uni.setStorageSync('token', token)
 					}
-					
+
 					calculateCacheSize()
 					uni.showToast({ title: '清理成功', icon: 'success' })
 				} catch (e) {
@@ -88,6 +90,8 @@ const onLogout = () => {
 		content: '确定要退出当前账号吗?',
 		success: (res) => {
 			if (res.confirm) {
+				// 清空 token
+				uni.removeStorageSync('token')
 				uni.showToast({ title: '已安全退出', icon: 'success' })
 				setTimeout(() => uni.reLaunch({ url: '/pages/login/index' }), 1000)
 			}

+ 0 - 31
pages/my/settings/password

@@ -1,31 +0,0 @@
-<template>
-	<view class="change-password-page">
-		<view class="form-card">
-			<view class="form-item"><text class="form-label">原密码</text><input class="form-input" v-model="form.oldPassword" type="password" placeholder="请输入原密码" /></view>
-			<view class="form-item"><text class="form-label">新密码</text><input class="form-input" v-model="form.newPassword" type="password" placeholder="请输入新密码" /></view>
-			<view class="form-item"><text class="form-label">确认密码</text><input class="form-input" v-model="form.confirmPassword" type="password" placeholder="请再次输入新密码" /></view>
-		</view>
-		<button class="save-btn" @click="onSave">确认修改</button>
-	</view>
-</template>
-
-<script setup>
-import { reactive } from 'vue'
-const form = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
-const onSave = () => {
-	if (!form.oldPassword || !form.newPassword) { uni.showToast({ title: '请填写完整', icon: 'none' }); return }
-	if (form.newPassword !== form.confirmPassword) { uni.showToast({ title: '两次密码不一致', icon: 'none' }); return }
-	uni.showToast({ title: '修改成功', icon: 'success' })
-	setTimeout(() => uni.navigateBack(), 1000)
-}
-</script>
-
-<style lang="scss" scoped>
-.change-password-page { min-height: 100vh; background: #f7f8fa; padding: 24rpx; padding-bottom: 160rpx; }
-.form-card { background: #fff; border-radius: 24rpx; padding: 8rpx 32rpx; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.form-item:last-child { border-bottom: none; }
-.form-label { width: 180rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
-.form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.save-btn { margin-top: 48rpx; width: 100%; height: 96rpx; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 48rpx; font-size: 32rpx; font-weight: bold; line-height: 96rpx; }
-</style>

+ 77 - 0
pages/my/settings/password/index.vue

@@ -0,0 +1,77 @@
+<template>
+	<view class="change-password-page">
+		<view class="form-card">
+			<view class="form-item"><text class="form-label">原密码</text><input class="form-input"
+					v-model="form.oldPassword" type="password" placeholder="请输入原密码" /></view>
+			<view class="form-item"><text class="form-label">新密码</text><input class="form-input"
+					v-model="form.newPassword" type="password" placeholder="请输入新密码" /></view>
+			<view class="form-item"><text class="form-label">确认密码</text><input class="form-input"
+					v-model="form.confirmPassword" type="password" placeholder="请再次输入新密码" /></view>
+		</view>
+		<button class="save-btn" @click="onSave">确认修改</button>
+	</view>
+</template>
+
+<script setup>
+import { reactive } from 'vue'
+const form = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
+const onSave = () => {
+	if (!form.oldPassword || !form.newPassword) { uni.showToast({ title: '请填写完整', icon: 'none' }); return }
+	if (form.newPassword !== form.confirmPassword) { uni.showToast({ title: '两次密码不一致', icon: 'none' }); return }
+	uni.showToast({ title: '修改成功', icon: 'success' })
+	setTimeout(() => uni.navigateBack(), 1000)
+}
+</script>
+
+<style lang="scss" scoped>
+.change-password-page {
+	min-height: 100vh;
+	background: #f7f8fa;
+	padding: 24rpx;
+	padding-bottom: 160rpx;
+}
+
+.form-card {
+	background: #fff;
+	border-radius: 24rpx;
+	padding: 8rpx 32rpx;
+}
+
+.form-item {
+	display: flex;
+	align-items: center;
+	padding: 28rpx 0;
+	border-bottom: 1rpx solid #f5f5f5;
+}
+
+.form-item:last-child {
+	border-bottom: none;
+}
+
+.form-label {
+	width: 180rpx;
+	font-size: 28rpx;
+	color: #333;
+	flex-shrink: 0;
+}
+
+.form-input {
+	flex: 1;
+	font-size: 28rpx;
+	color: #333;
+	text-align: right;
+}
+
+.save-btn {
+	margin-top: 48rpx;
+	width: 100%;
+	height: 96rpx;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	color: #333;
+	border: none;
+	border-radius: 48rpx;
+	font-size: 32rpx;
+	font-weight: bold;
+	line-height: 96rpx;
+}
+</style>

+ 198 - 29
pages/my/user/list/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="user-list-page">
+		<NavBar title="用户列表" bgColor="#fff" color="#000"></NavBar>
 		<!-- 顶部操作栏 -->
 		<view class="action-bar">
 			<view class="search-box">
@@ -63,6 +64,7 @@
 
 <script setup>
 import { ref } from 'vue'
+import NavBar from '@/components/nav-bar/index.vue'
 import userMockData from '@/mock/user.json'
 
 const searchValue = ref('')
@@ -85,33 +87,200 @@ const onStatusChange = (user) => {
 </script>
 
 <style lang="scss" scoped>
-.user-list-page { min-height: 100vh; background: #f2f2f2; padding-bottom: 40rpx; }
-.action-bar { display: flex; align-items: center; padding: 20rpx 24rpx; background: #fff; gap: 16rpx; }
-.search-box { flex: 1; display: flex; align-items: center; background: #f5f5f5; border-radius: 32rpx; padding: 12rpx 20rpx; gap: 12rpx; }
-.search-input { flex: 1; font-size: 26rpx; background: transparent; }
-.filter-btn { display: flex; align-items: center; gap: 8rpx; background: #f5f5f5; border-radius: 32rpx; padding: 12rpx 20rpx; font-size: 24rpx; color: #666; }
-.add-btn { font-size: 24rpx; font-weight: bold; background: linear-gradient(90deg, #ffd53f, #ff9500); color: #333; border: none; border-radius: 32rpx; padding: 12rpx 24rpx; white-space: nowrap; }
-.list-container { padding: 24rpx; }
-.user-card { background: #fff; border-radius: 24rpx; padding: 28rpx; margin-bottom: 24rpx; }
-.user-header { display: flex; align-items: center; margin-bottom: 24rpx; padding-bottom: 24rpx; border-bottom: 1rpx solid #f9f9f9; }
-.user-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; background: #f0f0f0; margin-right: 24rpx; }
-.user-info-main { flex: 1; }
-.user-name { display: block; font-size: 32rpx; font-weight: bold; color: #333; margin-bottom: 8rpx; }
-.phone-row { display: block; font-size: 26rpx; color: #666; }
-.user-status { display: flex; flex-direction: column; align-items: center; gap: 4rpx; }
-.status-text { font-size: 20rpx; color: #ff9800; }
-.user-body { font-size: 26rpx; }
-.info-row { display: flex; margin-bottom: 16rpx; }
-.label { color: #999; width: 100rpx; flex-shrink: 0; }
-.value { color: #333; flex: 1; }
-.info-grid { display: flex; background: #fdfdfd; border: 1rpx solid #f2f2f2; border-radius: 12rpx; padding: 20rpx; margin-bottom: 20rpx; }
-.grid-cell { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8rpx; }
-.grid-cell .label { width: auto; font-size: 24rpx; }
-.grid-cell .value { font-size: 30rpx; font-weight: bold; }
-.text-warning { color: #ff9800; }
-.source-box { background: #fff8e1; padding: 16rpx; border-radius: 8rpx; }
-.source-tag { display: block; color: #ff9800; font-weight: bold; font-size: 24rpx; margin-bottom: 8rpx; }
-.create-time { font-size: 22rpx; color: #a1887f; }
-.card-actions { display: flex; justify-content: flex-end; gap: 20rpx; margin-top: 24rpx; padding-top: 24rpx; border-top: 1rpx dashed #eee; }
-.action-btn { border: 1rpx solid #e0e0e0; color: #666; font-size: 24rpx; background: transparent; border-radius: 12rpx; padding: 8rpx 24rpx; }
+.user-list-page {
+	min-height: 100vh;
+	background: #f2f2f2;
+	padding-bottom: 40rpx;
+}
+
+.action-bar {
+	display: flex;
+	align-items: center;
+	padding: 20rpx 24rpx;
+	background: #fff;
+	gap: 16rpx;
+}
+
+.search-box {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	background: #f5f5f5;
+	border-radius: 32rpx;
+	padding: 12rpx 20rpx;
+	gap: 12rpx;
+}
+
+.search-input {
+	flex: 1;
+	font-size: 26rpx;
+	background: transparent;
+}
+
+.filter-btn {
+	display: flex;
+	align-items: center;
+	gap: 8rpx;
+	background: #f5f5f5;
+	border-radius: 32rpx;
+	padding: 12rpx 20rpx;
+	font-size: 24rpx;
+	color: #666;
+}
+
+.add-btn {
+	font-size: 24rpx;
+	font-weight: bold;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	color: #333;
+	border: none;
+	border-radius: 32rpx;
+	padding: 12rpx 24rpx;
+	white-space: nowrap;
+}
+
+.list-container {
+	padding: 24rpx;
+}
+
+.user-card {
+	background: #fff;
+	border-radius: 24rpx;
+	padding: 28rpx;
+	margin-bottom: 24rpx;
+}
+
+.user-header {
+	display: flex;
+	align-items: center;
+	margin-bottom: 24rpx;
+	padding-bottom: 24rpx;
+	border-bottom: 1rpx solid #f9f9f9;
+}
+
+.user-avatar {
+	width: 88rpx;
+	height: 88rpx;
+	border-radius: 50%;
+	background: #f0f0f0;
+	margin-right: 24rpx;
+}
+
+.user-info-main {
+	flex: 1;
+}
+
+.user-name {
+	display: block;
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+	margin-bottom: 8rpx;
+}
+
+.phone-row {
+	display: block;
+	font-size: 26rpx;
+	color: #666;
+}
+
+.user-status {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	gap: 4rpx;
+}
+
+.status-text {
+	font-size: 20rpx;
+	color: #ff9800;
+}
+
+.user-body {
+	font-size: 26rpx;
+}
+
+.info-row {
+	display: flex;
+	margin-bottom: 16rpx;
+}
+
+.label {
+	color: #999;
+	width: 100rpx;
+	flex-shrink: 0;
+}
+
+.value {
+	color: #333;
+	flex: 1;
+}
+
+.info-grid {
+	display: flex;
+	background: #fdfdfd;
+	border: 1rpx solid #f2f2f2;
+	border-radius: 12rpx;
+	padding: 20rpx;
+	margin-bottom: 20rpx;
+}
+
+.grid-cell {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	gap: 8rpx;
+}
+
+.grid-cell .label {
+	width: auto;
+	font-size: 24rpx;
+}
+
+.grid-cell .value {
+	font-size: 30rpx;
+	font-weight: bold;
+}
+
+.text-warning {
+	color: #ff9800;
+}
+
+.source-box {
+	background: #fff8e1;
+	padding: 16rpx;
+	border-radius: 8rpx;
+}
+
+.source-tag {
+	display: block;
+	color: #ff9800;
+	font-weight: bold;
+	font-size: 24rpx;
+	margin-bottom: 8rpx;
+}
+
+.create-time {
+	font-size: 22rpx;
+	color: #a1887f;
+}
+
+.card-actions {
+	display: flex;
+	justify-content: flex-end;
+	gap: 20rpx;
+	margin-top: 24rpx;
+	padding-top: 24rpx;
+	border-top: 1rpx dashed #eee;
+}
+
+.action-btn {
+	border: 1rpx solid #e0e0e0;
+	color: #666;
+	font-size: 24rpx;
+	background: transparent;
+	border-radius: 12rpx;
+	padding: 8rpx 24rpx;
+}
 </style>

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

@@ -246,8 +246,8 @@ const onSubmit = () => {
 		uni.hideLoading()
 		uni.showToast({ title: '下单成功', icon: 'success' })
 		setTimeout(() => {
-		uni.reLaunch({ url: '/pages/order/list/index' })
-	}, 1500)
+			uni.reLaunch({ url: '/pages/order/list/index' })
+		}, 1500)
 	}, 1500)
 }
 </script>

+ 564 - 94
pages/order/detail/index.vue

@@ -78,8 +78,7 @@
 		<view class="detail-tabs-wrap">
 			<view class="tab-nav">
 				<view v-for="tab in tabList" :key="tab.name"
-					:class="['tab-nav-item', { active: activeTab === tab.name }]"
-					@click="activeTab = tab.name">
+					:class="['tab-nav-item', { active: activeTab === tab.name }]" @click="activeTab = tab.name">
 					<text>{{ tab.title }}</text>
 				</view>
 			</view>
@@ -309,96 +308,567 @@ const onCancelOrder = () => {
 	padding-bottom: 160rpx;
 }
 
-.order-header { padding: 24rpx 32rpx 16rpx; }
-.order-id-row { display: flex; align-items: center; gap: 16rpx; flex-wrap: wrap; }
-.order-id { font-size: 26rpx; color: #666; font-family: monospace; }
-.status-badge { font-size: 22rpx; padding: 4rpx 16rpx; border-radius: 20rpx; font-weight: 600; }
-.badge-wait_dispatch { background: #fff0f0; color: #f44336; }
-.badge-wait_accept { background: #fff8e1; color: #ff9800; }
-.badge-serving { background: #e3f2fd; color: #2196f3; }
-.badge-done { background: #e8f5e9; color: #4caf50; }
-.badge-cancel { background: #f5f5f5; color: #9e9e9e; }
-.service-badge { font-size: 22rpx; padding: 4rpx 16rpx; border-radius: 20rpx; background: #fff3e0; color: #ff9500; }
-
-.progress-card { background: #fff; margin: 0 24rpx 24rpx; border-radius: 28rpx; padding: 32rpx 16rpx 24rpx; }
-.progress-steps { display: flex; align-items: flex-start; }
-.step-item { display: flex; flex-direction: column; align-items: center; flex: 1; position: relative; z-index: 1; }
-.step-circle { width: 44rpx; height: 44rpx; border-radius: 50%; background: #e0e0e0; display: flex; align-items: center; justify-content: center; margin-bottom: 8rpx; }
-.step-num { font-size: 20rpx; color: #fff; }
-.step-item.done .step-circle, .step-item.active .step-circle { background: #ff9500; }
-.step-line { position: absolute; top: 22rpx; left: 50%; width: 100%; height: 4rpx; background: #e0e0e0; z-index: 0; }
-.step-line.done { background: #ff9500; }
-.step-label { font-size: 20rpx; color: #999; text-align: center; }
-.step-item.done .step-label, .step-item.active .step-label { color: #ff9500; font-weight: 600; }
-.step-time { font-size: 18rpx; color: #bbb; margin-top: 4rpx; }
-
-.info-row-cards { display: flex; gap: 20rpx; margin: 0 24rpx 24rpx; }
-.info-card { flex: 1; background: #fff; border-radius: 28rpx; padding: 24rpx; min-width: 0; }
-.card-label { display: block; font-size: 24rpx; font-weight: 700; color: #333; margin-bottom: 20rpx; border-left: 6rpx solid #ff9500; padding-left: 12rpx; }
-.pet-header { display: flex; align-items: center; gap: 16rpx; 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-basic { flex: 1; min-width: 0; }
-.pet-name { display: block; font-size: 28rpx; font-weight: bold; color: #222; }
-.pet-tags { display: flex; gap: 8rpx; margin-top: 6rpx; }
-.mini-tag { font-size: 20rpx; background: #f5f5f5; border-radius: 8rpx; padding: 2rpx 10rpx; color: #666; }
-.breed-badge { font-size: 20rpx; background: #ffe0b2; color: #e65100; border-radius: 8rpx; padding: 4rpx 12rpx; }
-.pet-attrs { display: flex; flex-wrap: wrap; gap: 12rpx 0; }
-.attr-item { width: 50%; }
-.attr-item.full { width: 100%; }
-.attr-label { display: block; font-size: 20rpx; color: #aaa; }
-.attr-val { display: block; font-size: 22rpx; color: #333; }
-.attr-val.highlight { color: #4caf50; }
-.user-header { display: flex; align-items: center; gap: 16rpx; 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; font-weight: bold; color: #222; }
-.user-phone { display: block; font-size: 24rpx; color: #666; }
-.service-address-box { background: #fff8f0; border-radius: 16rpx; padding: 16rpx; }
-.addr-label { display: block; font-size: 20rpx; color: #ff9500; margin-bottom: 6rpx; }
-.addr-text { display: block; font-size: 22rpx; color: #333; line-height: 1.5; }
-
-.detail-tabs-wrap { margin: 0 24rpx; background: #fff; border-radius: 28rpx; overflow: hidden; }
-.tab-nav { display: flex; border-bottom: 1rpx solid #f0f0f0; }
-.tab-nav-item { flex: 1; text-align: center; padding: 24rpx 0; font-size: 24rpx; color: #666; position: relative; }
-.tab-nav-item.active { color: #ff9500; font-weight: bold; }
-.tab-nav-item.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 48rpx; height: 6rpx; background: #ff9500; border-radius: 6rpx; }
-.tab-content { padding: 28rpx; min-height: 280rpx; }
-
-.base-info-grid { display: flex; flex-wrap: wrap; gap: 20rpx; margin-bottom: 32rpx; }
-.bi-item { width: calc(50% - 10rpx); background: #f8f8f8; border-radius: 16rpx; padding: 16rpx 20rpx; }
-.bi-label { display: block; font-size: 20rpx; color: #aaa; margin-bottom: 6rpx; }
-.bi-val { display: block; font-size: 24rpx; color: #333; font-weight: 500; }
-.bi-val.highlight { color: #f44336; }
-.sub-title { display: block; font-size: 26rpx; font-weight: 700; color: #333; margin-bottom: 20rpx; padding-left: 12rpx; border-left: 6rpx solid #ff9500; }
-
-.route-block { background: #f9f9f9; border-radius: 20rpx; padding: 20rpx 24rpx; margin-bottom: 20rpx; }
-.route-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; }
-.route-tag { font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 8rpx; }
-.route-tag.pick { background: #e3f2fd; color: #2196f3; }
-.route-tag.send { background: #e8f5e9; color: #4caf50; }
-.route-tag.arrow { background: #2196f3; color: #fff; }
-.route-tag.send-arrow { background: #4caf50; }
-.route-time { font-size: 22rpx; color: #ff9500; margin-left: auto; }
-.route-addr { display: flex; align-items: center; gap: 8rpx; font-size: 24rpx; color: #555; }
-
-.assignee-card { background: #f9f9f9; border-radius: 24rpx; padding: 28rpx; }
-.assignee-header { display: flex; gap: 24rpx; }
-.assignee-avatar { width: 100rpx; height: 100rpx; border-radius: 50%; background: #e0e0e0; display: flex; align-items: center; justify-content: center; }
-.assignee-name { display: block; font-size: 30rpx; font-weight: bold; color: #222; margin-bottom: 8rpx; }
-.assignee-phone, .assignee-zone { display: block; font-size: 24rpx; color: #666; margin-bottom: 4rpx; }
-
-.timeline { position: relative; padding-left: 40rpx; }
-.tl-item { position: relative; margin-bottom: 40rpx; padding-left: 32rpx; }
-.tl-dot { position: absolute; left: -12rpx; top: 8rpx; width: 24rpx; height: 24rpx; border-radius: 50%; background: #ff9500; border: 4rpx solid #fff; box-shadow: 0 0 0 4rpx #ff9500; }
-.tl-item::before { content: ''; position: absolute; left: -2rpx; top: 32rpx; bottom: -40rpx; width: 4rpx; background: #f0f0f0; }
-.tl-item:last-child::before { display: none; }
-.tl-time { display: block; font-size: 22rpx; color: #aaa; margin-bottom: 6rpx; }
-.tl-title { display: block; font-size: 28rpx; font-weight: 600; color: #222; margin-bottom: 6rpx; }
-.tl-desc { display: block; font-size: 24rpx; color: #666; }
-.log-dot { background: #e0e0e0; box-shadow: 0 0 0 4rpx #e0e0e0; }
-
-.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 80rpx 0; gap: 24rpx; }
-.empty-text { font-size: 26rpx; color: #bbb; }
-
-.cancel-bar { position: fixed; bottom: 0; left: 0; right: 0; padding: 24rpx 32rpx; background: rgba(255,255,255,0.95); box-shadow: 0 -8rpx 28rpx rgba(0,0,0,0.08); z-index: 100; }
-.cancel-order-btn { width: 100%; height: 96rpx; font-size: 32rpx; font-weight: 600; border: 2rpx solid #f44336; color: #f44336; background: transparent; border-radius: 48rpx; line-height: 96rpx; }
+.order-header {
+	padding: 24rpx 32rpx 16rpx;
+}
+
+.order-id-row {
+	display: flex;
+	align-items: center;
+	gap: 16rpx;
+	flex-wrap: wrap;
+}
+
+.order-id {
+	font-size: 26rpx;
+	color: #666;
+	font-family: monospace;
+}
+
+.status-badge {
+	font-size: 22rpx;
+	padding: 4rpx 16rpx;
+	border-radius: 20rpx;
+	font-weight: 600;
+}
+
+.badge-wait_dispatch {
+	background: #fff0f0;
+	color: #f44336;
+}
+
+.badge-wait_accept {
+	background: #fff8e1;
+	color: #ff9800;
+}
+
+.badge-serving {
+	background: #e3f2fd;
+	color: #2196f3;
+}
+
+.badge-done {
+	background: #e8f5e9;
+	color: #4caf50;
+}
+
+.badge-cancel {
+	background: #f5f5f5;
+	color: #9e9e9e;
+}
+
+.service-badge {
+	font-size: 22rpx;
+	padding: 4rpx 16rpx;
+	border-radius: 20rpx;
+	background: #fff3e0;
+	color: #ff9500;
+}
+
+.progress-card {
+	background: #fff;
+	margin: 0 24rpx 24rpx;
+	border-radius: 28rpx;
+	padding: 32rpx 16rpx 24rpx;
+}
+
+.progress-steps {
+	display: flex;
+	align-items: flex-start;
+}
+
+.step-item {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	flex: 1;
+	position: relative;
+	z-index: 1;
+}
+
+.step-circle {
+	width: 44rpx;
+	height: 44rpx;
+	border-radius: 50%;
+	background: #e0e0e0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	margin-bottom: 8rpx;
+}
+
+.step-num {
+	font-size: 20rpx;
+	color: #fff;
+}
+
+.step-item.done .step-circle,
+.step-item.active .step-circle {
+	background: #ff9500;
+}
+
+.step-line {
+	position: absolute;
+	top: 22rpx;
+	left: 50%;
+	width: 100%;
+	height: 4rpx;
+	background: #e0e0e0;
+	z-index: 0;
+}
+
+.step-line.done {
+	background: #ff9500;
+}
+
+.step-label {
+	font-size: 20rpx;
+	color: #999;
+	text-align: center;
+}
+
+.step-item.done .step-label,
+.step-item.active .step-label {
+	color: #ff9500;
+	font-weight: 600;
+}
+
+.step-time {
+	font-size: 18rpx;
+	color: #bbb;
+	margin-top: 4rpx;
+}
+
+.info-row-cards {
+	display: flex;
+	gap: 20rpx;
+	margin: 0 24rpx 24rpx;
+}
+
+.info-card {
+	flex: 1;
+	background: #fff;
+	border-radius: 28rpx;
+	padding: 24rpx;
+	min-width: 0;
+}
+
+.card-label {
+	display: block;
+	font-size: 24rpx;
+	font-weight: 700;
+	color: #333;
+	margin-bottom: 20rpx;
+	border-left: 6rpx solid #ff9500;
+	padding-left: 12rpx;
+}
+
+.pet-header {
+	display: flex;
+	align-items: center;
+	gap: 16rpx;
+	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-basic {
+	flex: 1;
+	min-width: 0;
+}
+
+.pet-name {
+	display: block;
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #222;
+}
+
+.pet-tags {
+	display: flex;
+	gap: 8rpx;
+	margin-top: 6rpx;
+}
+
+.mini-tag {
+	font-size: 20rpx;
+	background: #f5f5f5;
+	border-radius: 8rpx;
+	padding: 2rpx 10rpx;
+	color: #666;
+}
+
+.breed-badge {
+	font-size: 20rpx;
+	background: #ffe0b2;
+	color: #e65100;
+	border-radius: 8rpx;
+	padding: 4rpx 12rpx;
+}
+
+.pet-attrs {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 12rpx 0;
+}
+
+.attr-item {
+	width: 50%;
+}
+
+.attr-item.full {
+	width: 100%;
+}
+
+.attr-label {
+	display: block;
+	font-size: 20rpx;
+	color: #aaa;
+}
+
+.attr-val {
+	display: block;
+	font-size: 22rpx;
+	color: #333;
+}
+
+.attr-val.highlight {
+	color: #4caf50;
+}
+
+.user-header {
+	display: flex;
+	align-items: center;
+	gap: 16rpx;
+	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;
+	font-weight: bold;
+	color: #222;
+}
+
+.user-phone {
+	display: block;
+	font-size: 24rpx;
+	color: #666;
+}
+
+.service-address-box {
+	background: #fff8f0;
+	border-radius: 16rpx;
+	padding: 16rpx;
+}
+
+.addr-label {
+	display: block;
+	font-size: 20rpx;
+	color: #ff9500;
+	margin-bottom: 6rpx;
+}
+
+.addr-text {
+	display: block;
+	font-size: 22rpx;
+	color: #333;
+	line-height: 1.5;
+}
+
+.detail-tabs-wrap {
+	margin: 0 24rpx;
+	background: #fff;
+	border-radius: 28rpx;
+	overflow: hidden;
+}
+
+.tab-nav {
+	display: flex;
+	border-bottom: 1rpx solid #f0f0f0;
+}
+
+.tab-nav-item {
+	flex: 1;
+	text-align: center;
+	padding: 24rpx 0;
+	font-size: 24rpx;
+	color: #666;
+	position: relative;
+}
+
+.tab-nav-item.active {
+	color: #ff9500;
+	font-weight: bold;
+}
+
+.tab-nav-item.active::after {
+	content: '';
+	position: absolute;
+	bottom: 0;
+	left: 50%;
+	transform: translateX(-50%);
+	width: 48rpx;
+	height: 6rpx;
+	background: #ff9500;
+	border-radius: 6rpx;
+}
+
+.tab-content {
+	padding: 28rpx;
+	min-height: 280rpx;
+}
+
+.base-info-grid {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 20rpx;
+	margin-bottom: 32rpx;
+}
+
+.bi-item {
+	width: calc(50% - 10rpx);
+	background: #f8f8f8;
+	border-radius: 16rpx;
+	padding: 16rpx 20rpx;
+}
+
+.bi-label {
+	display: block;
+	font-size: 20rpx;
+	color: #aaa;
+	margin-bottom: 6rpx;
+}
+
+.bi-val {
+	display: block;
+	font-size: 24rpx;
+	color: #333;
+	font-weight: 500;
+}
+
+.bi-val.highlight {
+	color: #f44336;
+}
+
+.sub-title {
+	display: block;
+	font-size: 26rpx;
+	font-weight: 700;
+	color: #333;
+	margin-bottom: 20rpx;
+	padding-left: 12rpx;
+	border-left: 6rpx solid #ff9500;
+}
+
+.route-block {
+	background: #f9f9f9;
+	border-radius: 20rpx;
+	padding: 20rpx 24rpx;
+	margin-bottom: 20rpx;
+}
+
+.route-header {
+	display: flex;
+	align-items: center;
+	gap: 12rpx;
+	margin-bottom: 16rpx;
+}
+
+.route-tag {
+	font-size: 22rpx;
+	padding: 4rpx 12rpx;
+	border-radius: 8rpx;
+}
+
+.route-tag.pick {
+	background: #e3f2fd;
+	color: #2196f3;
+}
+
+.route-tag.send {
+	background: #e8f5e9;
+	color: #4caf50;
+}
+
+.route-tag.arrow {
+	background: #2196f3;
+	color: #fff;
+}
+
+.route-tag.send-arrow {
+	background: #4caf50;
+}
+
+.route-time {
+	font-size: 22rpx;
+	color: #ff9500;
+	margin-left: auto;
+}
+
+.route-addr {
+	display: flex;
+	align-items: center;
+	gap: 8rpx;
+	font-size: 24rpx;
+	color: #555;
+}
+
+.assignee-card {
+	background: #f9f9f9;
+	border-radius: 24rpx;
+	padding: 28rpx;
+}
+
+.assignee-header {
+	display: flex;
+	gap: 24rpx;
+}
+
+.assignee-avatar {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 50%;
+	background: #e0e0e0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.assignee-name {
+	display: block;
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #222;
+	margin-bottom: 8rpx;
+}
+
+.assignee-phone,
+.assignee-zone {
+	display: block;
+	font-size: 24rpx;
+	color: #666;
+	margin-bottom: 4rpx;
+}
+
+.timeline {
+	position: relative;
+	padding-left: 40rpx;
+}
+
+.tl-item {
+	position: relative;
+	margin-bottom: 40rpx;
+	padding-left: 32rpx;
+}
+
+.tl-dot {
+	position: absolute;
+	left: -12rpx;
+	top: 8rpx;
+	width: 24rpx;
+	height: 24rpx;
+	border-radius: 50%;
+	background: #ff9500;
+	border: 4rpx solid #fff;
+	box-shadow: 0 0 0 4rpx #ff9500;
+}
+
+.tl-item::before {
+	content: '';
+	position: absolute;
+	left: -2rpx;
+	top: 32rpx;
+	bottom: -40rpx;
+	width: 4rpx;
+	background: #f0f0f0;
+}
+
+.tl-item:last-child::before {
+	display: none;
+}
+
+.tl-time {
+	display: block;
+	font-size: 22rpx;
+	color: #aaa;
+	margin-bottom: 6rpx;
+}
+
+.tl-title {
+	display: block;
+	font-size: 28rpx;
+	font-weight: 600;
+	color: #222;
+	margin-bottom: 6rpx;
+}
+
+.tl-desc {
+	display: block;
+	font-size: 24rpx;
+	color: #666;
+}
+
+.log-dot {
+	background: #e0e0e0;
+	box-shadow: 0 0 0 4rpx #e0e0e0;
+}
+
+.empty-state {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	padding: 80rpx 0;
+	gap: 24rpx;
+}
+
+.empty-text {
+	font-size: 26rpx;
+	color: #bbb;
+}
+
+.cancel-bar {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	padding: 24rpx 32rpx;
+	background: rgba(255, 255, 255, 0.95);
+	box-shadow: 0 -8rpx 28rpx rgba(0, 0, 0, 0.08);
+	z-index: 100;
+}
+
+.cancel-order-btn {
+	width: 100%;
+	height: 96rpx;
+	font-size: 32rpx;
+	font-weight: 600;
+	border: 2rpx solid #f44336;
+	color: #f44336;
+	background: transparent;
+	border-radius: 48rpx;
+	line-height: 96rpx;
+}
 </style>

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

@@ -1,11 +1,12 @@
 <template>
 	<view class="order-list-page">
+		<nav-bar title="订单列表" :showBack="false"></nav-bar>
 		<!-- 顶部状态栏 -->
 		<view class="sticky-header">
 			<scroll-view scroll-x class="tabs-scroll" :show-scrollbar="false">
 				<view class="tabs-row">
-					<view v-for="tab in tabList" :key="tab.name"
-						:class="['tab-item', { active: activeStatus === tab.name }]" @click="onTabClick(tab.name)">
+					<view v-for="tab in tabList" :key="tab.value"
+						:class="['tab-item', { active: activeStatus === tab.value }]" @click="onTabClick(tab.value)">
 						<text>{{ tab.title }}</text>
 					</view>
 				</view>
@@ -120,22 +121,26 @@
 
 <script setup>
 import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import navBar from '@/components/nav-bar/index.vue'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 import orderMockData from '@/mock/order.json'
+import orderStatusData from '@/json/orderStatus.json'
 
 // 筛选与搜索
-const activeStatus = ref('all')
+const activeStatus = ref(-1) // -1 表示全部,其他值为枚举值
 const filterType = ref(0)
 const searchValue = ref('')
 
-const tabList = [
-	{ title: '全部订单', name: 'all' },
-	{ title: '待派单', name: 'wait_dispatch' },
-	{ title: '待接单', name: 'wait_accept' },
-	{ title: '服务中', name: 'serving' },
-	{ title: '已完成', name: 'done' },
-	{ title: '已取消', name: 'cancel' }
-]
+// 从 orderStatus.json 生成 tabList
+const tabList = ref([
+	{ title: '全部订单', value: -1 },
+	...orderStatusData.map(item => ({
+		title: item.label,
+		value: item.value,
+		color: item.color
+	}))
+])
 
 const typeOptions = [
 	{ text: '全部类型', value: 0 },
@@ -148,25 +153,23 @@ const currentTypeName = computed(() => {
 	return typeOptions[filterType.value].text
 })
 
-const statusMap = {
-	all: '全部',
-	wait_dispatch: '待派单',
-	wait_accept: '待接单',
-	serving: '服务中',
-	done: '已完成',
-	cancel: '已取消'
-}
+// 从 URL 参数初始化状态
+onLoad((options) => {
+	if (options.status !== undefined) {
+		const statusValue = parseInt(options.status)
+		if (!isNaN(statusValue)) {
+			activeStatus.value = statusValue
+		}
+	}
+})
 
-const statusKeyMap = {
-	'待派单': 'wait_dispatch',
-	'待接单': 'wait_accept',
-	'服务中': 'serving',
-	'已完成': 'done',
-	'已取消': 'cancel'
+// 根据枚举值获取状态信息
+const getStatusInfo = (value) => {
+	return orderStatusData.find(item => item.value === value)
 }
 
-const onTabClick = (name) => {
-	activeStatus.value = name
+const onTabClick = (value) => {
+	activeStatus.value = value
 }
 
 const onTypeChange = (e) => {
@@ -176,7 +179,11 @@ const onTypeChange = (e) => {
 // 搜索与过滤后的订单列表
 const filteredOrders = computed(() => {
 	return orders.value.filter(order => {
-		const statusMatch = activeStatus.value === 'all' || order.statusText === statusMap[activeStatus.value]
+		let statusMatch = true
+		if (activeStatus.value !== -1) {
+			const statusInfo = getStatusInfo(activeStatus.value)
+			statusMatch = statusInfo && order.statusText === statusInfo.label
+		}
 		const typeMap = { 1: '宠物接送', 2: '上门喂遛', 3: '上门洗护' }
 		const typeMatch = filterType.value === 0 || order.serviceType === typeMap[filterType.value]
 		const searchLower = searchValue.value.toLowerCase()
@@ -200,7 +207,7 @@ const onCancelOrder = (order) => {
 				uni.showToast({ title: '订单已取消', icon: 'success' })
 				order.statusText = '已取消'
 				order.statusClass = 'text-gray'
-				activeStatus.value = 'cancel'
+				activeStatus.value = 5
 			}
 		}
 	})
@@ -208,10 +215,12 @@ const onCancelOrder = (order) => {
 
 const goToDetail = (order) => {
 	const serviceKey = order.serviceType === '宠物接送' ? 'transport' : (order.serviceType === '上门喂遛' ? 'feed' : 'wash')
-	const statusKey = statusKeyMap[order.statusText] || 'serving'
+	// 根据 statusText 查找对应的枚举值
+	const statusInfo = orderStatusData.find(item => item.label === order.statusText)
+	const statusValue = statusInfo ? statusInfo.value : 3
 	const cancelTime = order.cancelTime ? encodeURIComponent(order.cancelTime) : ''
 	uni.navigateTo({
-		url: `/pages/order/detail/index?service=${serviceKey}&status=${statusKey}${cancelTime ? '&cancelTime=' + cancelTime : ''}`
+		url: `/pages/order/detail/index?service=${serviceKey}&status=${statusValue}${cancelTime ? '&cancelTime=' + cancelTime : ''}`
 	})
 }
 
@@ -231,7 +240,7 @@ const onComplaint = (order) => {
 
 .sticky-header {
 	position: sticky;
-	top: 0;
+	top: calc(44px + var(--status-bar-height, 44px));
 	z-index: 99;
 	background-color: #fff;
 }

+ 170 - 23
pages/service/all/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="all-services-page">
+		<nav-bar title="全部分类" :showBack="false"></nav-bar>
 		<!-- 顶部搜索栏 -->
 		<view class="header-search">
 			<view class="location-box">
@@ -54,6 +55,7 @@
 
 <script setup>
 import { ref, computed } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 import servicesMockData from '@/mock/services.json'
 
@@ -72,27 +74,172 @@ const onServiceClick = (service) => {
 </script>
 
 <style lang="scss" scoped>
-.all-services-page { height: 100vh; display: flex; flex-direction: column; background-color: #fff; }
-.header-search { display: flex; align-items: center; padding: 16rpx 32rpx; background-color: #fff; border-bottom: 1rpx solid #f2f2f2; }
-.location-box { display: flex; align-items: center; gap: 8rpx; font-size: 28rpx; color: #333; margin-right: 24rpx; }
-.search-input-wrap { flex: 1; display: flex; align-items: center; background: #f5f5f5; border-radius: 32rpx; padding: 12rpx 20rpx; gap: 12rpx; }
-.search-input { flex: 1; font-size: 26rpx; }
-.placeholder-style { color: #999; font-size: 26rpx; }
-.main-content { flex: 1; display: flex; overflow: hidden; }
-.sidebar { width: 200rpx; background-color: #f7f8fa; }
-.sidebar-item { padding: 28rpx 0; text-align: center; font-size: 26rpx; color: #666; position: relative; }
-.sidebar-item.active { background-color: #fff; color: #333; font-weight: bold; }
-.sidebar-item.active::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 6rpx; height: 40rpx; background-color: #f7ca3e; border-radius: 0 6rpx 6rpx 0; }
-.content-view { flex: 1; padding: 32rpx; }
-.category-section { margin-bottom: 48rpx; }
-.cat-section-title { font-size: 28rpx; font-weight: bold; color: #333; display: block; margin-bottom: 24rpx; background-color: #f9f9f9; padding: 8rpx 20rpx; border-radius: 8rpx; }
-.service-grid { display: flex; flex-wrap: wrap; }
-.service-cell { width: 33.33%; display: flex; flex-direction: column; align-items: center; margin-bottom: 32rpx; }
-.icon-wrapper { width: 96rpx; height: 96rpx; background-color: #fff; border-radius: 24rpx; display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04); margin-bottom: 16rpx; }
-.service-icon { width: 56rpx; height: 56rpx; }
-.service-name { font-size: 24rpx; color: #666; text-align: center; }
-.contact-footer { margin-top: 40rpx; background-color: #f0f7ff; border-radius: 24rpx; padding: 32rpx; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 20rpx; }
-.kf-avatar { width: 80rpx; height: 80rpx; }
-.footer-msg { font-size: 26rpx; color: #333; }
-.kf-btn { font-size: 24rpx; color: #1989fa; background: transparent; border: 1rpx solid #1989fa; border-radius: 28rpx; padding: 8rpx 32rpx; line-height: 1.5; }
+.all-services-page {
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #fff;
+}
+
+.header-search {
+	display: flex;
+	align-items: center;
+	padding: 16rpx 32rpx;
+	background-color: #fff;
+	border-bottom: 1rpx solid #f2f2f2;
+}
+
+.location-box {
+	display: flex;
+	align-items: center;
+	gap: 8rpx;
+	font-size: 28rpx;
+	color: #333;
+	margin-right: 24rpx;
+}
+
+.search-input-wrap {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	background: #f5f5f5;
+	border-radius: 32rpx;
+	padding: 12rpx 20rpx;
+	gap: 12rpx;
+}
+
+.search-input {
+	flex: 1;
+	font-size: 26rpx;
+}
+
+.placeholder-style {
+	color: #999;
+	font-size: 26rpx;
+}
+
+.main-content {
+	flex: 1;
+	display: flex;
+	overflow: hidden;
+}
+
+.sidebar {
+	width: 200rpx;
+	background-color: #f7f8fa;
+}
+
+.sidebar-item {
+	padding: 28rpx 0;
+	text-align: center;
+	font-size: 26rpx;
+	color: #666;
+	position: relative;
+}
+
+.sidebar-item.active {
+	background-color: #fff;
+	color: #333;
+	font-weight: bold;
+}
+
+.sidebar-item.active::before {
+	content: '';
+	position: absolute;
+	left: 0;
+	top: 50%;
+	transform: translateY(-50%);
+	width: 6rpx;
+	height: 40rpx;
+	background-color: #f7ca3e;
+	border-radius: 0 6rpx 6rpx 0;
+}
+
+.content-view {
+	flex: 1;
+	padding: 32rpx;
+}
+
+.category-section {
+	margin-bottom: 48rpx;
+}
+
+.cat-section-title {
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #333;
+	display: block;
+	margin-bottom: 24rpx;
+	background-color: #f9f9f9;
+	padding: 8rpx 20rpx;
+	border-radius: 8rpx;
+}
+
+.service-grid {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.service-cell {
+	width: 33.33%;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	margin-bottom: 32rpx;
+}
+
+.icon-wrapper {
+	width: 96rpx;
+	height: 96rpx;
+	background-color: #fff;
+	border-radius: 24rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+	margin-bottom: 16rpx;
+}
+
+.service-icon {
+	width: 56rpx;
+	height: 56rpx;
+}
+
+.service-name {
+	font-size: 24rpx;
+	color: #666;
+	text-align: center;
+}
+
+.contact-footer {
+	margin-top: 40rpx;
+	background-color: #f0f7ff;
+	border-radius: 24rpx;
+	padding: 32rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	text-align: center;
+	gap: 20rpx;
+}
+
+.kf-avatar {
+	width: 80rpx;
+	height: 80rpx;
+}
+
+.footer-msg {
+	font-size: 26rpx;
+	color: #333;
+}
+
+.kf-btn {
+	font-size: 24rpx;
+	color: #1989fa;
+	background: transparent;
+	border: 1rpx solid #1989fa;
+	border-radius: 28rpx;
+	padding: 8rpx 32rpx;
+	line-height: 1.5;
+}
 </style>

+ 217 - 30
pages/service/detail/index.vue

@@ -1,5 +1,6 @@
 <template>
 	<view class="service-detail-page">
+		<nav-bar title="服务详情" bgColor="transparent" color="#fff"></nav-bar>
 		<!-- 顶部主图区域 -->
 		<view class="hero-section">
 			<image :src="serviceData.heroImg" class="hero-img" mode="aspectFill"></image>
@@ -59,6 +60,7 @@
 
 <script setup>
 import { ref, computed } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
 import { onLoad } from '@dcloudio/uni-app'
 
 const activeTab = ref('intro')
@@ -138,34 +140,219 @@ const goToOrderApply = () => {
 </script>
 
 <style lang="scss" scoped>
-.service-detail-page { background-color: #f5f5f5; min-height: 100vh; padding-bottom: 160rpx; }
-.hero-section { position: relative; width: 100%; height: 640rpx; overflow: hidden; }
-.hero-img { width: 100%; height: 100%; }
-.hero-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.4) 80%, rgba(0, 0, 0, 0.7) 100%); }
-.hero-title-box { position: absolute; bottom: 48rpx; left: 40rpx; right: 40rpx; z-index: 2; }
-.hero-main-title { display: block; font-size: 52rpx; font-weight: 900; color: #fff; margin-bottom: 12rpx; }
-.hero-sub-title { display: block; font-size: 28rpx; color: rgba(255, 255, 255, 0.9); font-weight: 600; }
-.card { background-color: #fff; margin-bottom: 20rpx; }
-.info-section { padding: 32rpx 40rpx; position: relative; z-index: 3; border-radius: 24rpx 24rpx 0 0; margin-top: -24rpx; }
-.service-price-row { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 20rpx; }
-.price-box { display: flex; align-items: baseline; color: #f44336; }
-.price-label { font-size: 26rpx; color: #333; font-weight: bold; margin-right: 12rpx; }
-.price-symbol { font-size: 28rpx; font-weight: bold; }
-.price-num { font-size: 56rpx; font-weight: 900; line-height: 1; }
-.price-unit { font-size: 24rpx; color: #999; margin-left: 4rpx; }
-.bought-count { font-size: 24rpx; color: #999; background: #f5f5f5; padding: 4rpx 16rpx; border-radius: 20rpx; }
-.service-name-text { font-size: 32rpx; color: #111; font-weight: bold; }
-.tab-section { padding: 0; }
-.tab-header { display: flex; border-bottom: 1rpx solid #f0f0f0; }
-.tab-btn { flex: 1; text-align: center; padding: 24rpx 0; font-size: 28rpx; color: #666; position: relative; }
-.tab-btn.active { color: #fcd53f; font-weight: bold; }
-.tab-btn.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 60rpx; height: 6rpx; background: #fcd53f; border-radius: 6rpx; }
-.tab-content { padding: 40rpx 32rpx; }
-.content-title { display: block; font-size: 32rpx; color: #333; font-weight: 900; margin-bottom: 24rpx; padding-left: 20rpx; position: relative; }
-.content-title::before { content: ''; position: absolute; left: 0; top: 4rpx; bottom: 4rpx; width: 8rpx; background: #fcd53f; border-radius: 4rpx; }
-.content-text { font-size: 28rpx; color: #555; line-height: 1.8; }
-.intro-images { margin-top: 32rpx; display: flex; flex-direction: column; gap: 24rpx; }
-.intro-img { width: 100%; border-radius: 20rpx; }
-.footer-bar { position: fixed; bottom: 0; left: 0; right: 0; padding: 20rpx 32rpx 40rpx; background-color: #fff; z-index: 10; box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05); }
-.buy-btn { width: 100%; height: 92rpx; font-size: 32rpx; font-weight: bold; color: #333; background: linear-gradient(90deg, #ffd53f, #ff9500); border: none; border-radius: 46rpx; line-height: 92rpx; }
+.service-detail-page {
+	background-color: #f5f5f5;
+	min-height: 100vh;
+	padding-bottom: 160rpx;
+}
+
+.hero-section {
+	position: relative;
+	width: 100%;
+	height: 640rpx;
+	overflow: hidden;
+	margin-top: calc(-44px - var(--status-bar-height, 44px));
+}
+
+.hero-img {
+	width: 100%;
+	height: 100%;
+}
+
+.hero-overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.4) 80%, rgba(0, 0, 0, 0.7) 100%);
+}
+
+.hero-title-box {
+	position: absolute;
+	bottom: 48rpx;
+	left: 40rpx;
+	right: 40rpx;
+	z-index: 2;
+}
+
+.hero-main-title {
+	display: block;
+	font-size: 52rpx;
+	font-weight: 900;
+	color: #fff;
+	margin-bottom: 12rpx;
+}
+
+.hero-sub-title {
+	display: block;
+	font-size: 28rpx;
+	color: rgba(255, 255, 255, 0.9);
+	font-weight: 600;
+}
+
+.card {
+	background-color: #fff;
+	margin-bottom: 20rpx;
+}
+
+.info-section {
+	padding: 32rpx 40rpx;
+	position: relative;
+	z-index: 3;
+	border-radius: 24rpx 24rpx 0 0;
+	margin-top: -24rpx;
+}
+
+.service-price-row {
+	display: flex;
+	justify-content: space-between;
+	align-items: flex-end;
+	margin-bottom: 20rpx;
+}
+
+.price-box {
+	display: flex;
+	align-items: baseline;
+	color: #f44336;
+}
+
+.price-label {
+	font-size: 26rpx;
+	color: #333;
+	font-weight: bold;
+	margin-right: 12rpx;
+}
+
+.price-symbol {
+	font-size: 28rpx;
+	font-weight: bold;
+}
+
+.price-num {
+	font-size: 56rpx;
+	font-weight: 900;
+	line-height: 1;
+}
+
+.price-unit {
+	font-size: 24rpx;
+	color: #999;
+	margin-left: 4rpx;
+}
+
+.bought-count {
+	font-size: 24rpx;
+	color: #999;
+	background: #f5f5f5;
+	padding: 4rpx 16rpx;
+	border-radius: 20rpx;
+}
+
+.service-name-text {
+	font-size: 32rpx;
+	color: #111;
+	font-weight: bold;
+}
+
+.tab-section {
+	padding: 0;
+}
+
+.tab-header {
+	display: flex;
+	border-bottom: 1rpx solid #f0f0f0;
+}
+
+.tab-btn {
+	flex: 1;
+	text-align: center;
+	padding: 24rpx 0;
+	font-size: 28rpx;
+	color: #666;
+	position: relative;
+}
+
+.tab-btn.active {
+	color: #fcd53f;
+	font-weight: bold;
+}
+
+.tab-btn.active::after {
+	content: '';
+	position: absolute;
+	bottom: 0;
+	left: 50%;
+	transform: translateX(-50%);
+	width: 60rpx;
+	height: 6rpx;
+	background: #fcd53f;
+	border-radius: 6rpx;
+}
+
+.tab-content {
+	padding: 40rpx 32rpx;
+}
+
+.content-title {
+	display: block;
+	font-size: 32rpx;
+	color: #333;
+	font-weight: 900;
+	margin-bottom: 24rpx;
+	padding-left: 20rpx;
+	position: relative;
+}
+
+.content-title::before {
+	content: '';
+	position: absolute;
+	left: 0;
+	top: 4rpx;
+	bottom: 4rpx;
+	width: 8rpx;
+	background: #fcd53f;
+	border-radius: 4rpx;
+}
+
+.content-text {
+	font-size: 28rpx;
+	color: #555;
+	line-height: 1.8;
+}
+
+.intro-images {
+	margin-top: 32rpx;
+	display: flex;
+	flex-direction: column;
+	gap: 24rpx;
+}
+
+.intro-img {
+	width: 100%;
+	border-radius: 20rpx;
+}
+
+.footer-bar {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	padding: 20rpx 32rpx 40rpx;
+	background-color: #fff;
+	z-index: 10;
+	box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
+}
+
+.buy-btn {
+	width: 100%;
+	height: 92rpx;
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	border: none;
+	border-radius: 46rpx;
+	line-height: 92rpx;
+}
 </style>

+ 92 - 14
pages/service/review/index.vue

@@ -29,18 +29,96 @@ const reviews = ref(reviewMockData)
 </script>
 
 <style lang="scss" scoped>
-.service-review-page { min-height: 100vh; background: #f7f8fa; }
-.summary-bar { background: #fff; padding: 40rpx; display: flex; align-items: baseline; gap: 16rpx; border-bottom: 1rpx solid #f5f5f5; }
-.avg-score { font-size: 56rpx; font-weight: 900; color: #ff9500; }
-.avg-label { font-size: 26rpx; color: #333; }
-.total-count { font-size: 24rpx; color: #999; margin-left: auto; }
-.review-list { padding: 24rpx; }
-.review-card { background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx; }
-.review-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16rpx; }
-.user-info { display: flex; align-items: center; gap: 16rpx; }
-.user-avatar { width: 64rpx; height: 64rpx; border-radius: 50%; background: #e3f2fd; color: #2196f3; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 28rpx; }
-.user-name { font-size: 28rpx; color: #333; font-weight: 600; }
-.stars { color: #f7ca3e; font-size: 26rpx; }
-.review-content { display: block; font-size: 28rpx; color: #555; line-height: 1.6; margin-bottom: 12rpx; }
-.review-time { display: block; font-size: 24rpx; color: #bbb; }
+.service-review-page {
+	min-height: 100vh;
+	background: #f7f8fa;
+}
+
+.summary-bar {
+	background: #fff;
+	padding: 40rpx;
+	display: flex;
+	align-items: baseline;
+	gap: 16rpx;
+	border-bottom: 1rpx solid #f5f5f5;
+}
+
+.avg-score {
+	font-size: 56rpx;
+	font-weight: 900;
+	color: #ff9500;
+}
+
+.avg-label {
+	font-size: 26rpx;
+	color: #333;
+}
+
+.total-count {
+	font-size: 24rpx;
+	color: #999;
+	margin-left: auto;
+}
+
+.review-list {
+	padding: 24rpx;
+}
+
+.review-card {
+	background: #fff;
+	border-radius: 20rpx;
+	padding: 28rpx;
+	margin-bottom: 20rpx;
+}
+
+.review-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 16rpx;
+}
+
+.user-info {
+	display: flex;
+	align-items: center;
+	gap: 16rpx;
+}
+
+.user-avatar {
+	width: 64rpx;
+	height: 64rpx;
+	border-radius: 50%;
+	background: #e3f2fd;
+	color: #2196f3;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-weight: bold;
+	font-size: 28rpx;
+}
+
+.user-name {
+	font-size: 28rpx;
+	color: #333;
+	font-weight: 600;
+}
+
+.stars {
+	color: #f7ca3e;
+	font-size: 26rpx;
+}
+
+.review-content {
+	display: block;
+	font-size: 28rpx;
+	color: #555;
+	line-height: 1.6;
+	margin-bottom: 12rpx;
+}
+
+.review-time {
+	display: block;
+	font-size: 24rpx;
+	color: #bbb;
+}
 </style>

+ 105 - 19
pages/store/apply/index.vue

@@ -5,15 +5,22 @@
 			<text class="hero-desc">加入我们,共享宠物服务市场</text>
 		</view>
 		<view class="form-card">
-			<view class="form-item"><text class="form-label">商家名称</text><input class="form-input" v-model="form.name" placeholder="请输入商家名称" /></view>
-			<view class="form-item"><text class="form-label">联系人</text><input class="form-input" v-model="form.contact" placeholder="请输入联系人姓名" /></view>
-			<view class="form-item"><text class="form-label">联系电话</text><input class="form-input" v-model="form.phone" type="number" placeholder="请输入联系电话" /></view>
-			<view class="form-item"><text class="form-label">经营地址</text><input class="form-input" v-model="form.address" placeholder="请输入经营地址" /></view>
+			<view class="form-item"><text class="form-label">商家名称</text><input class="form-input" v-model="form.name"
+					placeholder="请输入商家名称" /></view>
+			<view class="form-item"><text class="form-label">联系人</text><input class="form-input" v-model="form.contact"
+					placeholder="请输入联系人姓名" /></view>
+			<view class="form-item"><text class="form-label">联系电话</text><input class="form-input" v-model="form.phone"
+					type="number" placeholder="请输入联系电话" /></view>
+			<view class="form-item"><text class="form-label">经营地址</text><input class="form-input" v-model="form.address"
+					placeholder="请输入经营地址" /></view>
 			<view class="form-item">
 				<text class="form-label">服务类型</text>
-				<picker :range="serviceTypes" @change="onTypeChange"><view class="picker-value">{{ form.serviceType || '请选择' }}</view></picker>
+				<picker :range="serviceTypes" @change="onTypeChange">
+					<view class="picker-value">{{ form.serviceType || '请选择' }}</view>
+				</picker>
 			</view>
-			<view class="form-item column"><text class="form-label">商家简介</text><textarea class="form-textarea" v-model="form.intro" placeholder="请简要介绍商家情况"></textarea></view>
+			<view class="form-item column"><text class="form-label">商家简介</text><textarea class="form-textarea"
+					v-model="form.intro" placeholder="请简要介绍商家情况"></textarea></view>
 		</view>
 		<button class="submit-btn" @click="onSubmit">提交申请</button>
 	</view>
@@ -32,17 +39,96 @@ const onSubmit = () => {
 </script>
 
 <style lang="scss" scoped>
-.store-apply-page { min-height: 100vh; background: #f7f8fa; padding-bottom: 160rpx; }
-.hero-banner { background: linear-gradient(135deg, #ffd53f, #ff9500); padding: 80rpx 40rpx 60rpx; }
-.hero-title { display: block; font-size: 44rpx; font-weight: 900; color: #fff; }
-.hero-desc { display: block; font-size: 28rpx; color: rgba(255,255,255,0.85); margin-top: 12rpx; }
-.form-card { background: #fff; border-radius: 32rpx 32rpx 0 0; margin-top: -30rpx; padding: 40rpx 32rpx; position: relative; z-index: 3; }
-.form-item { display: flex; align-items: center; padding: 28rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.form-item.column { flex-direction: column; align-items: flex-start; }
-.form-item:last-child { border-bottom: none; }
-.form-label { width: 180rpx; font-size: 28rpx; color: #333; flex-shrink: 0; margin-bottom: 16rpx; }
-.form-input { flex: 1; font-size: 28rpx; color: #333; text-align: right; }
-.picker-value { font-size: 28rpx; color: #333; }
-.form-textarea { width: 100%; font-size: 28rpx; color: #333; height: 200rpx; background: #f9f9f9; border-radius: 16rpx; padding: 20rpx; }
-.submit-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; }
+.store-apply-page {
+	min-height: 100vh;
+	background: #f7f8fa;
+	padding-bottom: 160rpx;
+}
+
+.hero-banner {
+	background: linear-gradient(135deg, #ffd53f, #ff9500);
+	padding: 80rpx 40rpx 60rpx;
+}
+
+.hero-title {
+	display: block;
+	font-size: 44rpx;
+	font-weight: 900;
+	color: #fff;
+}
+
+.hero-desc {
+	display: block;
+	font-size: 28rpx;
+	color: rgba(255, 255, 255, 0.85);
+	margin-top: 12rpx;
+}
+
+.form-card {
+	background: #fff;
+	border-radius: 32rpx 32rpx 0 0;
+	margin-top: -30rpx;
+	padding: 40rpx 32rpx;
+	position: relative;
+	z-index: 3;
+}
+
+.form-item {
+	display: flex;
+	align-items: center;
+	padding: 28rpx 0;
+	border-bottom: 1rpx solid #f5f5f5;
+}
+
+.form-item.column {
+	flex-direction: column;
+	align-items: flex-start;
+}
+
+.form-item:last-child {
+	border-bottom: none;
+}
+
+.form-label {
+	width: 180rpx;
+	font-size: 28rpx;
+	color: #333;
+	flex-shrink: 0;
+	margin-bottom: 16rpx;
+}
+
+.form-input {
+	flex: 1;
+	font-size: 28rpx;
+	color: #333;
+	text-align: right;
+}
+
+.picker-value {
+	font-size: 28rpx;
+	color: #333;
+}
+
+.form-textarea {
+	width: 100%;
+	font-size: 28rpx;
+	color: #333;
+	height: 200rpx;
+	background: #f9f9f9;
+	border-radius: 16rpx;
+	padding: 20rpx;
+}
+
+.submit-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;
+}
 </style>

+ 7 - 0
static/icon/service-fee.svg

@@ -0,0 +1,7 @@
+<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M14 10H38L46 18V50H14V10Z" stroke="#000000" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round"/>
+  <path d="M22 26H34" stroke="#000000" stroke-width="4.5" stroke-linecap="round"/>
+  <path d="M22 36H30" stroke="#000000" stroke-width="4.5" stroke-linecap="round"/>
+  <circle cx="42" cy="42" r="10" fill="#fef6df" stroke="#000000" stroke-width="3"/>
+  <path d="M42 37V42M42 42V47M42 42H37M42 42H47" stroke="#000000" stroke-width="2" stroke-linecap="round"/>
+</svg>

+ 1 - 1
utils/config.js

@@ -1,7 +1,7 @@
 /* 全局配置文件 */
 
 // 接口基础地址
-export const BASE_URL = 'http://localhost:8080'
+export const BASE_URL = 'http://192.168.1.118:8080'
 
 // 请求超时时间(毫秒)
 export const TIMEOUT = 10000

+ 1 - 6
utils/request.js

@@ -39,9 +39,4 @@ const request = (options = {}) => {
 	})
 }
 
-export const get = (url, data, options = {}) => request({ ...options, url, data, method: 'GET' })
-export const post = (url, data, options = {}) => request({ ...options, url, data, method: 'POST' })
-export const put = (url, data, options = {}) => request({ ...options, url, data, method: 'PUT' })
-export const del = (url, data, options = {}) => request({ ...options, url, data, method: 'DELETE' })
-
-export default request
+export { request }