liyuliangjiazai 3 månader sedan
förälder
incheckning
7b5945f1f8
100 ändrade filer med 3302 tillägg och 9 borttagningar
  1. 0 1
      .gitignore
  2. 447 0
      code/ajyApp/components/ay-toast/ay-toast.nvue
  3. 48 0
      code/ajyApp/components/customPopup/customPopup.nvue
  4. 3 0
      code/ajyApp/main.js
  5. 100 8
      code/ajyApp/pages.json
  6. 117 0
      code/ajyApp/pages/ble-demo/ble-demo.nvue
  7. 198 0
      code/ajyApp/pages/device/list/list.nvue
  8. 162 0
      code/ajyApp/pages/find/index/index.nvue
  9. 259 0
      code/ajyApp/pages/home/home.nvue
  10. 511 0
      code/ajyApp/pages/login/login.nvue
  11. 378 0
      code/ajyApp/pages/my/crop/crop.vue
  12. 257 0
      code/ajyApp/pages/my/info/info.nvue
  13. 233 0
      code/ajyApp/pages/my/my.nvue
  14. 321 0
      code/ajyApp/pages/test-toast/test-toast.nvue
  15. 80 0
      code/ajyApp/pages/webview/webview.nvue
  16. BIN
      code/ajyApp/static/1024x1024.png
  17. BIN
      code/ajyApp/static/audio/audio1.mp3
  18. BIN
      code/ajyApp/static/audio/audio2.mp3
  19. BIN
      code/ajyApp/static/device/1s.png
  20. BIN
      code/ajyApp/static/device/aijiuyi2.png
  21. BIN
      code/ajyApp/static/device/anmoyi1.png
  22. BIN
      code/ajyApp/static/device/barimg.png
  23. BIN
      code/ajyApp/static/device/change.png
  24. BIN
      code/ajyApp/static/device/device1.png
  25. BIN
      code/ajyApp/static/device/devicebg.png
  26. BIN
      code/ajyApp/static/device/dianbo.png
  27. BIN
      code/ajyApp/static/device/gongneng1.png
  28. BIN
      code/ajyApp/static/device/gongneng2.png
  29. BIN
      code/ajyApp/static/device/gongneng3.png
  30. BIN
      code/ajyApp/static/device/haocai1.png
  31. BIN
      code/ajyApp/static/device/haocai2.png
  32. BIN
      code/ajyApp/static/device/heliu.png
  33. BIN
      code/ajyApp/static/device/line2.png
  34. BIN
      code/ajyApp/static/device/linepoint2.png
  35. BIN
      code/ajyApp/static/device/menubtn.png
  36. BIN
      code/ajyApp/static/device/pause.png
  37. BIN
      code/ajyApp/static/device/pauseicon.png
  38. BIN
      code/ajyApp/static/device/persionbg.png
  39. BIN
      code/ajyApp/static/device/personBody.png
  40. BIN
      code/ajyApp/static/device/playerbtn.png
  41. 8 0
      code/ajyApp/static/device/playing.svg
  42. BIN
      code/ajyApp/static/device/start.png
  43. BIN
      code/ajyApp/static/device/stop.png
  44. BIN
      code/ajyApp/static/font/iconfont.ttf
  45. BIN
      code/ajyApp/static/font/iconfont.woff
  46. BIN
      code/ajyApp/static/font/iconfont.woff2
  47. BIN
      code/ajyApp/static/home/banner.png
  48. BIN
      code/ajyApp/static/home/detail.png
  49. BIN
      code/ajyApp/static/login/apple@2x.png
  50. BIN
      code/ajyApp/static/login/phone.png
  51. BIN
      code/ajyApp/static/login/pwd.png
  52. BIN
      code/ajyApp/static/login/wechat@2x.png
  53. BIN
      code/ajyApp/static/my/arrowright.png
  54. BIN
      code/ajyApp/static/my/avator.png
  55. BIN
      code/ajyApp/static/my/fangan.png
  56. BIN
      code/ajyApp/static/my/icon1.png
  57. BIN
      code/ajyApp/static/my/icon2.png
  58. BIN
      code/ajyApp/static/my/icon3.png
  59. BIN
      code/ajyApp/static/my/icon4.png
  60. BIN
      code/ajyApp/static/my/icon5.png
  61. BIN
      code/ajyApp/static/my/icon6.png
  62. BIN
      code/ajyApp/static/public/arrow-left.png
  63. BIN
      code/ajyApp/static/public/back.png
  64. BIN
      code/ajyApp/static/public/backBlack.png
  65. BIN
      code/ajyApp/static/public/cc-menu-more.png
  66. BIN
      code/ajyApp/static/public/device.png
  67. BIN
      code/ajyApp/static/public/device1.png
  68. BIN
      code/ajyApp/static/public/doctorAvator.png
  69. BIN
      code/ajyApp/static/public/find.png
  70. BIN
      code/ajyApp/static/public/find1.png
  71. BIN
      code/ajyApp/static/public/home.png
  72. BIN
      code/ajyApp/static/public/home1.png
  73. BIN
      code/ajyApp/static/public/my.png
  74. BIN
      code/ajyApp/static/public/my1.png
  75. BIN
      code/ajyApp/static/public/view-list.png
  76. BIN
      code/ajyApp/static/temp/Rectangle 50@2x(1).png
  77. BIN
      code/ajyApp/static/temp/Rectangle 50@2x(2).png
  78. BIN
      code/ajyApp/static/temp/Rectangle 50@2x.png
  79. BIN
      code/ajyApp/static/temp/bannerpage.png
  80. BIN
      code/ajyApp/static/temp/image@2x(1).png
  81. BIN
      code/ajyApp/static/temp/image@2x(2).png
  82. BIN
      code/ajyApp/static/temp/image@2x(3).png
  83. BIN
      code/ajyApp/static/temp/image@2x(3).zip
  84. BIN
      code/ajyApp/static/temp/image@2x(4).png
  85. BIN
      code/ajyApp/static/temp/image@2x.png
  86. BIN
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-hdpi.png
  87. BIN
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-xhdpi.png
  88. BIN
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-xxhdpi.png
  89. BIN
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-xxxhdpi.png
  90. 15 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappautomator.js
  91. 31 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappchooselocation.js
  92. BIN
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniapperror.png
  93. 31 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappopenlocation.js
  94. 31 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniapppicker.js
  95. 6 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappquill.js
  96. 0 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappquillimageresize.js
  97. 31 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappscan.js
  98. BIN
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappsuccess.png
  99. 24 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappview.html
  100. 11 0
      code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/app-config-service.js

+ 0 - 1
.gitignore

@@ -24,4 +24,3 @@ Thumbs.db
 # AGENTS.md - 本地AI agent指南,不提交
 AGENTS.md
 code/app/
-code/ajyApp/

+ 447 - 0
code/ajyApp/components/ay-toast/ay-toast.nvue

@@ -0,0 +1,447 @@
+<template>
+	<view class="ay-toast-root" v-if="visible">
+		<!-- 遮罩层 -->
+		<view class="ay-toast-mask" v-if="mask" @click="onMaskClick"></view>
+		<!-- Toast主体 -->
+		<view class="ay-toast-container">
+			<view class="ay-toast-box" ref="toastBox">
+				<!-- 图标区域 -->
+				<view class="ay-toast-icon-wrap" v-if="type !== 'text'">
+					<!-- 成功图标 -->
+					<view class="ay-toast-icon-success" v-if="type === 'success'">
+						<view class="success-bg"></view>
+						<view class="success-line1"></view>
+						<view class="success-line2"></view>
+					</view>
+					<!-- 失败图标 -->
+					<view class="ay-toast-icon-error" v-if="type === 'error'">
+						<view class="error-bg"></view>
+						<view class="error-line1"></view>
+						<view class="error-line2"></view>
+					</view>
+					<!-- 加载图标 -->
+					<view class="ay-toast-icon-loading" v-if="type === 'loading'">
+						<view class="loading-ring" ref="loadingRef"></view>
+					</view>
+				</view>
+				<!-- 文字区域 -->
+				<text class="ay-toast-text" :class="textColorClass" v-if="title">{{ title }}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+// #ifdef APP-NVUE
+const animation = weex.requireModule('animation')
+// #endif
+
+export default {
+	name: 'ayToast',
+	data() {
+		return {
+			visible: false,
+			type: 'text', // success | error | loading | text
+			title: '',
+			duration: 2000,
+			mask: false,
+			timer: null,
+			loadingAnimTimer: null
+		}
+	},
+	computed: {
+		textColorClass() {
+			if (this.type === 'success') return 'text-success'
+			if (this.type === 'error') return 'text-error'
+			return 'text-default'
+		}
+	},
+	created() {
+		uni.$on('ayToast:show', this.show)
+		uni.$on('ayToast:hide', this.hide)
+	},
+	beforeUnmount() {
+		this._cleanup()
+	},
+	beforeDestroy() {
+		this._cleanup()
+	},
+	methods: {
+		_cleanup() {
+			uni.$off('ayToast:show', this.show)
+			uni.$off('ayToast:hide', this.hide)
+			this.clearTimer()
+			this._stopLoadingAnim()
+		},
+
+		show(options) {
+			this.clearTimer()
+			this._stopLoadingAnim()
+
+			if (typeof options === 'string') {
+				options = { title: options, type: 'text' }
+			}
+
+			const {
+				type = 'text',
+				title = '',
+				duration,
+				mask
+			} = options
+
+			this.type = type
+			this.title = title
+			this.mask = mask !== undefined ? mask : (type === 'loading')
+
+			if (duration !== undefined) {
+				this.duration = duration
+			} else {
+				this.duration = type === 'loading' ? 0 : 2000
+			}
+
+			this.visible = true
+
+			// 入场动画 + loading旋转
+			this.$nextTick(() => {
+				this._playEnterAnim()
+				if (type === 'loading') {
+					this._startLoadingAnim()
+				}
+			})
+
+			if (this.duration > 0) {
+				this.timer = setTimeout(() => {
+					this.hide()
+				}, this.duration)
+			}
+		},
+
+		success(title, options = {}) {
+			this.show({ type: 'success', title, ...options })
+		},
+
+		error(title, options = {}) {
+			this.show({ type: 'error', title, ...options })
+		},
+
+		loading(title = '加载中...', options = {}) {
+			this.show({ type: 'loading', title, duration: 0, mask: true, ...options })
+		},
+
+		hide() {
+			this.clearTimer()
+			this._stopLoadingAnim()
+			this.visible = false
+		},
+
+		clearTimer() {
+			if (this.timer) {
+				clearTimeout(this.timer)
+				this.timer = null
+			}
+		},
+
+		// 入场缩放动画
+		_playEnterAnim() {
+			// #ifdef APP-NVUE
+			const el = this.$refs.toastBox
+			if (!el) return
+			animation.transition(el, {
+				styles: { transform: 'scale(0.8)', opacity: 0 },
+				duration: 0
+			}, () => {
+				animation.transition(el, {
+					styles: { transform: 'scale(1)', opacity: 1 },
+					duration: 200,
+					timingFunction: 'ease-out'
+				})
+			})
+			// #endif
+		},
+
+		// loading 旋转动画
+		_startLoadingAnim() {
+			// #ifdef APP-NVUE
+			const el = this.$refs.loadingRef
+			if (!el) return
+			const doRotate = () => {
+				if (!this.visible || this.type !== 'loading') return
+				animation.transition(el, {
+					styles: { transform: 'rotate(360deg)' },
+					duration: 900,
+					timingFunction: 'linear'
+				}, () => {
+					if (!this.visible || this.type !== 'loading') return
+					animation.transition(el, {
+						styles: { transform: 'rotate(0deg)' },
+						duration: 0
+					}, () => {
+						doRotate()
+					})
+				})
+			}
+			doRotate()
+			// #endif
+		},
+
+		_stopLoadingAnim() {
+			if (this.loadingAnimTimer) {
+				clearTimeout(this.loadingAnimTimer)
+				this.loadingAnimTimer = null
+			}
+		},
+
+		onMaskClick() {
+			// loading状态下防止误操作
+		}
+	}
+}
+</script>
+
+<style scoped>
+.ay-toast-root {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	z-index: 99999;
+	justify-content: center;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	width: 100%;
+	height: 100%;
+	/* #endif */
+}
+
+.ay-toast-mask {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0, 0, 0, 0.15);
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	height: 100%;
+	/* #endif */
+}
+
+.ay-toast-container {
+	justify-content: center;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	flex-direction: column;
+	/* #endif */
+}
+
+.ay-toast-box {
+	background-color: #FFFFFF;
+	border-radius: 20rpx;
+	padding-top: 36rpx;
+	padding-bottom: 32rpx;
+	padding-left: 44rpx;
+	padding-right: 44rpx;
+	min-width: 220rpx;
+	max-width: 360rpx;
+	justify-content: center;
+	align-items: center;
+	/* #ifdef APP-NVUE */
+	border-width: 1px;
+	border-style: solid;
+	border-color: #f0f0f0;
+	/* #endif */
+	/* #ifndef APP-NVUE */
+	display: flex;
+	flex-direction: column;
+	box-shadow: 0 16rpx 48rpx rgba(56, 149, 136, 0.12);
+	/* #endif */
+}
+
+/* 图标容器 */
+.ay-toast-icon-wrap {
+	width: 76rpx;
+	height: 76rpx;
+	margin-bottom: 20rpx;
+	justify-content: center;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	position: relative;
+	/* #endif */
+}
+
+/* ===== 成功图标 ===== */
+.ay-toast-icon-success {
+	width: 76rpx;
+	height: 76rpx;
+	justify-content: center;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	position: relative;
+	/* #endif */
+}
+
+.success-bg {
+	position: absolute;
+	width: 76rpx;
+	height: 76rpx;
+	border-radius: 38rpx;
+	background-color: #1a9e78;
+	/* #ifndef APP-NVUE */
+	top: 0;
+	left: 0;
+	/* #endif */
+}
+
+.success-line1 {
+	position: absolute;
+	width: 18rpx;
+	height: 4rpx;
+	background-color: #ffffff;
+	border-radius: 2rpx;
+	top: 42rpx;
+	left: 20rpx;
+	transform: rotate(45deg);
+}
+
+.success-line2 {
+	position: absolute;
+	width: 32rpx;
+	height: 4rpx;
+	background-color: #ffffff;
+	border-radius: 2rpx;
+	top: 37rpx;
+	left: 28rpx;
+	transform: rotate(-45deg);
+}
+
+/* ===== 错误图标 ===== */
+.ay-toast-icon-error {
+	width: 76rpx;
+	height: 76rpx;
+	justify-content: center;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	position: relative;
+	/* #endif */
+}
+
+.error-bg {
+	position: absolute;
+	width: 76rpx;
+	height: 76rpx;
+	border-radius: 38rpx;
+	background-color: #F65252;
+	/* #ifndef APP-NVUE */
+	top: 0;
+	left: 0;
+	/* #endif */
+}
+
+.error-line1 {
+	position: absolute;
+	width: 32rpx;
+	height: 4rpx;
+	background-color: #ffffff;
+	border-radius: 2rpx;
+	transform: rotate(45deg);
+	/* #ifndef APP-NVUE */
+	top: 50%;
+	left: 50%;
+	margin-top: -2.5rpx;
+	margin-left: -20rpx;
+	/* #endif */
+}
+
+.error-line2 {
+	position: absolute;
+	width: 32rpx;
+	height: 4rpx;
+	background-color: #ffffff;
+	border-radius: 2rpx;
+	transform: rotate(-45deg);
+	/* #ifndef APP-NVUE */
+	top: 50%;
+	left: 50%;
+	margin-top: -2.5rpx;
+	margin-left: -20rpx;
+	/* #endif */
+}
+
+/* ===== 加载图标 ===== */
+.ay-toast-icon-loading {
+	width: 76rpx;
+	height: 76rpx;
+	justify-content: center;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	position: relative;
+	/* #endif */
+}
+
+.loading-ring {
+	width: 56rpx;
+	height: 56rpx;
+	border-radius: 28rpx;
+	border-width: 4rpx;
+	border-style: solid;
+	border-color: #e8f5f2;
+	/* #ifndef APP-NVUE */
+	border-top-color: #1a9e78;
+	box-sizing: border-box;
+	animation: loading-spin 0.9s linear infinite;
+	/* #endif */
+	/* #ifdef APP-NVUE */
+	border-top-color: #1a9e78;
+	/* #endif */
+}
+
+
+
+/* 文字 */
+.ay-toast-text {
+	font-size: 28rpx;
+	text-align: center;
+	/* #ifdef APP-NVUE */
+	lines: 2;
+	/* #endif */
+	/* #ifndef APP-NVUE */
+	overflow: hidden;
+	text-overflow: ellipsis;
+	display: -webkit-box;
+	-webkit-line-clamp: 2;
+	-webkit-box-orient: vertical;
+	word-break: break-all;
+	/* #endif */
+	max-width: 320rpx;
+}
+
+.text-default {
+	color: #545F71;
+}
+
+.text-success {
+	color: #1a9e78;
+}
+
+.text-error {
+	color: #F65252;
+}
+
+/* #ifndef APP-NVUE */
+@keyframes loading-spin {
+	from {
+		transform: rotate(0deg);
+	}
+	to {
+		transform: rotate(360deg);
+	}
+}
+/* #endif */
+</style>

