Explorar o código

新增注册功能

沐梦. hai 4 días
pai
achega
49e5e7f680

+ 28 - 0
androidPrivacy.json

@@ -0,0 +1,28 @@
+{
+  "version": "1",
+  "prompt": "template",
+  "title": "好萌友服务协议和隐私政策",
+  "message": "请您在使用好萌友APP前仔细阅读并同意<a href=\"static/privacy.html\">《好萌友隐私政策》</a>。当您点击“同意并继续”即代表您同意上述协议的全部内容。如您不同意,请点击“不同意并退出”。",
+  "buttonAccept": "同意并继续",
+  "buttonRefuse": "不同意并退出",
+  "hrefLoader": "default",
+  "second": {
+    "title": "提示",
+    "message": "进入应用前,您需要同意《好萌友隐私政策》以保障您的服务体验,否则将退出应用。",
+    "buttonAccept": "同意并继续",
+    "buttonRefuse": "退出应用"
+  },
+  "styles": {
+    "backgroundColor": "#FFFFFF",
+    "borderRadius": "16px",
+    "title": {
+      "color": "#1A1A1A"
+    },
+    "buttonAccept": {
+      "color": "#FF9500"
+    },
+    "buttonRefuse": {
+      "color": "#999999"
+    }
+  }
+}

+ 9 - 0
api/auth.js

@@ -7,3 +7,12 @@ export function login(data) {
     data
   })
 }
+
+export function register(data) {
+  return request({
+    url: '/system/merchant/audit/register',
+    method: 'post',
+    data
+  })
+}
+

+ 5 - 3
components/policy-dialog/index.vue

@@ -10,7 +10,7 @@
 
 			<scroll-view scroll-y class="dialog-body">
 				<view class="policy-content">
-					<text class="policy-text">{{ content }}</text>
+					<rich-text :nodes="content" class="policy-text"></rich-text>
 				</view>
 			</scroll-view>
 