+ 48 - 0
code/ajyApp/components/customPopup/customPopup.nvue

@@ -0,0 +1,48 @@
+<template>
+	<view class="popUpContainer" v-if="isShow" @click="closePop">
+		<view class="popUpMaskbox" @click.stop="">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'customPopup',
+	props: {
+		isShow: {
+			type: Boolean,
+			default: false
+		}
+	},
+	methods: {
+		closePop() {
+			this.$emit('closePop')
+		}
+	}
+}
+</script>
+
+<style>
+.popUpContainer {
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+	background-color: rgba(0, 0, 0, 0.5);
+}
+.popUpMaskbox {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	background-color: #ffffff;
+	border-top-left-radius: 24rpx;
+	border-top-right-radius: 24rpx;
+	padding-top: 30rpx;
+	padding-bottom: 40rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+}
+</style>

+ 3 - 0
code/ajyApp/main.js

@@ -1,9 +1,11 @@
 import App from './App'
+import toastMixin from './utils/toast-mixin.js'
 
 // #ifndef VUE3
 import Vue from 'vue'
 import './uni.promisify.adaptor'
 Vue.config.productionTip = false
+Vue.mixin(toastMixin)
 App.mpType = 'app'
 const app = new Vue({
   ...App
@@ -15,6 +17,7 @@ app.$mount()
 import { createSSRApp } from 'vue'
 export function createApp() {
   const app = createSSRApp(App)
+  app.mixin(toastMixin)
   return {
     app
   }

+ 100 - 8
code/ajyApp/pages.json

@@ -1,23 +1,115 @@
 {
-	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
-	{
-		"path": "pages/ble-demo/ble-demo",
-		"style": {
-			"navigationBarTitleText": "ble-demo"
+	"easycom": {
+		"autoscan": true,
+		"custom": {
+			"ay-toast": "@/components/ay-toast/ay-toast.nvue"
 		}
 	},
+	"pages": [
+	
 		{
-			"path": "pages/index/index",
+			"path": "pages/home/home",
 			"style": {
-				"navigationBarTitleText": "uni-app"
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/find/index/index",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": true
+			}
+		},
+		{
+			"path": "pages/login/login",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		
+		{
+			"path": "pages/device/list/list",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": true
+			}
+		},
+	
+	
+		{
+			"path": "pages/my/my",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "我的"
+			}
+		},
+		{
+			"path": "pages/webview/webview",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/my/info/info",
+			"style": {
+				"navigationBarTitleText": "个人资料"
+			}
+		},
+		{
+			"path": "pages/my/crop/crop",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/test-toast/test-toast",
+			"style": {
+				"navigationBarTitleText": "Toast测试"
 			}
 		}
 	],
 	"globalStyle": {
 		"navigationBarTextStyle": "black",
-		"navigationBarTitleText": "uni-app",
+		"navigationBarTitleText": "研年健康",
 		"navigationBarBackgroundColor": "#F8F8F8",
 		"backgroundColor": "#F8F8F8"
 	},
+	"tabBar": {
+		"color": "#9BA5B7",
+		"selectedColor": "#389588",
+		"borderStyle": "black",
+		"backgroundColor": "#ffffff",
+		"height": "56px",
+		"fontSize": "13px",
+		"iconWidth": "24px",
+		"spacing": "4px",
+		"list": [{
+			"pagePath": "pages/device/list/list",
+			"iconPath": "/static/public/home.png",
+			"selectedIconPath": "/static/public/home1.png",
+			"text": "首页"
+		},{
+			"pagePath": "pages/home/home",
+			"iconPath": "/static/public/device.png",
+			"selectedIconPath": "/static/public/device1.png",
+			"text": "发现"
+		},{
+			"pagePath": "pages/find/index/index",
+			"iconPath": "/static/public/find.png",
+			"selectedIconPath": "/static/public/find1.png",
+			"text": "健康"
+		},{
+			"pagePath": "pages/my/my",
+			"iconPath": "/static/public/my.png",
+			"selectedIconPath": "/static/public/my1.png",
+			"text": "我的"
+		}]
+	},
 	"uniIdRouter": {}
 }

+ 117 - 0
code/ajyApp/pages/ble-demo/ble-demo.nvue

@@ -0,0 +1,117 @@
+<template>
+	<view class="page">
+		<view class="status">
+			<text class="label">状态:</text>
+			<text class="value">{{ state }}</text>
+		</view>
+		<view class="status" v-if="device">
+			<text class="label">设备:</text>
+			<text class="value">{{ device.name }} ({{ device.deviceId }})</text>
+		</view>
+
+		<view class="btn-row">
+			<text class="btn" @click="onScanConnect">扫描并连接</text>
+			<text class="btn" @click="onDisconnect">断开</text>
+		</view>
+		<view class="btn-row">
+			<text class="btn" @click="onPowerOn">开机</text>
+			<text class="btn" @click="onPowerOff">关机</text>
+			<text class="btn" @click="onStart">开始艾灸</text>
+			<text class="btn" @click="onPause">暂停</text>
+		</view>
+
+		<view class="log-box">
+			<text class="log-line" v-for="(l, i) in logs" :key="i">{{ l }}</text>
+		</view>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import bleManager, {
+	BLE_STATE, MODE, SUB_MODE, TEMPERATURE,
+	ensureBlePrerequisite, openLocationSettings
+} from '@/utils/ble'
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			state: BLE_STATE.IDLE,
+			device: null,
+			logs: []
+		}
+	},
+	onLoad() {
+		bleManager.configure({ deviceNamePrefix: 'AJY', debug: true })
+
+		this._unbinders = [
+			bleManager.on('state', s => { this.state = s }),
+			bleManager.on('connected', d => { this.device = d; this.log('已连接 ' + d.deviceId) }),
+			bleManager.on('disconnected', e => { this.log('已断开 ' + (e.reason || '')) }),
+			bleManager.on('reconnected', d => { this.log('自动重连成功') }),
+			bleManager.on('report:GROUP_1', d => { this.log('参数组1 ' + JSON.stringify(d)) }),
+			bleManager.on('report:GROUP_2', d => { this.log('参数组2 ' + JSON.stringify(d)) })
+		]
+	},
+	onUnload() {
+		this._unbinders.forEach(fn => fn && fn())
+	},
+	methods: {
+		log(msg) {
+			const t = new Date().toTimeString().slice(0, 8)
+			this.logs.unshift(`[${t}] ${msg}`)
+			if (this.logs.length > 100) this.logs.pop()
+		},
+		async onScanConnect() {
+			try {
+				await ensureBlePrerequisite()
+				await bleManager.init()
+				const dev = await bleManager.scanAndConnect({ namePrefix: 'BT-', timeout: 8000 })
+				this.device = dev
+			} catch (e) {
+				this.log('失败: ' + e.message)
+				if (e.message === 'BLE_LOCATION_OFF') {
+					uni.showModal({
+						title: '提示', content: '请先开启位置服务', success: r => { if (r.confirm) openLocationSettings() }
+					})
+				} else if (e.message === 'BLE_ADAPTER_OFF') {
+					this.$toastRef.error('请开启蓝牙')
+				}
+			}
+		},
+		async onDisconnect() {
+			await bleManager.disconnect()
+			this.device = null
+		},
+		async onPowerOn()  { await this._safe(() => bleManager.powerOn()) },
+		async onPowerOff() { await this._safe(() => bleManager.powerOff()) },
+		async onStart() {
+			await this._safe(() => bleManager.startMoxi({
+				mode: MODE.BASIC, subMode: SUB_MODE.SUB_1,
+				temperature: TEMPERATURE.MID, duration: 30
+			}))
+		},
+		async onPause() { await this._safe(() => bleManager.pauseMoxi()) },
+
+		async _safe(fn) {
+			try { await fn(); this.log('指令已下发') }
+			catch (e) { this.log('指令失败: ' + e.message) }
+		}
+	}
+}
+</script>
+
+<style>
+.page        { flex: 1; padding: 24rpx; background-color: #f5f5f7; }
+.status      { flex-direction: row; padding: 12rpx 0; }
+.label       { width: 120rpx; color: #666; font-size: 28rpx; }
+.value       { flex: 1; color: #222; font-size: 28rpx; }
+.btn-row     { flex-direction: row; flex-wrap: wrap; margin-top: 12rpx; }
+.btn         { padding: 16rpx 24rpx; margin: 8rpx; background-color: #4f8ef7; color: #fff; border-radius: 8rpx; font-size: 28rpx; }
+.log-box     { flex: 1; margin-top: 24rpx; padding: 16rpx; background-color: #1e1e1e; border-radius: 8rpx; }
+.log-line    { color: #b2f5b2; font-size: 24rpx; line-height: 36rpx; }
+</style>

+ 198 - 0
code/ajyApp/pages/device/list/list.nvue

@@ -0,0 +1,198 @@
+<template>
+	<view class="page">
+		<image class="content-bg" src="/static/device/devicebg.png" mode="scaleToFill"></image>
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<view class="custom-head">
+			<text class="head-placeholder"> </text>
+			<text class="head-title">我的设备</text>
+			<text class="head-add" @click="goAddDevice">+</text>
+		</view>
+		<text class="section-title">已添加设备</text>
+		<view class="device-list">
+			<!-- 设备卡片 -->
+			<view class="device-card" v-for="(item, index) in list" :key="index" @click="goDeviceInfo(item)">
+				<image class="device-icon" src="/static/device/device1.png" mode="heightFix"></image>
+				<text class="device-name">{{item.name}}</text>
+				<text class="device-status"></text>
+			</view>
+			<!-- 添加设备卡片 -->
+			<view class="device-card add-card" @click="goAddDevice">
+				<text class="add-title">添加设备</text>
+				<view class="add-icon-wrap">
+					<view class="add-icon-circle">
+						<text class="add-icon-text">+</text>
+					</view>
+					<text class="add-tip">去添加</text>
+				</view>
+			</view>
+		</view>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			statusBarHeight: 44,
+			list: []
+		}
+	},
+	onShow() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.init()
+	},
+	onPullDownRefresh() {
+		uni.stopPullDownRefresh()
+		this.init()
+	},
+	methods: {
+		init() {
+			// 从本地存储读取设备列表
+			try {
+				const data = uni.getStorageSync('deviceList')
+				if (data && data.length > 0) {
+					this.list = data
+				} else {
+					this.list = []
+				}
+			} catch (e) {
+				this.list = []
+			}
+		},
+		goAddDevice() {
+			// TODO: 跳转添加设备页面
+			this.$toastRef.text('添加设备')
+		},
+		goDeviceInfo(item) {
+			// TODO: 跳转设备详情
+			this.$toastRef.text('设备: ' + item.name)
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	position: relative;
+}
+.content-bg {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 750rpx;
+	height: 1400rpx;
+}
+.status-bar {
+	background-color: rgba(0,0,0,0);
+}
+.custom-head {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	height: 88rpx;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+}
+.head-placeholder {
+	width: 36rpx;
+}
+.head-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.head-add {
+	width: 36rpx;
+	height: 36rpx;
+	border-width: 2px;
+	border-style: solid;
+	border-color: #545F71;
+	border-radius: 36rpx;
+	text-align: center;
+	line-height: 32rpx;
+	font-size: 26rpx;
+	font-weight: bold;
+	color: #545F71;
+}
+.section-title {
+	font-size: 32rpx;
+	color: #545F71;
+	margin-left: 38rpx;
+	margin-bottom: 38rpx;
+	margin-top: 20rpx;
+}
+.device-list {
+	flex-direction: row;
+	flex-wrap: wrap;
+	justify-content: space-between;
+	padding-left: 38rpx;
+	padding-right: 38rpx;
+}
+.device-card {
+	width: 324rpx;
+	height: 300rpx;
+	background-color: #FFFFFF;
+	border-radius: 24rpx;
+	margin-bottom: 26rpx;
+	align-items: center;
+	padding-top: 40rpx;
+}
+.device-icon {
+	width: 156rpx;
+	height: 156rpx;
+}
+.device-name {
+	margin-top: 36rpx;
+	font-weight: bold;
+	font-size: 28rpx;
+	color: #545F71;
+}
+.device-status {
+	font-size: 24rpx;
+	color: #999999;
+	margin-top: 10rpx;
+}
+.add-card {
+	align-items: flex-start;
+	padding-left: 40rpx;
+	padding-top: 40rpx;
+}
+.add-title {
+	font-weight: bold;
+	font-size: 28rpx;
+	color: #545F71;
+	margin-bottom: 48rpx;
+}
+.add-icon-wrap {
+	align-items: center;
+	width: 244rpx;
+}
+.add-icon-circle {
+	width: 60rpx;
+	height: 60rpx;
+	background-color: #C3DFDB;
+	border-radius: 60rpx;
+	justify-content: center;
+	align-items: center;
+}
+.add-icon-text {
+	font-size: 42rpx;
+	color: #389588;
+	font-weight: bold;
+	margin-top: -4rpx;
+}
+.add-tip {
+	text-align: center;
+	margin-top: 8rpx;
+	font-size: 24rpx;
+	color: #999999;
+}
+</style>

+ 162 - 0
code/ajyApp/pages/find/index/index.nvue

@@ -0,0 +1,162 @@
+<template>
+	<view class="page">
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<text class="page-title">推荐</text>
+		<scroll-view class="scroll-content" scroll-y="true" @scrolltolower="onReachBottom">
+			<view class="recommend-grid">
+				<view class="recommend-card" v-for="(item, index) in recommends" :key="index" @click="onItemClick(item)">
+					<image class="card-img" :src="item.img" mode="aspectFill"></image>
+					<text class="card-title">{{item.title}}</text>
+					<view class="card-footer">
+						<view class="badge-circle">
+							<text class="badge-text">研</text>
+						</view>
+						<text class="footer-name">研年健康贴士</text>
+						<view class="badge-official">
+							<text class="official-text">官方</text>
+						</view>
+					</view>
+				</view>
+			</view>
+			<text class="load-tip" v-if="recommends.length > 0">{{loadMore ? '加载中...' : ''}}</text>
+		</scroll-view>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			statusBarHeight: 44,
+			page: 1,
+			loadMore: false,
+			recommends: [
+				{ id: 1, img: '/static/home/banner.png', title: '春季艾灸养生:温补阳气祛湿寒' },
+				{ id: 2, img: '/static/home/detail.png', title: '失眠困扰?试试这几个穴位' },
+				{ id: 3, img: '/static/home/banner.png', title: '办公族必看:肩颈酸痛缓解方案' },
+				{ id: 4, img: '/static/home/detail.png', title: '女性调理:暖宫驱寒艾灸指南' },
+				{ id: 5, img: '/static/home/banner.png', title: '中老年腰膝酸软怎么办' },
+				{ id: 6, img: '/static/home/detail.png', title: '日常保健:提升免疫力的穴位' }
+			]
+		}
+	},
+	onShow() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+	},
+	onPullDownRefresh() {
+		this.page = 1
+		// TODO: 重新加载数据
+		uni.stopPullDownRefresh()
+	},
+	methods: {
+		onReachBottom() {
+			if (this.loadMore) return
+			this.page++
+			// TODO: 加载更多数据
+		},
+		onItemClick(item) {
+			// TODO: 跳转详情
+			this.$toastRef.text(item.title)
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #F5F5F5;
+}
+.status-bar {
+	background-color: #F5F5F5;
+}
+.page-title {
+	font-weight: bold;
+	font-size: 48rpx;
+	color: #545F71;
+	margin-left: 50rpx;
+	margin-top: 40rpx;
+	margin-bottom: 40rpx;
+}
+.scroll-content {
+	flex: 1;
+}
+.recommend-grid {
+	flex-direction: row;
+	flex-wrap: wrap;
+	justify-content: space-between;
+	padding-left: 40rpx;
+	padding-right: 40rpx;
+}
+.recommend-card {
+	width: 324rpx;
+	background-color: #FFFFFF;
+	border-radius: 20rpx;
+	margin-bottom: 28rpx;
+	overflow: hidden;
+}
+.card-img {
+	width: 324rpx;
+	height: 340rpx;
+}
+.card-title {
+	padding-left: 16rpx;
+	padding-right: 16rpx;
+	padding-top: 20rpx;
+	padding-bottom: 16rpx;
+	font-size: 32rpx;
+	color: #545F71;
+	lines: 2;
+	text-overflow: ellipsis;
+}
+.card-footer {
+	flex-direction: row;
+	align-items: center;
+	padding-left: 16rpx;
+	padding-bottom: 20rpx;
+}
+.badge-circle {
+	width: 32rpx;
+	height: 32rpx;
+	background-color: #389588;
+	border-radius: 32rpx;
+	justify-content: center;
+	align-items: center;
+	margin-right: 4rpx;
+}
+.badge-text {
+	font-size: 20rpx;
+	color: #FFFFFF;
+}
+.footer-name {
+	font-size: 24rpx;
+	color: #BBBDBF;
+}
+.badge-official {
+	width: 60rpx;
+	height: 32rpx;
+	background-color: #000000;
+	border-radius: 4rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 10rpx;
+}
+.official-text {
+	font-size: 24rpx;
+	color: #EAC384;
+}
+.load-tip {
+	text-align: center;
+	font-size: 24rpx;
+	color: #999999;
+	padding-top: 20rpx;
+	padding-bottom: 40rpx;
+}
+</style>

+ 259 - 0
code/ajyApp/pages/home/home.nvue

@@ -0,0 +1,259 @@
+<template>
+	<view class="page">
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<scroll-view class="scroll-content" scroll-y="true">
+			<text class="logo">LOGO</text>
+
+			<!-- 轮播区域 -->
+			<view class="swiper-box">
+				<swiper class="swiper" :current="current" @change="onChange" :autoplay="true" :interval="5000">
+					<swiper-item class="swiper-item" v-for="(item, index) in bannerList" :key="index">
+						<view class="slide" @click="onBannerClick(item)">
+							<image class="slide-img" :src="item.img" mode="aspectFill"></image>
+							<text class="slide-sub-title">{{item.subTitle}}</text>
+							<text class="slide-title">{{item.title}}</text>
+						</view>
+					</swiper-item>
+				</swiper>
+				<view class="dot-list">
+					<view v-for="(item, idx) in bannerList" :key="idx"
+						:class="['dot-item', idx === current ? 'dot-active' : '']"></view>
+				</view>
+			</view>
+
+			<!-- 热灸推荐 -->
+			<view class="recommend-section">
+				<text class="section-title">热灸推荐</text>
+				<view class="recommend-item" v-for="(item, index) in recommends" :key="index" @click="onRecommendClick(item)">
+					<image class="recommend-img" :src="item.img" mode="aspectFill"></image>
+					<view class="recommend-info">
+						<text class="recommend-title">{{item.title}}</text>
+						<text class="recommend-time">{{item.time}}</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- 灸研室 -->
+			<view class="news-section" v-if="news.length > 0">
+				<view class="news-head">
+					<text class="section-title">灸研室</text>
+					<text class="more-text" @click="onMoreNews">更多</text>
+				</view>
+				<view class="news-list">
+					<view class="news-item" v-for="(item, index) in news" :key="index" @click="onNewsClick(item)">
+						<image class="news-img" :src="item.img" mode="aspectFill"></image>
+						<text class="news-name">{{item.title}}</text>
+						<text class="news-author">{{item.author}}</text>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			statusBarHeight: 44,
+			current: 0,
+			bannerList: [
+				{ img: '/static/home/banner.png', title: '艾灸养生', subTitle: '中医传统理疗方式' },
+				{ img: '/static/home/detail.png', title: '智能穴位定位', subTitle: '精准理疗每一寸' }
+			],
+			recommends: [
+				{ id: 1, img: '/static/home/banner.png', title: '春季养生:温阳祛湿艾灸方案', time: '2026-05-20 10:30' },
+				{ id: 2, img: '/static/home/detail.png', title: '肩颈疲劳缓解方案推荐', time: '2026-05-18 14:20' },
+				{ id: 3, img: '/static/home/banner.png', title: '改善睡眠质量的艾灸穴位', time: '2026-05-15 09:00' }
+			],
+			news: [
+				{ id: 1, img: '/static/home/banner.png', title: '艾灸入门指南', author: '研年健康' },
+				{ id: 2, img: '/static/home/detail.png', title: '常见穴位科普', author: '研年健康' },
+				{ id: 3, img: '/static/home/banner.png', title: '四季养生之道', author: '研年健康' },
+				{ id: 4, img: '/static/home/detail.png', title: '经络疏通技巧', author: '研年健康' }
+			]
+		}
+	},
+	onShow() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+	},
+	methods: {
+		onChange(e) {
+			this.current = e.detail.current
+		},
+		onBannerClick(item) {
+			// TODO: 跳转banner详情
+		},
+		onRecommendClick(item) {
+			// TODO: 跳转推荐详情
+		},
+		onNewsClick(item) {
+			// TODO: 跳转灸研室详情
+		},
+		onMoreNews() {
+			// TODO: 跳转更多
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #F8F8F8;
+}
+.scroll-content {
+	flex: 1;
+}
+.status-bar {
+	background-color: #F8F8F8;
+}
+.logo {
+	font-weight: bold;
+	font-size: 36rpx;
+	margin-left: 38rpx;
+	margin-top: 16rpx;
+	margin-bottom: 16rpx;
+	color: #333333;
+}
+/* 轮播 */
+.swiper-box {
+	width: 676rpx;
+	margin-left: 37rpx;
+}
+.swiper {
+	height: 356rpx;
+	border-radius: 20rpx;
+}
+.swiper-item {
+	width: 676rpx;
+	height: 356rpx;
+}
+.slide {
+	width: 676rpx;
+	height: 356rpx;
+	border-radius: 20rpx;
+	overflow: hidden;
+	position: relative;
+}
+.slide-img {
+	width: 676rpx;
+	height: 356rpx;
+	position: absolute;
+	left: 0;
+	top: 0;
+}
+.slide-title {
+	font-size: 28rpx;
+	color: #FFFFFF;
+	position: absolute;
+	left: 30rpx;
+	bottom: 34rpx;
+}
+.slide-sub-title {
+	font-weight: 500;
+	font-size: 40rpx;
+	color: #FFFFFF;
+	position: absolute;
+	left: 30rpx;
+	bottom: 82rpx;
+}
+.dot-list {
+	flex-direction: row;
+	padding-top: 22rpx;
+}
+.dot-item {
+	width: 32rpx;
+	height: 6rpx;
+	background-color: #D8D8D8;
+	border-radius: 4rpx;
+	margin-right: 12rpx;
+}
+.dot-active {
+	width: 64rpx;
+	background-color: #389588;
+}
+/* 推荐 */
+.recommend-section {
+	width: 676rpx;
+	margin-left: 37rpx;
+	margin-top: 42rpx;
+}
+.section-title {
+	font-size: 36rpx;
+	color: #545F71;
+	margin-bottom: 24rpx;
+}
+.recommend-item {
+	flex-direction: row;
+	background-color: #FFFFFF;
+	border-radius: 20rpx;
+	margin-bottom: 24rpx;
+	padding: 18rpx;
+	padding-right: 36rpx;
+}
+.recommend-img {
+	width: 140rpx;
+	height: 140rpx;
+	border-radius: 20rpx;
+	margin-right: 16rpx;
+}
+.recommend-info {
+	flex: 1;
+	justify-content: space-between;
+}
+.recommend-title {
+	font-size: 32rpx;
+	color: #545F71;
+	lines: 2;
+	text-overflow: ellipsis;
+}
+.recommend-time {
+	font-size: 24rpx;
+	color: #9BA5B7;
+}
+/* 灸研室 */
+.news-section {
+	width: 676rpx;
+	margin-left: 37rpx;
+	margin-top: 42rpx;
+	padding-bottom: 40rpx;
+}
+.news-head {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 24rpx;
+}
+.more-text {
+	font-size: 28rpx;
+	color: #389588;
+}
+.news-list {
+	flex-direction: row;
+	flex-wrap: wrap;
+	justify-content: space-between;
+}
+.news-item {
+	width: 326rpx;
+	margin-bottom: 24rpx;
+}
+.news-img {
+	width: 326rpx;
+	height: 234rpx;
+	border-radius: 20rpx;
+}
+.news-name {
+	font-size: 32rpx;
+	color: #545F71;
+	margin-top: 16rpx;
+	lines: 2;
+	text-overflow: ellipsis;
+}
+.news-author {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	margin-top: 4rpx;
+}
+</style>

+ 511 - 0
code/ajyApp/pages/login/login.nvue

@@ -0,0 +1,511 @@
+<template>
+	<view class="login-page">
+		<!-- 顶部欢迎语 -->
+		<view class="header">
+			<text class="hello">HELLO,</text>
+			<text class="welcome">欢迎使用研年健康平台</text>
+		</view>
+
+		<!-- 表单区域 -->
+		<view class="form-area">
+			<!-- 手机号输入 -->
+			<view class="input-wrapper">
+				<image class="input-icon" src="/static/login/phone.png" />
+				<input
+					class="input-field"
+					type="number"
+					maxlength="11"
+					placeholder="请输入手机号"
+					placeholder-class="placeholder-text"
+					:value="phone"
+					@input="onPhoneInput"
+				/>
+			</view>
+
+			<!-- 验证码输入 -->
+			<view class="input-wrapper">
+				<image class="input-icon" src="/static/login/pwd.png" />
+				<input
+					class="input-field code-input"
+					type="number"
+					maxlength="6"
+					placeholder="请输入验证码"
+					placeholder-class="placeholder-text"
+					:value="code"
+					@input="onCodeInput"
+				/>
+				<text
+					class="code-btn"
+					:class="[codeBtnDisabled ? 'code-btn-disabled' : '']"
+					@click="onGetCode"
+				>{{ countdown > 0 ? countdown + 's后重发' : '获取验证码' }}</text>
+			</view>
+
+			<!-- 登录按钮 -->
+			<view
+				class="login-btn"
+				:class="[!canLogin ? 'login-btn-disabled' : '']"
+				@click="onLogin"
+			>
+				<text class="login-btn-text">登录</text>
+			</view>
+		</view>
+
+		<!-- 其他登录方式 -->
+		<view class="other-login">
+			<view class="divider">
+				<view class="divider-line"></view>
+				<text class="divider-text">其他登录方式</text>
+				<view class="divider-line"></view>
+			</view>
+
+			<view class="third-party-area">
+				<!-- 微信登录 -->
+				<view class="third-party-btn" @click="onWechatLogin">
+					<image class="third-party-icon" src="/static/login/wechat@2x.png" />
+				</view>
+			</view>
+
+			<!-- Apple 登录 - 仅iOS显示,符合苹果审核规范 -->
+			<view class="apple-section" >
+				<view class="apple-login-btn" @click="onAppleLogin">
+					<image class="apple-icon" src="/static/login/apple@2x.png" />
+					<text class="apple-btn-text">通过 Apple 登录</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 底部协议 -->
+		<view class="footer">
+			<view class="agreement-row" @click="onToggleAgreement">
+				<view class="checkbox" :class="[agreed ? 'checkbox-checked' : '']">
+					<text class="checkbox-icon" v-if="agreed">✓</text>
+				</view>
+				<text class="agreement-text">登录即代表同意</text>
+				<text class="agreement-link" @click.stop="onOpenPrivacy">隐私政策</text>
+				<text class="agreement-text">和</text>
+				<text class="agreement-link" @click.stop="onOpenTerms">用户协议</text>
+			</view>
+		</view>
+
+		<!-- 隐私政策弹窗 -->
+		<customPopup :isShow="isShowDrawer" @closePop="isShowDrawer=false">
+			<view class="boxcontent">
+				<view class="rich_header">
+					<text class="rich_title">服务协议与隐私政策</text>
+				</view>
+				<scroll-view class="privateContent" scroll-y="true">
+					<view class="contentBox">
+						<text class="content-text">感谢您信任并使用研年健康平台!我们深知保护个人隐私信息的重要性,请在使用研年健康平台的服务前,认真阅读我们的</text>
+						<view class="linebox">
+							<text class="link-text" @click="goWebview(1)">《用户协议》</text>
+							<text class="content-text">和</text>
+							<text class="link-text" @click="goWebview(2)">《隐私政策》</text>
+						</view>
+						<text class="content-text">,以帮助您了解我们在收集、使用、存储和共享您个人信息的情况以及您享有的相关权利。</text>
+						<text class="content-text"> 请您仔细阅读并作出适当的选择:</text>
+						<text class="content-text"> 1.《用户协议》的主要内容包括:协议适用范围、服务内容及形式、软件使用及许可、终端责任安全、用户权利与义务、用户行为规范、知识产权声明、用户信息保护。</text>
+						<text class="content-text"> 2.关于《隐私政策》,我们特别向您说明:</text>
+						<text class="content-text">(1)根据合法、正当、必要的原则,我们会收集实现产品功能所必要的信息,包括:您在注册帐号时所填写的信息(昵称、手机号码);您的日志信息及设备信息、设备型号;当您选择使用部分服务时,我们将在征求您的同意后获取相关权限。</text>
+					</view>
+				</scroll-view>
+				<view class="rich_btn">
+					<text class="btn-disagree" @click="onAgree(0)">不同意</text>
+					<text class="btn-agree" @click="onAgree(1)">同意</text>
+				</view>
+			</view>
+		</customPopup>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import customPopup from '@/components/customPopup/customPopup.nvue'
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		customPopup,
+		ayToast
+	},
+	data() {
+		return {
+			phone: '',
+			code: '',
+			countdown: 0,
+			agreed: false,
+			isIOS: false,
+			countdownTimer: null,
+			isShowDrawer: false
+		}
+	},
+	computed: {
+		isPhoneValid() {
+			return /^1[3-9]\d{9}$/.test(this.phone)
+		},
+		codeBtnDisabled() {
+			return this.countdown > 0 || !this.isPhoneValid
+		},
+		canLogin() {
+			return this.isPhoneValid && this.code.length >= 4 && this.agreed
+		}
+	},
+	onLoad() {
+		// 判断平台
+		const sysInfo = uni.getSystemInfoSync()
+		this.isIOS = sysInfo.platform === 'ios'
+	},
+	onUnload() {
+		if (this.countdownTimer) {
+			clearInterval(this.countdownTimer)
+			this.countdownTimer = null
+		}
+	},
+	methods: {
+		onPhoneInput(e) {
+			this.phone = e.detail.value
+		},
+		onCodeInput(e) {
+			this.code = e.detail.value
+		},
+		// 获取验证码
+		onGetCode() {
+			if (this.codeBtnDisabled) return
+			if (!this.isPhoneValid) {
+				this.$toastRef.error('请输入正确的手机号')
+				return
+			}
+			// 开始倒计时
+			this.countdown = 60
+			this.countdownTimer = setInterval(() => {
+				this.countdown--
+				if (this.countdown <= 0) {
+					clearInterval(this.countdownTimer)
+					this.countdownTimer = null
+				}
+			}, 1000)
+			this.$toastRef.success('验证码已发送')
+			// TODO: 调用发送验证码接口
+		},
+		// 登录
+		onLogin() {
+			if (!this.agreed) {
+				this.$toastRef.error('请先同意隐私政策和用户协议')
+				return
+			}
+			if (!this.isPhoneValid) {
+				this.$toastRef.error('请输入正确的手机号')
+				return
+			}
+			if (this.code.length < 4) {
+				this.$toastRef.error('请输入验证码')
+				return
+			}
+			this.$toastRef.loading('登录中...')
+			// TODO: 调用登录接口
+			// http.post('/auth/login', { phone: this.phone, code: this.code }, { withToken: false })
+		},
+		// 微信登录
+		onWechatLogin() {
+			if (!this.agreed) {
+				this.$toastRef.error('请先同意隐私政策和用户协议')
+				return
+			}
+			this.$toastRef.text('微信登录')
+			// TODO: 调用微信登录
+		},
+		// Apple登录
+		onAppleLogin() {
+			if (!this.agreed) {
+				this.$toastRef.error('请先同意隐私政策和用户协议')
+				return
+			}
+			this.$toastRef.text('Apple登录')
+			// TODO: 调用 Apple 登录
+		},
+		// 切换协议勾选
+		onToggleAgreement() {
+			this.agreed = !this.agreed
+		},
+		// 打开隐私政策/用户协议弹窗
+		onOpenPrivacy() {
+			this.isShowDrawer = true
+		},
+		onOpenTerms() {
+			this.isShowDrawer = true
+		},
+		// 弹窗中同意/不同意操作
+		onAgree(type) {
+			this.isShowDrawer = false
+			if (type === 1) {
+				this.agreed = true
+			}
+		},
+		// 跳转webview查看完整协议
+		goWebview(id) {
+			uni.navigateTo({
+				url: '/pages/webview/webview?id=' + id
+			})
+		}
+	}
+}
+</script>
+
+<style>
+.login-page {
+	flex: 1;
+	background-color: #ffffff;
+	padding-left: 40rpx;
+	padding-right: 40rpx;
+	padding-top: 160rpx;
+}
+
+/* 顶部欢迎语 */
+.header {
+	margin-bottom: 80rpx;
+}
+.hello {
+	font-size: 56rpx;
+	font-weight: bold;
+	color: #1a9e78;
+	margin-bottom: 10rpx;
+}
+.welcome {
+	font-size: 40rpx;
+	font-weight: bold;
+	color: #1a9e78;
+}
+
+/* 表单区域 */
+.form-area {
+	margin-bottom: 60rpx;
+}
+.input-wrapper {
+	flex-direction: row;
+	align-items: center;
+	background-color: #f5f5f5;
+	border-radius: 16rpx;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	height: 100rpx;
+	margin-bottom: 24rpx;
+}
+.input-icon {
+	width: 40rpx;
+	height: 40rpx;
+	margin-right: 16rpx;
+}
+.input-field {
+	flex: 1;
+	font-size: 30rpx;
+	color: #333333;
+	height: 100rpx;
+}
+.placeholder-text {
+	color: #999999;
+	font-size: 30rpx;
+}
+.code-input {
+	flex: 1;
+}
+.code-btn {
+	font-size: 28rpx;
+	color: #1a9e78;
+	padding-left: 20rpx;
+}
+.code-btn-disabled {
+	color: #cccccc;
+}
+
+/* 登录按钮 */
+.login-btn {
+	height: 96rpx;
+	background-color: #7ecdb8;
+	border-radius: 48rpx;
+	align-items: center;
+	justify-content: center;
+	margin-top: 20rpx;
+}
+.login-btn-disabled {
+	background-color: #c8e6dc;
+}
+.login-btn-text {
+	font-size: 32rpx;
+	color: #ffffff;
+	font-weight: bold;
+}
+
+/* 其他登录方式 */
+.other-login {
+	flex: 1;
+	align-items: center;
+}
+.divider {
+	flex-direction: row;
+	align-items: center;
+	margin-top: 60rpx;
+	margin-bottom: 50rpx;
+}
+.divider-line {
+	width: 140rpx;
+	height: 1px;
+	background-color: #e0e0e0;
+}
+.divider-text {
+	font-size: 26rpx;
+	color: #999999;
+	margin-left: 20rpx;
+	margin-right: 20rpx;
+}
+
+/* 第三方登录按钮 */
+.third-party-area {
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	margin-bottom: 40rpx;
+}
+.third-party-btn {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 50rpx;
+	background-color: #f5f5f5;
+	align-items: center;
+	justify-content: center;
+}
+.third-party-icon {
+	width: 56rpx;
+	height: 56rpx;
+}
+
+/* Apple 登录按钮 - 符合苹果HIG规范 */
+.apple-section {
+	align-items: center;
+	margin-top: 20rpx;
+}
+.apple-login-btn {
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	width: 580rpx;
+	height: 88rpx;
+	background-color: #000000;
+	border-radius: 44rpx;
+}
+.apple-icon {
+	width: 36rpx;
+	height: 36rpx;
+	margin-right: 12rpx;
+}
+.apple-btn-text {
+	font-size: 30rpx;
+	color: #ffffff;
+	font-weight: 500;
+}
+
+/* 底部协议 */
+.footer {
+	position: fixed;
+	bottom: 60rpx;
+	left: 40rpx;
+	right: 40rpx;
+}
+.agreement-row {
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+}
+.checkbox {
+	width: 32rpx;
+	height: 32rpx;
+	border-radius: 16rpx;
+	border-width: 2rpx;
+	border-style: solid;
+	border-color: #cccccc;
+	align-items: center;
+	justify-content: center;
+	margin-right: 10rpx;
+}
+.checkbox-checked {
+	background-color: #1a9e78;
+	border-color: #1a9e78;
+}
+.checkbox-icon {
+	font-size: 20rpx;
+	color: #ffffff;
+}
+.agreement-text {
+	font-size: 24rpx;
+	color: #999999;
+}
+.agreement-link {
+	font-size: 24rpx;
+	color: #1a9e78;
+}
+
+/* 隐私政策弹窗 */
+.boxcontent {
+	padding-left: 10rpx;
+	padding-right: 10rpx;
+}
+.rich_header {
+	align-items: center;
+	margin-bottom: 30rpx;
+}
+.rich_title {
+	font-size: 34rpx;
+	font-weight: bold;
+	color: #333333;
+}
+.privateContent {
+	height: 500rpx;
+}
+.contentBox {
+	padding-left: 10rpx;
+	padding-right: 10rpx;
+}
+.content-text {
+	font-size: 26rpx;
+	color: #666666;
+	line-height: 44rpx;
+}
+.linebox {
+	flex-direction: row;
+	flex-wrap: wrap;
+	align-items: center;
+}
+.link-text {
+	font-size: 26rpx;
+	color: #1a9e78;
+	line-height: 44rpx;
+}
+.rich_btn {
+	flex-direction: row;
+	justify-content: space-between;
+	margin-top: 30rpx;
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+}
+.btn-disagree {
+	flex: 1;
+	height: 80rpx;
+	line-height: 80rpx;
+	text-align: center;
+	font-size: 30rpx;
+	color: #999999;
+	background-color: #f5f5f5;
+	border-radius: 40rpx;
+	margin-right: 20rpx;
+}
+.btn-agree {
+	flex: 1;
+	height: 80rpx;
+	line-height: 80rpx;
+	text-align: center;
+	font-size: 30rpx;
+	color: #ffffff;
+	background-color: #1a9e78;
+	border-radius: 40rpx;
+	margin-left: 20rpx;
+}
+</style>

+ 378 - 0
code/ajyApp/pages/my/crop/crop.vue

@@ -0,0 +1,378 @@
+<template>
+	<view class="page">
+		<!-- 顶部导航 -->
+		<view class="nav-bar">
+			<view class="status-bar"></view>
+			<view class="nav-content">
+				<text class="nav-back" @click="goBack">&#xe6e2;</text>
+				<text class="nav-title">裁剪头像</text>
+				<text class="nav-save" @click="saveImage">完成</text>
+			</view>
+		</view>
+
+		<!-- 裁剪区域 -->
+		<view class="crop-area"
+			@touchstart="onTouchStart"
+			@touchmove.prevent="onTouchMove"
+			@touchend="onTouchEnd">
+			<!-- 图片 - 填满显示 -->
+			<image class="crop-image" :src="imageSrc" mode="scaleToFill"
+				:style="{
+					width: imgWidth + 'px',
+					height: imgHeight + 'px',
+					left: imgLeft + 'px',
+					top: imgTop + 'px'
+				}"></image>
+
+			<!-- 圆形遮罩 - 用box-shadow实现圆形镂空 -->
+			<view class="crop-circle-mask" :style="{
+				width: cropSize + 'px',
+				height: cropSize + 'px',
+				top: cropTop + 'px',
+				left: cropLeft + 'px'
+			}"></view>
+		</view>
+
+		<!-- 底部:实时预览 + 提示 -->
+		<view class="bottom-bar">
+			<view class="preview-circle">
+				<image :src="imageSrc" mode="scaleToFill" :style="previewStyle"></image>
+			</view>
+			<text class="tip-text">拖动裁剪框 · 双指缩放图片</text>
+		</view>
+
+		<!-- 输出canvas(隐藏) -->
+		<canvas canvas-id="cropCanvas" class="crop-canvas"
+			:style="{width: canvasSize + 'px', height: canvasSize + 'px'}"></canvas>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			statusBarHeight: 44,
+			imageSrc: '',
+			screenWidth: 375,
+			areaHeight: 500,
+			// 裁剪框
+			cropSize: 280,
+			cropTop: 0,
+			cropLeft: 0,
+			// 图片显示
+			imgWidth: 375,
+			imgHeight: 500,
+			imgLeft: 0,
+			imgTop: 0,
+			naturalWidth: 0,
+			naturalHeight: 0,
+			// 触摸状态
+			touchMode: 'none',
+			startX: 0,
+			startY: 0,
+			startCropLeft: 0,
+			startCropTop: 0,
+			startDistance: 0,
+			startImgWidth: 0,
+			startImgHeight: 0,
+			startImgLeft: 0,
+			startImgTop: 0,
+			// 输出
+			canvasSize: 400,
+			previewSize: 70
+		}
+	},
+	computed: {
+		// 实时预览样式 - 将裁剪框内容映射到预览圆中
+		previewStyle() {
+			if (!this.imgWidth || !this.cropSize) return {}
+			const ratio = this.previewSize / this.cropSize
+			const w = this.imgWidth * ratio
+			const h = this.imgHeight * ratio
+			const x = -(this.cropLeft - this.imgLeft) * ratio
+			const y = -(this.cropTop - this.imgTop) * ratio
+			return {
+				width: w + 'px',
+				height: h + 'px',
+				marginLeft: x + 'px',
+				marginTop: y + 'px'
+			}
+		}
+	},
+	onLoad(options) {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.screenWidth = sysInfo.windowWidth
+		const screenHeight = sysInfo.windowHeight
+		const navHeight = this.statusBarHeight + 44
+		const bottomHeight = 80
+		this.areaHeight = screenHeight - navHeight - bottomHeight
+
+		// 裁剪框大小为屏幕宽度的 75%
+		this.cropSize = Math.floor(this.screenWidth * 0.75)
+		// 裁剪框居中
+		this.cropLeft = Math.floor((this.screenWidth - this.cropSize) / 2)
+		this.cropTop = Math.floor((this.areaHeight - this.cropSize) / 2)
+
+		if (options.src) {
+			this.imageSrc = decodeURIComponent(options.src)
+			this.loadImageInfo()
+		}
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		loadImageInfo() {
+			uni.getImageInfo({
+				src: this.imageSrc,
+				success: (info) => {
+					this.naturalWidth = info.width
+					this.naturalHeight = info.height
+					const ratio = info.width / info.height
+
+					// 图片填满整个裁剪区域
+					const areaRatio = this.screenWidth / this.areaHeight
+					if (ratio > areaRatio) {
+						// 图片更宽 - 按高度适配
+						this.imgHeight = this.areaHeight
+						this.imgWidth = Math.floor(this.areaHeight * ratio)
+					} else {
+						// 图片更高 - 按宽度适配
+						this.imgWidth = this.screenWidth
+						this.imgHeight = Math.floor(this.screenWidth / ratio)
+					}
+					// 居中
+					this.imgLeft = Math.floor((this.screenWidth - this.imgWidth) / 2)
+					this.imgTop = Math.floor((this.areaHeight - this.imgHeight) / 2)
+				},
+				fail: () => {
+					this.imgWidth = this.screenWidth
+					this.imgHeight = this.areaHeight
+					this.imgLeft = 0
+					this.imgTop = 0
+				}
+			})
+		},
+
+		// === 触摸事件 ===
+		onTouchStart(e) {
+			const touches = e.touches || e.changedTouches
+			if (touches.length === 1) {
+				// 单指 → 拖动裁剪框
+				this.touchMode = 'drag'
+				this.startX = touches[0].pageX
+				this.startY = touches[0].pageY
+				this.startCropLeft = this.cropLeft
+				this.startCropTop = this.cropTop
+			} else if (touches.length === 2) {
+				// 双指 → 缩放图片
+				this.touchMode = 'zoom'
+				this.startDistance = this.getDistance(touches[0], touches[1])
+				this.startImgWidth = this.imgWidth
+				this.startImgHeight = this.imgHeight
+				this.startImgLeft = this.imgLeft
+				this.startImgTop = this.imgTop
+			}
+		},
+		onTouchMove(e) {
+			const touches = e.touches || e.changedTouches
+			if (this.touchMode === 'drag' && touches.length === 1) {
+				const dx = touches[0].pageX - this.startX
+				const dy = touches[0].pageY - this.startY
+				// 限制裁剪框在区域内
+				let newLeft = this.startCropLeft + dx
+				let newTop = this.startCropTop + dy
+				newLeft = Math.max(0, Math.min(this.screenWidth - this.cropSize, newLeft))
+				newTop = Math.max(0, Math.min(this.areaHeight - this.cropSize, newTop))
+				this.cropLeft = newLeft
+				this.cropTop = newTop
+			} else if (this.touchMode === 'zoom' && touches.length === 2) {
+				const dist = this.getDistance(touches[0], touches[1])
+				let ratio = dist / this.startDistance
+				ratio = Math.max(0.5, Math.min(3, ratio))
+
+				const newWidth = Math.floor(this.startImgWidth * ratio)
+				const newHeight = Math.floor(this.startImgHeight * ratio)
+				// 保持图片中心不变
+				const centerX = this.startImgLeft + this.startImgWidth / 2
+				const centerY = this.startImgTop + this.startImgHeight / 2
+				this.imgWidth = newWidth
+				this.imgHeight = newHeight
+				this.imgLeft = Math.floor(centerX - newWidth / 2)
+				this.imgTop = Math.floor(centerY - newHeight / 2)
+			}
+		},
+		onTouchEnd(e) {
+			const remaining = e.touches ? e.touches.length : 0
+			if (this.touchMode === 'zoom' && remaining === 1) {
+				// 双指结束剩一指 → 切换为拖动
+				this.touchMode = 'drag'
+				this.startX = e.touches[0].pageX
+				this.startY = e.touches[0].pageY
+				this.startCropLeft = this.cropLeft
+				this.startCropTop = this.cropTop
+			} else if (remaining === 0) {
+				this.touchMode = 'none'
+			}
+		},
+		getDistance(t1, t2) {
+			const dx = (t1.pageX || t1.screenX) - (t2.pageX || t2.screenX)
+			const dy = (t1.pageY || t1.screenY) - (t2.pageY || t2.screenY)
+			return Math.sqrt(dx * dx + dy * dy)
+		},
+
+		// === 保存裁剪 ===
+		saveImage() {
+			this.$toastRef.loading('处理中...')
+
+			const ctx = uni.createCanvasContext('cropCanvas', this)
+			const canvasSize = this.canvasSize
+
+			// 裁剪框对应canvas的缩放比
+			const outputScale = canvasSize / this.cropSize
+			// 图片在canvas上的绘制位置
+			const drawX = (this.imgLeft - this.cropLeft) * outputScale
+			const drawY = (this.imgTop - this.cropTop) * outputScale
+			const drawW = this.imgWidth * outputScale
+			const drawH = this.imgHeight * outputScale
+
+			// 圆形裁剪
+			ctx.save()
+			ctx.beginPath()
+			ctx.arc(canvasSize / 2, canvasSize / 2, canvasSize / 2, 0, 2 * Math.PI)
+			ctx.clip()
+			ctx.drawImage(this.imageSrc, drawX, drawY, drawW, drawH)
+			ctx.restore()
+
+			ctx.draw(false, () => {
+				setTimeout(() => {
+					uni.canvasToTempFilePath({
+						canvasId: 'cropCanvas',
+						x: 0, y: 0,
+						width: canvasSize,
+						height: canvasSize,
+						destWidth: canvasSize,
+						destHeight: canvasSize,
+						fileType: 'png',
+						success: (res) => {
+							this.$toastRef.hide()
+							uni.setStorageSync('avatar', res.tempFilePath)
+							this.$toastRef.success('头像已更新')
+							setTimeout(() => uni.navigateBack(), 500)
+						},
+						fail: (err) => {
+							console.error('canvasToTempFilePath fail:', err)
+							this.$toastRef.hide()
+							uni.setStorageSync('avatar', this.imageSrc)
+							this.$toastRef.success('头像已更新')
+							setTimeout(() => uni.navigateBack(), 500)
+						}
+					}, this)
+				}, 500)
+			})
+		}
+	}
+}
+</script>
+
+<style scoped>
+.page {
+	width: 100%;
+	height: 100vh;
+	background-color: #1A1A1A;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+}
+/* 导航栏 */
+.nav-bar {
+	background-color: #1A1A1A;
+}
+.status-bar {
+	height: var(--status-bar-height);
+}
+.nav-content {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	height: 44px;
+	padding: 0 15px;
+}
+.nav-back {
+	font-family: iconfont;
+	font-size: 20px;
+	color: #FFFFFF;
+	width: 40px;
+}
+.nav-title {
+	flex: 1;
+	text-align: center;
+	font-size: 17px;
+	color: #FFFFFF;
+	font-weight: bold;
+}
+.nav-save {
+	font-size: 15px;
+	color: #389588;
+	width: 40px;
+	text-align: right;
+}
+/* 裁剪区域 */
+.crop-area {
+	flex: 1;
+	position: relative;
+	overflow: hidden;
+}
+.crop-image {
+	position: absolute;
+}
+/* 圆形遮罩 - 用box-shadow实现圆形镂空 */
+.crop-circle-mask {
+	position: absolute;
+	border-radius: 50%;
+	box-sizing: border-box;
+	border: 2px solid rgba(255, 255, 255, 0.8);
+	box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.6);
+	pointer-events: none;
+}
+/* 底部栏 */
+.bottom-bar {
+	height: 80px;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	padding: 0 20px;
+	background-color: #1A1A1A;
+}
+/* 实时预览圆 */
+.preview-circle {
+	width: 70px;
+	height: 70px;
+	border-radius: 50%;
+	overflow: hidden;
+	border: 2px solid rgba(255, 255, 255, 0.5);
+	flex-shrink: 0;
+}
+.preview-circle image {
+	display: block;
+}
+.tip-text {
+	flex: 1;
+	text-align: center;
+	font-size: 13px;
+	color: #999999;
+}
+/* canvas隐藏 */
+.crop-canvas {
+	position: fixed;
+	left: -9999px;
+	top: -9999px;
+}
+</style>