@@ -106,18 +106,20 @@ const onMaskClick = () => {
 
 .dialog-body {
 	flex: 1;
-	padding: 32rpx;
 	overflow-y: auto;
 }
 
 .policy-content {
+	padding: 32rpx 40rpx 40rpx 40rpx;
 	line-height: 1.8;
 }
 
 .policy-text {
+	display: block;
 	font-size: 28rpx;
 	color: #666;
-	white-space: pre-wrap;
+	line-height: 1.8;
+	word-break: break-all;
 }
 
 .dialog-footer {

+ 13 - 3
manifest.json

@@ -2,10 +2,19 @@
     "name" : "好萌友",
     "appid" : "__UNI__F19BBAD",
     "description" : "宠物服务商家端",
-    "versionName" : "1.1.4",
-    "versionCode" : 42,
+    "versionName" : "1.1.6",
+    "versionCode" : 43,
     "transformPx" : false,
     "app-plus" : {
+        "privacy" : {
+            "prompt" : "template",
+            "template" : {
+                "title" : "好萌友服务协议和隐私政策",
+                "message" : "请您在使用好萌友APP前仔细阅读并同意<a href=\"static/privacy.html\">《好萌友隐私政策》</a>。当您点击“同意并继续”即代表您同意上述协议的全部内容。如您不同意,请点击“不同意并退出”。",
+                "buttonAccept" : "同意并继续",
+                "buttonRefuse" : "不同意并退出"
+            }
+        },
         "usingComponents" : true,
         "nvueStyleCompiler" : "uni-app",
         "compilerVersion" : 3,
@@ -38,7 +47,8 @@
                     "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.CAMERA\"/>",
                     "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
-                ]
+                ],
+                "targetSdkVersion" : 31
             },
             "ios" : {
                 "dSYMs" : false,

+ 14 - 0
pages.json

@@ -41,6 +41,20 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/login/register",
+			"style": {
+				"navigationBarTitleText": "注册",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/login/review",
+			"style": {
+				"navigationBarTitleText": "注册审核",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/service/detail/index",
 			"style": {

+ 208 - 1
pages/index/index.vue

@@ -70,13 +70,106 @@
 		</view>
 
 		<custom-tabbar></custom-tabbar>
+
+		<!-- 隐私政策弹窗 -->
+		<view class="privacy-modal-mask" v-if="showPrivacyModal">
+			<view class="privacy-modal-content">
+				<view class="privacy-modal-header">
+					<text class="privacy-modal-title">服务协议和隐私政策提示</text>
+				</view>
+				<scroll-view scroll-y="true" class="privacy-modal-body">
+					<view class="privacy-text-content">
+						<text class="text-p">欢迎您选择由重庆盈锐文化传播有限公司运营的“好萌友”!我们非常重视您的个人信息和隐私保护。</text>
+						<text class="text-p">在您使用“好萌友”产品及相关服务前,请仔细阅读并同意</text>
+						<text class="text-link-active" @click="goToPrivacy">《好萌友隐私政策》</text>
+						<text class="text-p">,以了解我们如何收集、使用、共享及保护您的个人信息,包括收集您的设备制造商、设备型号、设备标识符(OAID/VAID/AAID等)等相关信息。</text>
+						<text class="text-p">为了给您提供宠物接送、上门服务等核心功能,我们会请求定位、相机、相册及剪切板等权限。如果您同意,请点击“同意并继续”开始使用我们的服务。</text>
+					</view>
+				</scroll-view>
+				<view class="privacy-modal-footer">
+					<button class="btn-refuse" @click="handleRefuse">不同意并退出</button>
+					<button class="btn-accept" @click="handleAccept">同意并继续</button>
+				</view>
+			</view>
+		</view>
 	</view>
 </template>
 
 <script setup>
+import { ref, onMounted } from 'vue'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
-/** 首页脚本 @Author: Antigravity */
+const showPrivacyModal = ref(false)
+
+onMounted(() => {
+	const agreed = uni.getStorageSync('privacy_agreed')
+	if (!agreed) {
+		showPrivacyModal.value = true
+	} else {
+		// 已同意隐私政策,若未登录,则自动重定向至登录页
+		const token = uni.getStorageSync('token')
+		if (!token) {
+			uni.reLaunch({
+				url: '/pages/login/index'
+			})
+		}
+	}
+})
+
+const goToPrivacy = () => {
+	uni.navigateTo({
+		url: `/pages/my/agreement/detail/index?id=2&title=${encodeURIComponent('好萌友隐私政策')}`
+	})
+}
+
+const handleAccept = () => {
+	uni.setStorageSync('privacy_agreed', true)
+	showPrivacyModal.value = false
+	uni.showToast({
+		title: '已同意隐私政策',
+		icon: 'success'
+	})
+	
+	// 同意协议后,若没有 Token 则在一秒后重定向跳转至登录界面
+	const token = uni.getStorageSync('token')
+	if (!token) {
+		setTimeout(() => {
+			uni.reLaunch({
+				url: '/pages/login/index'
+			})
+		}, 1000)
+	}
+}
+
+const handleRefuse = () => {
+	uni.showModal({
+		title: '提示',
+		content: '我们需要您的授权同意隐私政策,以保障服务体验。若不同意,您将无法使用本应用服务。确认退出吗?',
+		confirmText: '确认退出',
+		cancelText: '重新阅读',
+		success: (res) => {
+			if (res.confirm) {
+				// #ifdef APP-PLUS
+				if (uni.getSystemInfoSync().platform === 'android') {
+					plus.runtime.quit()
+				} else {
+					uni.showToast({
+						title: '请手动关闭应用',
+						icon: 'none'
+					})
+				}
+				// #endif
+				// #ifndef APP-PLUS
+				uni.showToast({
+					title: '已拒绝,将限制部分功能使用',
+					icon: 'none'
+				})
+				showPrivacyModal.value = true
+				// #endif
+			}
+		}
+	})
+}
 </script>
 
 <style lang="scss" scoped>
@@ -244,4 +337,118 @@ import customTabbar from '@/components/custom-tabbar/index.vue'
 	color: #999;
 }
 
+/* ====== 隐私政策弹窗样式 ====== */
+.privacy-modal-mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.6);
+	backdrop-filter: blur(16rpx);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 9999;
+	padding: 48rpx;
+}
+
+.privacy-modal-content {
+	width: 100%;
+	max-height: 75vh;
+	background: #ffffff;
+	border-radius: 36rpx;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
+	animation: modalFadeIn 0.3s ease;
+}
+
+@keyframes modalFadeIn {
+	from {
+		opacity: 0;
+		transform: scale(0.9);
+	}
+	to {
+		opacity: 1;
+		transform: scale(1);
+	}
+}
+
+.privacy-modal-header {
+	padding: 40rpx 40rpx 24rpx;
+	text-align: center;
+}
+
+.privacy-modal-title {
+	font-size: 34rpx;
+	font-weight: 800;
+	color: #1a1a1a;
+	letter-spacing: -0.5rpx;
+}
+
+.privacy-modal-body {
+	flex: 1;
+	overflow-y: auto;
+}
+
+.privacy-text-content {
+	padding: 0 40rpx 32rpx;
+	line-height: 1.6;
+}
+
+.text-p {
+	font-size: 28rpx;
+	color: #4a4a4a;
+}
+
+.text-link-active {
+	font-size: 28rpx;
+	color: #ff9500;
+	font-weight: 700;
+	text-decoration: underline;
+	display: inline-block;
+	margin: 0 4rpx;
+}
+
+.privacy-modal-footer {
+	padding: 24rpx 40rpx 40rpx;
+	display: flex;
+	gap: 20rpx;
+	border-top: 2rpx solid #f5f6f8;
+	background: #ffffff;
+}
+
+.btn-refuse {
+	flex: 1;
+	height: 88rpx;
+	line-height: 88rpx;
+	font-size: 28rpx;
+	color: #666666;
+	background: #f5f6f8;
+	border-radius: 44rpx;
+	border: none;
+	font-weight: 600;
+	&::after {
+		border: none;
+	}
+}
+
+.btn-accept {
+	flex: 1.2;
+	height: 88rpx;
+	line-height: 88rpx;
+	font-size: 28rpx;
+	color: #ffffff;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	border-radius: 44rpx;
+	border: none;
+	font-weight: 700;
+	box-shadow: 0 8rpx 20rpx rgba(255, 149, 0, 0.2);
+	&::after {
+		border: none;
+	}
+}
+
 </style>

+ 44 - 2
pages/login/index.vue

@@ -65,6 +65,12 @@
 
 			<!-- 登录按钮 -->
 			<button class="login-btn" @click="onSubmit">安全登录</button>
+
+			<!-- 注册入口 -->
+			<view class="register-redirect-row">
+				<text class="redirect-hint">还没有账号?</text>
+				<text class="redirect-action" @click="goToRegister">立即注册</text>
+			</view>
 		</view>
 
 		<!-- 底部装饰 -->
@@ -96,6 +102,12 @@ const dialogContent = ref('')
 
 const onClickLeft = () => uni.reLaunch({ url: '/pages/index/index' })
 
+const goToRegister = () => {
+	uni.navigateTo({
+		url: '/pages/login/register'
+	})
+}
+
 const onCheckChange = () => {
 	checked.value = !checked.value
 }
@@ -161,7 +173,15 @@ const showAgreement = async (agreementId) => {
 
 		if (res && res.title) {
 			dialogTitle.value = res.title || '协议详情'
-			dialogContent.value = res.content || '暂无内容'
+			let decodedContent = res.content || '暂无内容'
+			if (res.content) {
+				try {
+					decodedContent = decodeURIComponent(escape(atob(res.content)))
+				} catch (e) {
+					decodedContent = res.content
+				}
+			}
+			dialogContent.value = decodedContent
 			dialogVisible.value = true
 		} else {
 			console.warn('接口返回数据格式异常:', res)
@@ -184,6 +204,10 @@ const showAgreement = async (agreementId) => {
 	flex-direction: column;
 }
 
+:deep(.nav-bar) {
+	position: absolute !important;
+}
+
 .hero-bg {
 	background: linear-gradient(150deg, #ffd53f 0%, #ff9500 100%);
 	padding: 0 40rpx 140rpx;
@@ -193,7 +217,6 @@ const showAgreement = async (agreementId) => {
 	display: flex;
 	flex-direction: column;
 	justify-content: flex-end;
-	margin-top: calc(-44px - var(--status-bar-height, 44px));
 }
 
 .deco-circle {
@@ -364,4 +387,23 @@ const showAgreement = async (agreementId) => {
 	padding: 32rpx 0 60rpx;
 	background: #fff;
 }
+
+.register-redirect-row {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	margin-top: 36rpx;
+	font-size: 26rpx;
+}
+
+.redirect-hint {
+	color: #999;
+}
+
+.redirect-action {
+	color: #ff9500;
+	font-weight: bold;
+	margin-left: 12rpx;
+	text-decoration: underline;
+}
 </style>

+ 532 - 0
pages/login/register.vue

@@ -0,0 +1,532 @@
+<template>
+	<view class="login-page">
+		<nav-bar title="注册" bgColor="transparent" color="#fff" :showBack="false"></nav-bar>
+		
+		<!-- 顶部渐变装饰区 -->
+		<view class="hero-bg">
+			<view class="deco-circle c1"></view>
+			<view class="deco-circle c2"></view>
+			<view class="deco-circle c3"></view>
+
+			<!-- 返回按钮 -->
+			<view class="back-btn" @click="onClickLeft">
+				<uni-icons type="left" size="22" color="#fff"></uni-icons>
+			</view>
+
+			<!-- Logo 区域 -->
+			<view class="hero-content">
+				<text class="brand-desc">开启您的好萌友服务之旅</text>
+			</view>
+		</view>
+
+		<!-- 表单白色卡片区 -->
+		<view class="form-card">
+
+			<!-- 头像上传 -->
+			<view class="avatar-upload-wrap" @click="onChooseAvatar">
+				<image v-if="avatar" :src="avatar" class="avatar-image" mode="aspectFill"></image>
+				<view v-else class="avatar-placeholder">
+					<uni-icons type="camera-filled" size="32" color="#ccc"></uni-icons>
+					<text class="avatar-text">上传头像</text>
+				</view>
+			</view>
+
+			<!-- 商户名称 -->
+			<view class="input-group">
+				<view class="input-icon-wrap">
+					<uni-icons type="shop" size="18" color="#ffc837"></uni-icons>
+				</view>
+				<input class="custom-input" v-model="companyName" placeholder="请输入商户名称"
+					placeholder-class="input-placeholder" />
+			</view>
+
+			<!-- 用户账号 -->
+			<view class="input-group">
+				<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" />
+			</view>
+
+			<!-- 姓名 -->
+			<view class="input-group">
+				<view class="input-icon-wrap">
+					<uni-icons type="staff" size="18" color="#ffc837"></uni-icons>
+				</view>
+				<input class="custom-input" v-model="name" placeholder="请输入姓名"
+					placeholder-class="input-placeholder" />
+			</view>
+
+			<!-- 邮箱 -->
+			<view class="input-group">
+				<view class="input-icon-wrap">
+					<uni-icons type="email" size="18" color="#ffc837"></uni-icons>
+				</view>
+				<input class="custom-input" v-model="email" placeholder="请输入邮箱"
+					placeholder-class="input-placeholder" />
+			</view>
+
+			<!-- 密码 -->
+			<view class="input-group">
+				<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" />
+			</view>
+
+			<!-- 确认密码 -->
+			<view class="input-group">
+				<view class="input-icon-wrap">
+					<uni-icons type="locked" size="18" color="#ffc837"></uni-icons>
+				</view>
+				<input class="custom-input" v-model="confirmPassword" type="password" placeholder="请再次输入密码"
+					placeholder-class="input-placeholder" />
+			</view>
+
+
+			<!-- 协议勾选 -->
+			<view class="agreement-row">
+				<checkbox-group @change="onCheckChange">
+					<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(2)">《隐私政策》</text>
+						<text class="agree-text">和</text>
+						<text class="text-link" @click.stop="showAgreement(4)">《托运协议》</text>
+					</label>
+				</checkbox-group>
+			</view>
+
+			<!-- 提交注册按钮 -->
+			<button class="login-btn" @click="onSubmit">提交注册申请</button>
+
+			<!-- 返回登录 -->
+			<view class="register-redirect-row">
+				<text class="redirect-hint">已有账号?</text>
+				<text class="redirect-action" @click="goToLogin">立即登录</text>
+			</view>
+		</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 { getAgreement } from '@/api/system/agreement'
+import { register } from '@/api/auth'
+
+const avatar = ref('')
+const companyName = ref('')
+const username = ref('')
+const name = ref('')
+const email = ref('')
+const password = ref('')
+const confirmPassword = ref('')
+const checked = ref(false)
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const dialogContent = ref('')
+
+
+const onChooseAvatar = () => {
+	uni.chooseImage({
+		count: 1,
+		success: (res) => {
+			avatar.value = res.tempFilePaths[0]
+		}
+	})
+}
+
+const onCheckChange = () => {
+	checked.value = !checked.value
+}
+
+const goToLogin = () => {
+	uni.reLaunch({
+		url: '/pages/login/index'
+	})
+}
+
+const onClickLeft = () => {
+	uni.navigateBack({
+		fail: () => {
+			goToLogin()
+		}
+	})
+}
+
+const showAgreement = async (agreementId) => {
+	try {
+		uni.showLoading({ title: '加载中...' })
+		const res = await getAgreement(agreementId)
+
+		if (res && res.title) {
+			dialogTitle.value = res.title || '协议详情'
+			let decodedContent = res.content || '暂无内容'
+			if (res.content) {
+				try {
+					decodedContent = decodeURIComponent(escape(window.atob(res.content)))
+				} catch (e) {
+					decodedContent = res.content
+				}
+			}
+			dialogContent.value = decodedContent
+			dialogVisible.value = true
+		} else {
+			uni.showToast({ title: '数据格式异常', icon: 'none' })
+		}
+	} catch (error) {
+		uni.showToast({ title: '加载失败,请稍后重试', icon: 'none' })
+	} finally {
+		uni.hideLoading()
+	}
+}
+
+const onSubmit = async () => {
+	// 1. 商户名称验证
+	if (!companyName.value) {
+		uni.showToast({ title: '请输入商户名称', icon: 'none' })
+		return
+	}
+	if (companyName.value.length < 2 || companyName.value.length > 50) {
+		uni.showToast({ title: '商户名称长度需在 2 到 50 个字符之间', icon: 'none' })
+		return
+	}
+
+	// 2. 用户账号验证
+	if (!username.value) {
+		uni.showToast({ title: '请输入用户账号', icon: 'none' })
+		return
+	}
+	const usernameRegex = /^[a-zA-Z0-9_-]{4,20}$/
+	if (!usernameRegex.test(username.value)) {
+		uni.showToast({ title: '账号必须为 4 到 20 位字母、数字、下划线或减号', icon: 'none' })
+		return
+	}
+
+	// 3. 姓名验证
+	if (!name.value) {
+		uni.showToast({ title: '请输入姓名', icon: 'none' })
+		return
+	}
+	if (name.value.length < 2 || name.value.length > 20) {
+		uni.showToast({ title: '姓名长度需在 2 到 20 个字符之间', icon: 'none' })
+		return
+	}
+
+	// 4. 邮箱验证
+	if (!email.value) {
+		uni.showToast({ title: '请输入邮箱', icon: 'none' })
+		return
+	}
+	const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
+	if (!emailRegex.test(email.value)) {
+		uni.showToast({ title: '请输入正确的邮箱格式', icon: 'none' })
+		return
+	}
+
+	// 5. 密码验证
+	if (!password.value) {
+		uni.showToast({ title: '请输入密码', icon: 'none' })
+		return
+	}
+	if (password.value.length < 6 || password.value.length > 20) {
+		uni.showToast({ title: '密码长度需在 6 到 20 个字符之间', icon: 'none' })
+		return
+	}
+
+	// 6. 确认密码验证
+	if (!confirmPassword.value) {
+		uni.showToast({ title: '请再次输入密码', icon: 'none' })
+		return
+	}
+	if (password.value !== confirmPassword.value) {
+		uni.showToast({ title: '两次输入的密码不一致', icon: 'none' })
+		return
+	}
+
+	// 7. 协议勾选验证
+	if (!checked.value) {
+		uni.showToast({ title: '请先阅读并勾选协议', icon: 'none' })
+		return
+	}
+
+	try {
+		uni.showLoading({ title: '提交申请中...' })
+		await register({
+			avatar: avatar.value,
+			companyName: companyName.value,
+			username: username.value,
+			name: name.value,
+			email: email.value,
+			password: password.value,
+			confirmPassword: confirmPassword.value
+		})
+		
+		uni.hideLoading()
+		uni.showToast({
+			title: '提交成功',
+			icon: 'success'
+		})
+		
+		// 缓存商户名称和提交时间,供审核界面展示使用
+		uni.setStorageSync('registered_merchant', companyName.value)
+		uni.setStorageSync('registered_time', new Date().toLocaleString())
+		
+		setTimeout(() => {
+			uni.reLaunch({
+				url: '/pages/login/review'
+			})
+		}, 1000)
+	} catch (error) {
+		console.error('Register error:', error)
+		uni.hideLoading()
+		setTimeout(() => {
+			uni.showToast({ title: typeof error === 'string' ? error : '注册提交失败', icon: 'none' })
+		}, 100)
+	}
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+.login-page {
+	min-height: 100vh;
+	background: #f2f3f7;
+	display: flex;
+	flex-direction: column;
+}
+
+:deep(.nav-bar) {
+	position: absolute !important;
+}
+
+.back-btn {
+	position: absolute;
+	top: calc(var(--status-bar-height, 44px) + 20rpx);
+	left: 32rpx;
+	width: 76rpx;
+	height: 76rpx;
+	border-radius: 50%;
+	background: rgba(255, 255, 255, 0.35);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+	z-index: 1000;
+}
+
+.hero-bg {
+	background: linear-gradient(150deg, #ffd53f 0%, #ff9500 100%);
+	padding: 0 40rpx 80rpx;
+	position: relative;
+	overflow: hidden;
+	min-height: 300rpx;
+	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;
+}
+
+.hero-content {
+	position: relative;
+	z-index: 2;
+}
+
+.brand-desc {
+	display: block;
+	font-size: 28rpx;
+	color: rgba(255, 255, 255, 0.8);
+	letter-spacing: 2rpx;
+}
+
+.form-card {
+	background: #fff;
+	border-radius: 56rpx 56rpx 0 0;
+	margin-top: -60rpx;
+	padding: 60rpx 48rpx 60rpx;
+	flex: 1;
+	position: relative;
+	z-index: 10;
+}
+
+.input-group {
+	display: flex;
+	align-items: center;
+	background: #f5f6f8;
+	border-radius: 28rpx;
+	padding: 0 32rpx;
+	height: 100rpx;
+	margin-bottom: 32rpx;
+}
+
+.input-icon-wrap {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	margin-right: 20rpx;
+}
+
+.custom-input {
+	flex: 1;
+	height: 100rpx;
+	font-size: 28rpx;
+	color: #333;
+}
+
+.input-placeholder {
+	color: #bbb;
+}
+
+.agreement-row {
+	margin: 32rpx 0 48rpx 0;
+}
+
+.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: #fff;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	border: none;
+	border-radius: 52rpx;
+	letter-spacing: 4rpx;
+	&::after { border: none; }
+}
+
+.register-redirect-row {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	margin-top: 36rpx;
+	font-size: 26rpx;
+}
+
+.redirect-hint {
+	color: #999;
+}
+
+.redirect-action {
+	color: #ff9500;
+	font-weight: bold;
+	margin-left: 12rpx;
+	text-decoration: underline;
+}
+
+.custom-picker {
+	flex: 1;
+	height: 100rpx;
+}
+
+.picker-value {
+	height: 100rpx;
+	line-height: 100rpx;
+	font-size: 28rpx;
+	color: #333;
+	text-align: left;
+}
+
+.picker-value.placeholder {
+	color: #bbb;
+}
+
+.textarea-group {
+	height: auto;
+	align-items: flex-start;
+	padding-top: 24rpx;
+	padding-bottom: 24rpx;
+}
+
+.textarea-icon-wrap {
+	margin-top: 4rpx;
+}
+
+.custom-textarea {
+	flex: 1;
+	height: 140rpx;
+	font-size: 28rpx;
+	color: #333;
+	line-height: 1.4;
+}
+
+.avatar-upload-wrap {
+	display: flex;
+	justify-content: center;
+	margin-bottom: 40rpx;
+}
+
+.avatar-placeholder {
+	width: 160rpx;
+	height: 160rpx;
+	background: #f5f6f8;
+	border-radius: 50%;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	border: 2rpx dashed #d9d9d9;
+}
+
+.avatar-image {
+	width: 160rpx;
+	height: 160rpx;
+	border-radius: 50%;
+	border: 2rpx solid #eee;
+}
+
+.avatar-text {
+	font-size: 20rpx;
+	color: #999;
+	margin-top: 8rpx;
+}
+</style>

+ 359 - 0
pages/login/review.vue

@@ -0,0 +1,359 @@
+<template>
+	<view class="review-page">
+		<nav-bar title="申请审核" bgColor="transparent" color="#fff" :showBack="false"></nav-bar>
+		
+		<!-- 头部渐变背景 -->
+		<view class="hero-bg">
+			<view class="deco-circle c1"></view>
+			<view class="deco-circle c2"></view>
+			<view class="deco-circle c3"></view>
+		</view>
+
+		<!-- 审核状态卡片 -->
+		<view class="status-card">
+			<view class="status-icon-wrap">
+				<uni-icons type="spinner-cycle" size="48" color="#ff9500" class="spinner"></uni-icons>
+			</view>
+			<text class="status-title">资料审核中</text>
+			<text class="status-desc">系统已收到您的注册申请,正在为您全力审核</text>
+			
+			<!-- 审核时间轴 -->
+			<view class="timeline">
+				<view class="timeline-item active">
+					<view class="timeline-dot">
+						<uni-icons type="checkmarkempty" size="14" color="#fff"></uni-icons>
+					</view>
+					<view class="timeline-content">
+						<text class="time-title">提交申请</text>
+						<text class="time-desc">注册资料已成功提交</text>
+					</view>
+				</view>
+				
+				<view class="timeline-item active processing">
+					<view class="timeline-dot">
+						<view class="inner-dot"></view>
+					</view>
+					<view class="timeline-content">
+						<text class="time-title">平台审核中</text>
+						<text class="time-desc">预计在 1-2 个工作日内完成审核</text>
+					</view>
+				</view>
+				
+				<view class="timeline-item">
+					<view class="timeline-dot"></view>
+					<view class="timeline-content">
+						<text class="time-title">审核通过 & 开户</text>
+						<text class="time-desc">开通后台登录账户</text>
+					</view>
+				</view>
+			</view>
+			
+			<!-- 详情划分线 -->
+			<view class="divider"></view>
+			
+			<!-- 注册申请详情信息 -->
+			<view class="info-list">
+				<view class="info-item">
+					<text class="info-label">企业名称</text>
+					<text class="info-val">{{ merchantName || '好萌友' }}</text>
+				</view>
+				<view class="info-item">
+					<text class="info-label">提交时间</text>
+					<text class="info-val">{{ submitTime || '刚刚' }}</text>
+				</view>
+				<view class="info-item">
+					<text class="info-label">当前状态</text>
+					<text class="info-val status-text">正在审核</text>
+				</view>
+			</view>
+			
+			<!-- 按钮区 -->
+			<button class="back-login-btn" @click="goToLogin">返回登录</button>
+		</view>
+		
+		<!-- 底部页脚 -->
+		<view class="footer-hint">
+			<text>好萌友 APP 运营中心 提供技术支持</text>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import navBar from '@/components/nav-bar/index.vue'
+
+const merchantName = ref('')
+const submitTime = ref('')
+
+onMounted(() => {
+	merchantName.value = uni.getStorageSync('registered_merchant') || ''
+	submitTime.value = uni.getStorageSync('registered_time') || ''
+})
+
+const goToLogin = () => {
+	uni.reLaunch({
+		url: '/pages/login/index'
+	})
+}
+</script>
+
+<style lang="scss" scoped>
+.review-page {
+	min-height: 100vh;
+	background: #f5f6fa;
+	display: flex;
+	flex-direction: column;
+}
+
+:deep(.nav-bar) {
+	position: absolute !important;
+}
+
+.hero-bg {
+	background: linear-gradient(150deg, #ffd53f 0%, #ff9500 100%);
+	height: 380rpx;
+	position: relative;
+	overflow: hidden;
+}
+
+.deco-circle {
+	position: absolute;
+	border-radius: 50%;
+	background: rgba(255, 255, 255, 0.12);
+}
+
+.c1 {
+	width: 300rpx;
+	height: 300rpx;
+	top: -120rpx;
+	right: -80rpx;
+}
+
+.c2 {
+	width: 200rpx;
+	height: 200rpx;
+	top: 60rpx;
+	left: -80rpx;
+}
+
+.c3 {
+	width: 120rpx;
+	height: 120rpx;
+	bottom: 40rpx;
+	right: 40rpx;
+}
+
+.status-card {
+	background: #ffffff;
+	border-radius: 40rpx;
+	margin: -140rpx 40rpx 40rpx;
+	padding: 64rpx 48rpx;
+	box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.05);
+	position: relative;
+	z-index: 10;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+}
+
+.status-icon-wrap {
+	width: 160rpx;
+	height: 160rpx;
+	background: rgba(255, 149, 0, 0.08);
+	border-radius: 50%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	margin-bottom: 32rpx;
+}
+
+.spinner {
+	animation: spin 3s linear infinite;
+}
+
+@keyframes spin {
+	from { transform: rotate(0deg); }
+	to { transform: rotate(360deg); }
+}
+
+.status-title {
+	font-size: 40rpx;
+	font-weight: 800;
+	color: #1a1a1a;
+	margin-bottom: 12rpx;
+}
+
+.status-desc {
+	font-size: 26rpx;
+	color: #888;
+	text-align: center;
+	line-height: 1.5;
+	margin-bottom: 60rpx;
+}
+
+.timeline {
+	width: 100%;
+	padding: 0 10rpx;
+	margin-bottom: 60rpx;
+}
+
+.timeline-item {
+	display: flex;
+	position: relative;
+	padding-bottom: 48rpx;
+	
+	&:last-child {
+		padding-bottom: 0;
+		&::after {
+			display: none;
+		}
+	}
+	
+	&::after {
+		content: '';
+		position: absolute;
+		left: 22rpx;
+		top: 40rpx;
+		bottom: -10rpx;
+		width: 4rpx;
+		background: #eef2f6;
+		z-index: 1;
+	}
+	
+	&.active {
+		&::after {
+			background: #ff9500;
+		}
+		
+		.timeline-dot {
+			background: #ff9500;
+			border-color: #ff9500;
+		}
+		
+		.time-title {
+			color: #1a1a1a;
+			font-weight: 700;
+		}
+	}
+	
+	&.processing {
+		&::after {
+			background: repeating-linear-gradient(0deg, #eef2f6, #eef2f6 8rpx, #ff9500 8rpx, #ff9500 16rpx);
+		}
+		
+		.timeline-dot {
+			background: #fff;
+			border-color: #ff9500;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			
+			.inner-dot {
+				width: 12rpx;
+				height: 12rpx;
+				border-radius: 50%;
+				background: #ff9500;
+				animation: pulse 1.6s infinite;
+			}
+		}
+	}
+}
+
+@keyframes pulse {
+	0% { transform: scale(0.8); opacity: 0.5; }
+	50% { transform: scale(1.2); opacity: 1; }
+	100% { transform: scale(0.8); opacity: 0.5; }
+}
+
+.timeline-dot {
+	width: 44rpx;
+	height: 44rpx;
+	border-radius: 50%;
+	background: #f5f6fa;
+	border: 4rpx solid #eef2f6;
+	z-index: 2;
+	margin-right: 28rpx;
+	flex-shrink: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.timeline-content {
+	flex: 1;
+}
+
+.time-title {
+	display: block;
+	font-size: 28rpx;
+	color: #666;
+	margin-bottom: 6rpx;
+}
+
+.time-desc {
+	display: block;
+	font-size: 24rpx;
+	color: #999;
+}
+
+.divider {
+	width: 100%;
+	height: 2rpx;
+	background: #f2f3f7;
+	margin-bottom: 40rpx;
+}
+
+.info-list {
+	width: 100%;
+	margin-bottom: 64rpx;
+}
+
+.info-item {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 24rpx;
+	font-size: 26rpx;
+	
+	&:last-child {
+		margin-bottom: 0;
+	}
+}
+
+.info-label {
+	color: #999;
+}
+
+.info-val {
+	color: #333;
+	font-weight: 500;
+}
+
+.status-text {
+	color: #ff9500;
+	font-weight: bold;
+}
+
+.back-login-btn {
+	width: 100%;
+	height: 96rpx;
+	line-height: 96rpx;
+	font-size: 32rpx;
+	font-weight: 700;
+	color: #fff;
+	background: linear-gradient(90deg, #ffd53f, #ff9500);
+	border: none;
+	border-radius: 48rpx;
+	letter-spacing: 2rpx;
+	box-shadow: 0 8rpx 20rpx rgba(255, 149, 0, 0.15);
+	&::after { border: none; }
+}
+
+.footer-hint {
+	text-align: center;
+	font-size: 22rpx;
+	color: #ccc;
+	padding: 40rpx 0 60rpx;
+	margin-top: auto;
+}
+</style>

+ 221 - 0
static/privacy.html

@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes, viewport-fit=cover">
+    <title>好萌友隐私政策</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+        body {
+            background-color: #f5f7fb;
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+            line-height: 1.5;
+            padding: 20px 0 48px 0;
+        }
+        .privacy-container {
+            max-width: 720px;
+            margin: 0 auto;
+            background-color: #ffffff;
+            border-radius: 20px;
+            box-shadow: 0 8px 24px rgba(0,0,0,0.05);
+            overflow: hidden;
+            padding: 28px 20px 40px 20px;
+        }
+        @media (min-width: 600px) {
+            .privacy-container {
+                padding: 40px 36px 52px 36px;
+            }
+        }
+        h1 {
+            font-size: 26px;
+            font-weight: 700;
+            text-align: center;
+            margin-bottom: 8px;
+            letter-spacing: -0.3px;
+        }
+        .update-date {
+            text-align: center;
+            font-size: 14px;
+            border-bottom: 1px solid #eef2f6;
+            padding-bottom: 20px;
+            margin-bottom: 24px;
+        }
+        .update-date p {
+            margin: 4px 0;
+        }
+        h2 {
+            font-size: 20px;
+            font-weight: 700;
+            margin: 1.6em 0 0.8em 0;
+            padding-left: 12px;
+            border-left: 4px solid #ccc;
+        }
+        h3 {
+            font-size: 18px;
+            font-weight: 600;
+            margin: 1.2em 0 0.5em 0;
+        }
+        h4 {
+            font-size: 16px;
+            font-weight: 600;
+            margin: 1em 0 0.5em 0;
+        }
+        .sub-section {
+            font-weight: 600;
+            font-size: 16px;
+            margin: 1em 0 0.3em 0;
+        }
+        p {
+            font-size: 15px;
+            margin-bottom: 0.9em;
+            line-height: 1.55;
+        }
+        .text-muted {
+            font-size: 14px;
+        }
+        strong, b {
+            font-weight: 600;
+        }
+        ul, ol {
+            margin: 0.6em 0 1em 1.8em;
+            font-size: 15px;
+        }
+        li {
+            margin: 0.5em 0;
+            line-height: 1.5;
+        }
+        hr {
+            margin: 28px 0;
+            border: 0;
+            height: 1px;
+            background: #e0e7ed;
+        }
+        .contact-info {
+            background-color: #f8fafc;
+            border-radius: 16px;
+            padding: 16px 20px;
+            margin-top: 24px;
+            font-size: 14px;
+        }
+        footer {
+            margin-top: 32px;
+            text-align: center;
+            font-size: 12px;
+            border-top: 1px solid #ecf3f9;
+            padding-top: 24px;
+        }
+        a {
+            text-decoration: none;
+            word-break: break-all;
+        }
+        a:hover {
+            text-decoration: underline;
+        }
+        .small-note {
+            font-size: 13px;
+            background: #f9fafc;
+            padding: 10px 14px;
+            border-radius: 12px;
+            margin: 12px 0;
+        }
+        .sdk-table-like {
+            background: #fefef9;
+            border-left: 3px solid #d0dbe9;
+            padding: 12px 18px;
+            margin: 16px 0;
+            border-radius: 14px;
+        }
+        @media (max-width: 480px) {
+            .privacy-container {
+                padding: 20px 16px 32px 16px;
+            }
+            h1 {
+                font-size: 24px;
+            }
+            h2 {
+                font-size: 19px;
+            }
+        }
+    </style>
+</head>
+<body>
+<div class="privacy-container">
+    <h1 id="privacy-title" style="text-align: center; margin-bottom: 20px;">好萌友隐私政策</h1>
+    <div id="privacy-content" style="padding: 100px 0; text-align: center; color: #999; font-size: 16px;">
+        正在获取最新的隐私政策,请稍候...
+    </div>
+    <div id="privacy-footer" style="margin-top: 32px; text-align: center; font-size: 12px; border-top: 1px solid #ecf3f9; padding-top: 24px; color: #999;">
+        © 好萌友APP · 重庆盈锐文化传播有限公司
+    </div>
+</div>
+<script>
+    (function() {
+        var apiURL = 'http://www.hoomeng.pet/api/system/agreement/2';
+        var headers = {
+            'clientid': 'fe63fea7be31b0200b496d08bc6b517d',
+            'X-Platform-Code': 'MfJkMNMW2JKXBuPcbP2rxkD3ynXmReAZZFm4fN7cAGwGJdKCmd',
+            'Content-Type': 'application/json'
+        };
+        
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', apiURL, true);
+        for (var key in headers) {
+            if (headers.hasOwnProperty(key)) {
+                xhr.setRequestHeader(key, headers[key]);
+            }
+        }
+        xhr.onreadystatechange = function() {
+            if (xhr.readyState === 4) {
+                var loaded = false;
+                if (xhr.status === 200) {
+                    try {
+                        var response = JSON.parse(xhr.responseText);
+                        if (response && response.code === 200 && response.data) {
+                            var data = response.data;
+                            var title = data.title;
+                            var content = data.content;
+                            
+                            var decodedContent = content;
+                            if (content) {
+                                try {
+                                    decodedContent = decodeURIComponent(escape(window.atob(content)));
+                                } catch (e) {
+                                    decodedContent = content;
+                                }
+                            }
+                            
+                            if (decodedContent) {
+                                var titleEl = document.getElementById('privacy-title');
+                                var contentEl = document.getElementById('privacy-content');
+                                if (titleEl && title) titleEl.innerText = title;
+                                if (contentEl) {
+                                    contentEl.style.padding = '0';
+                                    contentEl.style.textAlign = 'left';
+                                    contentEl.style.color = 'inherit';
+                                    contentEl.style.fontSize = 'inherit';
+                                    contentEl.innerHTML = decodedContent;
+                                }
+                                loaded = true;
+                            }
+                        }
+                    } catch (e) {
+                        console.error('Failed to parse dynamic privacy policy', e);
+                    }
+                }
+                if (!loaded) {
+                    var contentEl = document.getElementById('privacy-content');
+                    if (contentEl) {
+                        contentEl.innerText = '加载隐私政策失败,请检查网络连接后重试。';
+                    }
+                }
+            }
+        };
+        xhr.send();
+    })();
+</script>
+</body>
+</html>