+ 257 - 0
code/ajyApp/pages/my/info/info.nvue

@@ -0,0 +1,257 @@
+<template>
+	<view class="page">
+		<!-- 头像区 -->
+		<view class="section">
+			<view class="info-item" @click="changeAvatar">
+				<text class="item-label">头像</text>
+				<view class="item-right">
+					<image class="avatar-preview" :src="avatar" mode="aspectFill"></image>
+					<image class="item-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+				</view>
+			</view>
+		</view>
+
+		<!-- 基本信息 -->
+		<view class="section">
+			<view class="info-item" @click="editNickname">
+				<text class="item-label">昵称</text>
+				<view class="item-right">
+					<text class="item-value">{{nickname}}</text>
+					<image class="item-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+				</view>
+			</view>
+			<view class="info-item">
+				<text class="item-label">手机号</text>
+				<view class="item-right">
+					<text class="item-value">{{phone}}</text>
+				</view>
+			</view>
+			<view class="info-item">
+				<text class="item-label">AppId</text>
+				<view class="item-right">
+					<text class="item-value">{{appid}}</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 修改昵称弹窗 -->
+		<view class="nickname-overlay" v-if="showNicknamePopup" @click="closeNicknamePopup">
+			<view class="nickname-dialog" @click.stop="" :style="{marginBottom: keyboardHeight + 'px'}">
+				<text class="dialog-title">修改昵称</text>
+				<view class="input-box">
+					<input class="nickname-input" v-model="nicknameInput" placeholder="请输入昵称" placeholder-class="input-placeholder"
+						maxlength="12" :adjust-position="false" :focus="showNicknamePopup"
+						@keyboardheightchange="onKeyboardHeight" />
+				</view>
+				<view class="dialog-btns">
+					<view class="dialog-btn" @click="closeNicknamePopup">
+						<text class="dialog-cancel">取消</text>
+					</view>
+					<view class="dialog-divider"></view>
+					<view class="dialog-btn" @click="saveNickname">
+						<text class="dialog-confirm">确定</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			avatar: '/static/my/avator.png',
+			nickname: '用户',
+			phone: '',
+			appid: '',
+			showNicknamePopup: false,
+			nicknameInput: '',
+			keyboardHeight: 0
+		}
+	},
+	onLoad() {
+		this.loadUserInfo()
+	},
+	methods: {
+		loadUserInfo() {
+			const phone = uni.getStorageSync('phone') || '138****8888'
+			const nickname = uni.getStorageSync('nickname') || '用户'
+			const appid = uni.getStorageSync('appid') || ''
+			const avatar = uni.getStorageSync('avatar') || '/static/my/avator.png'
+			this.phone = phone
+			this.nickname = nickname
+			this.appid = appid
+			this.avatar = avatar
+		},
+		changeAvatar() {
+			uni.chooseImage({
+				count: 1,
+				sizeType: ['compressed'],
+				sourceType: ['album', 'camera'],
+				success: (res) => {
+					const tempPath = res.tempFilePaths[0]
+					uni.navigateTo({
+						url: '/pages/my/crop/crop?src=' + encodeURIComponent(tempPath)
+					})
+				}
+			})
+		},
+		editNickname() {
+			this.nicknameInput = this.nickname
+			this.showNicknamePopup = true
+		},
+		closeNicknamePopup() {
+			this.showNicknamePopup = false
+			this.keyboardHeight = 0
+		},
+		onKeyboardHeight(e) {
+			this.keyboardHeight = e.detail.height || 0
+		},
+		saveNickname() {
+			const val = this.nicknameInput.trim()
+			if (!val) {
+				this.$refs.ayToast.error('昵称不能为空')
+				return
+			}
+			this.nickname = val
+			uni.setStorageSync('nickname', val)
+			this.showNicknamePopup = false
+			this.keyboardHeight = 0
+			this.$refs.ayToast.success('修改成功')
+		}
+	},
+	onShow() {
+		// 从裁剪页返回后刷新头像
+		const avatar = uni.getStorageSync('avatar')
+		if (avatar) {
+			this.avatar = avatar
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #F5F6F8;
+}
+/* 分组 */
+.section {
+	background-color: #FFFFFF;
+	margin-top: 20rpx;
+	border-radius: 20rpx;
+	margin-left: 24rpx;
+	margin-right: 24rpx;
+	overflow: hidden;
+}
+/* 信息项 */
+.info-item {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 110rpx;
+	padding-left: 32rpx;
+	padding-right: 32rpx;
+	border-bottom-width: 1px;
+	border-bottom-color: #F0F0F0;
+	border-bottom-style: solid;
+}
+.item-label {
+	font-size: 30rpx;
+	color: #333333;
+}
+.item-right {
+	flex-direction: row;
+	align-items: center;
+}
+.item-value {
+	font-size: 30rpx;
+	color: #999999;
+	margin-right: 16rpx;
+}
+.item-arrow {
+	width: 32rpx;
+	height: 32rpx;
+}
+.avatar-preview {
+	width: 80rpx;
+	height: 80rpx;
+	border-radius: 80rpx;
+	margin-right: 16rpx;
+}
+/* 昵称修改弹窗 */
+.nickname-overlay {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0, 0, 0, 0.4);
+	justify-content: center;
+	align-items: center;
+}
+.nickname-dialog {
+	width: 600rpx;
+	background-color: #FFFFFF;
+	border-radius: 28rpx;
+	align-items: center;
+	overflow: hidden;
+}
+.dialog-title {
+	font-size: 34rpx;
+	color: #333333;
+	font-weight: bold;
+	margin-top: 48rpx;
+	margin-bottom: 40rpx;
+}
+.input-box {
+	width: 520rpx;
+	height: 88rpx;
+	background-color: #F5F6F8;
+	border-radius: 16rpx;
+	justify-content: center;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	margin-bottom: 48rpx;
+}
+.nickname-input {
+	font-size: 30rpx;
+	color: #333333;
+}
+.dialog-btns {
+	flex-direction: row;
+	align-self: stretch;
+	border-top-width: 1px;
+	border-top-color: #F0F0F0;
+	border-top-style: solid;
+	height: 96rpx;
+	align-items: center;
+}
+.dialog-btn {
+	flex: 1;
+	height: 96rpx;
+	justify-content: center;
+	align-items: center;
+}
+.dialog-cancel {
+	font-size: 32rpx;
+	color: #999999;
+}
+.dialog-divider {
+	width: 1px;
+	height: 96rpx;
+	background-color: #F0F0F0;
+}
+.dialog-confirm {
+	font-size: 32rpx;
+	color: #389588;
+	font-weight: bold;
+}
+</style>

+ 233 - 0
code/ajyApp/pages/my/my.nvue

@@ -0,0 +1,233 @@
+<template>
+	<view class="page">
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<!-- 用户信息区 -->
+		<view class="user-header">
+			<view class="user-info">
+				<text class="user-name">{{name || '未登录'}}</text>
+				<text class="user-detail">AppId:{{appid}}</text>
+				<text class="user-detail">手机号:{{phone}}</text>
+			</view>
+			<image class="avatar" src="/static/my/avator.png" mode="aspectFill" @click="toInfo"></image>
+		</view>
+
+		<!-- 菜单列表 -->
+		<view class="menu-list">
+			<view class="menu-item" @click="goPage('/pages/device/mycases/mycases')">
+				<image class="menu-icon" src="/static/my/icon3.png" mode="aspectFill"></image>
+				<text class="menu-text">个性化方案</text>
+				<image class="menu-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+			<view class="menu-item" @click="goPage('/pages/my/msg/msg')">
+				<image class="menu-icon" src="/static/my/icon6.png" mode="aspectFill"></image>
+				<view class="menu-text-wrap">
+					<text class="menu-text" style="margin-left:0;">系统消息</text>
+					<view class="msg-dot" v-if="msgNum > 0"></view>
+				</view>
+				<image class="menu-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+			<view class="menu-item" @click="goPage('/pages/my/version/version')">
+				<image class="menu-icon" src="/static/my/icon2.png" mode="aspectFill"></image>
+				<text class="menu-text">版本升级</text>
+				<image class="menu-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+			<view class="menu-item" @click="goPage('/pages/my/questions/questions')">
+				<image class="menu-icon" src="/static/my/icon5.png" mode="aspectFill"></image>
+				<text class="menu-text">帮助</text>
+				<image class="menu-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+			<view class="menu-item" @click="goPage('/pages/my/history/history')">
+				<image class="menu-icon" src="/static/my/icon1.png" mode="aspectFill"></image>
+				<text class="menu-text">使用记录</text>
+				<image class="menu-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+			<view class="menu-item" @click="onLogout">
+				<image class="menu-icon" src="/static/my/icon4.png" mode="aspectFill"></image>
+				<text class="menu-text">退出登录</text>
+				<image class="menu-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+		</view>
+
+		<!-- 退出确认弹窗 -->
+		<customPopup :isShow="isShowDrawer" @closePop="isShowDrawer=false">
+			<view class="popup-content">
+				<text class="popup-title">确定退出登录?</text>
+				<view class="popup-btns">
+					<text class="btn-cancel" @click="isShowDrawer=false">取消</text>
+					<text class="btn-confirm" @click="logoutConfirm">退出登录</text>
+				</view>
+			</view>
+		</customPopup>
+	</view>
+</template>
+
+<script>
+import customPopup from '@/components/customPopup/customPopup.nvue'
+
+export default {
+	components: {
+		customPopup
+	},
+	data() {
+		return {
+			statusBarHeight: 44,
+			isShowDrawer: false,
+			name: '用户',
+			phone: '138****8888',
+			appid: '',
+			msgNum: 0
+		}
+	},
+	onShow() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.init()
+	},
+	methods: {
+		init() {
+			// TODO: 获取用户信息接口
+			// 暂用本地存储数据
+			const phone = uni.getStorageSync('phone') || ''
+			if (phone) {
+				this.phone = phone
+			}
+		},
+		toInfo() {
+			uni.navigateTo({ url: '/pages/my/info/info' })
+		},
+		goPage(url) {
+			uni.navigateTo({ url })
+		},
+		onLogout() {
+			this.isShowDrawer = true
+		},
+		logoutConfirm() {
+			uni.setStorageSync('token', '')
+			uni.setStorageSync('phone', '')
+			this.isShowDrawer = false
+			uni.reLaunch({ url: '/pages/login/login' })
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-image: linear-gradient(to bottom, #389588, #38958800);
+}
+.status-bar {
+	background-color: rgba(0,0,0,0);
+}
+/* 用户头部 */
+.user-header {
+	flex-direction: row;
+	justify-content: space-between;
+	padding-left: 38rpx;
+	padding-right: 38rpx;
+	padding-top: 62rpx;
+	padding-bottom: 62rpx;
+}
+.user-info {
+	flex: 1;
+}
+.user-name {
+	font-size: 48rpx;
+	color: #FFFFFF;
+	margin-bottom: 8rpx;
+}
+.user-detail {
+	font-size: 32rpx;
+	color: #FFFFFF;
+	padding-left: 30rpx;
+	line-height: 66rpx;
+}
+.avatar {
+	width: 150rpx;
+	height: 150rpx;
+	border-radius: 150rpx;
+}
+/* 菜单列表 */
+.menu-list {
+	background-color: #FFFFFF;
+	border-top-left-radius: 30rpx;
+	border-top-right-radius: 30rpx;
+	padding-top: 40rpx;
+	flex: 1;
+}
+.menu-item {
+	flex-direction: row;
+	align-items: center;
+	height: 104rpx;
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	border-bottom-width: 1px;
+	border-bottom-color: #EEF1F4;
+	border-bottom-style: solid;
+}
+.menu-icon {
+	width: 48rpx;
+	height: 48rpx;
+	margin-left: 32rpx;
+}
+.menu-text {
+	flex: 1;
+	font-size: 32rpx;
+	color: #545F71;
+	margin-left: 40rpx;
+}
+.menu-text-wrap {
+	flex: 1;
+	flex-direction: row;
+	align-items: center;
+	margin-left: 40rpx;
+}
+.msg-dot {
+	width: 16rpx;
+	height: 16rpx;
+	background-color: #F65252;
+	border-radius: 16rpx;
+	margin-left: 10rpx;
+}
+.menu-arrow {
+	width: 40rpx;
+	height: 40rpx;
+	margin-right: 16rpx;
+}
+/* 弹窗 */
+.popup-content {
+	align-items: center;
+}
+.popup-title {
+	font-size: 32rpx;
+	color: #545F71;
+	text-align: center;
+	margin-top: 80rpx;
+	margin-bottom: 80rpx;
+}
+.popup-btns {
+	flex-direction: row;
+	justify-content: space-around;
+	width: 680rpx;
+}
+.btn-cancel {
+	width: 306rpx;
+	height: 88rpx;
+	background-color: #F3F3F3;
+	border-radius: 20rpx;
+	font-size: 32rpx;
+	color: #545F71;
+	text-align: center;
+	line-height: 88rpx;
+}
+.btn-confirm {
+	width: 306rpx;
+	height: 88rpx;
+	background-color: #F65252;
+	border-radius: 20rpx;
+	font-size: 32rpx;
+	color: #FFFFFF;
+	text-align: center;
+	line-height: 88rpx;
+}
+</style>

+ 321 - 0
code/ajyApp/pages/test-toast/test-toast.nvue

@@ -0,0 +1,321 @@
+<template>
+	<view class="page">
+		<view class="header">
+			<text class="title">Toast 组件测试</text>
+		</view>
+
+		<!-- 基础功能测试 -->
+		<view class="section">
+			<text class="section-title">基础功能测试</text>
+
+			<view class="btn" @click="testSuccess">
+				<text class="btn-text">测试 Success</text>
+			</view>
+
+			<view class="btn" @click="testError">
+				<text class="btn-text">测试 Error</text>
+			</view>
+
+			<view class="btn" @click="testLoading">
+				<text class="btn-text">测试 Loading (3秒后关闭)</text>
+			</view>
+
+			<view class="btn" @click="testText">
+				<text class="btn-text">测试 纯文字</text>
+			</view>
+		</view>
+
+		<!-- 全局事件测试(模拟HTTP模块调用) -->
+		<view class="section">
+			<text class="section-title">全局事件测试 (模拟HTTP调用)</text>
+
+			<view class="btn btn-orange" @click="testGlobalSuccess">
+				<text class="btn-text">uni.$emit Success</text>
+			</view>
+
+			<view class="btn btn-orange" @click="testGlobalError">
+				<text class="btn-text">uni.$emit Error</text>
+			</view>
+
+			<view class="btn btn-orange" @click="testGlobalLoading">
+				<text class="btn-text">uni.$emit Loading</text>
+			</view>
+		</view>
+
+		<!-- HTTP 请求集成测试 -->
+		<view class="section">
+			<text class="section-title">HTTP 请求测试</text>
+
+			<view class="btn btn-blue" @click="testHttpSuccess">
+				<text class="btn-text">模拟成功请求 (带loading)</text>
+			</view>
+
+			<view class="btn btn-blue" @click="testHttpError">
+				<text class="btn-text">模拟失败请求 (显示错误)</text>
+			</view>
+
+			<view class="btn btn-blue" @click="testHttpNoLoading">
+				<text class="btn-text">模拟请求 (不显示loading)</text>
+			</view>
+
+			<view class="btn btn-blue" @click="testHttpConcurrent">
+				<text class="btn-text">模拟并发请求 (loading计数)</text>
+			</view>
+		</view>
+
+		<!-- toast 工具方法测试 -->
+		<view class="section">
+			<text class="section-title">toast.js 工具方法测试</text>
+
+			<view class="btn btn-green" @click="testToastUtil">
+				<text class="btn-text">import toast 调用</text>
+			</view>
+		</view>
+
+		<!-- 结果展示 -->
+		<view class="section" v-if="logs.length > 0">
+			<text class="section-title">测试日志</text>
+			<view v-for="(log, index) in logs" :key="index" class="log-item">
+				<text class="log-text">{{ log }}</text>
+			</view>
+		</view>
+
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import toast from '@/utils/toast.js'
+
+export default {
+	data() {
+		return {
+			logs: []
+		}
+	},
+	methods: {
+		addLog(msg) {
+			const time = new Date().toLocaleTimeString()
+			this.logs.unshift(`[${time}] ${msg}`)
+			// 最多保留20条
+			if (this.logs.length > 20) {
+				this.logs = this.logs.slice(0, 20)
+			}
+		},
+
+		// ===== 基础功能测试(通过 $refs 直接调用组件方法) =====
+		testSuccess() {
+			this.addLog('调用 this.$refs.ayToast.success')
+			this.$refs.ayToast.success('操作成功!')
+		},
+
+		testError() {
+			this.addLog('调用 this.$refs.ayToast.error')
+			this.$refs.ayToast.error('操作失败!')
+		},
+
+		testLoading() {
+			this.addLog('调用 this.$refs.ayToast.loading, 3秒后hide')
+			this.$refs.ayToast.loading('加载中...')
+			setTimeout(() => {
+				this.$refs.ayToast.hide()
+				this.addLog('loading 已关闭')
+			}, 3000)
+		},
+
+		testText() {
+			this.addLog('调用 this.$refs.ayToast.show (text)')
+			this.$refs.ayToast.show({ type: 'text', title: '这是一条纯文字提示', duration: 2000 })
+		},
+
+		// ===== 全局事件测试 =====
+		testGlobalSuccess() {
+			this.addLog('uni.$emit ayToast:show (success)')
+			uni.$emit('ayToast:show', {
+				type: 'success',
+				title: '全局事件-成功',
+				duration: 2000
+			})
+		},
+
+		testGlobalError() {
+			this.addLog('uni.$emit ayToast:show (error)')
+			uni.$emit('ayToast:show', {
+				type: 'error',
+				title: '全局事件-失败',
+				duration: 2500
+			})
+		},
+
+		testGlobalLoading() {
+			this.addLog('uni.$emit ayToast:show (loading), 3秒后hide')
+			uni.$emit('ayToast:show', {
+				type: 'loading',
+				title: '全局加载中...',
+				duration: 0,
+				mask: true
+			})
+			setTimeout(() => {
+				uni.$emit('ayToast:hide')
+				this.addLog('全局loading 已关闭')
+			}, 3000)
+		},
+
+		// ===== HTTP 请求集成测试 =====
+		testHttpSuccess() {
+			this.addLog('发起模拟成功请求...')
+			this._mockRequest(1500, true).then(res => {
+				this.addLog(`请求成功: ${JSON.stringify(res)}`)
+			}).catch(err => {
+				this.addLog(`请求失败: ${err.message}`)
+			})
+		},
+
+		testHttpError() {
+			this.addLog('发起模拟失败请求...')
+			this._mockRequest(1000, false).then(res => {
+				this.addLog(`请求成功: ${JSON.stringify(res)}`)
+			}).catch(err => {
+				this.addLog(`请求失败: ${err.message}`)
+			})
+		},
+
+		testHttpNoLoading() {
+			this.addLog('发起请求(无loading)...')
+			this._mockRequest(1000, true, false).then(res => {
+				this.addLog(`请求成功(无loading): ${JSON.stringify(res)}`)
+			}).catch(err => {
+				this.addLog(`请求失败: ${err.message}`)
+			})
+		},
+
+		testHttpConcurrent() {
+			this.addLog('发起3个并发请求...')
+			const p1 = this._mockRequest(1000, true)
+			const p2 = this._mockRequest(2000, true)
+			const p3 = this._mockRequest(3000, true)
+			Promise.all([p1, p2, p3]).then(() => {
+				this.addLog('3个并发请求全部完成')
+			})
+		},
+
+		// ===== toast.js 工具方法测试 =====
+		testToastUtil() {
+			this.addLog('调用 toast.success (import方式)')
+			toast.success('通过 import toast 调用')
+		},
+
+		// ===== 模拟HTTP请求 =====
+		_mockRequest(delay = 1500, success = true, showLoading = true) {
+			if (showLoading) {
+				toast.loading('请求中...')
+			}
+
+			return new Promise((resolve, reject) => {
+				setTimeout(() => {
+					if (showLoading) {
+						toast.hide()
+					}
+
+					if (success) {
+						toast.success('请求成功')
+						resolve({ code: 200, data: { id: 1, name: '测试数据' } })
+					} else {
+						toast.error('网络错误,请重试')
+						reject(new Error('模拟网络错误'))
+					}
+				}, delay)
+			})
+		}
+	}
+}
+</script>
+
+<style scoped>
+.page {
+	padding-top: 40rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+	padding-bottom: 60rpx;
+	/* #ifndef APP-NVUE */
+	min-height: 100vh;
+	/* #endif */
+	background-color: #f5f5f5;
+}
+
+.header {
+	padding-top: 40rpx;
+	padding-bottom: 40rpx;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	justify-content: center;
+	/* #endif */
+}
+
+.title {
+	font-size: 36rpx;
+	font-weight: bold;
+	color: #333333;
+}
+
+.section {
+	margin-bottom: 40rpx;
+	background-color: #ffffff;
+	border-radius: 16rpx;
+	padding-top: 24rpx;
+	padding-bottom: 24rpx;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+}
+
+.section-title {
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #666666;
+	margin-bottom: 20rpx;
+}
+
+.btn {
+	background-color: #389588;
+	border-radius: 12rpx;
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+	margin-top: 16rpx;
+	align-items: center;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	justify-content: center;
+	/* #endif */
+}
+
+.btn-orange {
+	background-color: #e6a23c;
+}
+
+.btn-blue {
+	background-color: #409eff;
+}
+
+.btn-green {
+	background-color: #67c23a;
+}
+
+.btn-text {
+	color: #ffffff;
+	font-size: 28rpx;
+}
+
+.log-item {
+	padding-top: 8rpx;
+	padding-bottom: 8rpx;
+	border-bottom-width: 1rpx;
+	border-bottom-style: solid;
+	border-bottom-color: #eeeeee;
+}
+
+.log-text {
+	font-size: 22rpx;
+	color: #999999;
+}
+</style>

+ 80 - 0
code/ajyApp/pages/webview/webview.nvue

@@ -0,0 +1,80 @@
+<template>
+	<view class="webview-page">
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<view class="custom-head">
+			<text class="back-btn" @click="goBack">← 返回</text>
+			<text class="head-title">{{ title }}</text>
+			<view class="placeholder"></view>
+		</view>
+		<web-view :src="url" class="web-content"></web-view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			id: '',
+			title: '',
+			url: '',
+			statusBarHeight: 44
+		}
+	},
+	created() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+	},
+	onLoad(options) {
+		this.id = options.id || ''
+		if (this.id === '1') {
+			this.title = '用户协议'
+			this.url = 'http://8.155.56.12:8081/profile/upload/userAgreement.html'
+		} else if (this.id === '2') {
+			this.title = '隐私政策'
+			this.url = 'http://8.155.56.12:8081/profile/upload/privatery.html'
+		}
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		}
+	}
+}
+</script>
+
+<style>
+.webview-page {
+	flex: 1;
+	background-color: #ffffff;
+}
+.status-bar {
+	background-color: #ffffff;
+}
+.custom-head {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 100rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+	background-color: #ffffff;
+	border-bottom-width: 1px;
+	border-bottom-color: #eeeeee;
+	border-bottom-style: solid;
+}
+.back-btn {
+	font-size: 34rpx;
+	color: #333333;
+}
+.head-title {
+	font-size: 36rpx;
+	font-weight: bold;
+	color: #333333;
+}
+.placeholder {
+	width: 80rpx;
+}
+.web-content {
+	flex: 1;
+}
+</style>

BIN
code/ajyApp/static/1024x1024.png


BIN
code/ajyApp/static/audio/audio1.mp3


BIN
code/ajyApp/static/audio/audio2.mp3


BIN
code/ajyApp/static/device/1s.png


BIN
code/ajyApp/static/device/aijiuyi2.png


BIN
code/ajyApp/static/device/anmoyi1.png


BIN
code/ajyApp/static/device/barimg.png


BIN
code/ajyApp/static/device/change.png


BIN
code/ajyApp/static/device/device1.png


BIN
code/ajyApp/static/device/devicebg.png


BIN
code/ajyApp/static/device/dianbo.png


BIN
code/ajyApp/static/device/gongneng1.png


BIN
code/ajyApp/static/device/gongneng2.png


BIN
code/ajyApp/static/device/gongneng3.png


BIN
code/ajyApp/static/device/haocai1.png


BIN
code/ajyApp/static/device/haocai2.png


BIN
code/ajyApp/static/device/heliu.png


BIN
code/ajyApp/static/device/line2.png


BIN
code/ajyApp/static/device/linepoint2.png


BIN
code/ajyApp/static/device/menubtn.png


BIN
code/ajyApp/static/device/pause.png


BIN
code/ajyApp/static/device/pauseicon.png


BIN
code/ajyApp/static/device/persionbg.png


BIN
code/ajyApp/static/device/personBody.png


BIN
code/ajyApp/static/device/playerbtn.png


+ 8 - 0
code/ajyApp/static/device/playing.svg

@@ -0,0 +1,8 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751790748375"
+	class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16497"
+	xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+	<path
+		d="M60.8 1024c-33.6 0-60.8-27.2-60.8-60.8v-243.2c0-33.6 27.2-60.8 60.8-60.8s60.8 27.2 60.8 60.8v243.2c0 33.6-27.2 60.8-60.8 60.8zM361.6 1024c-33.6 0-60.8-27.2-60.8-60.8v-473.6c0-33.6 27.2-60.8 60.8-60.8s60.8 27.2 60.8 60.8v473.6c0 33.6-27.2 60.8-60.8 60.8zM662.4 1024c-33.6 0-60.8-27.2-60.8-60.8v-678.4c0-33.6 27.2-60.8 60.8-60.8s60.8 27.2 60.8 60.8v678.4c0 33.6-27.2 60.8-60.8 60.8zM963.2 "
+		fill="#389588" p-id="16498"></path>
+</svg>

BIN
code/ajyApp/static/device/start.png


BIN
code/ajyApp/static/device/stop.png


BIN
code/ajyApp/static/font/iconfont.ttf


BIN
code/ajyApp/static/font/iconfont.woff


BIN
code/ajyApp/static/font/iconfont.woff2


BIN
code/ajyApp/static/home/banner.png


BIN
code/ajyApp/static/home/detail.png


BIN
code/ajyApp/static/login/apple@2x.png


BIN
code/ajyApp/static/login/phone.png


BIN
code/ajyApp/static/login/pwd.png


BIN
code/ajyApp/static/login/wechat@2x.png


BIN
code/ajyApp/static/my/arrowright.png


BIN
code/ajyApp/static/my/avator.png


BIN
code/ajyApp/static/my/fangan.png


BIN
code/ajyApp/static/my/icon1.png


BIN
code/ajyApp/static/my/icon2.png


BIN
code/ajyApp/static/my/icon3.png


BIN
code/ajyApp/static/my/icon4.png


BIN
code/ajyApp/static/my/icon5.png


BIN
code/ajyApp/static/my/icon6.png


BIN
code/ajyApp/static/public/arrow-left.png


BIN
code/ajyApp/static/public/back.png


BIN
code/ajyApp/static/public/backBlack.png


BIN
code/ajyApp/static/public/cc-menu-more.png


BIN
code/ajyApp/static/public/device.png


BIN
code/ajyApp/static/public/device1.png


BIN
code/ajyApp/static/public/doctorAvator.png


BIN
code/ajyApp/static/public/find.png


BIN
code/ajyApp/static/public/find1.png


BIN
code/ajyApp/static/public/home.png


BIN
code/ajyApp/static/public/home1.png


BIN
code/ajyApp/static/public/my.png


BIN
code/ajyApp/static/public/my1.png


BIN
code/ajyApp/static/public/view-list.png


BIN
code/ajyApp/static/temp/Rectangle 50@2x(1).png


BIN
code/ajyApp/static/temp/Rectangle 50@2x(2).png


BIN
code/ajyApp/static/temp/Rectangle 50@2x.png


BIN
code/ajyApp/static/temp/bannerpage.png


BIN
code/ajyApp/static/temp/image@2x(1).png


BIN
code/ajyApp/static/temp/image@2x(2).png


BIN
code/ajyApp/static/temp/image@2x(3).png


BIN
code/ajyApp/static/temp/image@2x(3).zip


BIN
code/ajyApp/static/temp/image@2x(4).png


BIN
code/ajyApp/static/temp/image@2x.png


BIN
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-hdpi.png


BIN
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-xhdpi.png


BIN
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-xxhdpi.png


BIN
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/.manifest/icon-android-xxxhdpi.png


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 15 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappautomator.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 31 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappchooselocation.js


BIN
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniapperror.png


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 31 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappopenlocation.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 31 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniapppicker.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 6 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappquill.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappquillimageresize.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 31 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappscan.js


BIN
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappsuccess.png


+ 24 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/__uniappview.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <title>View</title>
+    <link rel="icon" href="data:,">
+    <link rel="stylesheet" href="app.css" />
+    <script>var __uniConfig = {"globalStyle":{},"darkmode":false}</script>
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script src="uni-app-view.umd.js"></script>
+    
+    
+    
+  </body>
+</html>

+ 11 - 0
code/ajyApp/unpackage/cache/wgt/__UNI__A3304B5/app-config-service.js

@@ -0,0 +1,11 @@
+
+  ;(function(){
+  let u=void 0,isReady=false,onReadyCallbacks=[],isServiceReady=false,onServiceReadyCallbacks=[];
+  const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"uni-app","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"ajyApp","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"4.76","entryPagePath":"pages/index/index","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"fallbackLocale":"zh-Hans","locales":{},"darkmode":false,"themeConfig":{}};
+  const __uniRoutes = [{"path":"pages/index/index","meta":{"isQuit":true,"isEntry":true,"navigationBar":{"titleText":"uni-app","type":"default"},"isNVue":false}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute));
+  __uniConfig.styles=[];//styles
+  __uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
+  __uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
+  service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:16})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,global:u,window:u,document:u,frames:u,self:u,location:u,navigator:u,localStorage:u,history:u,Caches:u,screen:u,alert:u,confirm:u,prompt:u,fetch:u,XMLHttpRequest:u,WebSocket:u,webkit:u,print:u}}}}); 
+  })();
+  

Vissa filer visades inte eftersom för många filer har ändrats