Jelajahi Sumber

feat: 合并 origin/master(APP端 + 内容管理 + 设备管理 + 系统优化)

合并远程 20 个新提交,包含:
- APP端完整功能(设备管理、文章浏览、个人中心、BLE 蓝牙通信)
- 内容管理模块(文章审核、封面图片水印)
- 设备管理增强(搜索、用户管理、多设备支持)
- 用户类别管理配置化
- 用户档案地址字段扩展
- 安全修复(短信配置外部化、TokenUtil 初始化、DOMPurify 输入清洗)
- 管理后台优化(管理员管理、操作日志)
- 前端资源优化(tinymce 内置、分块加载)

合并冲突解决:
- router/index.js: 重写为嵌套 system 路由结构
- layout/index.vue: 保留 hasPermission 过滤
- content/index.vue: 保留 DOMPurify 清洗
- app-user/index.vue: 混合远程 userType 硬编码 + 本地 userCategory 动态选项
- ResultCode.java: 合并两方错误码区间
liaoxg 3 bulan lalu
induk
melakukan
24ceb2b7cc
100 mengubah file dengan 6982 tambahan dan 1785 penghapusan
  1. 2 0
      .gitignore
  2. 1 0
      AGENTS.md
  3. 21 0
      code/ajyApp/App.vue
  4. 256 0
      code/ajyApp/components/l-sliderRange/l-sliderRange.nvue
  5. 11 2
      code/ajyApp/manifest.json
  6. 117 6
      code/ajyApp/pages.json
  7. 2 2
      code/ajyApp/pages/ble-demo/ble-demo.nvue
  8. 373 0
      code/ajyApp/pages/device/bodyParams/bodyParams.nvue
  9. 336 0
      code/ajyApp/pages/device/detail/detail.nvue
  10. 993 0
      code/ajyApp/pages/device/deviceInfo/deviceInfo.nvue
  11. 50 0
      code/ajyApp/pages/device/deviceInfo/mixins/acupoint-mixin.js
  12. 84 0
      code/ajyApp/pages/device/deviceInfo/mixins/audio-mixin.js
  13. 282 0
      code/ajyApp/pages/device/deviceInfo/mixins/ble-mixin.js
  14. 12 10
      code/ajyApp/pages/device/list/list.nvue
  15. 112 0
      code/ajyApp/pages/device/moxibustion/moxibustion.nvue
  16. 89 0
      code/ajyApp/pages/device/mycases/mycases.nvue
  17. 334 0
      code/ajyApp/pages/device/search/search.nvue
  18. 373 0
      code/ajyApp/pages/device/userManage/userManage.nvue
  19. 288 0
      code/ajyApp/pages/find/detail/detail.nvue
  20. 59 21
      code/ajyApp/pages/find/index/index.nvue
  21. 8 4
      code/ajyApp/pages/home/home.nvue
  22. 82 39
      code/ajyApp/pages/login/login.nvue
  23. 140 0
      code/ajyApp/pages/my/history/history.nvue
  24. 34 17
      code/ajyApp/pages/my/info/info.nvue
  25. 167 0
      code/ajyApp/pages/my/msg/msg.nvue
  26. 18 12
      code/ajyApp/pages/my/my.nvue
  27. 98 0
      code/ajyApp/pages/my/questions/quesinfo.nvue
  28. 130 0
      code/ajyApp/pages/my/questions/questions.nvue
  29. 120 0
      code/ajyApp/pages/my/version/version.nvue
  30. 7 1
      code/ajyApp/pages/webview/webview.nvue
  31. TEMPAT SAMPAH
      code/ajyApp/static/144x144.png
  32. TEMPAT SAMPAH
      code/ajyApp/static/public/back-arrow.png
  33. 3 0
      code/ajyApp/static/public/back-arrow.svg
  34. 17 0
      code/ajyApp/utils/api/article.js
  35. 21 0
      code/ajyApp/utils/api/login.js
  36. 29 0
      code/ajyApp/utils/api/user.js
  37. 17 1
      code/ajyApp/utils/ble/BleManager.js
  38. 2 2
      code/ajyApp/utils/ble/constants.js
  39. 2 2
      code/ajyApp/utils/http/config.js
  40. 2 0
      code/ajyApp/utils/http/request.js
  41. 128 0
      code/ajyApp/艾炙椅主控板—通迅协议V2.csv
  42. 158 0
      code/app用户管理.md
  43. 24 0
      code/backend/src/main/java/com/aijiuyi/admin/common/config/JacksonConfig.java
  44. 47 33
      code/backend/src/main/java/com/aijiuyi/admin/common/constant/ResultCode.java
  45. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/AdminController.java
  46. 82 0
      code/backend/src/main/java/com/aijiuyi/admin/controller/AppAuthController.java
  47. 66 0
      code/backend/src/main/java/com/aijiuyi/admin/controller/AppProfileController.java
  48. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/AppUserController.java
  49. 288 0
      code/backend/src/main/java/com/aijiuyi/admin/controller/ArticleViewController.java
  50. 78 0
      code/backend/src/main/java/com/aijiuyi/admin/controller/AuthController.java
  51. 3 2
      code/backend/src/main/java/com/aijiuyi/admin/controller/PlanController.java
  52. 20 34
      code/backend/src/main/java/com/aijiuyi/admin/controller/UserCategoryController.java
  53. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/UserProfileController.java
  54. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AdminQueryDTO.java
  55. 22 0
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AppLoginDTO.java
  56. 4 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AppUserQueryDTO.java
  57. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/PlanQueryDTO.java
  58. 5 3
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/PlanSaveDTO.java
  59. 18 0
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/SendCodeDTO.java
  60. 1 4
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/UserCategoryQueryDTO.java
  61. 4 1
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/UserProfileDTO.java
  62. 2 2
      code/backend/src/main/java/com/aijiuyi/admin/controller/dto/UserProfileQueryDTO.java
  63. 32 3
      code/backend/src/main/java/com/aijiuyi/admin/entity/AppUser.java
  64. 6 0
      code/backend/src/main/java/com/aijiuyi/admin/entity/ContentArticle.java
  65. 3 3
      code/backend/src/main/java/com/aijiuyi/admin/entity/Plan.java
  66. 7 19
      code/backend/src/main/java/com/aijiuyi/admin/entity/UserCategory.java
  67. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/entity/UserPlan.java
  68. 9 1
      code/backend/src/main/java/com/aijiuyi/admin/entity/UserProfile.java
  69. 1 1
      code/backend/src/main/java/com/aijiuyi/admin/mapper/UserCategoryMapper.java
  70. 20 1
      code/backend/src/main/java/com/aijiuyi/admin/service/AppUserService.java
  71. 11 18
      code/backend/src/main/java/com/aijiuyi/admin/service/UserCategoryService.java
  72. 68 9
      code/backend/src/main/java/com/aijiuyi/admin/service/impl/AdminServiceImpl.java
  73. 256 5
      code/backend/src/main/java/com/aijiuyi/admin/service/impl/AppUserServiceImpl.java
  74. 11 6
      code/backend/src/main/java/com/aijiuyi/admin/service/impl/ContentArticleServiceImpl.java
  75. 131 4
      code/backend/src/main/java/com/aijiuyi/admin/service/impl/PlanServiceImpl.java
  76. 52 91
      code/backend/src/main/java/com/aijiuyi/admin/service/impl/UserCategoryServiceImpl.java
  77. 43 3
      code/backend/src/main/java/com/aijiuyi/admin/service/impl/UserProfileServiceImpl.java
  78. 67 0
      code/backend/src/main/resources/application-liyu.yml
  79. 4 1
      code/backend/src/main/resources/application.yml
  80. 12 3
      code/backend/src/main/resources/mapper/UserProfileMapper.xml
  81. 0 493
      code/backend/src/main/resources/sql/acupoint_full_data_import.sql
  82. 0 614
      code/backend/src/main/resources/sql/init.sql
  83. 0 30
      code/backend/src/main/resources/sql/migration_acupoint_auto_parse.sql
  84. 0 31
      code/backend/src/main/resources/sql/migration_algorithm_v3.sql
  85. 6 0
      code/backend/src/main/resources/sql/migration_article_watermark_image.sql
  86. 0 33
      code/backend/src/main/resources/sql/migration_content_article.sql
  87. 0 29
      code/backend/src/main/resources/sql/migration_user_category.sql
  88. 0 23
      code/backend/src/main/resources/sql/migration_user_profile_address.sql
  89. 40 159
      code/frontend/package-lock.json
  90. 2 0
      code/frontend/package.json
  91. TEMPAT SAMPAH
      code/frontend/public/default-avatar.png
  92. 362 0
      code/frontend/public/tinymce/langs/zh_CN.js
  93. 75 0
      code/frontend/public/tinymce/skins/content/dark/content.css
  94. 10 0
      code/frontend/public/tinymce/skins/content/dark/content.js
  95. 10 0
      code/frontend/public/tinymce/skins/content/dark/content.min.css
  96. 3 0
      code/frontend/public/tinymce/skins/content/dark/content.min.ts
  97. 3 0
      code/frontend/public/tinymce/skins/content/dark/content.ts
  98. 70 0
      code/frontend/public/tinymce/skins/content/default/content.css
  99. 10 0
      code/frontend/public/tinymce/skins/content/default/content.js
  100. 10 0
      code/frontend/public/tinymce/skins/content/default/content.min.css

+ 2 - 0
.gitignore

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

+ 1 - 0
AGENTS.md

@@ -58,3 +58,4 @@
 ## 项目信息
 - 项目名称: aijiuyi
 - 可以读取并使用application.yml、application-dev.yml中的datasource属性
+- 你可以随时查看我的dev环境数据库,但是没有编写权限。如果你要编辑数据库,需要征求我的同意

+ 21 - 0
code/ajyApp/App.vue

@@ -2,16 +2,37 @@
 	export default {
 		onLaunch: function() {
 			console.log('App Launch')
+			this.checkLoginState()
 		},
 		onShow: function() {
 			console.log('App Show')
 		},
 		onHide: function() {
 			console.log('App Hide')
+		},
+		methods: {
+			/**
+			 * 检查登录状态
+			 * 如果没有 token 则跳转到登录页
+			 */
+			checkLoginState() {
+				const token = uni.getStorageSync('user_token')
+				if (!token) {
+					// 未登录,跳转登录页
+					uni.reLaunch({
+						url: '/pages/login/login'
+					})
+				}
+				// 已登录且 token 存在,保持在首页
+				// token 过期的情况由 HTTP 拦截器处理(401/403 自动跳转登录页)
+			}
 		}
 	}
 </script>
 
 <style>
+/* .uni-tabbar-border {
+    display: none !important;
+} */
 	/*每个页面公共css */
 </style>

+ 256 - 0
code/ajyApp/components/l-sliderRange/l-sliderRange.nvue

@@ -0,0 +1,256 @@
+<template>
+	<view class="lView" ref="lViewRef">
+		<view class="angleNumber" :style="angleNumStyle">
+			<text class="angleTxt">{{angleNumber}}°</text>
+		</view>
+		<view class="outbox">
+			<view class="plusicon" @click="clickSub">
+				<text class="plusTxt">-</text>
+			</view>
+			<view class="container" ref="containerRef" @touchstart="onTouchStart" @touchmove="onTouchMove"
+				@touchend="onTouchEnd">
+				<view class="trackBg"></view>
+				<view class="block" :style="blockStyle" @touchstart.stop="onBlockTouchStart"
+					@touchmove.stop="onBlockTouchMove" @touchend.stop="onTouchEnd">
+					<image class="barimg" src="/static/device/barimg.png" mode="aspectFill"></image>
+				</view>
+			</view>
+			<view class="plusicon" @click="clickPlus">
+				<text class="plusTxt">+</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	const dom = weex.requireModule('dom')
+
+	export default {
+		props: {
+			// 当前角度值
+			anglenum: {
+				type: Number,
+				default: 90
+			},
+			// 最小值
+			min: {
+				type: Number,
+				default: 90
+			},
+			// 最大值
+			max: {
+				type: Number,
+				default: 150
+			},
+			// 步长
+			step: {
+				type: Number,
+				default: 15
+			}
+		},
+		data() {
+			return {
+				angleNumber: 90,
+				containerWidth: 0,
+				containerLeft: 0,
+				lViewLeft: 0, // lView左侧偏移
+				containerOffsetX: 0, // container相对于lView的左侧偏移
+				blockWidth: 62, // 滑块宽度 px (约124rpx)
+				currentX: 0, // 当前滑块left值
+				isDragging: false
+			}
+		},
+		computed: {
+			blockStyle() {
+				return 'left:' + this.currentX + 'px;'
+			},
+			angleNumStyle() {
+				// 数字容器与滑块同宽,加上 container 相对 lView 的偏移量
+				const left = this.currentX + this.containerOffsetX
+				return 'left:' + left + 'px;width:' + this.blockWidth + 'px;'
+			}
+		},
+		watch: {
+			anglenum: {
+				immediate: true,
+				handler(newVal) {
+					this.angleNumber = newVal
+					this.$nextTick(() => {
+						this.updatePositionFromValue(newVal)
+					})
+				}
+			}
+		},
+		mounted() {
+			setTimeout(() => {
+				this.getContainerRect()
+			}, 300)
+		},
+		methods: {
+			getContainerRect() {
+				// 先获取 lView 的位置
+				dom.getComponentRect(this.$refs.lViewRef, (lRes) => {
+					if (lRes && lRes.result) {
+						this.lViewLeft = lRes.size.left
+					}
+					// 再获取 container 的位置
+					dom.getComponentRect(this.$refs.containerRef, (res) => {
+						if (res && res.result) {
+							const rect = res.size
+							this.containerWidth = rect.width
+							this.containerLeft = rect.left
+							// container 相对于 lView 的偏移
+							this.containerOffsetX = rect.left - this.lViewLeft
+							// 初始化位置
+							this.updatePositionFromValue(this.angleNumber)
+						}
+					})
+				})
+			},
+			// 根据值计算位置
+			updatePositionFromValue(val) {
+				if (this.containerWidth <= 0) return
+				const trackWidth = this.containerWidth - this.blockWidth
+				const ratio = (val - this.min) / (this.max - this.min)
+				this.currentX = ratio * trackWidth
+			},
+			// 根据位置计算值(不吸附,实时显示)
+			getRawValueFromPosition(x) {
+				const trackWidth = this.containerWidth - this.blockWidth
+				if (trackWidth <= 0) return this.min
+				let ratio = x / trackWidth
+				ratio = Math.max(0, Math.min(1, ratio))
+				const rawValue = this.min + ratio * (this.max - this.min)
+				// 四舍五入到步长
+				const steps = Math.round((rawValue - this.min) / this.step)
+				return Math.min(this.max, Math.max(this.min, this.min + steps * this.step))
+			},
+			// 容器触摸开始(点击轨道跳转)
+			onTouchStart(e) {
+				this.isDragging = true
+				const touch = e.touches[0]
+				const x = touch.screenX - this.containerLeft - this.blockWidth / 2
+				this.smoothMove(x)
+			},
+			onTouchMove(e) {
+				const touch = e.touches[0]
+				const x = touch.screenX - this.containerLeft - this.blockWidth / 2
+				this.smoothMove(x)
+			},
+			// 滑块触摸
+			onBlockTouchStart(e) {
+				this.isDragging = true
+			},
+			onBlockTouchMove(e) {
+				if (!this.isDragging) return
+				const touch = e.touches[0]
+				const x = touch.screenX - this.containerLeft - this.blockWidth / 2
+				this.smoothMove(x)
+			},
+			onTouchEnd(e) {
+				this.isDragging = false
+				// 松手时吸附到步进位置
+				const val = this.getRawValueFromPosition(this.currentX)
+				this.angleNumber = val
+				this.updatePositionFromValue(val)
+				this.$emit('change', this.angleNumber)
+			},
+			// 拖拽时丝滑跟手,不吸附
+			smoothMove(x) {
+				const trackWidth = this.containerWidth - this.blockWidth
+				x = Math.max(0, Math.min(trackWidth, x))
+				this.currentX = x
+				// 实时计算角度显示(吸附到最近步进值)
+				this.angleNumber = this.getRawValueFromPosition(x)
+			},
+			clickSub() {
+				if (this.angleNumber > this.min) {
+					this.angleNumber -= this.step
+					this.updatePositionFromValue(this.angleNumber)
+					this.$emit('change', this.angleNumber)
+				}
+			},
+			clickPlus() {
+				if (this.angleNumber < this.max) {
+					this.angleNumber += this.step
+					this.updatePositionFromValue(this.angleNumber)
+					this.$emit('change', this.angleNumber)
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.lView {
+		height: 96rpx;
+		position: relative;
+		padding-top: 40rpx;
+	}
+
+	.angleNumber {
+		position: absolute;
+		top: 0;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.angleTxt {
+		font-size: 36rpx;
+		color: #FFFFFF;
+		font-weight: bold;
+		text-align: center;
+	}
+
+	.outbox {
+		height: 56rpx;
+		background-color: rgba(255, 255, 255, 0.7);
+		border-radius: 40rpx;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		padding-left: 10rpx;
+		padding-right: 10rpx;
+	}
+
+	.plusicon {
+		width: 50rpx;
+		height: 50rpx;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.plusTxt {
+		font-size: 44rpx;
+		color: #389588;
+		font-weight: bold;
+	}
+
+	.container {
+		flex: 1;
+		height: 56rpx;
+		position: relative;
+		justify-content: center;
+	}
+
+	.trackBg {
+		height: 6rpx;
+		background-color: rgba(56, 149, 136, 0.3);
+		border-radius: 3rpx;
+	}
+
+	.block {
+		position: absolute;
+		width: 124rpx;
+		height: 44rpx;
+		background-color: #389588;
+		border-radius: 40rpx;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.barimg {
+		width: 30rpx;
+		height: 36rpx;
+	}
+</style>

+ 11 - 2
code/ajyApp/manifest.json

@@ -19,7 +19,8 @@
         /* 模块配置 */
         "modules" : {
             "Bluetooth" : {},
-            "Push" : {}
+            "Push" : {},
+            "Camera" : {}
         },
         /* 应用发布信息 */
         "distribute" : {
@@ -45,7 +46,15 @@
             },
             /* ios打包配置 */
             "ios" : {
-                "dSYMs" : false
+                "dSYMs" : false,
+                "privacyDescription" : {
+                    "NSPhotoLibraryUsageDescription" : "需要访问您的相册,以便您选择照片作为头像或发布内容",
+                    "NSPhotoLibraryAddUsageDescription" : "需要保存图片/视频到相册,方便您随时查看",
+                    "NSCameraUsageDescription" : "需要访问摄像头,以便您拍摄照片、录制视频或进行扫码",
+                    "NSMicrophoneUsageDescription" : "录制视频时需要使用麦克风采集声音",
+                    "NSBluetoothAlwaysUsageDescription" : "需要访问蓝牙,以便搜索并连接附近的智能设备",
+                    "NSBluetoothPeripheralUsageDescription" : "需要访问蓝牙,以便与周边设备进行数据交互"
+                }
             },
             /* SDK配置 */
             "sdkConfigs" : {

+ 117 - 6
code/ajyApp/pages.json

@@ -6,6 +6,25 @@
 		}
 	},
 	"pages": [
+		// {
+		// 	"path": "pages/ble-demo/ble-demo",
+		// 	"style": {
+		// 		"navigationStyle": "custom",
+		// 		"navigationBarTitleText": "",
+		// 		"enablePullDownRefresh": true
+		// 	}
+		// },
+		
+		
+			{
+			"path": "pages/device/list/list",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": true
+			}
+		},
+	
 	
 		{
 			"path": "pages/home/home",
@@ -23,21 +42,20 @@
 			}
 		},
 		{
-			"path": "pages/login/login",
+			"path": "pages/find/detail/detail",
 			"style": {
 				"navigationStyle": "custom",
 				"navigationBarTitleText": ""
 			}
 		},
-		
 		{
-			"path": "pages/device/list/list",
+			"path": "pages/login/login",
 			"style": {
 				"navigationStyle": "custom",
-				"navigationBarTitleText": "",
-				"enablePullDownRefresh": true
+				"navigationBarTitleText": ""
 			}
 		},
+		
 	
 	
 		{
@@ -67,11 +85,104 @@
 				"navigationBarTitleText": ""
 			}
 		},
+		{
+			"path": "pages/device/search/search",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/device/detail/detail",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/device/deviceInfo/deviceInfo",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"app-plus": {
+					"backgroundColor": "#389588"
+				}
+			}
+		},
+		{
+			"path": "pages/device/moxibustion/moxibustion",
+			"style": {
+				"navigationBarTitleText": "耗材管理"
+			}
+		},
+		{
+			"path": "pages/device/mycases/mycases",
+			"style": {
+				"navigationBarTitleText": "我的方案"
+			}
+		},
+		{
+			"path": "pages/device/userManage/userManage",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"app-plus": {
+					"backgroundColor": "#389588"
+				}
+			}
+		},
+		{
+			"path": "pages/device/bodyParams/bodyParams",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"app-plus": {
+					"backgroundColor": "#389588"
+				}
+			}
+		},
 		{
 			"path": "pages/test-toast/test-toast",
 			"style": {
 				"navigationBarTitleText": "Toast测试"
 			}
+		},
+		{
+			"path": "pages/my/msg/msg",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": true
+			}
+		},
+		{
+			"path": "pages/my/version/version",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/my/questions/questions",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/my/questions/quesinfo",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/my/history/history",
+			"style": {
+				"navigationStyle": "custom",
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": true
+			}
 		}
 	],
 	"globalStyle": {
@@ -83,7 +194,7 @@
 	"tabBar": {
 		"color": "#9BA5B7",
 		"selectedColor": "#389588",
-		"borderStyle": "black",
+		"borderStyle": "white",
 		"backgroundColor": "#ffffff",
 		"height": "56px",
 		"fontSize": "13px",

+ 2 - 2
code/ajyApp/pages/ble-demo/ble-demo.nvue

@@ -46,7 +46,7 @@ export default {
 		}
 	},
 	onLoad() {
-		bleManager.configure({ deviceNamePrefix: 'AJY', debug: true })
+		bleManager.configure({ debug: true })
 
 		this._unbinders = [
 			bleManager.on('state', s => { this.state = s }),
@@ -70,7 +70,7 @@ export default {
 			try {
 				await ensureBlePrerequisite()
 				await bleManager.init()
-				const dev = await bleManager.scanAndConnect({ namePrefix: 'BT-', timeout: 8000 })
+				const dev = await bleManager.scanAndConnect({ timeout: 8000 })
 				this.device = dev
 			} catch (e) {
 				this.log('失败: ' + e.message)

+ 373 - 0
code/ajyApp/pages/device/bodyParams/bodyParams.nvue

@@ -0,0 +1,373 @@
+<template>
+	<view class="page">
+		<image class="content-bg" src="/static/device/devicebg.png" mode="scaleToFill"></image>
+		<view class="pageContent">
+			<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+			<view class="customHead">
+				<view class="back-wrap" @click="goBack">
+					<image src="/static/public/back.png" mode="aspectFill" class="backimg"></image>
+				</view>
+				<text class="customTitle">体形参数</text>
+				<view class="placeholder"></view>
+			</view>
+			<text class="tipTxt">请录入体形参数,用于精准定位穴位</text>
+			<scroll-view class="formArea" scroll-y="true">
+				<!-- 用户姓名 -->
+				<view class="formItem">
+					<text class="formLabel">姓名</text>
+					<view class="inputWrap">
+						<input class="formInput" v-model="form.name" placeholder="请输入姓名" placeholder-class="pholder" maxlength="10" />
+					</view>
+				</view>
+
+				<!-- 性别 -->
+				<view class="formItem">
+					<text class="formLabel">性别</text>
+					<view class="genderRow">
+						<view class="genderBtn" :class="{'genderActive': form.gender === 1}" @click="form.gender = 1">
+							<text class="genderTxt" :style="{color: form.gender === 1 ? '#FFFFFF' : '#545F71'}">男</text>
+						</view>
+						<view class="genderBtn" :class="{'genderActive': form.gender === 2}" @click="form.gender = 2">
+							<text class="genderTxt" :style="{color: form.gender === 2 ? '#FFFFFF' : '#545F71'}">女</text>
+						</view>
+					</view>
+				</view>
+
+				<!-- 年龄 -->
+				<view class="formItem">
+					<text class="formLabel">年龄</text>
+					<view class="inputWrap">
+						<input class="formInput" v-model="form.age" type="number" placeholder="例如:28" placeholder-class="pholder" maxlength="3" />
+						<text class="unitTxt">岁</text>
+					</view>
+				</view>
+
+				<!-- 肩高 -->
+				<view class="formItem">
+					<text class="formLabel">肩高(地面到肩膀的高度)</text>
+					<view class="inputWrap">
+						<input class="formInput" v-model="form.shoulderHeight" type="digit" placeholder="例如:135" placeholder-class="pholder" />
+						<text class="unitTxt">cm</text>
+					</view>
+					<view class="sliderWrap">
+						<slider :value="Number(form.shoulderHeight) || 100" :min="50" :max="250" :step="1" 
+							activeColor="#389588" backgroundColor="#E8E8E8" block-size="20"
+							@change="onShoulderHeightChange" />
+						<view class="sliderRange">
+							<text class="rangeTxt">50cm</text>
+							<text class="rangeTxt">250cm</text>
+						</view>
+					</view>
+				</view>
+
+				<!-- 肩宽 -->
+				<view class="formItem">
+					<text class="formLabel">肩宽(两肩之间的宽度)</text>
+					<view class="inputWrap">
+						<input class="formInput" v-model="form.shoulderWidth" type="digit" placeholder="例如:42" placeholder-class="pholder" />
+						<text class="unitTxt">cm</text>
+					</view>
+					<view class="sliderWrap">
+						<slider :value="Number(form.shoulderWidth) || 40" :min="20" :max="80" :step="1"
+							activeColor="#389588" backgroundColor="#E8E8E8" block-size="20"
+							@change="onShoulderWidthChange" />
+						<view class="sliderRange">
+							<text class="rangeTxt">20cm</text>
+							<text class="rangeTxt">80cm</text>
+						</view>
+					</view>
+				</view>
+
+				<!-- 体重 -->
+				<view class="formItem">
+					<text class="formLabel">体重</text>
+					<view class="inputWrap">
+						<input class="formInput" v-model="form.weight" type="digit" placeholder="例如:65" placeholder-class="pholder" />
+						<text class="unitTxt">kg</text>
+					</view>
+				</view>
+
+				<!-- 保存按钮 -->
+				<view class="saveBtn" @click="onSave">
+					<text class="saveTxt">保存</text>
+				</view>
+
+				<view class="bottomSpace"></view>
+			</scroll-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,
+			mode: 'add', // add / edit
+			userId: '',
+			form: {
+				name: '',
+				gender: 1,
+				age: '',
+				shoulderHeight: '',
+				shoulderWidth: '',
+				weight: ''
+			}
+		}
+	},
+	onLoad(options) {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.mode = options.mode || 'add'
+		this.userId = options.userId || ''
+		if (this.mode === 'edit' && this.userId) {
+			this.loadUserData()
+		}
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		/** 加载编辑数据 */
+		loadUserData() {
+			const list = uni.getStorageSync('user_manage_list') || []
+			const user = list.find(u => u.id === this.userId)
+			if (user) {
+				this.form.name = user.name || ''
+				this.form.gender = user.gender || 1
+				this.form.age = user.age ? String(user.age) : ''
+				this.form.shoulderHeight = user.shoulderHeight ? String(user.shoulderHeight) : ''
+				this.form.shoulderWidth = user.shoulderWidth ? String(user.shoulderWidth) : ''
+				this.form.weight = user.weight ? String(user.weight) : ''
+			}
+		},
+		onShoulderHeightChange(e) {
+			this.form.shoulderHeight = String(e.detail.value)
+		},
+		onShoulderWidthChange(e) {
+			this.form.shoulderWidth = String(e.detail.value)
+		},
+		/** 校验并保存 */
+		onSave() {
+			// 校验
+			if (!this.form.name.trim()) {
+				this.$refs.ayToast.error('请输入姓名')
+				return
+			}
+			const height = Number(this.form.shoulderHeight)
+			if (!height || height < 50 || height > 250) {
+				this.$refs.ayToast.error('肩高范围为50-250cm')
+				return
+			}
+			const width = Number(this.form.shoulderWidth)
+			if (!width || width < 20 || width > 80) {
+				this.$refs.ayToast.error('肩宽范围为20-80cm')
+				return
+			}
+			const weight = Number(this.form.weight)
+			if (!weight || weight < 10 || weight > 300) {
+				this.$refs.ayToast.error('体重范围为10-300kg')
+				return
+			}
+
+			let list = uni.getStorageSync('user_manage_list') || []
+
+			if (this.mode === 'add') {
+				// 再次检查是否超过10个
+				if (list.length >= 10) {
+					this.$refs.ayToast.error('最多可添加10个用户')
+					return
+				}
+				const newUser = {
+					id: 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6),
+					name: this.form.name.trim(),
+					gender: this.form.gender,
+					age: Number(this.form.age) || 0,
+					shoulderHeight: height,
+					shoulderWidth: width,
+					weight: weight,
+					createTime: Date.now()
+				}
+				list.push(newUser)
+				uni.setStorageSync('user_manage_list', list)
+				// 如果是第一个用户,自动设为当前用户
+				if (list.length === 1) {
+					uni.setStorageSync('current_manage_user_id', newUser.id)
+					uni.setStorageSync('current_manage_user', newUser)
+				}
+				this.$refs.ayToast.success('添加成功')
+			} else {
+				// 编辑模式
+				const index = list.findIndex(u => u.id === this.userId)
+				if (index === -1) {
+					this.$refs.ayToast.error('用户信息已过期,请刷新')
+					return
+				}
+				list[index] = {
+					...list[index],
+					name: this.form.name.trim(),
+					gender: this.form.gender,
+					age: Number(this.form.age) || 0,
+					shoulderHeight: height,
+					shoulderWidth: width,
+					weight: weight
+				}
+				uni.setStorageSync('user_manage_list', list)
+				// 如果编辑的是当前用户,更新当前用户缓存
+				const curId = uni.getStorageSync('current_manage_user_id')
+				if (curId === this.userId) {
+					uni.setStorageSync('current_manage_user', list[index])
+				}
+				this.$refs.ayToast.success('保存成功')
+			}
+
+			setTimeout(() => {
+				uni.navigateBack()
+			}, 800)
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	position: relative;
+}
+.content-bg {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 750rpx;
+	height: 2000px;
+}
+.pageContent {
+	flex: 1;
+	background-image: linear-gradient(to bottom, rgba(56,149,136,1), rgba(56,149,136,0.5));
+}
+.status-bar {
+	background-color: rgba(0,0,0,0);
+}
+.customHead {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	height: 88rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+}
+.back-wrap {
+	width: 50rpx;
+	height: 50rpx;
+	justify-content: center;
+	align-items: center;
+}
+.backimg {
+	width: 50rpx;
+	height: 50rpx;
+}
+.customTitle {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #FFFFFF;
+}
+.placeholder {
+	width: 50rpx;
+}
+.tipTxt {
+	font-size: 24rpx;
+	color: rgba(255,255,255,0.7);
+	margin-left: 38rpx;
+	margin-top: 10rpx;
+	margin-bottom: 20rpx;
+}
+.formArea {
+	flex: 1;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+}
+.formItem {
+	background-color: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 28rpx;
+	margin-bottom: 20rpx;
+}
+.formLabel {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	margin-bottom: 16rpx;
+}
+.inputWrap {
+	flex-direction: row;
+	align-items: center;
+	background-color: #F5F7FA;
+	border-radius: 12rpx;
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+	height: 80rpx;
+}
+.formInput {
+	flex: 1;
+	font-size: 30rpx;
+	color: #333333;
+}
+.unitTxt {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	margin-left: 10rpx;
+}
+.genderRow {
+	flex-direction: row;
+}
+.genderBtn {
+	width: 160rpx;
+	height: 72rpx;
+	border-radius: 36rpx;
+	background-color: #F5F7FA;
+	justify-content: center;
+	align-items: center;
+	margin-right: 24rpx;
+}
+.genderActive {
+	background-color: #389588;
+}
+.genderTxt {
+	font-size: 28rpx;
+}
+.sliderWrap {
+	margin-top: 20rpx;
+}
+.sliderRange {
+	flex-direction: row;
+	justify-content: space-between;
+	margin-top: 4rpx;
+}
+.rangeTxt {
+	font-size: 22rpx;
+	color: #9BA5B7;
+}
+.saveBtn {
+	background-color: #389588;
+	border-radius: 44rpx;
+	height: 88rpx;
+	justify-content: center;
+	align-items: center;
+	margin-top: 30rpx;
+	margin-left: 20rpx;
+	margin-right: 20rpx;
+}
+.saveTxt {
+	font-size: 32rpx;
+	color: #FFFFFF;
+	font-weight: bold;
+}
+.bottomSpace {
+	height: 60rpx;
+}
+</style>

+ 336 - 0
code/ajyApp/pages/device/detail/detail.nvue

@@ -0,0 +1,336 @@
+<template>
+	<view class="container">
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<view class="customHead">
+			<view class="back-wrap" @click="goBack">
+				<image src="/static/public/backBlack.png" mode="aspectFill" class="backimg"></image>
+			</view>
+			<text class="head-title">设备详情</text>
+			<view class="plus"></view>
+		</view>
+		<scroll-view class="list" scroll-y="true">
+			<!-- 连接状态 -->
+			<view class="connect-status">
+				<view class="status-dot" :style="{backgroundColor: connected ? '#389588' : '#CCCCCC'}"></view>
+				<text class="status-text">{{connected ? '已连接' : '未连接'}}</text>
+				<text class="connect-btn" v-if="!connected && !connecting" @click="connectDevice">连接设备</text>
+				<text class="connect-btn" v-if="connecting">连接中...</text>
+			</view>
+
+			<text class="txt">通用设置</text>
+			<view class="menuitem">
+				<text>设备名称</text>
+				<text class="gray">{{name}}</text>
+			</view>
+			<view class="menuitem">
+				<text>设备ID</text>
+				<text class="gray device-id-text">{{deviceId}}</text>
+			</view>
+			<view class="menuitem">
+				<text>信号强度</text>
+				<text class="gray">{{rssiText}}</text>
+			</view>
+			<view class="menuitem">
+				<text>固件版本</text>
+				<text class="gray">{{firmwareVersion || '未获取'}}</text>
+			</view>
+			<view class="menuitem lastItem">
+				<text>连接状态</text>
+				<text class="gray">{{connected ? '已连接' : '未连接'}}</text>
+			</view>
+
+			<text class="txt">设备操作</text>
+			<view class="menuitem" @click="disconnectDevice" v-if="connected">
+				<text>断开连接</text>
+				<image class="arrowright" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+			<view class="menuitem lastItem">
+				<text>帮助与反馈</text>
+				<image class="arrowright" src="/static/my/arrowright.png" mode="aspectFill"></image>
+			</view>
+
+			<view class="delbtn" @click="delDevice">
+				<text class="delbtn-text">删除设备</text>
+			</view>
+		</scroll-view>
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import bleManager, { BLE_STATE } from '@/utils/ble'
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			statusBarHeight: 44,
+			name: '',
+			deviceId: '',
+			rssi: '',
+			connected: false,
+			connecting: false,
+			firmwareVersion: '',
+			_unbinders: []
+		}
+	},
+	computed: {
+		rssiText() {
+			if (!this.rssi) return '未知'
+			const val = Number(this.rssi)
+			if (val >= -50) return '极强 (' + this.rssi + 'dBm)'
+			if (val >= -65) return '强 (' + this.rssi + 'dBm)'
+			if (val >= -80) return '中 (' + this.rssi + 'dBm)'
+			return '弱 (' + this.rssi + 'dBm)'
+		}
+	},
+	onLoad(options) {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.name = options.name || ''
+		this.deviceId = options.deviceId || ''
+		this.rssi = options.rssi || ''
+	},
+	onShow() {
+		// 监听BLE状态
+		this._unbinders = [
+			bleManager.on('state', (s) => {
+				this.connected = (s === BLE_STATE.READY_COMM)
+				if (s === BLE_STATE.DISCONNECTED) {
+					this.connecting = false
+				}
+			}),
+			bleManager.on('disconnected', () => {
+				this.connected = false
+				this.connecting = false
+			}),
+			bleManager.on('report:GROUP_2', (data) => {
+				if (data && data.firmwareVersion) {
+					this.firmwareVersion = 'V' + data.firmwareVersion
+				}
+			})
+		]
+		// 检查当前连接状态
+		this.connected = bleManager.isConnected
+	},
+	onHide() {
+		this._cleanListeners()
+	},
+	onUnload() {
+		this._cleanListeners()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		_cleanListeners() {
+			if (this._unbinders && this._unbinders.length) {
+				this._unbinders.forEach(fn => fn && fn())
+				this._unbinders = []
+			}
+		},
+		async connectDevice() {
+			if (this.connecting || this.connected) return
+			this.connecting = true
+			try {
+				await bleManager.init()
+				await bleManager.connect(this.deviceId)
+				this.connected = true
+				this.$refs.ayToast.success('连接成功')
+			} catch (e) {
+				this.$refs.ayToast.error('连接失败: ' + (e.message || ''))
+			} finally {
+				this.connecting = false
+			}
+		},
+		async disconnectDevice() {
+			try {
+				await bleManager.disconnect()
+				this.connected = false
+				this.$refs.ayToast.success('已断开连接')
+			} catch (e) {
+				this.$refs.ayToast.error('断开失败')
+			}
+		},
+		delDevice() {
+			uni.showModal({
+				title: '提示',
+				content: '此操作将删除该设备,是否继续?',
+				cancelText: '取消',
+				confirmText: '继续',
+				success: (res) => {
+					if (res.confirm) {
+						// 断开连接
+						if (this.connected) {
+							bleManager.disconnect().catch(() => {})
+						}
+						// 从本地存储中移除
+						try {
+							let deviceList = uni.getStorageSync('deviceList') || []
+							deviceList = deviceList.filter(item => item.deviceId !== this.deviceId)
+							uni.setStorageSync('deviceList', deviceList)
+						} catch (e) {}
+
+						this.$refs.ayToast.success('设备删除成功')
+						setTimeout(() => {
+							uni.navigateBack()
+						}, 1500)
+					}
+				}
+			})
+		}
+	}
+}
+</script>
+
+<style>
+.container {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+
+.status-bar {
+	background-color: #FFFFFF;
+}
+
+.customHead {
+	width: 750rpx;
+	background-color: #FFFFFF;
+	flex-direction: row;
+	justify-content: space-between;
+	height: 88rpx;
+	align-items: center;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+}
+
+.back-wrap {
+	width: 50rpx;
+	height: 50rpx;
+	justify-content: center;
+	align-items: center;
+}
+
+.backimg {
+	width: 50rpx;
+	height: 50rpx;
+}
+
+.head-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+
+.plus {
+	width: 50rpx;
+	height: 50rpx;
+}
+
+.list {
+	flex: 1;
+}
+
+.connect-status {
+	flex-direction: row;
+	align-items: center;
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	margin-top: 30rpx;
+	margin-bottom: 10rpx;
+	padding-top: 24rpx;
+	padding-bottom: 24rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+	background-color: #F8F9FB;
+	border-radius: 12rpx;
+}
+
+.status-dot {
+	width: 16rpx;
+	height: 16rpx;
+	border-radius: 16rpx;
+	background-color: #CCCCCC;
+	margin-right: 12rpx;
+}
+
+.status-text {
+	font-size: 28rpx;
+	color: #545F71;
+	flex: 1;
+}
+
+.connect-btn {
+	font-size: 26rpx;
+	color: #389588;
+	padding-top: 8rpx;
+	padding-bottom: 8rpx;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	border-width: 1px;
+	border-style: solid;
+	border-color: #389588;
+	border-radius: 24rpx;
+}
+
+.txt {
+	margin-top: 50rpx;
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	font-size: 28rpx;
+	color: #9BA5B7;
+}
+
+.lastItem {
+	border-bottom-width: 0;
+}
+
+.menuitem {
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 104rpx;
+	font-size: 32rpx;
+	color: #545F71;
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	border-bottom-color: #EEF1F4;
+}
+
+.gray {
+	font-size: 28rpx;
+	color: #9BA5B7;
+	padding-right: 20rpx;
+}
+
+.device-id-text {
+	font-size: 22rpx;
+}
+
+.arrowright {
+	width: 40rpx;
+	height: 40rpx;
+}
+
+.delbtn {
+	width: 654rpx;
+	height: 96rpx;
+	background-color: #F3F3F3;
+	border-radius: 12rpx;
+	margin-top: 60rpx;
+	margin-left: 48rpx;
+	margin-right: 48rpx;
+	justify-content: center;
+	align-items: center;
+}
+
+.delbtn-text {
+	font-size: 32rpx;
+	color: #F65252;
+}
+</style>

+ 993 - 0
code/ajyApp/pages/device/deviceInfo/deviceInfo.nvue

@@ -0,0 +1,993 @@
+<template>
+	<view class="infoBox">
+		<image class="content-bg" src="/static/device/devicebg.png" mode="scaleToFill"></image>
+		<view class="pageContent">
+			<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+			<view class="customHead">
+				<view class="back-wrap" @click="goBack">
+					<image src="/static/public/back.png" mode="aspectFill" class="backimg"></image>
+				</view>
+				<text class="customTitle">{{picMode===1?'我的艾灸椅':'穴位编辑'}}</text>
+				<view class="headRight">
+					<view class="userSwitchBtn" @click="goUserManage">
+						<text class="userSwitchTxt">{{currentUserName || '选择用户'}}</text>
+					</view>
+					<navigator :url="'/pages/device/detail/detail?name='+deviceName+'&deviceId='+deviceId+'&rssi='+rssi"
+						class="plus">
+						<image v-if="picMode===1" src="/static/public/view-list.png" mode="aspectFill" class="menuimg"></image>
+					</navigator>
+				</view>
+			</view>
+			<scroll-view class="boxs" scroll-y="true">
+				<view class="proBox" v-if="picMode == 1">
+					<image class="proimg" src="/static/device/aijiuyi2.png" mode="aspectFill"></image>
+					<view class="pointbox" v-if="deviceStatus == 3">
+						<view class="pointContainer">
+							<text class="pointLabel" v-if="acupointList.length > 0 && curCaseIndex < acupointList.length">{{acupointList[curCaseIndex].name}}</text>
+						</view>
+					</view>
+					<text class="prostatus">蓝牙{{linked?'已':'未'}}连接</text>
+				</view>
+
+				<!-- 座椅角度 -->
+				<view class="angleSection" v-if="picMode == 1">
+					<text class="angleLabel">座椅角度(拖动滑块调整)</text>
+					<l-sliderRange ref="slideRange" :anglenum="chairAngle" :min="90" :max="150" :step="15" @change="onSliderAngleChange"></l-sliderRange>
+				</view>
+
+				<!-- 模式选择 -->
+				<view class="controlMode" v-if="picMode == 1">
+					<text class="modetxt">选择模式</text>
+					<view class="modechange" @click="isShowDrawer2 = true">
+						<text class="modeName">{{modelist[modeType]}}</text>
+						<view class="changeBtn">
+							<text class="changetxt">切换</text>
+							<image class="changeinmg" src="/static/device/change.png" mode="aspectFill"></image>
+						</view>
+					</view>
+				</view>
+
+				<!-- 其他功能 -->
+				<view class="otherSetting" v-if="modeType != 0 && picMode == 1">
+					<text class="otherTxt">其他功能</text>
+					<view class="func">
+						<navigator v-if="modeType == 3" url="/pages/device/mycases/mycases" class="funcitem" hover-class="none">
+							<image class="funcimg" src="/static/device/gongneng3.png" mode="aspectFill"></image>
+							<text class="functxt">我的方案</text>
+						</navigator>
+						<navigator :url="`/pages/device/moxibustion/moxibustion?p1=${otherSetting.aijiuNum}&tab=p1`" class="funcitem" hover-class="none">
+							<image class="funcimg" src="/static/device/gongneng1.png" mode="aspectFill"></image>
+							<text class="functxt">艾灸余量</text>
+						</navigator>
+						<navigator :url="`/pages/device/moxibustion/moxibustion?p2=${otherSetting.lvxinNum}&tab=p2`" class="funcitem" hover-class="none">
+							<image class="funcimg" src="/static/device/gongneng2.png" mode="aspectFill"></image>
+							<text class="functxt">滤芯状态</text>
+						</navigator>
+						<navigator :url="`/pages/device/moxibustion/moxibustion?p3=${otherSetting.huishouNum}&tab=p3`" class="funcitem" hover-class="none">
+							<image class="funcimg" src="/static/device/gongneng2.png" mode="aspectFill"></image>
+							<text class="functxt">回收仓占用</text>
+						</navigator>
+					</view>
+				</view>
+
+				<!-- 自定义模式切换按钮 -->
+				<view class="modeChange" v-if="modeType == 2">
+					<view class="modeBtn" v-if="picMode===2" @click="picModeChange(1)" :class="picMode===1?'curBtn':''">
+						<text class="modeBtnTxt" :style="{color:picMode===1?'#545F71':'#fff'}">设备</text>
+					</view>
+					<view class="modeBtn" @click="picModeChange(2)" :class="picMode===2?'curBtn':''">
+						<text class="modeBtnTxt" :style="{color:picMode===2?'#545F71':'#fff'}">穴位</text>
+					</view>
+				</view>
+
+				<!-- 穴位图 -->
+				<view class="acupointPic" v-if="modeType == 2 && picMode == 2">
+					<image class="acupointBg" src="/static/device/persionbg.png" mode="aspectFill"></image>
+					<view class="acupoint" :class="index == acupointIndex ? 'curAcupoint':''" @click="showTimeDraw(index)"
+						v-for="(item,index) in acupointList" :key="index"
+						:style="{left:item.x+'rpx',top:item.y+'rpx'}">
+					</view>
+					<view class="tipsbox" v-if="acupointIndex != -1"
+						:style="{left:50+acupointList[acupointIndex].x+'rpx',top:-50+acupointList[acupointIndex].y+'rpx'}">
+						<text class="tipsTxt">{{acupointList[acupointIndex].name}}</text>
+					</view>
+				</view>
+
+				<!-- 白噪音 -->
+				<view class="musicCard" v-if="modeType == 0 && picMode == 1">
+					<text class="musicname">白噪音</text>
+					<view class="musliclist">
+						<text class="musictxt" :class="{'current':curAudioInx==inx}" v-for="(itm,inx) in audiolist"
+							:key="inx" @click="changeAudio(itm.url,inx)">{{itm.name}}</text>
+					</view>
+				</view>
+	
+
+				<!-- 开始/暂停/停止 -->
+				<view class="startbox" v-if="modeType != 0">
+					<view class="stop" v-if="deviceStatus==0">
+						<image class="startbtn" @click="startDeviceEvt()" src="/static/device/start.png" mode="aspectFill"></image>
+						<text class="starttxt">开始</text>
+					</view>
+					<view class="hot" v-if="deviceStatus==1">
+						<view class="hotone">
+							<text class="hoting">{{ispreHot == false?'正在预热':'点火中'}}</text>
+							<text class="hotnum">{{hotPercentage}}</text>
+						</view>
+						<view class="stopbox" @click="stopPreHot">
+							<image class="stopimg" src="/static/device/stop.png" mode="aspectFill"></image>
+							<text class="stoptext">结束</text>
+						</view>
+					</view>
+					<view class="hot" v-if="deviceStatus==3 || deviceStatus==5">
+						<view class="hotone" v-if="deviceStatus==3">
+							<text class="hoting">正在艾灸</text>
+							<text class="hotnum">{{subTime}}</text>
+						</view>
+						<view class="hotone" v-if="deviceStatus==5">
+							<text class="hoting">已暂停</text>
+							<text class="hotnum">{{subTime}}</text>
+						</view>
+						<view class="pausebox" @click="stopAijiu">
+							<image class="pauseimg" :src="deviceStatus==5?'/static/device/start.png':'/static/device/pause.png'" mode="aspectFill"></image>
+							<text class="pausetxt">{{deviceStatus==5?'开始':'暂停'}}</text>
+						</view>
+						<view class="stopbox" @click="stopPreHot">
+							<image class="stopimg" src="/static/device/stop.png" mode="aspectFill"></image>
+							<text class="stoptext">结束</text>
+						</view>
+					</view>
+					<view class="hot" v-if="deviceStatus==4">
+						<view class="hotone">
+							<text class="hoting unfire">正在灭火中,请勿操作</text>
+						</view>
+					</view>
+				</view>
+
+				<view class="empty" v-if="modeType == 0"></view>
+			</scroll-view>
+		</view>
+
+		<!-- 音乐播放器 - 固定底部 -->
+		<view class="musicPlayerFixed" v-if="modeType == 0">
+			<view class="musicPlayer">
+				<view class="leftbox">
+					<image class="playerimg" :src="audiolist[curAudioInx].posterimg" mode="aspectFill"></image>
+					<view class="musicnamebox">
+						<text class="mtime">{{formatTime(currentTime)}}</text>
+					</view>
+				</view>
+				<view class="rightbox">
+					<view class="playBtn" @click="playAudio">
+						<image class="playericon"
+							:src="ispause?'/static/device/playerbtn.png':'/static/device/pauseicon.png'"
+							mode="aspectFit"></image>
+					</view>
+					<image @click="isShowDrawer3 = true" class="menuicon" src="/static/device/menubtn.png"
+						mode="aspectFill"></image>
+				</view>
+			</view>
+		</view>
+
+		<!-- 异常提示弹框 -->
+		<customPopup :isShow="excepDrawer" @closePop="excepDrawer=false">
+			<text class="tip1 toptip" v-if="exceTxt == 1">检查到有异物</text>
+			<text class="tip1" v-if="exceTxt == 1">已暂停艾灸,清理后重试</text>
+			<text class="tip1 toptip" v-if="exceTxt == 2">艾条识别错误</text>
+			<text class="tip1" v-if="exceTxt == 2">已暂停艾灸,重新插入艾条后重试</text>
+			<text class="tip1 toptip" v-if="exceTxt == 3">上艾失败</text>
+			<text class="tip1 toptip" v-if="exceTxt == 4">点火失败</text>
+			<view class="confirmBtnWrap">
+				<text class="confirmBtn" @click="excepDrawer = false; exceTxt = 0;">我知道了</text>
+			</view>
+		</customPopup>
+
+		<!-- 确认结束弹框 -->
+		<customPopup :isShow="isShowConfirm" @closePop="isShowConfirm=false">
+			<text class="confirmTxt toptip">确认结束艾灸?</text>
+			<view class="confirmBtns">
+				<view class="cbtn" @click="isShowConfirm=false">
+					<text class="cbtnTxt">取消</text>
+				</view>
+				<view class="cbtn redbtn" @click="confirmStop">
+					<text class="cbtnTxt">结束</text>
+				</view>
+			</view>
+		</customPopup>
+
+		<!-- 模式选择弹框 -->
+		<customPopup :isShow="isShowDrawer2" @closePop="isShowDrawer2=false">
+			<view class="modeList">
+				<view class="modeItem" @click="changeMode(0)">
+					<text class="modeItemTxt">无艾灸模式</text>
+					<view class="checkboxCustom">
+						<view v-if="modeType == 0" class="checkboxCircle"></view>
+					</view>
+				</view>
+				<view class="modeItem" @click="changeMode(1)">
+					<text class="modeItemTxt">专业模式</text>
+					<view class="checkboxCustom">
+						<view v-if="modeType == 1" class="checkboxCircle"></view>
+					</view>
+				</view>
+				<view class="modeItem" @click="changeMode(2)">
+					<text class="modeItemTxt">自定义模式</text>
+					<view class="checkboxCustom">
+						<view v-if="modeType == 2" class="checkboxCircle"></view>
+					</view>
+				</view>
+				<view class="modeItem" @click="changeMode(3)">
+					<text class="modeItemTxt">专家模式</text>
+					<view class="checkboxCustom">
+						<view v-if="modeType == 3" class="checkboxCircle"></view>
+					</view>
+				</view>
+			</view>
+		</customPopup>
+
+		<!-- 音乐弹窗 -->
+		<customPopup :isShow="isShowDrawer3" @closePop="isShowDrawer3=false">
+			<view class="audiolists">
+				<text class="playname">当前播放</text>
+				<view class="audiolist" :class="curAudioInx==inx?'curAudio':''" v-for="(itm,inx) in audiolist"
+					:key="inx" @click="changeAudio(itm.url,inx)">
+					<text class="audioTxt">{{itm.name}}</text>
+					<view class="signal-container" v-if="curAudioInx==inx">
+						<view class="signal-bar bar1"></view>
+						<view class="signal-bar bar2"></view>
+						<view class="signal-bar bar3"></view>
+					</view>
+				</view>
+			</view>
+		</customPopup>
+
+		<!-- 时间配置器 -->
+		<customPopup :isShow="isShowTimeDrawer" @closePop="isShowTimeDrawer=false">
+			<text class="aijiuTime">选择灸疗时间</text>
+			<view class="timePickerRow">
+				<picker mode="multiSelector" :range="timeRange" @change="bindTimeChange">
+					<text class="timePickerTxt">{{selectedHour}}小时 {{selectedMinute}}分钟</text>
+				</picker>
+			</view>
+			<view class="timeBtns">
+				<view class="tbtn" @click="cancelAijiuTime(0)">
+					<text class="tbtnTxt">取消</text>
+				</view>
+				<view class="tbtn confirmBtn2" @click="cancelAijiuTime(1)">
+					<text class="tbtnTxt confirmBtn2Txt">确认</text>
+				</view>
+			</view>
+		</customPopup>
+
+		<!-- 重连弹框 -->
+		<view class="maskbg" v-if="reconnectDrawer">
+			<view class="maskbox">
+				<text class="maskTxt">蓝牙连接断开,第{{reconnectCount}}次重连中...</text>
+			</view>
+		</view>
+
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import customPopup from '@/components/customPopup/customPopup.nvue'
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+import lSliderRange from '@/components/l-sliderRange/l-sliderRange.nvue'
+import bleMixin from './mixins/ble-mixin.js'
+import audioMixin from './mixins/audio-mixin.js'
+import acupointMixin from './mixins/acupoint-mixin.js'
+import bleManager, { CHAIR_ANGLE } from '@/utils/ble'
+
+export default {
+	components: {
+		customPopup,
+		ayToast,
+		lSliderRange
+	},
+	mixins: [bleMixin, audioMixin, acupointMixin],
+	data() {
+		return {
+			statusBarHeight: 44,
+			deviceId: '',
+			deviceName: '',
+			rssi: '',
+			subTime: '00:00:00',
+			isShowDrawer2: false,
+			modeType: 0, // 0无艾灸 1专业 2自定义 3专家
+			modelist: ['无艾灸模式', '专业模式', '自定义模式', '专家模式'],
+			chairAngle: 90,
+			otherSetting: {
+				aijiuNum: '0',
+				lvxinNum: '0',
+				huishouNum: '0'
+			},
+			currentUserName: ''
+		}
+	},
+	onLoad(options) {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+
+		this.deviceId = options.deviceId || ''
+		this.deviceName = options.name || ''
+		this.rssi = options.rssi || ''
+
+		// TODO: 后续接口确定后实现 - 下载穴位列表
+		// TODO: 后续接口确定后实现 - 下载专业模式推荐方案
+
+		this.loadCurrentUser()
+		this.initAudio()
+		this.initBle()
+	},
+	onShow() {
+		// 返回页面时刷新当前用户
+		this.loadCurrentUser()
+	},
+	onUnload() {
+		this.destroyAudio()
+		this.disconnectBle()
+	},
+	methods: {
+		goBack() {
+			// 自定义模式下穴位编辑视图,返回切回设备视图而非真正返回页面
+			if (this.modeType == 2 && this.picMode == 2) {
+				this.picMode = 1
+				return
+			}
+			uni.navigateBack()
+		},
+		/** 加载当前用户信息 */
+		loadCurrentUser() {
+			const user = uni.getStorageSync('current_manage_user')
+			if (user && user.name) {
+				this.currentUserName = user.name
+			} else {
+				this.currentUserName = ''
+			}
+		},
+		/** 跳转用户管理页 */
+		goUserManage() {
+			uni.navigateTo({
+				url: '/pages/device/userManage/userManage'
+			})
+		},
+		// 座椅角度控制 - 来自 l-sliderRange 组件
+		onSliderAngleChange(val) {
+			this.chairAngle = val
+			this._sendAngleCommand()
+		},
+		_sendAngleCommand() {
+			const angleMap = {
+				90: CHAIR_ANGLE.LEVEL_1,
+				105: CHAIR_ANGLE.LEVEL_2,
+				120: CHAIR_ANGLE.LEVEL_3,
+				135: CHAIR_ANGLE.LEVEL_4,
+				150: CHAIR_ANGLE.LEVEL_5
+			}
+			const level = angleMap[this.chairAngle] || CHAIR_ANGLE.LEVEL_1
+			if (this.linked) {
+				bleManager.setChairAngle(level).catch(e => console.error('setAngle fail', e))
+			}
+		}
+	}
+}
+</script>
+
+<style>
+.infoBox {
+	flex: 1;
+	position: relative;
+}
+.content-bg {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 750rpx;
+	height: 2000px;
+}
+.gradientBg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+
+}
+.pageBg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 750rpx;
+	height: 1400rpx;
+}
+.pageContent {
+	flex: 1;
+	background-image: linear-gradient(to bottom, rgba(56,149,136,1), rgba(56,149,136,0.5));
+	/* background-color: #333; */
+}
+.status-bar {
+	background-color: rgba(0,0,0,0);
+}
+.customHead {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	height: 88rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+	
+}
+.back-wrap {
+	width: 50rpx;
+	height: 50rpx;
+	justify-content: center;
+	align-items: center;
+}
+.backimg {
+	width: 50rpx;
+	height: 50rpx;
+}
+.customTitle {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #FFFFFF;
+}
+.plus {
+	width: 50rpx;
+	height: 50rpx;
+	justify-content: center;
+	align-items: center;
+}
+.menuimg {
+	width: 40rpx;
+	height: 40rpx;
+}
+.headRight {
+	flex-direction: row;
+	align-items: center;
+}
+.userSwitchBtn {
+	background-color: rgba(255,255,255,0.2);
+	border-radius: 24rpx;
+	padding-left: 16rpx;
+	padding-right: 16rpx;
+	padding-top: 8rpx;
+	padding-bottom: 8rpx;
+	margin-right: 12rpx;
+}
+.userSwitchTxt {
+	font-size: 24rpx;
+	color: #FFFFFF;
+}
+.boxs {
+	flex: 1;
+}
+.proBox {
+	align-items: center;
+	padding-top: 30rpx;
+	padding-bottom: 20rpx;
+}
+.proimg {
+	width: 600rpx;
+	height: 500rpx;
+}
+.pointbox {
+	margin-top: 20rpx;
+	align-items: center;
+}
+.pointContainer {
+	background-color: #389588;
+	border-radius: 20rpx;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	padding-top: 8rpx;
+	padding-bottom: 8rpx;
+}
+.pointLabel {
+	color: #FFFFFF;
+	font-size: 24rpx;
+}
+.prostatus {
+	margin-top: 20rpx;
+	font-size: 26rpx;
+	color: #FFFFFF;
+}
+/* 座椅角度 */
+.angleSection {
+	padding-left: 38rpx;
+	padding-right: 38rpx;
+	padding-top: 20rpx;
+	padding-bottom: 30rpx;
+}
+.angleLabel {
+	font-size: 26rpx;
+	color: #FFFFFF;
+	margin-bottom: 10rpx;
+}
+/* 模式选择 */
+.controlMode {
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	margin-top: 20rpx;
+	background-color: rgba(255,255,255,0.9);
+	border-radius: 16rpx;
+	padding: 30rpx;
+}
+.modetxt {
+	font-size: 28rpx;
+	color: #9BA5B7;
+	margin-bottom: 20rpx;
+}
+.modechange {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	background-color: #389588;
+	border-radius: 40rpx;
+	padding-left: 30rpx;
+	padding-right: 20rpx;
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+}
+.modeName {
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #ffffff;
+}
+.changeBtn {
+	flex-direction: row;
+	align-items: center;
+}
+.changetxt {
+	font-size: 26rpx;
+	color: #389588;
+	margin-right: 8rpx;
+}
+.changeinmg {
+	width: 20px;
+	height: 20px;
+}
+/* 其他功能 */
+.otherSetting {
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	margin-top: 20rpx;
+	background-color: #ffffff;
+	border-radius: 16rpx;
+	padding: 30rpx;
+}
+.otherTxt {
+	font-size: 28rpx;
+	color: #9BA5B7;
+	margin-bottom: 20rpx;
+}
+.func {
+	flex-direction: row;
+	flex-wrap: wrap;
+}
+.funcitem {
+	width: 200rpx;
+	align-items: center;
+	margin-bottom: 20rpx;
+}
+.funcimg {
+	width: 60rpx;
+	height: 60rpx;
+}
+.functxt {
+	font-size: 24rpx;
+	color: #9BA5B7;
+	margin-top: 10rpx;
+}
+/* 自定义模式切换 */
+.modeChange {
+	flex-direction: row;
+	justify-content: center;
+	margin-top: 30rpx;
+	margin-bottom: 20rpx;
+}
+.modeBtn {
+	width: 160rpx;
+	height: 60rpx;
+	background-color: #545F71;
+	justify-content: center;
+	align-items: center;
+	border-radius: 30rpx;
+	margin-left: 10rpx;
+	margin-right: 10rpx;
+}
+.curBtn {
+	background-color: #FFFFFF;
+}
+.modeBtnTxt {
+	font-size: 26rpx;
+}
+/* 穴位图 */
+.acupointPic {
+	position: relative;
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	height: 700rpx;
+	align-items: center;
+}
+.acupointBg {
+	width: 300rpx;
+	height: 650rpx;
+}
+.acupoint {
+	position: absolute;
+	width: 20rpx;
+	height: 20rpx;
+	background-color: #389588;
+	border-radius: 20rpx;
+}
+.curAcupoint {
+	background-color: #F65252;
+	width: 28rpx;
+	height: 28rpx;
+	border-radius: 28rpx;
+}
+.tipsbox {
+	position: absolute;
+	background-color: #333;
+	border-radius: 8rpx;
+	padding-left: 12rpx;
+	padding-right: 12rpx;
+	padding-top: 6rpx;
+	padding-bottom: 6rpx;
+}
+.tipsTxt {
+	color: #FFFFFF;
+	font-size: 22rpx;
+}
+/* 白噪音 */
+.musicCard {
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	margin-top: 20rpx;
+	background-color: rgba(255,255,255,0.9);
+	border-radius: 16rpx;
+	padding: 30rpx;
+	height:100px;
+}
+.musicname {
+	font-size: 28rpx;
+	color: #545F71;
+	font-weight: bold;
+	margin-bottom: 20rpx;
+}
+.musliclist {
+	flex-direction: row;
+	flex-wrap: wrap;
+}
+.musictxt {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	padding-top: 12rpx;
+	padding-bottom: 12rpx;
+	margin-right: 20rpx;
+	border-radius: 24rpx;
+	background-color: #F5F7FA;
+}
+.current {
+	background-color: #389588;
+	color: #FFFFFF;
+}
+.musicPlayerFixed {
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+}
+.musicPlayer {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	background-color: rgba(255,255,255,0.5);
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+	padding-top: 50px;
+	padding-bottom: 40px;
+	border-radius:16px 16px 0 0;
+}
+.leftbox {
+	flex-direction: row;
+	align-items: center;
+}
+.playerimg {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 12rpx;
+}
+.musicnamebox {
+	margin-left: 16rpx;
+}
+.mtime {
+	font-size: 28rpx;
+	color: #545F71;
+}
+.rightbox {
+	flex-direction: row;
+	align-items: center;
+}
+.playBtn {
+	width: 60rpx;
+	height: 60rpx;
+	justify-content: center;
+	align-items: center;
+}
+.playericon {
+	width: 28rpx;
+	height: 28rpx;
+}
+.menuicon {
+	width: 80rpx;
+	height: 80rpx;
+	margin-left: 20rpx;
+}
+/* 开始/暂停/停止 */
+.startbox {
+	align-items: center;
+	margin-top: 40rpx;
+	padding-bottom: 40rpx;
+}
+.stop {
+	align-items: center;
+}
+.startbtn {
+	width: 120rpx;
+	height: 120rpx;
+}
+.starttxt {
+	font-size: 28rpx;
+	color: #FFFFFF;
+	margin-top: 10rpx;
+}
+.hot {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	align-self: stretch;
+	padding: 0 80rpx;
+}
+.hotone {
+	align-items: center;
+	margin-right: 40rpx;
+}
+.hoting {
+	font-size: 28rpx;
+	color: #FFFFFF;
+}
+.unfire {
+	color: #F65252;
+}
+.hotnum {
+	font-size: 36rpx;
+	color: #E0FFF8;
+	font-weight: bold;
+	margin-top: 8rpx;
+}
+.pausebox {
+	align-items: center;
+	margin-right: 40rpx;
+}
+.pauseimg {
+	width: 80rpx;
+	height: 80rpx;
+}
+.pausetxt {
+	font-size: 24rpx;
+	color: #FFFFFF;
+	margin-top: 6rpx;
+}
+.stopbox {
+	align-items: center;
+}
+.stopimg {
+	width: 80rpx;
+	height: 80rpx;
+}
+.stoptext {
+	font-size: 24rpx;
+	color: #F65252;
+	margin-top: 6rpx;
+}
+.empty {
+	height: 180rpx;
+}
+/* 弹框样式 */
+.tip1 {
+	font-size: 28rpx;
+	color: #545F71;
+	text-align: center;
+	margin-bottom: 10rpx;
+}
+.toptip {
+	margin-top: 20rpx;
+	font-weight: bold;
+}
+.confirmBtnWrap {
+	margin-top: 30rpx;
+	align-items: center;
+}
+.confirmBtn {
+	font-size: 28rpx;
+	color: #FFFFFF;
+	background-color: #389588;
+	border-radius: 40rpx;
+	padding-left: 60rpx;
+	padding-right: 60rpx;
+	padding-top: 16rpx;
+	padding-bottom: 16rpx;
+}
+.confirmTxt {
+	font-size: 32rpx;
+	color: #545F71;
+	font-weight: bold;
+	text-align: center;
+}
+.confirmBtns {
+	flex-direction: row;
+	justify-content: center;
+	margin-top: 40rpx;
+}
+.cbtn {
+	width: 240rpx;
+	height: 80rpx;
+	border-radius: 40rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 20rpx;
+	margin-right: 20rpx;
+	background-color: #F3F3F3;
+}
+.redbtn {
+	background-color: #F65252;
+}
+.cbtnTxt {
+	font-size: 28rpx;
+	color: #545F71;
+}
+/* 模式选择弹框 */
+.modeList {
+	margin-top: 10rpx;
+}
+.modeItem {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	height: 100rpx;
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	border-bottom-color: #EEF1F4;
+}
+.modeItemTxt {
+	font-size: 30rpx;
+	color: #545F71;
+}
+.checkboxCustom {
+	width: 36rpx;
+	height: 36rpx;
+	border-radius: 36rpx;
+	border-width: 2px;
+	border-style: solid;
+	border-color: #389588;
+	justify-content: center;
+	align-items: center;
+}
+.checkboxCircle {
+	width: 20rpx;
+	height: 20rpx;
+	border-radius: 20rpx;
+	background-color: #389588;
+}
+/* 音乐弹窗 */
+.audiolists {
+	margin-top: 10rpx;
+}
+.playname {
+	font-size: 28rpx;
+	color: #9BA5B7;
+	margin-bottom: 20rpx;
+}
+.audiolist {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	height: 90rpx;
+	padding-left: 10rpx;
+	padding-right: 10rpx;
+	border-radius: 12rpx;
+}
+.curAudio {
+	background-color: #F0F8F7;
+}
+.audioTxt {
+	font-size: 30rpx;
+	color: #545F71;
+}
+.signal-container {
+	flex-direction: row;
+	align-items: flex-end;
+}
+.signal-bar {
+	width: 6rpx;
+	margin-left: 4rpx;
+	border-radius: 4rpx;
+	background-color: #389588;
+}
+.bar1 {
+	height: 16rpx;
+}
+.bar2 {
+	height: 24rpx;
+}
+.bar3 {
+	height: 32rpx;
+}
+/* 时间选择器 */
+.aijiuTime {
+	font-size: 32rpx;
+	color: #545F71;
+	font-weight: bold;
+	text-align: center;
+	margin-bottom: 30rpx;
+}
+.timePickerRow {
+	align-items: center;
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+}
+.timePickerTxt {
+	font-size: 32rpx;
+	color: #545F71;
+}
+.timeBtns {
+	flex-direction: row;
+	justify-content: center;
+	margin-top: 30rpx;
+}
+.tbtn {
+	width: 240rpx;
+	height: 80rpx;
+	border-radius: 40rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 20rpx;
+	margin-right: 20rpx;
+	background-color: #F3F3F3;
+}
+.confirmBtn2 {
+	background-color: #389588;
+}
+.tbtnTxt {
+	font-size: 28rpx;
+	color: #545F71;
+}
+.confirmBtn2Txt {
+	color: #FFFFFF;
+}
+/* 重连遮罩 */
+.maskbg {
+	position: fixed;
+	left: 0;
+	right: 0;
+	top: 0;
+	bottom: 0;
+	background-color: rgba(0,0,0,0.6);
+	justify-content: center;
+	align-items: center;
+}
+.maskbox {
+	background-color: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 40rpx;
+}
+.maskTxt {
+	font-size: 28rpx;
+	color: #545F71;
+}
+</style>

+ 50 - 0
code/ajyApp/pages/device/deviceInfo/mixins/acupoint-mixin.js

@@ -0,0 +1,50 @@
+/**
+ * 穴位编辑与自定义模式 mixin
+ * 负责:穴位图交互、时间选择、方案管理
+ */
+export default {
+	data() {
+		return {
+			picMode: 1, // 1设备 2穴位
+			acupointIndex: -1,
+			acupointList: [],
+			curCase: [],
+			curCaseIndex: 0,
+			isShowTimeDrawer: false,
+			timeRange: [[0, 1, 2, 3], [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]],
+			selectedHour: 0,
+			selectedMinute: 0
+		}
+	},
+	methods: {
+		picModeChange(value) {
+			this.picMode = value
+		},
+
+		showTimeDraw(inx) {
+			this.acupointIndex = inx
+			this.isShowTimeDrawer = true
+		},
+
+		bindTimeChange(e) {
+			const val = e.detail.value
+			this.selectedHour = this.timeRange[0][val[0]]
+			this.selectedMinute = this.timeRange[1][val[1]]
+		},
+
+		cancelAijiuTime(val) {
+			this.isShowTimeDrawer = false
+			if (val == 1 && this.acupointIndex >= 0) {
+				const sumMinutes = this.selectedHour * 60 + this.selectedMinute
+				const point = this.acupointList[this.acupointIndex]
+				this.curCase = [{
+					id: point.id,
+					time: sumMinutes,
+					_x: point._x || 0,
+					_y: point._y || 0
+				}]
+				this.curCaseIndex = this.acupointIndex
+			}
+		}
+	}
+}

+ 84 - 0
code/ajyApp/pages/device/deviceInfo/mixins/audio-mixin.js

@@ -0,0 +1,84 @@
+/**
+ * 音频播放 mixin
+ * 负责:白噪音初始化、播放/暂停、切换音源
+ */
+let innerAudioContext = null
+
+export default {
+	data() {
+		return {
+			audiolist: [
+				{ name: '河流', posterimg: '/static/device/heliu.png', url: '/static/audio/audio1.mp3' },
+				{ name: '下雨', posterimg: '/static/device/heliu.png', url: '/static/audio/audio2.mp3' }
+			],
+			curAudioInx: 0,
+			totalTime: 0,
+			currentTime: 0,
+			ispause: true,
+			isShowDrawer3: false
+		}
+	},
+	methods: {
+		initAudio() {
+			innerAudioContext = uni.createInnerAudioContext()
+			innerAudioContext.src = '/static/audio/audio1.mp3'
+			innerAudioContext.loop = true
+			innerAudioContext.onPlay(() => {
+				this.totalTime = innerAudioContext.duration
+			})
+			innerAudioContext.onCanplay(() => {
+				this.totalTime = innerAudioContext.duration
+			})
+			innerAudioContext.onError((res) => {
+				console.log('播放错误', res.errMsg)
+			})
+			innerAudioContext.onTimeUpdate(() => {
+				this.currentTime = innerAudioContext.currentTime
+			})
+		},
+
+		formatTime(totalSeconds) {
+			totalSeconds = Math.floor(totalSeconds)
+			const minutes = Math.floor(totalSeconds / 60)
+			const seconds = totalSeconds % 60
+			return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0')
+		},
+
+		changeAudio(url, inx) {
+			this.curAudioInx = inx
+			if (innerAudioContext) {
+				innerAudioContext.stop()
+				setTimeout(() => {
+					innerAudioContext.src = url
+					this.ispause = true
+					this.playAudio()
+				}, 500)
+			}
+		},
+
+		playAudio() {
+			if (!innerAudioContext) return
+			if (this.ispause) {
+				innerAudioContext.play()
+			} else {
+				innerAudioContext.pause()
+			}
+			this.ispause = !this.ispause
+		},
+
+		/** 供 ble-mixin 模式切换时调用 */
+		_stopAudioOnModeChange() {
+			if (innerAudioContext) {
+				innerAudioContext.stop()
+			}
+		},
+
+		destroyAudio() {
+			if (innerAudioContext) {
+				innerAudioContext.stop()
+				innerAudioContext.destroy()
+				innerAudioContext = null
+			}
+		}
+	}
+}

+ 282 - 0
code/ajyApp/pages/device/deviceInfo/mixins/ble-mixin.js

@@ -0,0 +1,282 @@
+/**
+ * BLE 蓝牙连接与设备控制 mixin
+ * 负责:权限检查、连接/断连/重连、指令收发、设备状态管理
+ */
+import bleManager, { BLE_STATE, MODE, MOXI_STATE, POWER, MUTE, TEMPERATURE, CHAIR_ANGLE } from '@/utils/ble'
+import { ensureBlePrerequisite } from '@/utils/ble/permission.js'
+
+export default {
+	data() {
+		return {
+			linked: false,
+			deviceStatus: 0, // 0停止 1预热 2点火 3艾灸 4灭火 5暂停
+			hotPercentage: '0%',
+			ispreHot: false,
+			reconnectDrawer: false,
+			reconnectCount: 0,
+			excepDrawer: false,
+			exceTxt: 0,
+			isShowConfirm: false,
+			_unbinders: []
+		}
+	},
+	methods: {
+		// ========== BLE 初始化 ==========
+		async initBle() {
+			// 1. Android权限检查(iOS由系统自动弹窗,无需手动处理)
+			try {
+				await ensureBlePrerequisite()
+			} catch (e) {
+				if (e.message === 'BLE_PERMISSION_DENIED') {
+					this.$refs.ayToast.error('请授予蓝牙权限后重试')
+					return
+				}
+				if (e.message === 'BLE_LOCATION_OFF') {
+					this.$refs.ayToast.error('请开启位置服务后重试')
+					return
+				}
+			}
+
+			// 2. 重置BLE适配器,清除search页面可能残留的扫描状态
+			try { await bleManager.destroy() } catch (_) {}
+
+			// 3. 重新初始化适配器
+			await bleManager.init()
+
+			// 4. 监听BLE事件(必须在destroy之后注册,因为destroy会清除所有监听器)
+			this._unbinders = [
+				bleManager.on('state', (s) => {
+					this.linked = (s === BLE_STATE.READY_COMM)
+					if (s === BLE_STATE.DISCONNECTED) {
+						this.linked = false
+						this._handleBleDisconnect()
+					}
+				}),
+				bleManager.on('disconnected', (info) => {
+					this.linked = false
+					if (!info.manual) {
+						this._handleBleDisconnect()
+					}
+				}),
+				bleManager.on('reconnected', () => {
+					this.reconnectDrawer = false
+					this.reconnectCount = 0
+					this.linked = true
+					this.$refs.ayToast.success('重连成功')
+				}),
+				bleManager.on('report:GROUP_1', (data) => {
+					this._handleGroup1(data)
+				}),
+				bleManager.on('report:GROUP_2', (data) => {
+					this._handleGroup2(data)
+				})
+			]
+
+			// 5. 扫描并连接设备(与ble-demo一致的可靠方式:先扫描发现设备,再连接)
+			try {
+				const scanOpt = { timeout: 8000 }
+				if (this.deviceName) {
+					scanOpt.deviceName = this.deviceName
+				}
+				await bleManager.scanAndConnect(scanOpt)
+				this.linked = true
+				this._sendCurrentState()
+			} catch (e) {
+				console.error('BLE连接失败', e)
+				this.$refs.ayToast.error('连接失败: ' + (e.message || e.code || ''))
+			}
+		},
+
+		_cleanListeners() {
+			if (this._unbinders && this._unbinders.length) {
+				this._unbinders.forEach(fn => fn && fn())
+				this._unbinders = []
+			}
+		},
+
+		_handleBleDisconnect() {
+			this.reconnectCount++
+			if (this.reconnectCount >= 4) {
+				this.reconnectDrawer = false
+				this.$refs.ayToast.error('连接失败,请返回重试')
+				setTimeout(() => {
+					uni.navigateBack()
+				}, 1500)
+			} else {
+				this.reconnectDrawer = true
+			}
+		},
+
+		// ========== 设备上报数据解析 ==========
+		_handleGroup1(data) {
+			// 参数组1: 设备状态
+			switch (data.runtimeState) {
+				case 0x00:
+					this.deviceStatus = 0
+					break
+				case 0x01:
+				case 0x02:
+					this.deviceStatus = 1
+					break
+				case 0x03:
+					this.deviceStatus = 3
+					break
+				case 0x04:
+					this.deviceStatus = 4
+					break
+				case 0x05:
+					this.deviceStatus = 5
+					break
+			}
+			// 模式
+			switch (data.mode) {
+				case MODE.LEISURE:
+					this.modeType = 0; break
+				case MODE.PROFESSIONAL:
+					this.modeType = 1; break
+				case MODE.PERSONAL:
+					this.modeType = 2; break
+				case MODE.EXPERT:
+					this.modeType = 3; break
+			}
+			// 剩余时间
+			const m = data.remainMinute || 0
+			const s = data.remainSecond || 0
+			const h = Math.floor(m / 60)
+			const m1 = m % 60
+			this.subTime = `${h.toString().padStart(2,'0')}:${m1.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`
+
+			// 异常检测
+			if (data.foreignDetect === 0x02) {
+				this.excepDrawer = true
+				this.exceTxt = 1
+			}
+			// 耗材状态
+			this.otherSetting.aijiuNum = String(data.consumable || 0)
+			this.otherSetting.lvxinNum = String(data.filterPercent || 0)
+			this.otherSetting.huishouNum = String(data.recycleBinPct || 0)
+		},
+
+		_handleGroup2(data) {
+			// 参数组2: 预热/点火进度
+			if (data.preheatPercent < 100) {
+				this.ispreHot = false
+				this.hotPercentage = data.preheatPercent + '%'
+			} else {
+				this.ispreHot = true
+				this.hotPercentage = data.ignitePercent + '%'
+			}
+		},
+
+		// ========== 发送指令 ==========
+		_sendCurrentState() {
+			const modeMap = [MODE.LEISURE, MODE.PROFESSIONAL, MODE.PERSONAL, MODE.EXPERT]
+			bleManager.sendBasic({
+				power: POWER.ON,
+				moxiState: MOXI_STATE.DONE,
+				preheatState: MOXI_STATE.DONE,
+				mute: MUTE.OFF,
+				mode: modeMap[this.modeType] || MODE.LEISURE,
+				subMode: 0x01,
+				temperature: TEMPERATURE.MID,
+				angle: CHAIR_ANGLE.OFF,
+				duration: 0
+			}).catch(e => console.error('sendBasic fail', e))
+		},
+
+		// ========== 开始艾灸 ==========
+		startDeviceEvt() {
+			this.deviceStatus = 1
+			this.ispreHot = false
+			let totalDuration = 0
+			this.curCase.forEach(item => {
+				totalDuration += item.time
+			})
+			// 发送开始指令
+			const modeMap = [MODE.LEISURE, MODE.PROFESSIONAL, MODE.PERSONAL, MODE.EXPERT]
+			bleManager.sendBasic({
+				power: POWER.ON,
+				moxiState: MOXI_STATE.START,
+				preheatState: MOXI_STATE.START,
+				mute: MUTE.OFF,
+				mode: modeMap[this.modeType] || MODE.LEISURE,
+				subMode: 0x01,
+				temperature: TEMPERATURE.MID,
+				angle: CHAIR_ANGLE.OFF,
+				duration: totalDuration || 30
+			}).catch(e => console.error('startDevice fail', e))
+
+			// 发送穴位坐标
+			if (this.curCase.length > 0) {
+				const points = this.curCase.map(item => ({
+					point: item.id,
+					x: item._x,
+					y: item._y
+				}))
+				bleManager.sendAcupoints(points).catch(e => console.error('sendAcupoints fail', e))
+			}
+		},
+
+		// ========== 暂停/继续 ==========
+		stopAijiu() {
+			if (this.deviceStatus == 5) {
+				// 继续
+				this.deviceStatus = 3
+				bleManager.sendBasic({ moxiState: MOXI_STATE.START }).catch(() => {})
+			} else if (this.deviceStatus == 3) {
+				// 暂停
+				this.deviceStatus = 5
+				bleManager.sendBasic({ moxiState: MOXI_STATE.PAUSE }).catch(() => {})
+			}
+		},
+
+		// ========== 停止 ==========
+		stopPreHot() {
+			this.isShowConfirm = true
+		},
+		confirmStop() {
+			this.deviceStatus = 0
+			this.isShowConfirm = false
+			bleManager.sendBasic({ moxiState: MOXI_STATE.DONE }).catch(() => {})
+		},
+
+		// ========== 模式切换 ==========
+		changeMode(num) {
+			uni.showModal({
+				title: '提示',
+				content: '切换模式会停止当前的艾灸,是否继续',
+				cancelText: '取消',
+				confirmText: '继续',
+				success: (res) => {
+					if (res.confirm) {
+						this.modeType = num
+						this.deviceStatus = 0
+						this.isShowDrawer2 = false
+						if (this._stopAudioOnModeChange) {
+							this._stopAudioOnModeChange()
+						}
+						// 发送模式切换指令
+						const modeMap = [MODE.LEISURE, MODE.PROFESSIONAL, MODE.PERSONAL, MODE.EXPERT]
+						bleManager.sendBasic({
+							power: POWER.ON,
+							moxiState: MOXI_STATE.DONE,
+							preheatState: MOXI_STATE.DONE,
+							mute: MUTE.OFF,
+							mode: modeMap[num] || MODE.LEISURE,
+							subMode: 0x01,
+							temperature: TEMPERATURE.MID,
+							angle: CHAIR_ANGLE.OFF,
+							duration: 0
+						}).catch(e => console.error('changeMode fail', e))
+					}
+				}
+			})
+		},
+
+		// ========== 断开BLE ==========
+		disconnectBle() {
+			this._cleanListeners()
+			bleManager.disconnect().catch(() => {})
+		}
+	}
+}

+ 12 - 10
code/ajyApp/pages/device/list/list.nvue

@@ -67,12 +67,14 @@ export default {
 			}
 		},
 		goAddDevice() {
-			// TODO: 跳转添加设备页面
-			this.$toastRef.text('添加设备')
+			uni.navigateTo({
+				url: '/pages/device/search/search'
+			})
 		},
 		goDeviceInfo(item) {
-			// TODO: 跳转设备详情
-			this.$toastRef.text('设备: ' + item.name)
+			uni.navigateTo({
+				url: '/pages/device/deviceInfo/deviceInfo?name=' + encodeURIComponent(item.name || '') + '&deviceId=' + encodeURIComponent(item.deviceId || '') + '&rssi=' + encodeURIComponent(item.RSSI || '')
+			})
 		}
 	}
 }
@@ -88,7 +90,7 @@ export default {
 	left: 0;
 	top: 0;
 	width: 750rpx;
-	height: 1400rpx;
+	height: 2000px;
 }
 .status-bar {
 	background-color: rgba(0,0,0,0);
@@ -110,15 +112,15 @@ export default {
 	color: #545F71;
 }
 .head-add {
-	width: 36rpx;
-	height: 36rpx;
+	width: 20px;
+	height: 20px;
 	border-width: 2px;
 	border-style: solid;
 	border-color: #545F71;
-	border-radius: 36rpx;
+	border-radius: 20px;
 	text-align: center;
-	line-height: 32rpx;
-	font-size: 26rpx;
+	line-height: 16px;
+	font-size: 20px;
 	font-weight: bold;
 	color: #545F71;
 }

+ 112 - 0
code/ajyApp/pages/device/moxibustion/moxibustion.nvue

@@ -0,0 +1,112 @@
+<template>
+	<view class="container">
+		<view class="list">
+			<view class="item" v-if="num2 != '0'">
+				<image class="img" src="/static/device/haocai1.png" mode="aspectFill"></image>
+				<view class="info">
+					<text class="txt1">尘盒滤芯</text>
+					<text class="txt2">尘盒收集率:{{num2}}%</text>
+					<view class="linebg">
+						<view class="bar" :style="{width: num2+'%'}"></view>
+					</view>
+				</view>
+			</view>
+			<view class="item" v-if="num1 != '0'">
+				<image class="img" src="/static/device/haocai2.png" mode="aspectFill"></image>
+				<view class="info">
+					<text class="txt1">艾灸柱</text>
+					<text class="txt2">剩余量:{{num1}}个</text>
+					<view class="linebg">
+						<view class="bar" :style="{width: persent+'rpx'}"></view>
+					</view>
+				</view>
+			</view>
+			<view class="item" v-if="num3 != '0'">
+				<image class="img" src="/static/device/haocai1.png" mode="aspectFill"></image>
+				<view class="info">
+					<text class="txt1">回收仓</text>
+					<text class="txt2">占用率:{{num3}}%</text>
+					<view class="linebg">
+						<view class="bar" :style="{width: num3+'%'}"></view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			num1: '0',
+			num2: '0',
+			num3: '0',
+			persent: 0
+		}
+	},
+	onLoad(options) {
+		this.num1 = options.p1 || '0'
+		this.num2 = options.p2 || '0'
+		this.num3 = options.p3 || '0'
+		let num = parseInt(options.p1 || '0')
+		this.persent = (400 * num / 12)
+	},
+	methods: {
+	}
+}
+</script>
+
+<style>
+.container {
+	flex: 1;
+	background-color: #ffffff;
+}
+.list {
+	padding-top: 30rpx;
+	align-items: center;
+}
+.item {
+	flex-direction: row;
+	width: 676rpx;
+	height: 204rpx;
+	background-color: #F3F3F3;
+	border-radius: 20rpx;
+	align-items: center;
+	margin-bottom: 16rpx;
+}
+.img {
+	width: 140rpx;
+	height: 140rpx;
+	margin-left: 28rpx;
+	margin-right: 48rpx;
+	border-radius: 20rpx;
+}
+.info {
+	flex: 1;
+}
+.txt1 {
+	font-size: 32rpx;
+	color: #545F71;
+	line-height: 44rpx;
+	margin-top: 8rpx;
+	margin-bottom: 12rpx;
+}
+.txt2 {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	line-height: 44rpx;
+	margin-bottom: 12rpx;
+}
+.linebg {
+	width: 392rpx;
+	height: 12rpx;
+	background-color: #B6DAD5;
+	border-radius: 20rpx;
+}
+.bar {
+	height: 12rpx;
+	background-color: #389588;
+	border-radius: 20rpx;
+}
+</style>

+ 89 - 0
code/ajyApp/pages/device/mycases/mycases.nvue

@@ -0,0 +1,89 @@
+<template>
+	<view class="container">
+		<scroll-view class="list" scroll-y="true">
+			<view class="caseItem" v-for="(item, index) in list" :key="index">
+				<text class="caseName">{{item.name}}</text>
+				<text class="useBtn" @click="useCase(item.id)">使用</text>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+import request from '@/utils/http/request.js'
+
+export default {
+	data() {
+		return {
+			list: []
+		}
+	},
+	onLoad() {
+		this.getList()
+	},
+	methods: {
+		async getList() {
+			try {
+				const res = await request({
+					url: '/userCare/careList',
+					method: 'POST',
+					data: { page: 0, size: 100 }
+				})
+				if (res && res.data && res.data.records) {
+					this.list = res.data.records.map(item => ({
+						id: item.id,
+						name: item.title || item.name || ''
+					}))
+				}
+			} catch (e) {
+				console.error('获取方案列表失败', e)
+			}
+		},
+		useCase(id) {
+			const deviceId = uni.getStorageSync('deviceId')
+			if (!deviceId) {
+				uni.navigateTo({
+					url: `/pages/device/search/search?case=${id}`
+				})
+			} else {
+				uni.navigateTo({
+					url: `/pages/device/deviceInfo/deviceInfo?case=${id}&deviceId=${deviceId}`
+				})
+			}
+		}
+	}
+}
+</script>
+
+<style>
+.container {
+	flex: 1;
+	background-color: #ffffff;
+}
+.list {
+	flex: 1;
+	padding-left: 46rpx;
+	padding-right: 46rpx;
+	padding-top: 30rpx;
+}
+.caseItem {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	width: 658rpx;
+	height: 118rpx;
+	background-color: #F3F3F3;
+	border-radius: 20rpx;
+	padding-left: 28rpx;
+	padding-right: 28rpx;
+	margin-bottom: 24rpx;
+}
+.caseName {
+	font-size: 32rpx;
+	color: #545F71;
+}
+.useBtn {
+	font-size: 26rpx;
+	color: #389588;
+}
+</style>

+ 334 - 0
code/ajyApp/pages/device/search/search.nvue

@@ -0,0 +1,334 @@
+<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">
+			<view class="head-back" @click="goBack">
+				<image class="back-icon" src="/static/public/back-arrow.png" mode="aspectFit"></image>
+			</view>
+			<text class="head-title">添加设备</text>
+			<view class="head-placeholder"></view>
+		</view>
+
+		<!-- 扫描状态提示 -->
+		<view class="search-tip">
+			<text class="search-tip-text">{{searching ? '正在搜索附近设备...' : '搜索完成'}}</text>
+			<text class="search-btn" @click="onRefresh">{{searching ? '停止' : '重新搜索'}}</text>
+		</view>
+
+		<!-- 设备列表 -->
+		<scroll-view class="device-scroll" scroll-y="true">
+			<view class="device-item" v-for="(item, index) in deviceList" :key="index" @click="onSelectDevice(item)">
+				<view class="device-info">
+					<text class="device-name">{{item.name || '未知设备'}}</text>
+					<text class="device-id">{{item.deviceId}}</text>
+				</view>
+				<view class="device-right">
+					<!-- 信号强度图标 -->
+					<view class="signal-wrap">
+						<view class="signal-bar signal-bar1" :class="{'signal-active': getSignalLevel(item.RSSI) >= 1}"></view>
+						<view class="signal-bar signal-bar2" :class="{'signal-active': getSignalLevel(item.RSSI) >= 2}"></view>
+						<view class="signal-bar signal-bar3" :class="{'signal-active': getSignalLevel(item.RSSI) >= 3}"></view>
+						<view class="signal-bar signal-bar4" :class="{'signal-active': getSignalLevel(item.RSSI) >= 4}"></view>
+					</view>
+					<text class="connect-btn">添加</text>
+				</view>
+			</view>
+
+			<view class="empty-tip" v-if="!searching && deviceList.length === 0">
+				<text class="empty-text">未发现设备,请确认设备已开启</text>
+			</view>
+		</scroll-view>
+
+		<ay-toast ref="ayToast" />
+	</view>
+</template>
+
+<script>
+import bleManager, {
+	BLE_STATE, ensureBlePrerequisite, openLocationSettings
+} from '@/utils/ble'
+import ayToast from '@/components/ay-toast/ay-toast.nvue'
+
+export default {
+	components: {
+		ayToast
+	},
+	data() {
+		return {
+			statusBarHeight: 44,
+			searching: false,
+			deviceList: [],
+			_unbinders: []
+		}
+	},
+	onLoad() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+
+		// 配置并监听事件
+		bleManager.configure({ debug: true })
+
+		this._unbinders = [
+			bleManager.on('state', s => {
+				this.searching = (s === BLE_STATE.SCANNING)
+			}),
+			bleManager.on('deviceFound', d => {
+				// 避免重复
+				const exists = this.deviceList.find(item => item.deviceId === d.deviceId)
+				if (!exists) {
+					this.deviceList.push({
+						deviceId: d.deviceId,
+						name: d.name || d.localName || '',
+						RSSI: d.RSSI || ''
+					})
+				}
+			})
+		]
+
+		this.startScan()
+	},
+	onUnload() {
+		// 离开页面停止扫描
+		this.stopScan()
+		if (this._unbinders && this._unbinders.length) {
+			this._unbinders.forEach(fn => fn && fn())
+		}
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		async startScan() {
+			this.deviceList = []
+			try {
+				await ensureBlePrerequisite()
+				await bleManager.init()
+				// 使用returnAll模式,超时后resolve所有设备
+				bleManager.scan({ timeout: 15000, returnAll: true }).then(devices => {
+					// 扫描结束
+					this.searching = false
+				}).catch(e => {
+					this.searching = false
+					console.error('扫描异常', e)
+				})
+			} catch (e) {
+				this.searching = false
+				if (e.message === 'BLE_LOCATION_OFF') {
+					uni.showModal({
+						title: '提示',
+						content: '请先开启位置服务以搜索蓝牙设备',
+						success: r => {
+							if (r.confirm) openLocationSettings()
+						}
+					})
+				} else if (e.message === 'BLE_ADAPTER_OFF') {
+					this.$refs.ayToast.error('请先开启蓝牙')
+				} else if (e.message === 'BLE_PERMISSION_DENIED') {
+					this.$refs.ayToast.error('请授权蓝牙权限')
+				} else {
+					this.$refs.ayToast.error('扫描失败: ' + (e.message || ''))
+				}
+			}
+		},
+		stopScan() {
+			try {
+				uni.stopBluetoothDevicesDiscovery({
+					success() {},
+					fail() {}
+				})
+			} catch (e) {}
+		},
+		onRefresh() {
+			if (this.searching) {
+				this.stopScan()
+				this.searching = false
+			} else {
+				this.startScan()
+			}
+		},
+		getSignalLevel(rssi) {
+			// RSSI转信号格数: 4格(强) / 3格 / 2格 / 1格(弱)
+			if (!rssi) return 0
+			const val = Number(rssi)
+			if (val >= -50) return 4   // 非常强
+			if (val >= -65) return 3   // 强
+			if (val >= -80) return 2   // 中
+			if (val >= -95) return 1   // 弱
+			return 1
+		},
+		onSelectDevice(device) {
+			// 将设备添加到本地存储的设备列表
+			let deviceList = []
+			try {
+				const data = uni.getStorageSync('deviceList')
+				if (data && data.length > 0) {
+					deviceList = data
+				}
+			} catch (e) {}
+
+			// 检查是否已添加
+			const exists = deviceList.find(item => item.deviceId === device.deviceId)
+			if (exists) {
+				this.$refs.ayToast.show('该设备已添加')
+				return
+			}
+
+			// 添加设备
+			deviceList.push({
+				deviceId: device.deviceId,
+				name: device.name || '艾灸椅',
+				RSSI: device.RSSI
+			})
+			uni.setStorageSync('deviceList', deviceList)
+			this.$refs.ayToast.success('设备添加成功')
+
+			// 延迟返回
+			setTimeout(() => {
+				uni.navigateBack()
+			}, 1000)
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	position: relative;
+}
+.content-bg {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 750rpx;
+	height: 2000px;
+}
+.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: 30rpx;
+	padding-right: 54rpx;
+}
+.head-back {
+	width: 60rpx;
+	height: 60rpx;
+	justify-content: center;
+	align-items: center;
+}
+.back-icon {
+	width: 60rpx;
+	height: 60rpx;
+}
+.head-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.head-placeholder {
+	width: 60rpx;
+	height: 60rpx;
+}
+.search-tip {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	padding-left: 38rpx;
+	padding-right: 38rpx;
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+}
+.search-tip-text {
+	font-size: 26rpx;
+	color: #999;
+}
+.search-btn {
+	font-size: 26rpx;
+	color: #389588;
+	padding: 10rpx 24rpx;
+	border-width: 1px;
+	border-style: solid;
+	border-color: #389588;
+	border-radius: 24rpx;
+}
+.device-scroll {
+	flex: 1;
+	padding-left: 38rpx;
+	padding-right: 38rpx;
+}
+.device-item {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	background-color: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 30rpx;
+	margin-bottom: 20rpx;
+}
+.device-info {
+	flex: 1;
+}
+.device-name {
+	font-size: 30rpx;
+	color: #333;
+	font-weight: bold;
+}
+.device-id {
+	font-size: 22rpx;
+	color: #999;
+	margin-top: 8rpx;
+}
+.device-right {
+	align-items: center;
+}
+.signal-wrap {
+	flex-direction: row;
+	align-items: flex-end;
+	margin-bottom: 12rpx;
+}
+.signal-bar {
+	width: 8rpx;
+	margin-left: 4rpx;
+	border-radius: 4rpx;
+	background-color: #ddd;
+}
+.signal-bar1 {
+	height: 14rpx;
+}
+.signal-bar2 {
+	height: 22rpx;
+}
+.signal-bar3 {
+	height: 30rpx;
+}
+.signal-bar4 {
+	height: 38rpx;
+}
+.signal-active {
+	background-color: #389588;
+}
+.connect-btn {
+	font-size: 26rpx;
+	color: #FFFFFF;
+	background-color: #389588;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	padding-top: 12rpx;
+	padding-bottom: 12rpx;
+	border-radius: 20rpx;
+}
+.empty-tip {
+	align-items: center;
+	padding-top: 100rpx;
+}
+.empty-text {
+	font-size: 28rpx;
+	color: #999;
+}
+</style>

+ 373 - 0
code/ajyApp/pages/device/userManage/userManage.nvue

@@ -0,0 +1,373 @@
+<template>
+	<view class="page">
+		<image class="content-bg" src="/static/device/devicebg.png" mode="scaleToFill"></image>
+		<view class="pageContent">
+			<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+			<view class="customHead">
+				<view class="back-wrap" @click="goBack">
+					<image src="/static/public/back.png" mode="aspectFill" class="backimg"></image>
+				</view>
+				<text class="customTitle">用户管理</text>
+				<view class="plus" @click="addUser">
+					<text class="plusTxt">+ 新增</text>
+				</view>
+			</view>
+			<text class="tipTxt">最多可添加10个用户,点击卡片切换用户</text>
+			<scroll-view class="userList" scroll-y="true">
+				<view class="userCard" :class="{'curUserCard': item.id === currentUserId}" 
+					v-for="(item, index) in userList" :key="item.id" @click="switchUser(item)">
+					<view class="userInfo">
+						<view class="avatarWrap">
+							<text class="avatarTxt">{{item.name ? item.name.substring(0,1) : '用'}}</text>
+						</view>
+						<view class="infoDetail">
+							<view class="nameRow">
+								<text class="userName">{{item.name}}</text>
+								<text class="userGender">{{item.gender === 1 ? '男' : '女'}}</text>
+								<text class="userAge" v-if="item.age">{{item.age}}岁</text>
+							</view>
+							<view class="bodyRow">
+								<text class="bodyTxt">肩高:{{item.shoulderHeight || '--'}}cm</text>
+								<text class="bodyTxt">肩宽:{{item.shoulderWidth || '--'}}cm</text>
+								<text class="bodyTxt">体重:{{item.weight || '--'}}kg</text>
+							</view>
+						</view>
+					</view>
+					<view class="cardBtns">
+						<text class="editBtn" @click.stop="editUser(item)">编辑</text>
+						<text class="deleteBtn" @click.stop="confirmDelete(item)">删除</text>
+					</view>
+					<view class="curTag" v-if="item.id === currentUserId">
+						<text class="curTagTxt">当前</text>
+					</view>
+				</view>
+			</scroll-view>
+		</view>
+
+		<!-- 删除确认弹框 -->
+		<customPopup :isShow="showDeletePopup" @closePop="showDeletePopup=false">
+			<view class="deletePopContent">
+				<text class="deleteTip">确定删除用户"{{deleteTarget ? deleteTarget.name : ''}}"?</text>
+				<text class="deleteSub">删除后将无法恢复</text>
+				<view class="deleteBtns">
+					<view class="dBtn cancelBtn" @click="showDeletePopup=false">
+						<text class="dBtnTxt">取消</text>
+					</view>
+					<view class="dBtn redBtn" @click="doDelete">
+						<text class="dBtnTxt redTxt">确定删除</text>
+					</view>
+				</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 {
+			statusBarHeight: 44,
+			userList: [],
+			currentUserId: '',
+			showDeletePopup: false,
+			deleteTarget: null
+		}
+	},
+	onLoad() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+	},
+	onShow() {
+		this.loadUsers()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		/** 从本地存储加载用户列表 */
+		loadUsers() {
+			const list = uni.getStorageSync('user_manage_list') || []
+			this.userList = list
+			this.currentUserId = uni.getStorageSync('current_manage_user_id') || ''
+			// 如果没有当前用户且列表不为空,默认选中第一个
+			if (!this.currentUserId && list.length > 0) {
+				this.currentUserId = list[0].id
+				uni.setStorageSync('current_manage_user_id', this.currentUserId)
+			}
+		},
+		/** 切换用户 */
+		switchUser(item) {
+			this.currentUserId = item.id
+			uni.setStorageSync('current_manage_user_id', item.id)
+			uni.setStorageSync('current_manage_user', item)
+			this.$refs.ayToast.success('已切换至用户:' + item.name)
+			setTimeout(() => {
+				uni.navigateBack()
+			}, 800)
+		},
+		/** 新增用户 */
+		addUser() {
+			if (this.userList.length >= 10) {
+				this.$refs.ayToast.error('最多可添加10个用户,请先删除其他用户')
+				return
+			}
+			uni.navigateTo({
+				url: '/pages/device/bodyParams/bodyParams?mode=add'
+			})
+		},
+		/** 编辑用户 */
+		editUser(item) {
+			uni.navigateTo({
+				url: '/pages/device/bodyParams/bodyParams?mode=edit&userId=' + item.id
+			})
+		},
+		/** 确认删除 */
+		confirmDelete(item) {
+			if (this.userList.length <= 1) {
+				this.$refs.ayToast.error('至少保留一个用户')
+				return
+			}
+			this.deleteTarget = item
+			this.showDeletePopup = true
+		},
+		/** 执行删除 */
+		doDelete() {
+			if (!this.deleteTarget) return
+			const id = this.deleteTarget.id
+			this.userList = this.userList.filter(u => u.id !== id)
+			uni.setStorageSync('user_manage_list', this.userList)
+			// 如果删除的是当前用户,切换到第一个
+			if (this.currentUserId === id && this.userList.length > 0) {
+				this.currentUserId = this.userList[0].id
+				uni.setStorageSync('current_manage_user_id', this.currentUserId)
+				uni.setStorageSync('current_manage_user', this.userList[0])
+			}
+			this.showDeletePopup = false
+			this.deleteTarget = null
+			this.$refs.ayToast.success('删除成功')
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	position: relative;
+}
+.content-bg {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 750rpx;
+	height: 2000px;
+}
+.pageContent {
+	flex: 1;
+	background-image: linear-gradient(to bottom, rgba(56,149,136,1), rgba(56,149,136,0.5));
+}
+.status-bar {
+	background-color: rgba(0,0,0,0);
+}
+.customHead {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	height: 88rpx;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+}
+.back-wrap {
+	width: 50rpx;
+	height: 50rpx;
+	justify-content: center;
+	align-items: center;
+}
+.backimg {
+	width: 50rpx;
+	height: 50rpx;
+}
+.customTitle {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #FFFFFF;
+}
+.plus {
+	padding-left: 16rpx;
+	padding-right: 16rpx;
+	padding-top: 8rpx;
+	padding-bottom: 8rpx;
+	background-color: rgba(255,255,255,0.2);
+	border-radius: 24rpx;
+}
+.plusTxt {
+	font-size: 26rpx;
+	color: #FFFFFF;
+}
+.tipTxt {
+	font-size: 24rpx;
+	color: rgba(255,255,255,0.7);
+	margin-left: 38rpx;
+	margin-top: 10rpx;
+	margin-bottom: 20rpx;
+}
+.userList {
+	flex: 1;
+	padding-left: 30rpx;
+	padding-right: 30rpx;
+}
+.userCard {
+	background-color: #FFFFFF;
+	border-radius: 20rpx;
+	padding: 30rpx;
+	margin-bottom: 24rpx;
+	position: relative;
+	overflow: hidden;
+}
+.curUserCard {
+	border-width: 2px;
+	border-style: solid;
+	border-color: #389588;
+}
+.userInfo {
+	flex-direction: row;
+	align-items: center;
+}
+.avatarWrap {
+	width: 80rpx;
+	height: 80rpx;
+	border-radius: 80rpx;
+	background-color: #389588;
+	justify-content: center;
+	align-items: center;
+	margin-right: 20rpx;
+}
+.avatarTxt {
+	font-size: 32rpx;
+	color: #FFFFFF;
+	font-weight: bold;
+}
+.infoDetail {
+	flex: 1;
+}
+.nameRow {
+	flex-direction: row;
+	align-items: center;
+	margin-bottom: 8rpx;
+}
+.userName {
+	font-size: 30rpx;
+	color: #333333;
+	font-weight: bold;
+	margin-right: 16rpx;
+}
+.userGender {
+	font-size: 24rpx;
+	color: #9BA5B7;
+	margin-right: 12rpx;
+}
+.userAge {
+	font-size: 24rpx;
+	color: #9BA5B7;
+}
+.bodyRow {
+	flex-direction: row;
+}
+.bodyTxt {
+	font-size: 24rpx;
+	color: #9BA5B7;
+	margin-right: 20rpx;
+}
+.cardBtns {
+	flex-direction: row;
+	justify-content: flex-end;
+	margin-top: 20rpx;
+}
+.editBtn {
+	font-size: 26rpx;
+	color: #389588;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	padding-top: 10rpx;
+	padding-bottom: 10rpx;
+	border-radius: 24rpx;
+	background-color: #F0F8F7;
+	margin-right: 20rpx;
+}
+.deleteBtn {
+	font-size: 26rpx;
+	color: #F65252;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	padding-top: 10rpx;
+	padding-bottom: 10rpx;
+	border-radius: 24rpx;
+	background-color: #FFF2F2;
+}
+.curTag {
+	position: absolute;
+	right: 0;
+	top: 0;
+	background-color: #389588;
+	padding-left: 16rpx;
+	padding-right: 16rpx;
+	padding-top: 4rpx;
+	padding-bottom: 4rpx;
+	border-bottom-left-radius: 12rpx;
+}
+.curTagTxt {
+	font-size: 20rpx;
+	color: #FFFFFF;
+}
+/* 删除弹框 */
+.deletePopContent {
+	align-items: center;
+	padding-top: 20rpx;
+}
+.deleteTip {
+	font-size: 32rpx;
+	color: #333333;
+	font-weight: bold;
+	margin-bottom: 16rpx;
+}
+.deleteSub {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	margin-bottom: 40rpx;
+}
+.deleteBtns {
+	flex-direction: row;
+	justify-content: center;
+}
+.dBtn {
+	width: 260rpx;
+	height: 80rpx;
+	border-radius: 40rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 20rpx;
+	margin-right: 20rpx;
+}
+.cancelBtn {
+	background-color: #F3F3F3;
+}
+.redBtn {
+	background-color: #F65252;
+}
+.dBtnTxt {
+	font-size: 28rpx;
+	color: #545F71;
+}
+.redTxt {
+	color: #FFFFFF;
+}
+</style>

+ 288 - 0
code/ajyApp/pages/find/detail/detail.nvue

@@ -0,0 +1,288 @@
+<template>
+	<view class="page">
+		<scroll-view class="scroll-content" scroll-y="true">
+			<image class="detail-img" src="/static/home/detail.png" mode="aspectFill"></image>
+			<view class="back-btn" @click="goBack">
+				<image class="back-img" src="/static/public/back.png" mode="aspectFill"></image>
+			</view>
+			<view class="detail-box">
+				<text class="detail-title">{{form.title}}</text>
+				<text class="detail-author">{{form.createUserName || '无名氏'}}</text>
+				<view class="detail-btn" @click="isShowDrawer = true">
+					<text class="detail-btn-txt">立即体验</text>
+				</view>
+				<view class="line"></view>
+				<!-- 简介 -->
+				<text class="section-title">简介</text>
+				<view class="detail-info" :style="{height: isExpanded ? '84rpx' : 'auto'}">
+					<text class="detail-info-text">{{form.detail || '暂无'}}</text>
+					<view class="more-btn-box" @click="toggle">
+						<text class="more-btn">{{isExpanded ? '展开' : '收缩'}}</text>
+						<text class="more-arrow" :class="[isExpanded ? '' : 'collapse']">▼</text>
+					</view>
+				</view>
+				<!-- 艾灸穴位 -->
+				<text class="section-title">艾灸穴位</text>
+				<view class="acupoint-box">
+					<view v-for="(itm, inx) in points" :key="inx">
+						<text class="line-info">{{inx + 1}}.{{itm.name}}</text>
+						<text class="line-info emsp">定位:{{itm.position}}</text>
+						<text class="line-info emsp">作用:{{itm.effect}}</text>
+					</view>
+				</view>
+				<!-- 适用人群 -->
+				<text class="section-title">适用人群</text>
+				<view class="people-box">
+					<text class="line-info" v-for="(itm, inx) in peoples" :key="inx">{{inx + 1}}.{{itm}}</text>
+				</view>
+				<!-- 注意事项 -->
+				<text class="section-title">注意事项</text>
+				<view class="tips-box">
+					<text class="line-info" v-for="(itm, inx) in atts" :key="inx">{{inx + 1}}.{{itm}}</text>
+				</view>
+			</view>
+		</scroll-view>
+
+		<customPopup :isShow="isShowDrawer" @closePop="isShowDrawer = false">
+			<text class="popup-tips">确认添加到我的方案?</text>
+			<view class="popup-btns">
+				<view class="popup-btn" @click="isShowDrawer = false">
+					<text class="popup-btn-text">取消</text>
+				</view>
+				<view class="popup-btn popup-btn-confirm" @click="confirmAdd">
+					<text class="popup-btn-text popup-btn-text-confirm">确认</text>
+				</view>
+			</view>
+		</customPopup>
+	</view>
+</template>
+
+<script>
+import customPopup from '@/components/customPopup/customPopup.nvue'
+
+export default {
+	components: {
+		customPopup
+	},
+	data() {
+		return {
+			id: '',
+			isExpanded: true,
+			isShowDrawer: false,
+			form: {
+				title: '',
+				createUserName: '',
+				detail: ''
+			},
+			peoples: [],
+			atts: [],
+			points: []
+		}
+	},
+	onLoad(options) {
+		this.id = options.id || ''
+		// 使用传入的标题
+		if (options.title) {
+			this.form.title = decodeURIComponent(options.title)
+		}
+		this.init()
+	},
+	methods: {
+		init() {
+			// TODO: 等后端接口出来后替换为真实请求
+			// 暂时使用模拟数据展示
+			this.form = {
+				title: this.form.title || '春季艾灸养生方案',
+				createUserName: '研年健康',
+				detail: '本方案针对春季阳气初升、湿寒未退的时节特点,通过温补督脉、健脾祛湿等艾灸手法,帮助调理身体,增强免疫力。适合久坐办公、体寒怕冷的人群。'
+			}
+			this.points = [
+				{ name: '大椎穴', position: '第七颈椎棘突下凹陷中', effect: '通阳解表,缓解颈肩疼痛' },
+				{ name: '肩井穴', position: '大椎与肩峰端连线的中点', effect: '疏通经络,缓解肩颈僵硬' },
+				{ name: '足三里', position: '外膝眼下3寸,胫骨前嵴外1横指', effect: '健脾和胃,扶正培元' }
+			]
+			this.peoples = [
+				'久坐办公室',
+				'低头族',
+				'体寒阳虚体质者',
+				'慢性颈肩疼痛患者'
+			]
+			this.atts = [
+				'艾灸前30分钟避免剧烈运动',
+				'每个穴位建议艾灸10-20分钟',
+				'灸后30分钟内避免洗澡',
+				'皮肤发红或起泡应立即停止艾灸'
+			]
+		},
+		toggle() {
+			this.isExpanded = !this.isExpanded
+		},
+		goBack() {
+			uni.navigateBack()
+		},
+		confirmAdd() {
+			// TODO: 等后端接口出来后替换为真实请求
+			uni.showToast({
+				icon: 'none',
+				title: '已加入我的方案'
+			})
+			this.isShowDrawer = false
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+.scroll-content {
+	flex: 1;
+}
+.detail-img {
+	width: 750rpx;
+	height: 560rpx;
+}
+.back-btn {
+	position: absolute;
+	left: 30rpx;
+	top: 142rpx;
+}
+.back-img {
+	width: 50rpx;
+	height: 50rpx;
+}
+.detail-box {
+	width: 750rpx;
+	margin-top: -88rpx;
+	border-top-left-radius: 30rpx;
+	border-top-right-radius: 30rpx;
+	background-color: #FFFFFF;
+	padding-top: 40rpx;
+	padding-bottom: 40rpx;
+	padding-left: 60rpx;
+	padding-right: 60rpx;
+}
+.detail-title {
+	font-weight: bold;
+	font-size: 40rpx;
+	color: #545F71;
+	lines: 2;
+	text-overflow: ellipsis;
+	margin-bottom: 4rpx;
+}
+.detail-author {
+	font-size: 32rpx;
+	color: #545F71;
+	line-height: 44rpx;
+}
+.detail-btn {
+	background-color: #389588;
+	height: 66rpx;
+	border-radius: 20rpx;
+	margin-top: 26rpx;
+	margin-bottom: 26rpx;
+	justify-content: center;
+	align-items: center;
+}
+.detail-btn-txt {
+	color: #FFFFFF;
+	font-size: 28rpx;
+}
+.line {
+	height: 1px;
+	background-color: #ECECEC;
+	margin-bottom: 32rpx;
+}
+.section-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+	line-height: 44rpx;
+	margin-bottom: 16rpx;
+}
+.detail-info {
+	position: relative;
+	margin-bottom: 24rpx;
+	padding-bottom: 32rpx;
+}
+.detail-info-text {
+	font-size: 28rpx;
+	color: #545F71;
+	line-height: 44rpx;
+}
+.more-btn-box {
+	position: absolute;
+	right: 0;
+	bottom: 0;
+	background-color: #FFFFFF;
+	flex-direction: row;
+	align-items: center;
+	padding-left: 6rpx;
+	padding-right: 6rpx;
+}
+.more-btn {
+	font-size: 28rpx;
+	color: #389588;
+	line-height: 40rpx;
+	margin-right: 4rpx;
+}
+.more-arrow {
+	color: #389588;
+	font-size: 20rpx;
+}
+.collapse {
+	transform: rotateZ(180deg);
+}
+.acupoint-box {
+	margin-bottom: 24rpx;
+}
+.people-box {
+	margin-bottom: 24rpx;
+}
+.tips-box {
+	margin-bottom: 24rpx;
+}
+.line-info {
+	line-height: 44rpx;
+	color: #545F71;
+	font-size: 28rpx;
+	margin-left: 20rpx;
+}
+.emsp {
+	padding-left: 40rpx;
+}
+.popup-tips {
+	text-align: center;
+	margin-top: 48rpx;
+	margin-bottom: 48rpx;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.popup-btns {
+	flex-direction: row;
+	justify-content: space-between;
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+}
+.popup-btn {
+	width: 296rpx;
+	height: 88rpx;
+	background-color: #F3F3F3;
+	border-radius: 20rpx;
+	justify-content: center;
+	align-items: center;
+}
+.popup-btn-confirm {
+	background-color: #389588;
+	margin-left: 20rpx;
+}
+.popup-btn-text {
+	font-size: 30rpx;
+	color: #545F71;
+}
+.popup-btn-text-confirm {
+	color: #FFFFFF;
+}
+</style>

+ 59 - 21
code/ajyApp/pages/find/index/index.nvue

@@ -4,21 +4,23 @@
 		<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>
+				<view class="recommend-card" v-for="(item, index) in recommends" :key="item.id || index" @click="onItemClick(item)">
+					<image class="card-img" :src="item.coverImage" mode="aspectFill" v-if="item.coverImage"></image>
+					<image class="card-img" src="/static/home/banner.png" mode="aspectFill" v-else></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>
+						<text class="footer-name">{{item.author || '研年健康贴士'}}</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>
+			<text class="load-tip" v-if="loadMore">加载中...</text>
+			<text class="load-tip" v-else-if="noMore && recommends.length > 0">已经到底了</text>
 		</scroll-view>
 		<ay-toast ref="ayToast" />
 	</view>
@@ -26,6 +28,11 @@
 
 <script>
 import ayToast from '@/components/ay-toast/ay-toast.nvue'
+import { getPublishedArticles } from '@/utils/api/article.js'
+import config from '@/utils/http/config.js'
+
+// 文章阅读页基础地址(去掉 /api 后缀)
+const serverBase = config.baseUrl
 
 export default {
 	components: {
@@ -34,36 +41,67 @@ export default {
 	data() {
 		return {
 			statusBarHeight: 44,
-			page: 1,
+			pageNum: 1,
+			pageSize: 10,
 			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: '日常保健:提升免疫力的穴位' }
-			]
+			noMore: false,
+			recommends: []
 		}
 	},
 	onShow() {
 		const sysInfo = uni.getSystemInfoSync()
 		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		// 首次加载数据
+		if (this.recommends.length === 0) {
+			this.loadArticles()
+		}
 	},
 	onPullDownRefresh() {
-		this.page = 1
-		// TODO: 重新加载数据
-		uni.stopPullDownRefresh()
+		this.pageNum = 1
+		this.noMore = false
+		this.loadArticles(true)
 	},
 	methods: {
-		onReachBottom() {
+		/** 加载文章列表 */
+		async loadArticles(isRefresh) {
 			if (this.loadMore) return
-			this.page++
-			// TODO: 加载更多数据
+			this.loadMore = true
+			try {
+				const res = await getPublishedArticles({
+					pageNum: this.pageNum,
+					pageSize: this.pageSize
+				})
+				const records = res.records || []
+				if (isRefresh) {
+					this.recommends = records
+				} else {
+					this.recommends = this.recommends.concat(records)
+				}
+				// 判断是否还有更多数据
+				if (records.length < this.pageSize) {
+					this.noMore = true
+				}
+			} catch (e) {
+				console.error('加载文章列表失败', e)
+			} finally {
+				this.loadMore = false
+				if (isRefresh) {
+					uni.stopPullDownRefresh()
+				}
+			}
+		},
+		/** 触底加载更多 */
+		onReachBottom() {
+			if (this.loadMore || this.noMore) return
+			this.pageNum++
+			this.loadArticles(false)
 		},
+		/** 点击文章,跳转webview阅读页 */
 		onItemClick(item) {
-			// TODO: 跳转详情
-			this.$toastRef.text(item.title)
+			const articleUrl = serverBase + '/article/view/' + item.id
+			uni.navigateTo({
+				url: '/pages/webview/webview?url=' + encodeURIComponent(articleUrl) + '&title=' + encodeURIComponent(item.title || '文章详情')
+			})
 		}
 	}
 }

+ 8 - 4
code/ajyApp/pages/home/home.nvue

@@ -34,7 +34,7 @@
 			</view>
 
 			<!-- 灸研室 -->
-			<view class="news-section" v-if="news.length > 0">
+<!-- 			<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>
@@ -46,7 +46,7 @@
 						<text class="news-author">{{item.author}}</text>
 					</view>
 				</view>
-			</view>
+			</view> -->
 		</scroll-view>
 	</view>
 </template>
@@ -86,10 +86,14 @@ export default {
 			// TODO: 跳转banner详情
 		},
 		onRecommendClick(item) {
-			// TODO: 跳转推荐详情
+			uni.navigateTo({
+				url: '/pages/find/detail/detail?id=' + item.id + '&title=' + encodeURIComponent(item.title)
+			})
 		},
 		onNewsClick(item) {
-			// TODO: 跳转灸研室详情
+			uni.navigateTo({
+				url: '/pages/find/detail/detail?id=' + item.id + '&title=' + encodeURIComponent(item.title)
+			})
 		},
 		onMoreNews() {
 			// TODO: 跳转更多

+ 82 - 39
code/ajyApp/pages/login/login.nvue

@@ -17,8 +17,7 @@
 					maxlength="11"
 					placeholder="请输入手机号"
 					placeholder-class="placeholder-text"
-					:value="phone"
-					@input="onPhoneInput"
+					v-model="phone"
 				/>
 			</view>
 
@@ -31,8 +30,7 @@
 					maxlength="6"
 					placeholder="请输入验证码"
 					placeholder-class="placeholder-text"
-					:value="code"
-					@input="onCodeInput"
+					v-model="code"
 				/>
 				<text
 					class="code-btn"
@@ -47,7 +45,7 @@
 				:class="[!canLogin ? 'login-btn-disabled' : '']"
 				@click="onLogin"
 			>
-				<text class="login-btn-text">登录</text>
+				<text class="login-btn-text" @click="onLogin">登录</text>
 			</view>
 		</view>
 
@@ -122,6 +120,9 @@
 <script>
 import customPopup from '@/components/customPopup/customPopup.nvue'
 import ayToast from '@/components/ay-toast/ay-toast.nvue'
+import toast from '@/utils/toast.js'
+import http from '@/utils/http'
+import { sendCode, login } from '@/utils/api/login.js'
 
 export default {
 	components: {
@@ -150,10 +151,22 @@ export default {
 			return this.isPhoneValid && this.code.length >= 4 && this.agreed
 		}
 	},
+	watch: {
+		isShowDrawer(val) {
+			if (val) {
+				uni.hideKeyboard()
+			}
+		}
+	},
 	onLoad() {
 		// 判断平台
 		const sysInfo = uni.getSystemInfoSync()
 		this.isIOS = sysInfo.platform === 'ios'
+		// 读取持久化的隐私政策同意状态
+		const agreedFlag = uni.getStorageSync('privacy_agreed')
+		if (agreedFlag) {
+			this.agreed = true
+		}
 	},
 	onUnload() {
 		if (this.countdownTimer) {
@@ -162,70 +175,96 @@ export default {
 		}
 	},
 	methods: {
-		onPhoneInput(e) {
-			this.phone = e.detail.value
-		},
-		onCodeInput(e) {
-			this.code = e.detail.value
-		},
 		// 获取验证码
-		onGetCode() {
+		async onGetCode() {
 			if (this.codeBtnDisabled) return
 			if (!this.isPhoneValid) {
-				this.$toastRef.error('请输入正确的手机号')
+				toast.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: 调用发送验证码接口
+			try {
+				await sendCode(this.phone)
+				// 发送成功后开始倒计时
+				this.countdown = 60
+				this.countdownTimer = setInterval(() => {
+					this.countdown--
+					if (this.countdown <= 0) {
+						clearInterval(this.countdownTimer)
+						this.countdownTimer = null
+					}
+				}, 1000)
+				toast.success('验证码已发送')
+			} catch (e) {
+				// 错误已由 http 层自动 toast 展示
+			}
 		},
 		// 登录
-		onLogin() {
+		async onLogin() {
 			if (!this.agreed) {
-				this.$toastRef.error('请先同意隐私政策和用户协议')
+				this.isShowDrawer = true
 				return
 			}
 			if (!this.isPhoneValid) {
-				this.$toastRef.error('请输入正确的手机号')
+				toast.error('请输入正确的手机号')
 				return
 			}
 			if (this.code.length < 4) {
-				this.$toastRef.error('请输入验证码')
+				toast.error('请输入验证码')
 				return
 			}
-			this.$toastRef.loading('登录中...')
-			// TODO: 调用登录接口
-			// http.post('/auth/login', { phone: this.phone, code: this.code }, { withToken: false })
+			toast.loading('登录中...')
+			try {
+				const data = await login(this.phone, this.code)
+				// 存储 Token
+				http.setToken(data.token)
+				// 存储用户基本信息
+				uni.setStorageSync('user_info', {
+					userId: data.userId,
+					phone: data.phone,
+					nickname: data.nickname,
+					avatar: data.avatar,
+					appId: data.appId
+				})
+				// 单独存储各字段(便于页面直接读取)
+				uni.setStorageSync('phone', data.phone)
+				uni.setStorageSync('nickname', data.nickname || '')
+				uni.setStorageSync('avatar', data.avatar || '')
+				uni.setStorageSync('appId', data.appId || '')
+				toast.hide()
+				console.log("登录成功")
+				// 跳转首页
+				uni.reLaunch({ url:"/pages/device/list/list" })
+			} catch (e) {
+				toast.hide()
+				toast.error(e.message || '登录失败,请稍后重试')
+			}
 		},
-		// 微信登录
+		// 微信登录(暂未实现)
 		onWechatLogin() {
 			if (!this.agreed) {
-				this.$toastRef.error('请先同意隐私政策和用户协议')
+				this.isShowDrawer = true
 				return
 			}
-			this.$toastRef.text('微信登录')
-			// TODO: 调用微信登录
+			toast.text('微信登录暂未开放')
+			// TODO: 微信登录实现(需绑定手机号)
 		},
-		// Apple登录
+		// Apple登录(暂未实现)
 		onAppleLogin() {
 			if (!this.agreed) {
-				this.$toastRef.error('请先同意隐私政策和用户协议')
+				this.isShowDrawer = true
 				return
 			}
-			this.$toastRef.text('Apple登录')
-			// TODO: 调用 Apple 登录
+			toast.text('Apple登录暂未开放')
+			// TODO: Apple 登录实现(需绑定手机号)
 		},
 		// 切换协议勾选
 		onToggleAgreement() {
 			this.agreed = !this.agreed
+			if (this.agreed) {
+				uni.setStorageSync('privacy_agreed', true)
+			} else {
+				uni.removeStorageSync('privacy_agreed')
+			}
 		},
 		// 打开隐私政策/用户协议弹窗
 		onOpenPrivacy() {
@@ -239,6 +278,7 @@ export default {
 			this.isShowDrawer = false
 			if (type === 1) {
 				this.agreed = true
+				uni.setStorageSync('privacy_agreed', true)
 			}
 		},
 		// 跳转webview查看完整协议
@@ -330,9 +370,12 @@ export default {
 	background-color: #c8e6dc;
 }
 .login-btn-text {
+	flex: 1;
 	font-size: 32rpx;
 	color: #ffffff;
 	font-weight: bold;
+	text-align: center;
+	line-height: 96rpx;
 }
 
 /* 其他登录方式 */

+ 140 - 0
code/ajyApp/pages/my/history/history.nvue

@@ -0,0 +1,140 @@
+<template>
+	<view class="page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<!-- 自定义导航栏 -->
+		<view class="nav-bar">
+			<image src="/static/public/backBlack.png" mode="aspectFill" class="back-img" @click="goBack"></image>
+			<text class="nav-title">使用记录</text>
+			<view class="nav-right"></view>
+		</view>
+
+		<!-- 使用记录列表 -->
+		<list class="history-list" v-if="list.length > 0">
+			<cell v-for="(itm, index) in list" :key="index">
+				<view class="history-item">
+					<view class="history-top-line">
+						<text class="history-name">{{itm.name || '未知设备'}}</text>
+						<text class="history-type">{{itm.type || ''}}</text>
+					</view>
+					<text class="history-time">{{itm.createTime || ''}}</text>
+				</view>
+			</cell>
+		</list>
+
+		<!-- 空状态 -->
+		<view class="empty-wrap" v-else>
+			<text class="empty-text">暂无使用记录</text>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			statusBarHeight: 44,
+			list: [],
+			page: 1
+		}
+	},
+	onLoad() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.init()
+	},
+	onPullDownRefresh() {
+		this.page = 1
+		this.list = []
+		this.init()
+	},
+	onReachBottom() {
+		this.page++
+		this.init()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		init() {
+			// TODO: 后续对接后台接口获取使用记录
+			uni.stopPullDownRefresh()
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+.status-bar {
+	background-color: #FFFFFF;
+}
+.nav-bar {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+	background-color: #FFFFFF;
+}
+.back-img {
+	width: 50rpx;
+	height: 50rpx;
+}
+.nav-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.nav-right {
+	width: 50rpx;
+	height: 50rpx;
+}
+/* 使用记录列表 */
+.history-list {
+	flex: 1;
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+}
+.history-item {
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+	border-bottom-width: 1px;
+	border-bottom-color: #EEF1F4;
+	border-bottom-style: solid;
+}
+.history-top-line {
+	flex-direction: row;
+	justify-content: space-between;
+	margin-bottom: 6rpx;
+}
+.history-name {
+	font-size: 32rpx;
+	color: #545F71;
+	line-height: 44rpx;
+}
+.history-type {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	line-height: 44rpx;
+}
+.history-time {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	line-height: 44rpx;
+}
+/* 空状态 */
+.empty-wrap {
+	flex: 1;
+	align-items: center;
+	padding-top: 200rpx;
+}
+.empty-text {
+	font-size: 28rpx;
+	color: #9BA5B7;
+}
+</style>

+ 34 - 17
code/ajyApp/pages/my/info/info.nvue

@@ -60,6 +60,7 @@
 
 <script>
 import ayToast from '@/components/ay-toast/ay-toast.nvue'
+import { uploadAvatar as uploadAvatarApi, updateProfile } from '@/utils/api/user.js'
 
 export default {
 	components: {
@@ -67,7 +68,7 @@ export default {
 	},
 	data() {
 		return {
-			avatar: '/static/my/avator.png',
+			avatar: '/static/144x144.png',
 			nickname: '用户',
 			phone: '',
 			appid: '',
@@ -83,8 +84,8 @@ export default {
 		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'
+			const appid = uni.getStorageSync('appId') || ''
+			const avatar = uni.getStorageSync('avatar') || '/static/144x144.png'
 			this.phone = phone
 			this.nickname = nickname
 			this.appid = appid
@@ -97,12 +98,25 @@ export default {
 				sourceType: ['album', 'camera'],
 				success: (res) => {
 					const tempPath = res.tempFilePaths[0]
-					uni.navigateTo({
-						url: '/pages/my/crop/crop?src=' + encodeURIComponent(tempPath)
-					})
+					this.uploadAvatar(tempPath)
 				}
 			})
 		},
+		async uploadAvatar(filePath) {
+			try {
+				// 上传图片到 OSS
+				const res = await uploadAvatarApi(filePath)
+				const avatarUrl = res.url
+				// 更新后端用户资料
+				await updateProfile({ avatar: avatarUrl })
+				// 更新本地存储和页面显示
+				this.avatar = avatarUrl
+				uni.setStorageSync('avatar', avatarUrl)
+				this.$refs.ayToast.success('头像修改成功')
+			} catch (e) {
+				// 错误已由 http 层统一提示
+			}
+		},
 		editNickname() {
 			this.nicknameInput = this.nickname
 			this.showNicknamePopup = true
@@ -114,25 +128,28 @@ export default {
 		onKeyboardHeight(e) {
 			this.keyboardHeight = e.detail.height || 0
 		},
-		saveNickname() {
+		async 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('修改成功')
+			try {
+				// 更新后端用户资料
+				await updateProfile({ nickname: val })
+				this.nickname = val
+				uni.setStorageSync('nickname', val)
+				this.showNicknamePopup = false
+				this.keyboardHeight = 0
+				this.$refs.ayToast.success('修改成功')
+			} catch (e) {
+				// 错误已由 http 层统一提示
+			}
 		}
 	},
 	onShow() {
-		// 从裁剪页返回后刷新头像
-		const avatar = uni.getStorageSync('avatar')
-		if (avatar) {
-			this.avatar = avatar
-		}
+		// 返回时刷新用户信息
+		this.loadUserInfo()
 	}
 }
 </script>

+ 167 - 0
code/ajyApp/pages/my/msg/msg.nvue

@@ -0,0 +1,167 @@
+<template>
+	<view class="page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<!-- 自定义导航栏 -->
+		<view class="nav-bar">
+			<image src="/static/public/backBlack.png" mode="aspectFill" class="back-img" @click="goBack"></image>
+			<text class="nav-title">消息中心</text>
+			<view class="nav-right"></view>
+		</view>
+
+		<!-- 消息列表 -->
+		<list class="msg-list" v-if="list.length > 0">
+			<cell v-for="(itm, index) in list" :key="index">
+				<view class="msg-item" @click="toDetail(itm)">
+					<image class="msg-avatar" :src="itm.avatar || '/static/144x144.png'" mode="aspectFill"></image>
+					<view class="msg-info">
+						<view class="msg-top-line">
+							<view class="msg-name-wrap">
+								<text class="msg-name">{{itm.name || itm.phone || '系统'}}</text>
+								<view class="msg-unread" v-if="itm.readFlag == '0'"></view>
+							</view>
+							<text class="msg-time">{{itm.createTime || ''}}</text>
+						</view>
+						<text class="msg-content">{{itm.content || '向您推荐了方案'}}</text>
+					</view>
+				</view>
+			</cell>
+		</list>
+
+		<!-- 空状态 -->
+		<view class="empty-wrap" v-else>
+			<text class="empty-text">暂无消息</text>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			statusBarHeight: 44,
+			list: []
+		}
+	},
+	onShow() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.init()
+	},
+	onPullDownRefresh() {
+		this.list = []
+		this.init()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		toDetail(row) {
+			// TODO: 后续对接后台接口
+			// uni.navigateTo({ url: '/pages/detail/detail?id=' + row.dataId })
+		},
+		init() {
+			// TODO: 后续对接后台接口获取消息列表
+			uni.stopPullDownRefresh()
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+.status-bar {
+	background-color: #FFFFFF;
+}
+.nav-bar {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+	background-color: #FFFFFF;
+}
+.back-img {
+	width: 50rpx;
+	height: 50rpx;
+}
+.nav-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.nav-right {
+	width: 50rpx;
+	height: 50rpx;
+}
+/* 消息列表 */
+.msg-list {
+	flex: 1;
+}
+.msg-item {
+	flex-direction: row;
+	align-items: center;
+	margin-left: 38rpx;
+	margin-right: 38rpx;
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+	border-bottom-width: 1px;
+	border-bottom-color: #EEF1F4;
+	border-bottom-style: solid;
+}
+.msg-avatar {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 100rpx;
+	margin-right: 16rpx;
+}
+.msg-info {
+	flex: 1;
+}
+.msg-top-line {
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 6rpx;
+}
+.msg-name-wrap {
+	flex-direction: row;
+	align-items: center;
+}
+.msg-name {
+	font-size: 32rpx;
+	color: #545F71;
+	line-height: 44rpx;
+	margin-right: 10rpx;
+}
+.msg-unread {
+	width: 10rpx;
+	height: 10rpx;
+	background-color: #F65252;
+	border-radius: 10rpx;
+}
+.msg-time {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	line-height: 44rpx;
+}
+.msg-content {
+	font-size: 26rpx;
+	color: #9BA5B7;
+	line-height: 44rpx;
+}
+/* 空状态 */
+.empty-wrap {
+	flex: 1;
+	align-items: center;
+	padding-top: 200rpx;
+}
+.empty-text {
+	font-size: 28rpx;
+	color: #9BA5B7;
+}
+</style>

+ 18 - 12
code/ajyApp/pages/my/my.nvue

@@ -4,11 +4,11 @@
 		<!-- 用户信息区 -->
 		<view class="user-header">
 			<view class="user-info">
-				<text class="user-name">{{name || '未登录'}}</text>
-				<text class="user-detail">AppId:{{appid}}</text>
+				<text class="user-name">{{nickname || '未登录'}}</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>
+			<image class="avatar" :src="avatar" mode="aspectFill" @click="toInfo"></image>
 		</view>
 
 		<!-- 菜单列表 -->
@@ -72,9 +72,10 @@ export default {
 		return {
 			statusBarHeight: 44,
 			isShowDrawer: false,
-			name: '用户',
+			nickname: '用户',
 			phone: '138****8888',
-			appid: '',
+			appId: '',
+			avatar: '/static/144x144.png',
 			msgNum: 0
 		}
 	},
@@ -85,12 +86,15 @@ export default {
 	},
 	methods: {
 		init() {
-			// TODO: 获取用户信息接口
-			// 暂用本地存储数据
+			// 从本地存储读取用户信息(登录时已存入)
 			const phone = uni.getStorageSync('phone') || ''
-			if (phone) {
-				this.phone = phone
-			}
+			const nickname = uni.getStorageSync('nickname') || '用户'
+			const appId = uni.getStorageSync('appId') || ''
+			const avatar = uni.getStorageSync('avatar') || '/static/144x144.png'
+			if (phone) this.phone = phone
+			this.nickname = nickname
+			this.appId = appId
+			this.avatar = avatar
 		},
 		toInfo() {
 			uni.navigateTo({ url: '/pages/my/info/info' })
@@ -102,8 +106,9 @@ export default {
 			this.isShowDrawer = true
 		},
 		logoutConfirm() {
-			uni.setStorageSync('token', '')
-			uni.setStorageSync('phone', '')
+			// 清除用户会话数据,保留隐私政策同意状态
+			uni.removeStorageSync('user_token')
+			uni.removeStorageSync('user_info')
 			this.isShowDrawer = false
 			uni.reLaunch({ url: '/pages/login/login' })
 		}
@@ -154,6 +159,7 @@ export default {
 	border-top-right-radius: 30rpx;
 	padding-top: 40rpx;
 	flex: 1;
+	border-bottom: 1rpx solid #EEF1F4;
 }
 .menu-item {
 	flex-direction: row;

+ 98 - 0
code/ajyApp/pages/my/questions/quesinfo.nvue

@@ -0,0 +1,98 @@
+<template>
+	<view class="page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<!-- 自定义导航栏 -->
+		<view class="nav-bar">
+			<image src="/static/public/backBlack.png" mode="aspectFill" class="back-img" @click="goBack"></image>
+			<text class="nav-title">问题解答</text>
+			<view class="nav-right"></view>
+		</view>
+
+		<!-- 内容区域 -->
+		<scroll-view class="content-scroll" scroll-y="true">
+			<text class="ques-title">{{title}}</text>
+			<rich-text class="rich-content" :nodes="detail"></rich-text>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			statusBarHeight: 44,
+			id: '',
+			title: '',
+			detail: ''
+		}
+	},
+	onLoad(options) {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		if (options && options.id) {
+			this.id = options.id
+		}
+		if (options && options.title) {
+			this.title = decodeURIComponent(options.title)
+		}
+		this.init()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		init() {
+			// TODO: 后续对接后台接口获取问题详情
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+.status-bar {
+	background-color: #FFFFFF;
+}
+.nav-bar {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+	background-color: #FFFFFF;
+}
+.back-img {
+	width: 50rpx;
+	height: 50rpx;
+}
+.nav-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.nav-right {
+	width: 50rpx;
+	height: 50rpx;
+}
+/* 内容区域 */
+.content-scroll {
+	flex: 1;
+	padding-left: 38rpx;
+	padding-right: 38rpx;
+	padding-top: 20rpx;
+}
+.ques-title {
+	font-weight: bold;
+	font-size: 36rpx;
+	color: #545F71;
+	margin-bottom: 36rpx;
+}
+.rich-content {
+	width: 650rpx;
+}
+</style>

+ 130 - 0
code/ajyApp/pages/my/questions/questions.nvue

@@ -0,0 +1,130 @@
+<template>
+	<view class="page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<!-- 自定义导航栏 -->
+		<view class="nav-bar">
+			<image src="/static/public/backBlack.png" mode="aspectFill" class="back-img" @click="goBack"></image>
+			<text class="nav-title">全部问题</text>
+			<view class="nav-right"></view>
+		</view>
+
+		<!-- 问题列表 -->
+		<list class="ques-list">
+			<cell>
+				<text class="ques-section-title">常见问题</text>
+			</cell>
+			<cell v-for="(itm, index) in list" :key="index">
+				<view class="ques-item" @click="toDetail(itm)">
+					<text class="ques-item-text">{{index + 1}}、{{itm.title}}</text>
+					<image class="ques-arrow" src="/static/my/arrowright.png" mode="aspectFill"></image>
+				</view>
+			</cell>
+		</list>
+
+		<!-- 空状态 -->
+		<view class="empty-wrap" v-if="list.length === 0">
+			<text class="empty-text">暂无问题</text>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			statusBarHeight: 44,
+			list: []
+		}
+	},
+	onLoad() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.init()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		toDetail(itm) {
+			uni.navigateTo({
+				url: '/pages/my/questions/quesinfo?id=' + itm.id + '&title=' + encodeURIComponent(itm.title || '')
+			})
+		},
+		init() {
+			// TODO: 后续对接后台接口获取问题列表
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+.status-bar {
+	background-color: #FFFFFF;
+}
+.nav-bar {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+	background-color: #FFFFFF;
+}
+.back-img {
+	width: 50rpx;
+	height: 50rpx;
+}
+.nav-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.nav-right {
+	width: 50rpx;
+	height: 50rpx;
+}
+/* 问题列表 */
+.ques-list {
+	flex: 1;
+	padding-left: 48rpx;
+	padding-right: 48rpx;
+}
+.ques-section-title {
+	font-size: 28rpx;
+	color: #9BA5B7;
+	margin-top: 48rpx;
+	margin-bottom: 10rpx;
+}
+.ques-item {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 108rpx;
+	border-bottom-width: 1px;
+	border-bottom-color: #EEF1F4;
+	border-bottom-style: solid;
+}
+.ques-item-text {
+	flex: 1;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.ques-arrow {
+	width: 40rpx;
+	height: 40rpx;
+}
+/* 空状态 */
+.empty-wrap {
+	align-items: center;
+	padding-top: 200rpx;
+}
+.empty-text {
+	font-size: 28rpx;
+	color: #9BA5B7;
+}
+</style>

+ 120 - 0
code/ajyApp/pages/my/version/version.nvue

@@ -0,0 +1,120 @@
+<template>
+	<view class="page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar" :style="{height: statusBarHeight + 'px'}"></view>
+		<!-- 自定义导航栏 -->
+		<view class="nav-bar">
+			<image src="/static/public/backBlack.png" mode="aspectFill" class="back-img" @click="goBack"></image>
+			<text class="nav-title">版本信息</text>
+			<view class="nav-right"></view>
+		</view>
+
+		<!-- 版本信息区 -->
+		<view class="version-content">
+			<image class="logo" src="/static/144x144.png" mode="aspectFill"></image>
+			<text class="app-name">研年健康平台</text>
+			<text class="version-code">V{{localVersion}}</text>
+			<text class="check-btn" @click="checkUpdate">检查更新</text>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			statusBarHeight: 44,
+			localVersion: '1.0.0',
+			platform: 'android'
+		}
+	},
+	onLoad() {
+		const sysInfo = uni.getSystemInfoSync()
+		this.statusBarHeight = sysInfo.statusBarHeight || 44
+		this.platform = sysInfo.platform || 'android'
+		// 获取本地版本号
+		const appBase = uni.getAppBaseInfo()
+		if (appBase && appBase.appVersion) {
+			this.localVersion = appBase.appVersion
+		}
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		checkUpdate() {
+			// TODO: 后续对接后台接口检查更新
+			uni.showToast({
+				title: '已是最新版本',
+				icon: 'none'
+			})
+		}
+	}
+}
+</script>
+
+<style>
+.page {
+	flex: 1;
+	background-color: #FFFFFF;
+}
+.status-bar {
+	background-color: #FFFFFF;
+}
+.nav-bar {
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	padding-left: 54rpx;
+	padding-right: 54rpx;
+	background-color: #FFFFFF;
+}
+.back-img {
+	width: 50rpx;
+	height: 50rpx;
+}
+.nav-title {
+	font-weight: bold;
+	font-size: 32rpx;
+	color: #545F71;
+}
+.nav-right {
+	width: 50rpx;
+	height: 50rpx;
+}
+/* 版本信息区 */
+.version-content {
+	align-items: center;
+	padding-top: 100rpx;
+}
+.logo {
+	width: 208rpx;
+	height: 208rpx;
+	border-radius: 120rpx;
+	margin-bottom: 16rpx;
+}
+.app-name {
+	font-size: 32rpx;
+	color: #545F71;
+	line-height: 60rpx;
+	text-align: center;
+}
+.version-code {
+	font-size: 32rpx;
+	color: #BAC0CA;
+	line-height: 60rpx;
+	text-align: center;
+}
+.check-btn {
+	margin-top: 78rpx;
+	width: 644rpx;
+	height: 88rpx;
+	background-color: #389588;
+	border-radius: 20rpx;
+	color: #FFFFFF;
+	font-size: 32rpx;
+	line-height: 88rpx;
+	text-align: center;
+}
+</style>

+ 7 - 1
code/ajyApp/pages/webview/webview.nvue

@@ -26,7 +26,13 @@ export default {
 	},
 	onLoad(options) {
 		this.id = options.id || ''
-		if (this.id === '1') {
+		// 支持直接传入 url 和 title 参数(如文章阅读页)
+		if (options.url) {
+			console.log(options.url)
+			this.url = decodeURIComponent(options.url)
+			console.log(this.url)
+			this.title = options.title ? decodeURIComponent(options.title) : '详情'
+		} else if (this.id === '1') {
 			this.title = '用户协议'
 			this.url = 'http://8.155.56.12:8081/profile/upload/userAgreement.html'
 		} else if (this.id === '2') {

TEMPAT SAMPAH
code/ajyApp/static/144x144.png


TEMPAT SAMPAH
code/ajyApp/static/public/back-arrow.png


+ 3 - 0
code/ajyApp/static/public/back-arrow.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
+  <path d="M30 36L18 24L30 12" stroke="#545F71" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 17 - 0
code/ajyApp/utils/api/article.js

@@ -0,0 +1,17 @@
+/**
+ * 文章模块 API
+ */
+import http from '@/utils/http'
+
+/**
+ * 获取已发布文章列表(分页)
+ * @param {Object} params - { pageNum, pageSize }
+ * @returns {Promise<{records, total, pages, current}>}
+ */
+export function getPublishedArticles(params = {}) {
+	return http.get('/content/articles', {
+		status: 'PUBLISHED',
+		pageNum: params.pageNum || 1,
+		pageSize: params.pageSize || 10
+	})
+}

+ 21 - 0
code/ajyApp/utils/api/login.js

@@ -0,0 +1,21 @@
+/**
+ * 登录模块 API
+ */
+import http from '@/utils/http'
+
+/**
+ * 发送短信验证码
+ * @param {string} phone - 手机号
+ */
+export function sendCode(phone) {
+	return http.post('/app/auth/send-code', { phone }, { withToken: false, showLoading: false })
+}
+
+/**
+ * 手机号验证码登录
+ * @param {string} phone - 手机号
+ * @param {string} code - 验证码
+ */
+export function login(phone, code) {
+	return http.post('/app/auth/login', { phone, code }, { withToken: false, showLoading: false, showError: false })
+}

+ 29 - 0
code/ajyApp/utils/api/user.js

@@ -0,0 +1,29 @@
+/**
+ * 用户模块 API
+ */
+import http from '@/utils/http'
+
+/**
+ * 获取当前用户个人资料
+ * @returns {Promise<{userId, nickname, avatar, phone, appId}>}
+ */
+export function getProfile() {
+	return http.get('/app/user/profile')
+}
+
+/**
+ * 修改当前用户个人资料(昵称、头像)
+ * @param {Object} data - { nickname?, avatar? }
+ */
+export function updateProfile(data) {
+	return http.put('/app/user/profile', data)
+}
+
+/**
+ * 上传头像图片到 OSS
+ * @param {string} filePath - 本地图片临时路径
+ * @returns {Promise<{url: string}>} OSS 公网访问 URL
+ */
+export function uploadAvatar(filePath) {
+	return http.upload('/file/image', { filePath })
+}

+ 17 - 1
code/ajyApp/utils/ble/BleManager.js

@@ -47,6 +47,7 @@ class BleManager extends EventEmitter {
     )
     this._writeLock = Promise.resolve() // 写入串行化
     this._bound = false                 // 是否已绑定系统监听
+    this._cancelScan = null             // 取消残留扫描的回调
   }
 
   // ============== 基础 ==============
@@ -94,6 +95,8 @@ class BleManager extends EventEmitter {
   /** 彻底释放 */
   async destroy() {
     this._clearReconnect()
+    // 取消残留的扫描
+    if (this._cancelScan) { this._cancelScan(); this._cancelScan = null }
     try { await this.disconnect() } catch (_) {}
     try { await this._invoke(uni.closeBluetoothAdapter, {}) } catch (_) {}
     this._unbindSystemListeners()
@@ -144,6 +147,9 @@ class BleManager extends EventEmitter {
    * @returns {Promise<Object|Object[]>}
    */
   async scan(opt = {}) {
+    // 取消上一次残留的扫描(如 search 页面未正确清理)
+    if (this._cancelScan) { this._cancelScan(); this._cancelScan = null }
+
     await this._ensureReady()
     const {
       namePrefix = this.config.deviceNamePrefix,
@@ -157,14 +163,18 @@ class BleManager extends EventEmitter {
     const matched = []
 
     return new Promise(async (resolve, reject) => {
+      let finished = false   // 防重入:确保 finish 只执行一次
+
       const onFound = res => {
+        if (finished) return
         for (const d of res.devices) {
           if (devices.has(d.deviceId)) continue
           devices.set(d.deviceId, d)
           const name = d.name || d.localName || ''
           const hit =
             (deviceName  && name === deviceName) ||
-            (namePrefix  && name.startsWith(namePrefix))
+            (namePrefix  && name.startsWith(namePrefix)) ||
+            (!deviceName && !namePrefix)
           if (hit) {
             matched.push(d)
             this.emit('deviceFound', d)
@@ -180,6 +190,9 @@ class BleManager extends EventEmitter {
       }, timeout)
 
       const finish = (err, data) => {
+        if (finished) return   // 防重入
+        finished = true
+        this._cancelScan = null
         clearTimeout(timer)
         uni.offBluetoothDeviceFound && uni.offBluetoothDeviceFound(onFound)
         this._invoke(uni.stopBluetoothDevicesDiscovery, {}).catch(() => {})
@@ -187,6 +200,9 @@ class BleManager extends EventEmitter {
         err ? reject(err) : resolve(data)
       }
 
+      // 暴露取消句柄,供 destroy() / 下次 scan() 调用
+      this._cancelScan = () => finish(this._err(BLE_ERROR.SCAN_FAIL, { msg: '扫描已取消' }))
+
       uni.onBluetoothDeviceFound(onFound)
       this._setState(BLE_STATE.SCANNING)
 

+ 2 - 2
code/ajyApp/utils/ble/constants.js

@@ -113,8 +113,8 @@ export const BLE_STATE = {
 //   Char F003:   WRITE          (备用写通道)
 // 如发指令后收不到回包,可按 FAQ 在 FFF1 / FFF2 / F003 间互换写/通知特征再试。
 export const DEFAULT_CONFIG = {
-  // 目标设备名称前缀(扫描过滤)
-  deviceNamePrefix: 'BT-',
+  // 目标设备名称前缀(扫描过滤,空字符串表示不过滤
+  deviceNamePrefix: '',
   // 主服务 UUID
   serviceId: '0000FFF0-0000-1000-8000-00805F9B34FB',
   // 写特征(APP -> 设备)

+ 2 - 2
code/ajyApp/utils/http/config.js

@@ -11,9 +11,9 @@ baseUrl = '/api' // H5 走代理
 // #endif
 
 // #ifdef APP-PLUS || MP-WEIXIN || MP-ALIPAY
-baseUrl = 'https://api.example.com' // TODO: 替换为实际接口域名
+baseUrl = 'http://192.168.1.4:18888/api' // TODO: 替换为实际接口域名
 // #endif
-
+// http://ajy.iwintrue.com/api
 export default {
   // 接口基础地址
   baseUrl,

+ 2 - 0
code/ajyApp/utils/http/request.js

@@ -154,6 +154,7 @@ function request(options = {}) {
 
   // 拼接完整 URL
   const base = customBaseUrl !== undefined ? customBaseUrl : config.baseUrl
+  console.log(base)
   if (url && !url.startsWith('http')) {
     url = base + url
   }
@@ -233,6 +234,7 @@ function request(options = {}) {
         }
       },
       fail: (err) => {
+		console.error(err)
         // 网络错误 / 超时
         const msg = err.errMsg || ''
         let errorText = '网络异常,请检查网络连接'

+ 128 - 0
code/ajyApp/艾炙椅主控板—通迅协议V2.csv

@@ -0,0 +1,128 @@
+艾炙椅主控板协议说明,,,,,,,,,,,,,,,,,,
+序号,指令,通迅码(16进制)说明,,,,,,,通迅码-115200 8N1,,,,,,,,,
+,,1,2,3,4,5,6,7,8,9,10,11,12,13,14,,15,16
+1,APP发送,0x24,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x05,0x23,,,,,
+,,开始符,"功能码-
+基本功能","电源开关
+0x00-关机;0x01-开机;","艾灸开始/暂停/完成;
+0x00-完成;
+0x01-开始;
+0x02-暂停;","预热开始/暂停/完成;
+0x00-完成;
+0x01-开始;
+0x02-暂停;","静音开关
+0x00-静音关;
+0x01-静音开;","模式选择
+0x01-休闲模式;
+0x02-基础模式;
+0x03-专业模式;
+0x04-个性模式;
+0x05-专家模式;","子模式选择
+0x01-子模式1;
+0x02-子模式2;
+0x03-子模式3;
+0x04-子模式4;
+0x05-子模式5;","温度设置;
+0x01-低;
+0x02-中;
+0x03-高;","座椅角度
+00-关;
+01-角度1;
+02-角度2;
+03-角度3;
+04-角度4;
+05-角度5;",艾炙时间设置(分钟),结束符,,,,,
+,,,,,,,,,,,,,,,,,,
+2,APP发送,0x24,0x02,0x04,0x01,0x01,0x01,0x02,0x05,0x01,...,0x08,0x05,0x23,,,,
+,,开始符,"功能码-
+模式参数设置1","模式选择
+0x01-休闲模式;
+0x02-基础模式;
+0x03-专业模式;
+0x04-个性模式;
+0x05-专家模式;","子模式选择
+0x01-子模式1;
+0x02-子模式2;
+0x03-子模式3;
+0x04-子模式4;
+0x05-子模式5;","温度设置;
+0x01-低;
+0x02-中;
+0x03-高;",步骤1穴位编号1,步骤1时长;2分钟,步骤2穴位编号5,步骤2时长;1分钟,...,步骤7穴位编号8,步骤7时长;5分钟,结束符,,,,
+,APP发送,0x24,0x03,0x04,0x01,0x01,0x01,0x02,0x05,0x01,...,0x08,0x05,0x23,,,,
+,,开始符,"功能码-
+模式参数设置2","模式选择
+0x01-休闲模式;
+0x02-基础模式;
+0x03-专业模式;
+0x04-个性模式;
+0x05-专家模式;","子模式选择
+0x01-子模式1;
+0x02-子模式2;
+0x03-子模式3;
+0x04-子模式4;
+0x05-子模式5;",预留,步骤8穴位编号1,步骤8时长;2分钟,步骤9穴位编号5,步骤9时长;1分钟,...,步骤14穴位编号8,步骤14时长;5分钟,结束符,,,,
+,,,,,,,,,,,,,,,,,,
+3,APP发送,0x24,0x05,0x01,0x7D,0x00,0x00,0x3C,0x02,0x13,0x88,0x00,0x64,0x23,,,,
+,,开始符,"功能码-
+模式参数设置5",穴位编号1,X座标高8位,X座标低8位,Y座标高8位,Y座标低8位,穴位编号2,X座标高8位,X座标低8位,Y座标高8位,Y座标低8位,结束符,,,,
+,,,,,,,,,,,,,,,,,,
+3,主控板上报,24,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1E,0x05,0x08,0x02,0x05,0x20,0x01,23
+,,开始符,"参数组1
+",预留,"电源开关
+0x00-关机;0x01-开机;","
+0x00-完成;
+0x01-预热;
+0x02-点火;
+0x03-艾灸;
+0x04-灭火;
+0x05-暂停;","静音开关
+0x00-静音关;
+0x01-静音开;","模式选择
+0x01-休闲模式;
+0x02-基础模式;
+0x03-专业模式;
+0x04-个性模式;
+0x05-专家模式;","子模式选择
+0x01-子模式1;
+0x02-子模式2;
+0x03-子模式3;
+0x04-子模式4;
+0x05-子模式5;","温度设置;
+0x01-低;
+0x02-中;
+0x03-高;",艾炙设置时间(30分钟),艾炙剩余时间(5分钟),艾炙剩余时间(8秒),"耗材状态
+02-剩余2个;","滤芯状态
+已使用占比:5%",回收仓占用32%,"异物检测
+01-正常;
+02-异常;",结束符
+4,主控板上报,24,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x05,0x01,0x68,0x0a,23,,,
+,,开始符,"参数组2
+","预热状态
+0x00-完成;
+0x01-预热中;
+0x02-暂停;
+","预热进度百分比:
+0x01-1%","点火进度百分比:
+0x01-1%","体状检测
+01-正常;
+02-异常;","机器前后异物检测
+01-正常;
+02-异常","座椅角度
+00-关;
+01-角度1;
+02-角度2;
+03-角度3;
+04-角度4;
+05-角度5;",当前步骤编号,当前穴位编号,当前艾炙头温度高8位(36.0摄氏度),当前艾炙头温度低8位,固件版本V1.0,结束符,,,
+,,,,,,"自动上艾和点火状态
+0x01-上艾完成;
+0x02-点火完成;
+0x10-艾条识别错误;
+0x11-上艾失败;
+0x12-点火失败;",,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,,
+,,,,,,,,,,,,,,,,,,

+ 158 - 0
code/app用户管理.md

@@ -0,0 +1,158 @@
+## 6. 用户管理模块
+
+### 6.1 用户列表管理
+
+**入口:** 设备信息页 → 点击用户切换按钮
+你这儿帮我设计一下怎么弄。最好切换了用户后能在设备信息页面也能显示当前用户,看放到设备信息页面的哪里合适。
+
+**页面布局:**
+```
++----------------------------------+
+|  <  用户管理          [+ 新增]   |
++----------------------------------+
+|  最多可添加10个用户                |
++----------------------------------+
+|  [========= 用户卡片 =========]   |
+|  头像  张三  男  28岁             |
+|       肩高:135cm 肩宽:42cm       |
+|       [编辑]  [删除]              |
+|  [============================]   |
+|                                    |
+|  [========= 用户卡片 =========]   |
+|  头像  李四  女  56岁             |
+|       肩高:128cm 肩宽:38cm       |
+|       [编辑]  [删除]              |
+|  [============================]   |
++----------------------------------+
+```
+
+**操作流程 - 查看用户列表:**
+
+```
+步骤1:进入用户管理页
+  → 调用接口获取用户列表
+  → 显示loading动画
+  → 加载完成显示用户卡片列表
+
+步骤2:用户切换
+  → 点击某个用户卡片
+  → 该卡片高亮显示(选中状态)
+  → 保存选中用户ID到本地
+  → Toast提示"已切换至用户:XXX"
+  → 自动返回设备详情页
+```
+
+**操作流程 - 新增用户:**
+
+```
+步骤1:点击右上角"+ 新增"按钮
+  → 进入体形数据录入页
+
+步骤2:填写用户信息
+  → 同5.2节补充信息表单
+
+步骤3:点击保存
+  → 校验:当前用户数量是否已达到10个
+  → 已达10个:Toast提示"最多可添加10个用户,请先删除其他用户"
+  → 未满10个:调用新增接口
+  → 成功:返回用户列表,显示新用户
+  → 失败:提示错误信息
+```
+
+**操作流程 - 编辑用户:**
+
+```
+步骤1:点击用户卡片的"编辑"按钮
+  → 进入编辑页面
+  → 表单回显当前用户信息
+
+步骤2:修改信息
+  → 同新增流程
+
+步骤3:点击保存
+  → 调用编辑接口
+  → 成功:返回列表页,刷新显示
+```
+
+**操作流程 - 删除用户:**
+
+```
+步骤1:点击用户卡片的"删除"按钮
+  → 弹出确认弹窗:"确定删除用户XXX?删除后将无法恢复"
+  → 按钮:"取消" / "确定删除"(红色)
+
+步骤2:点击确定
+  → 调用删除接口
+  → 删除成功:移除该卡片,Toast提示"删除成功"
+  → 删除失败:Toast提示错误
+```
+
+**详细错误提示清单:**
+
+| 场景 | 错误提示 |
+|------|---------|
+| 删除最后一个用户 | "至少保留一个用户" |
+| 删除失败 | "删除失败,请重试" |
+| 编辑时用户不存在 | "用户信息已过期,请刷新" |
+| 网络错误 | "网络连接失败,请检查网络" |
+
+### 6.2 体形数据录入
+
+**页面布局:**
+```
++----------------------------------+
+|  <  体形参数                      |
++----------------------------------+
+|  请录入体形参数,用于精准定位穴位   |
++----------------------------------+
+|                                    |
+|  肩高(地面到肩膀的高度,cm)       |
+|  +------------------------------+  |
+|  | 例如:135                     |  |
+|  +------------------------------+  |
+|  [========滑块========] 135cm    |
+|                                    |
+|  肩宽(两肩之间的宽度,cm)         |
+|  +------------------------------+  |
+|  | 例如:42                      |  |
+|  +------------------------------+  |
+|  [========滑块========] 42cm     |
+|                                    |
+|  体重(kg)                        |
+|  +------------------------------+  |
+|  | 例如:65                      |  |
+|  +------------------------------+  |
+|                                    |
+|  +------------------------------+  |
+|  |            保存              |  |
+|  +------------------------------+  |
++----------------------------------+
+```
+
+**操作流程:**
+
+```
+步骤1:进入体形参数页
+  → 如已有数据则回显
+
+步骤2:填写肩高
+  → 输入框输入数字
+  → 或拖动滑块调节
+  → 范围:50-250cm
+
+步骤3:填写肩宽
+  → 输入框输入数字
+  → 或拖动滑块调节
+  → 范围:20-80cm
+
+步骤4:填写体重
+  → 输入框输入数字
+  → 范围:10-300kg
+
+步骤5:点击保存
+  → 校验所有字段
+  → 调用体形数据保存接口
+  → 后台触发穴位坐标计算算法
+  → 保存成功:返回上一页
+  → 保存失败:提示错误
+```

+ 24 - 0
code/backend/src/main/java/com/aijiuyi/admin/common/config/JacksonConfig.java

@@ -0,0 +1,24 @@
+package com.aijiuyi.admin.common.config;
+
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Jackson 全局序列化配置
+ * 将 Long / long 类型统一序列化为字符串,避免前端 JavaScript 精度丢失
+ * (JavaScript Number 安全整数范围为 2^53 - 1,雪花ID为64位Long,超出安全范围)
+ */
+@Configuration
+public class JacksonConfig {
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            // Long 和 long 类型统一输出为字符串(不影响其他默认模块如 JavaTimeModule)
+            builder.serializerByType(Long.class, ToStringSerializer.instance);
+            builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
+        };
+    }
+}

+ 47 - 33
code/backend/src/main/java/com/aijiuyi/admin/common/constant/ResultCode.java

@@ -42,6 +42,18 @@ public enum ResultCode {
     PHONE_EXISTS(1101, "手机号已注册"),
     /** 应用用户已被禁用 */
     APP_USER_DISABLED(1102, "用户已被禁用"),
+    /** 验证码错误 */
+    SMS_CODE_ERROR(1103, "验证码错误"),
+    /** 验证码已过期 */
+    SMS_CODE_EXPIRED(1104, "验证码已过期,请重新获取"),
+    /** 短信发送失败 */
+    SMS_SEND_FAILED(1105, "短信发送失败,请稍后重试"),
+    /** 短信发送过于频繁(60秒内重复发送) */
+    SMS_SEND_TOO_FREQUENT(1106, "请勿频繁发送,请60秒后再试"),
+    /** 短信发送达到每日上限 */
+    SMS_DAILY_LIMIT(1107, "今日短信发送次数已达上限,请明天再试"),
+    /** 手机号格式不正确 */
+    PHONE_FORMAT_INVALID(1108, "手机号格式不正确"),
 
     // ======================== 子用户相关 1200-1299 ========================
     /** 子用户不存在 */
@@ -102,6 +114,8 @@ public enum ResultCode {
     USER_PLAN_NOT_FOUND(1705, "用户方案不存在"),
     /** 方案步骤至少保留1个 */
     PLAN_STEP_MIN_ERROR(1706, "方案步骤至少保留1个"),
+    /** 系统预置方案仅允许查看和编辑 */
+    PLAN_SYSTEM_PRESET_FORBIDDEN(1707, "系统预置方案仅允许查看和编辑"),
 
     // ======================== 管理员管理相关 1800-1899 ========================
     /** 管理员不存在 */
@@ -111,39 +125,39 @@ public enum ResultCode {
     /** 不能禁用自身账号 */
     ADMIN_CANNOT_DISABLE_SELF(1802, "不能禁用当前登录账号"),
     /** 管理员角色不合法 */
-	ADMIN_ROLE_INVALID(1803, "管理员角色不合法"),
-
-	// ======================== 内容管理相关 1900-1949 ========================
-	/** 文章不存在 */
-	ARTICLE_NOT_FOUND(1900, "文章不存在"),
-	/** 文章状态不允许该操作 */
-	ARTICLE_STATUS_INVALID(1901, "当前状态下不允许该操作"),
-	/** 无权限操作文章 */
-	ARTICLE_PERMISSION_DENIED(1902, "没有权限执行该操作"),
-	/** 拒绝原因不能为空 */
-	ARTICLE_REJECT_REASON_REQUIRED(1903, "拒绝审核必须填写原因"),
-
-	// ======================== 文件上传相关 1950-1969 ========================
-	/** 文件为空 */
-	FILE_EMPTY(1950, "上传文件不能为空"),
-	/** 文件类型不支持 */
-	FILE_TYPE_NOT_SUPPORTED(1951, "文件类型不支持"),
-	/** 文件大小超限 */
-	FILE_SIZE_EXCEEDED(1952, "文件大小超过限制"),
-	/** 文件上传失败 */
-	FILE_UPLOAD_FAILED(1953, "文件上传失败"),
-
-	// ======================== 用户类别相关 1970-1989 ========================
-	/** 用户类别不存在 */
-	USER_CATEGORY_NOT_FOUND(1970, "用户类别不存在"),
-	/** 用户类别编码已存在 */
-	USER_CATEGORY_CODE_EXISTS(1971, "用户类别编码已存在"),
-	/** 用户类别名称已存在 */
-	USER_CATEGORY_NAME_EXISTS(1972, "用户类别名称已存在"),
-	/** 内置用户类别不可删除 */
-	USER_CATEGORY_BUILTIN_FORBIDDEN(1973, "内置用户类别不可删除或修改编码"),
-	/** 用户类别已被使用 */
-	USER_CATEGORY_IN_USE(1974, "用户类别已被用户使用,不可删除");
+    ADMIN_ROLE_INVALID(1803, "管理员角色不合法"),
+
+    // ======================== 内容管理相关 1900-1949 ========================
+    /** 文章不存在 */
+    ARTICLE_NOT_FOUND(1900, "文章不存在"),
+    /** 文章状态不允许该操作 */
+    ARTICLE_STATUS_INVALID(1901, "当前状态下不允许该操作"),
+    /** 无权限操作文章 */
+    ARTICLE_PERMISSION_DENIED(1902, "没有权限执行该操作"),
+    /** 拒绝原因不能为空 */
+    ARTICLE_REJECT_REASON_REQUIRED(1903, "拒绝审核必须填写原因"),
+
+    // ======================== 文件上传相关 1950-1969 ========================
+    /** 文件为空 */
+    FILE_EMPTY(1950, "上传文件不能为空"),
+    /** 文件类型不支持 */
+    FILE_TYPE_NOT_SUPPORTED(1951, "文件类型不支持"),
+    /** 文件大小超限 */
+    FILE_SIZE_EXCEEDED(1952, "文件大小超过限制"),
+    /** 文件上传失败 */
+    FILE_UPLOAD_FAILED(1953, "文件上传失败"),
+
+    // ======================== 用户类别相关 1970-1989 ========================
+    /** 用户类别不存在 */
+    USER_CATEGORY_NOT_FOUND(1970, "用户类别不存在"),
+    /** 用户类别编码已存在 */
+    USER_CATEGORY_CODE_EXISTS(1971, "用户类别编码已存在"),
+    /** 用户类别名称已存在 */
+    USER_CATEGORY_NAME_EXISTS(1972, "用户类别名称已存在"),
+    /** 内置用户类别不可删除 */
+    USER_CATEGORY_BUILTIN_FORBIDDEN(1973, "内置用户类别不可删除或修改编码"),
+    /** 用户类别已被使用 */
+    USER_CATEGORY_IN_USE(1974, "用户类别已被用户使用,不可删除");
 
     // ======================== 其他业务错误码在此区间之后继续添加 ========================
 

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/AdminController.java

@@ -52,7 +52,7 @@ public class AdminController {
     /**
      * 新增管理员
      *
-     * @param admin 管理员信息(username/password/nickname/adminRole/status 必填)
+     * @param admin 管理员信息(username/password/nickname/adminRole/status 必填,phone 选填
      * @return 操作结果
      */
     @PostMapping

+ 82 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/AppAuthController.java

@@ -0,0 +1,82 @@
+package com.aijiuyi.admin.controller;
+
+import com.aijiuyi.admin.common.annotation.Log;
+import com.aijiuyi.admin.common.annotation.NoAuth;
+import com.aijiuyi.admin.common.entity.Result;
+import com.aijiuyi.admin.common.enums.OperationType;
+import com.aijiuyi.admin.controller.dto.AppLoginDTO;
+import com.aijiuyi.admin.controller.dto.SendCodeDTO;
+import com.aijiuyi.admin.service.AppUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.Map;
+
+/**
+ * App 端认证 Controller
+ * 提供手机号验证码登录、发送验证码、退出登录接口
+ *
+ * 登录方式:
+ *   - 手机号+短信验证码(当前实现)
+ *   - 微信登录(预留,后续实现)
+ *   - 苹果登录(预留,后续实现)
+ *
+ * Token 策略:
+ *   - Token 存入 Redis,有效期 3 年
+ *   - 除非后台踢下线(删除 Redis Token),否则 Token 永不失效
+ *   - 登录后调用 /app/auth/logout 可主动退出
+ */
+@RestController
+@RequestMapping("/app/auth")
+public class AppAuthController {
+
+    @Autowired
+    private AppUserService appUserService;
+
+    /**
+     * 发送短信验证码
+     * 60 秒内同一手机号只能发送一次,验证码有效期 5 分钟
+     *
+     * @param dto 手机号
+     * @return 发送结果
+     */
+    @PostMapping("/send-code")
+    @NoAuth
+    @Log(value = "发送短信验证码", module = "App认证", operationType = OperationType.OTHER)
+    public Result<Void> sendCode(@RequestBody @Valid SendCodeDTO dto) {
+        appUserService.sendSmsCode(dto.getPhone());
+        return Result.success();
+    }
+
+    /**
+     * App 端手机号验证码登录
+     * 手机号未注册时自动注册为用户(userType=1)
+     * 登录成功返回永久有效 Token(除非后台主动踢下线)
+     *
+     * @param loginDTO 登录参数(手机号、验证码)
+     * @return 包含 token 和用户基本信息的 Result
+     */
+    @PostMapping("/login")
+    @NoAuth
+    @Log(value = "App用户登录", module = "App认证", operationType = OperationType.OTHER)
+    public Result<Map<String, Object>> login(@RequestBody @Valid AppLoginDTO loginDTO) {
+        return appUserService.appLogin(loginDTO);
+    }
+
+    /**
+     * App 端退出登录
+     * 删除 Redis 中的 Token,使当前 Token 立即失效
+     *
+     * @param request HTTP 请求(用于获取 Authorization 头中的 Token)
+     * @return 操作结果
+     */
+    @PostMapping("/logout")
+    @Log(value = "App用户退出", module = "App认证", operationType = OperationType.OTHER)
+    public Result<Void> logout(HttpServletRequest request) {
+        String token = request.getHeader("Authorization");
+        appUserService.logout(token);
+        return Result.success();
+    }
+}

+ 66 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/AppProfileController.java

@@ -0,0 +1,66 @@
+package com.aijiuyi.admin.controller;
+
+import com.aijiuyi.admin.common.annotation.Log;
+import com.aijiuyi.admin.common.context.RequestContext;
+import com.aijiuyi.admin.common.entity.Result;
+import com.aijiuyi.admin.common.enums.OperationType;
+import com.aijiuyi.admin.entity.AppUser;
+import com.aijiuyi.admin.service.AppUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * App 端用户个人资料 Controller
+ * 已登录用户获取/修改自己的个人信息
+ */
+@RestController
+@RequestMapping("/app/user")
+public class AppProfileController {
+
+    @Autowired
+    private AppUserService appUserService;
+
+    /**
+     * 获取当前用户个人信息
+     *
+     * @return 用户基本信息(nickname、avatar、phone、appId)
+     */
+    @GetMapping("/profile")
+    @Log(value = "获取个人资料", module = "App用户", operationType = OperationType.QUERY)
+    public Result<Map<String, Object>> getProfile() {
+        Long userId = RequestContext.getUserId();
+        AppUser user = appUserService.getById(userId);
+        Map<String, Object> data = new HashMap<>(8);
+        data.put("userId", user.getId());
+        data.put("nickname", user.getNickname());
+        data.put("avatar", user.getAvatar());
+        data.put("phone", user.getPhone());
+        data.put("appId", user.getAppId());
+        return Result.success(data);
+    }
+
+    /**
+     * 修改当前用户个人信息(昵称、头像)
+     *
+     * @param body 包含 nickname 和/或 avatar 的请求体
+     * @return 操作结果
+     */
+    @PutMapping("/profile")
+    @Log(value = "修改个人资料", module = "App用户", operationType = OperationType.UPDATE)
+    public Result<Void> updateProfile(@RequestBody Map<String, String> body) {
+        Long userId = RequestContext.getUserId();
+        AppUser update = new AppUser();
+        update.setId(userId);
+        if (body.containsKey("nickname")) {
+            update.setNickname(body.get("nickname"));
+        }
+        if (body.containsKey("avatar")) {
+            update.setAvatar(body.get("avatar"));
+        }
+        appUserService.updateById(update);
+        return Result.success();
+    }
+}

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/AppUserController.java

@@ -28,7 +28,7 @@ public class AppUserController {
      * 获取应用用户选项列表(用于方案创作人选择框)
      * 返回所有启用状态的用户,前端根据 userType 区分角色
      *
-     * @param userType 可选过滤:1=普通用户,2=专家,3=管理员;null=全部
+     * @param userType 可选过滤:1=用户,2=专家,3=管理员;null=全部
      * @return 用户列表(id + nickname + username + userType)
      */
     @GetMapping("/list")

+ 288 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/ArticleViewController.java

@@ -0,0 +1,288 @@
+package com.aijiuyi.admin.controller;
+
+import com.aijiuyi.admin.common.annotation.NoAuth;
+import com.aijiuyi.admin.entity.ContentArticle;
+import com.aijiuyi.admin.service.ContentArticleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 文章公开阅读页 Controller
+ * 提供无需登录即可访问的文章 HTML 阅读页面
+ * 特性:
+ *   - 手机端自适应(viewport + 响应式CSS)
+ *   - 支持水印(watermarkEnabled=1时展示)
+ *   - 禁止文本复制(CSS + JS)
+ *   - 增加浏览量统计
+ */
+@RestController
+@RequestMapping("/article")
+@NoAuth
+public class ArticleViewController {
+
+    @Autowired
+    private ContentArticleService articleService;
+
+    /**
+     * 公开阅读文章页面
+     * 仅已发布状态的文章可被访问
+     *
+     * @param id 文章ID
+     * @return HTML 页面内容
+     */
+    @GetMapping(value = "/view/{id}", produces = MediaType.TEXT_HTML_VALUE + ";charset=UTF-8")
+    public String viewArticle(@PathVariable Long id) {
+        ContentArticle article = articleService.getById(id);
+        if (article == null || !"PUBLISHED".equals(article.getStatus())) {
+            return buildErrorPage("文章不存在或未发布");
+        }
+
+        // 增加浏览量
+        ContentArticle update = new ContentArticle();
+        update.setId(id);
+        update.setViewCount(article.getViewCount() == null ? 1 : article.getViewCount() + 1);
+        articleService.updateById(update);
+
+        return buildArticlePage(article);
+    }
+
+    /**
+     * 构建文章阅读页 HTML
+     */
+    private String buildArticlePage(ContentArticle article) {
+        String watermarkText = article.getWatermarkText();
+        String watermarkImage = article.getWatermarkImage();
+        boolean hasTextWatermark = watermarkText != null && !watermarkText.trim().isEmpty();
+        boolean hasImageWatermark = watermarkImage != null && !watermarkImage.trim().isEmpty();
+        boolean hasWatermark = hasTextWatermark || hasImageWatermark;
+        String title = escapeHtml(article.getTitle() != null ? article.getTitle() : "");
+        String author = escapeHtml(article.getAuthor() != null ? article.getAuthor() : "");
+        String publishTime = article.getPublishTime() != null ? article.getPublishTime().toString().replace("T", " ") : "";
+        String content = article.getContent() != null ? article.getContent() : "";
+
+        StringBuilder html = new StringBuilder();
+        html.append("<!DOCTYPE html>\n");
+        html.append("<html lang=\"zh-CN\">\n<head>\n");
+        html.append("<meta charset=\"UTF-8\">\n");
+        html.append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n");
+        html.append("<title>").append(title).append("</title>\n");
+        html.append("<style>\n");
+        // 页面外层样式(不影响文章内容区域)
+        html.append("*, *::before, *::after { box-sizing: border-box; }\n");
+        html.append("body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Microsoft YaHei', sans-serif; ");
+        html.append("line-height: 1.6; color: #333; background: #f5f5f5; }\n");
+        html.append(".article-container { max-width: 800px; margin: 0 auto; padding: 20px 16px; background: #fff; min-height: 100vh; }\n");
+        html.append(".article-title { font-size: 22px; font-weight: 700; color: #1a1a1a; margin: 0 0 12px 0; word-break: break-word; }\n");
+        html.append(".article-meta { font-size: 13px; color: #999; margin: 0 0 20px 0; padding-bottom: 16px; border-bottom: 1px solid #eee; }\n");
+        html.append(".article-meta span { margin-right: 16px; }\n");
+        // ========== TinyMCE content 区域样式(完全复刻编辑器 iframe 环境) ==========
+        // 对应 TinyMCE 配置中的 content_style(body 样式映射到 .article-content)
+        html.append(".article-content { ");
+        html.append("font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Microsoft YaHei', sans-serif; ");
+        html.append("font-size: 14px; line-height: 1.6; color: #333; ");
+        html.append("padding: 8px 12px; word-break: break-word; overflow-wrap: break-word; }\n");
+        // 对应 content_style 中的 img/video
+        html.append(".article-content img { max-width: 100%; height: auto; }\n");
+        html.append(".article-content video { max-width: 100%; height: auto !important; }\n");
+        // ---- 以下对应 TinyMCE content.css (skins/content/default/content.css) ----
+        html.append(".article-content table { border-collapse: collapse; }\n");
+        html.append(".article-content table:not([cellpadding]) th, .article-content table:not([cellpadding]) td { padding: 0.4rem; }\n");
+        html.append(".article-content table[border]:not([border='0']):not([style*='border-width']) th, ");
+        html.append(".article-content table[border]:not([border='0']):not([style*='border-width']) td { border-width: 1px; }\n");
+        html.append(".article-content table[border]:not([border='0']):not([style*='border-style']) th, ");
+        html.append(".article-content table[border]:not([border='0']):not([style*='border-style']) td { border-style: solid; }\n");
+        html.append(".article-content table[border]:not([border='0']):not([style*='border-color']) th, ");
+        html.append(".article-content table[border]:not([border='0']):not([style*='border-color']) td { border-color: #ccc; }\n");
+        html.append(".article-content figure { display: table; margin: 1rem auto; }\n");
+        html.append(".article-content figure figcaption { color: #999; display: block; margin-top: 0.25rem; text-align: center; }\n");
+        html.append(".article-content hr { border-color: #ccc; border-style: solid; border-width: 1px 0 0 0; }\n");
+        html.append(".article-content code { background-color: #e8e8e8; border-radius: 3px; padding: 0.1rem 0.2rem; }\n");
+        html.append(".article-content pre { background: #f6f8fa; padding: 12px; border-radius: 6px; overflow-x: auto; margin: 12px 0; }\n");
+        html.append(".article-content pre code { background-color: transparent; padding: 0; border-radius: 0; }\n");
+        html.append(".article-content blockquote { border-left: 2px solid #ccc; margin-left: 1.5rem; padding-left: 1rem; }\n");
+        // ---- TinyMCE 默认浏览器样式补充(编辑器 iframe 中自带这些默认渲染) ----
+        html.append(".article-content p { margin: 0 0 1em 0; }\n");
+        html.append(".article-content h1 { display: block; font-size: 2em; margin: 0.67em 0; font-weight: bold; }\n");
+        html.append(".article-content h2 { display: block; font-size: 1.5em; margin: 0.83em 0; font-weight: bold; }\n");
+        html.append(".article-content h3 { display: block; font-size: 1.17em; margin: 1em 0; font-weight: bold; }\n");
+        html.append(".article-content h4 { display: block; font-size: 1em; margin: 1.33em 0; font-weight: bold; }\n");
+        html.append(".article-content h5 { display: block; font-size: 0.83em; margin: 1.67em 0; font-weight: bold; }\n");
+        html.append(".article-content h6 { display: block; font-size: 0.67em; margin: 2.33em 0; font-weight: bold; }\n");
+        html.append(".article-content ul, .article-content ol { padding-left: 40px; margin: 1em 0; }\n");
+        html.append(".article-content li { display: list-item; }\n");
+        html.append(".article-content ul { list-style-type: disc; }\n");
+        html.append(".article-content ol { list-style-type: decimal; }\n");
+        html.append(".article-content a { color: #2563eb; text-decoration: underline; }\n");
+        html.append(".article-content sub { vertical-align: sub; font-size: smaller; }\n");
+        html.append(".article-content sup { vertical-align: super; font-size: smaller; }\n");
+        // TinyMCE 对齐辅助类
+        html.append(".article-content [style*='text-align: center'], .article-content [align='center'] { text-align: center; }\n");
+        html.append(".article-content [style*='text-align: right'], .article-content [align='right'] { text-align: right; }\n");
+        html.append(".article-content [style*='text-align: justify'] { text-align: justify; }\n");
+        // TinyMCE 表格编辑器生成的常见样式
+        html.append(".article-content td[valign='top'] { vertical-align: top; }\n");
+        html.append(".article-content td[valign='middle'] { vertical-align: middle; }\n");
+        html.append(".article-content td[valign='bottom'] { vertical-align: bottom; }\n");
+        // 禁止复制样式
+        html.append(".no-copy { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }\n");
+        // 水印样式
+        if (hasWatermark) {
+            html.append(".watermark { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999; overflow: hidden; }\n");
+            if (hasImageWatermark) {
+                html.append(".watermark-item { position: absolute; transform: rotate(-30deg); opacity: 0.08; }\n");
+                html.append(".watermark-item img { width: 120px; height: auto; }\n");
+            } else {
+                html.append(".watermark-text { position: absolute; color: rgba(0,0,0,0.06); font-size: 16px; transform: rotate(-30deg); white-space: nowrap; font-weight: 500; }\n");
+            }
+        }
+        // 移动端适配
+        html.append("@media (max-width: 768px) {\n");
+        html.append("  .article-container { padding: 16px 12px; }\n");
+        html.append("  .article-title { font-size: 20px; }\n");
+        html.append("}\n");
+        html.append("</style>\n");
+        html.append("</head>\n<body>\n");
+
+        html.append("<div class=\"article-container no-copy\">\n");
+        html.append("  <h1 class=\"article-title\">").append(title).append("</h1>\n");
+        html.append("  <div class=\"article-meta\">\n");
+        if (!author.isEmpty()) {
+            html.append("    <span>").append(author).append("</span>\n");
+        }
+        if (!publishTime.isEmpty()) {
+            html.append("    <span>").append(publishTime).append("</span>\n");
+        }
+        html.append("  </div>\n");
+        html.append("  <div class=\"article-content\">\n");
+        html.append(content);
+        html.append("\n  </div>\n");
+        html.append("</div>\n");
+
+        // 水印层
+        if (hasWatermark) {
+            html.append("<div class=\"watermark\" id=\"watermark\"></div>\n");
+        }
+
+        // 禁止复制 JS
+        html.append("<script>\n");
+        html.append("// 禁止右键菜单\n");
+        html.append("document.addEventListener('contextmenu', function(e) { e.preventDefault(); });\n");
+        html.append("// 禁止复制\n");
+        html.append("document.addEventListener('copy', function(e) { e.preventDefault(); });\n");
+        html.append("// 禁止剪切\n");
+        html.append("document.addEventListener('cut', function(e) { e.preventDefault(); });\n");
+        html.append("// 禁止选择\n");
+        html.append("document.addEventListener('selectstart', function(e) { e.preventDefault(); });\n");
+        html.append("// 禁止拖拽\n");
+        html.append("document.addEventListener('dragstart', function(e) { e.preventDefault(); });\n");
+
+        // 视频显示首帧
+        html.append("\n// 视频显示首帧\n");
+        html.append("document.querySelectorAll('video').forEach(function(video) {\n");
+        html.append("  video.setAttribute('preload', 'metadata');\n");
+        html.append("  video.setAttribute('playsinline', '');\n");
+        html.append("  video.setAttribute('webkit-playsinline', '');\n");
+        html.append("  video.addEventListener('loadeddata', function() {\n");
+        html.append("    if (video.currentTime === 0) { video.currentTime = 0.1; }\n");
+        html.append("  });\n");
+        html.append("  if (video.readyState >= 2) { video.currentTime = 0.1; }\n");
+        html.append("});\n");
+
+        // 水印生成 JS
+        if (hasWatermark) {
+            html.append("\n// 生成水印\n");
+            html.append("(function() {\n");
+            html.append("  var container = document.getElementById('watermark');\n");
+            if (hasImageWatermark) {
+                // 图片水印优先
+                html.append("  var imgUrl = '").append(escapeJs(watermarkImage.trim())).append("';\n");
+                html.append("  var cols = Math.ceil(window.innerWidth / 240);\n");
+                html.append("  var rows = Math.ceil(window.innerHeight / 200);\n");
+                html.append("  for (var i = 0; i < rows + 5; i++) {\n");
+                html.append("    for (var j = 0; j < cols + 3; j++) {\n");
+                html.append("      var div = document.createElement('div');\n");
+                html.append("      div.className = 'watermark-item';\n");
+                html.append("      var img = document.createElement('img');\n");
+                html.append("      img.src = imgUrl;\n");
+                html.append("      img.draggable = false;\n");
+                html.append("      div.appendChild(img);\n");
+                html.append("      div.style.left = (j * 240 - 60) + 'px';\n");
+                html.append("      div.style.top = (i * 200 - 40) + 'px';\n");
+                html.append("      container.appendChild(div);\n");
+                html.append("    }\n");
+                html.append("  }\n");
+            } else {
+                // 文本水印
+                html.append("  var text = '").append(escapeJs(watermarkText.trim())).append("';\n");
+                html.append("  var cols = Math.ceil(window.innerWidth / 200);\n");
+                html.append("  var rows = Math.ceil(window.innerHeight / 150);\n");
+                html.append("  for (var i = 0; i < rows + 5; i++) {\n");
+                html.append("    for (var j = 0; j < cols + 3; j++) {\n");
+                html.append("      var span = document.createElement('span');\n");
+                html.append("      span.className = 'watermark-text';\n");
+                html.append("      span.textContent = text;\n");
+                html.append("      span.style.left = (j * 200 - 50) + 'px';\n");
+                html.append("      span.style.top = (i * 150 - 30) + 'px';\n");
+                html.append("      container.appendChild(span);\n");
+                html.append("    }\n");
+                html.append("  }\n");
+            }
+            html.append("})();\n");
+        }
+
+        html.append("</script>\n");
+        html.append("</body>\n</html>");
+
+        return html.toString();
+    }
+
+    /**
+     * 构建错误页面 HTML
+     */
+    private String buildErrorPage(String message) {
+        return "<!DOCTYPE html>\n" +
+                "<html lang=\"zh-CN\">\n<head>\n" +
+                "<meta charset=\"UTF-8\">\n" +
+                "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
+                "<title>页面不存在</title>\n" +
+                "<style>\n" +
+                "body { font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; " +
+                "display: flex; align-items: center; justify-content: center; min-height: 100vh; background: #f5f5f5; }\n" +
+                ".error-box { text-align: center; padding: 40px 20px; }\n" +
+                ".error-box h2 { font-size: 20px; color: #666; margin-bottom: 12px; }\n" +
+                ".error-box p { font-size: 14px; color: #999; }\n" +
+                "</style>\n</head>\n<body>\n" +
+                "<div class=\"error-box\">\n" +
+                "  <h2>" + escapeHtml(message) + "</h2>\n" +
+                "  <p>请确认链接是否正确</p>\n" +
+                "</div>\n</body>\n</html>";
+    }
+
+    /**
+     * HTML 转义
+     */
+    private String escapeHtml(String text) {
+        if (text == null) return "";
+        return text.replace("&", "&amp;")
+                   .replace("<", "&lt;")
+                   .replace(">", "&gt;")
+                   .replace("\"", "&quot;")
+                   .replace("'", "&#39;");
+    }
+
+    /**
+     * JS 字符串转义
+     */
+    private String escapeJs(String text) {
+        if (text == null) return "";
+        return text.replace("\\", "\\\\")
+                   .replace("'", "\\'")
+                   .replace("\"", "\\\"")
+                   .replace("\n", "\\n")
+                   .replace("\r", "\\r");
+    }
+}

+ 78 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/AuthController.java

@@ -16,6 +16,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
 import java.util.HashMap;
 import java.util.Map;
+import org.springframework.util.DigestUtils;
+import org.springframework.util.StringUtils;
 
 /**
  * 认证管理 Controller
@@ -83,9 +85,85 @@ public class AuthController {
         data.put("username", user != null ? user.getUsername() : null);
         data.put("nickname", user != null ? user.getNickname() : null);
         data.put("avatar", user != null ? user.getAvatar() : null);
+        data.put("phone", user != null ? user.getPhone() : null);
+        data.put("address", user != null ? user.getAddress() : null);
         data.put("userType", user != null ? user.getUserType() : null);
         data.put("adminRole", user != null ? user.getAdminRole() : null);
         data.put("permissions", permissions);
         return Result.success(data);
     }
+
+    /**
+     * 更新当前管理员个人信息(昵称、手机、地址、头像、密码)
+     *
+     * @param payload 请求体(nickname/phone/address/avatar/password/oldPassword)
+     * @return 操作结果
+     */
+    @PutMapping("/profile")
+    @Log(value = "更新个人信息", module = "认证管理", operationType = OperationType.UPDATE)
+    public Result<Void> updateProfile(@RequestBody Map<String, String> payload,
+                                      HttpServletRequest request) {
+        Long userId = RequestContext.getUserId();
+        AppUser user = appUserService.getById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        // 如果修改密码,验证旧密码
+        String newPassword = payload.get("password");
+        String oldPassword = payload.get("oldPassword");
+        if (StringUtils.hasText(newPassword)) {
+            if (!StringUtils.hasText(oldPassword)) {
+                return Result.error("请输入旧密码");
+            }
+            String oldPasswordMd5 = DigestUtils.md5DigestAsHex(oldPassword.getBytes());
+            if (!oldPasswordMd5.equals(user.getPassword())) {
+                return Result.error("旧密码不正确");
+            }
+        }
+
+        // 如果修改了手机号,校验唯一性
+        String newPhone = payload.get("phone");
+        if (StringUtils.hasText(newPhone) && !newPhone.equals(user.getPhone())) {
+            long phoneCount = appUserService.lambdaQuery()
+                    .eq(AppUser::getPhone, newPhone)
+                    .ne(AppUser::getId, userId)
+                    .count();
+            if (phoneCount > 0) {
+                return Result.error("该手机号已注册,请更换手机号");
+            }
+        }
+
+        AppUser update = new AppUser();
+        update.setId(userId);
+        // 更新昵称
+        if (payload.containsKey("nickname")) {
+            update.setNickname(payload.get("nickname"));
+        }
+        // 更新手机号
+        if (payload.containsKey("phone")) {
+            update.setPhone(payload.get("phone"));
+        }
+        // 更新地址
+        if (payload.containsKey("address")) {
+            update.setAddress(payload.get("address"));
+        }
+        // 更新头像
+        if (payload.containsKey("avatar")) {
+            update.setAvatar(payload.get("avatar"));
+        }
+        // 更新密码
+        if (StringUtils.hasText(newPassword)) {
+            update.setPassword(DigestUtils.md5DigestAsHex(newPassword.getBytes()));
+        }
+        appUserService.updateById(update);
+
+        // 修改密码后,从 Redis 删除当前 Token,使其立即失效,强制重新登录
+        if (StringUtils.hasText(newPassword)) {
+            String token = request.getHeader("Authorization");
+            appUserService.logout(token);
+        }
+
+        return Result.success();
+    }
 }

+ 3 - 2
code/backend/src/main/java/com/aijiuyi/admin/controller/PlanController.java

@@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import javax.validation.Valid;
 import java.util.List;
 
 /**
@@ -56,7 +57,7 @@ public class PlanController {
      */
     @PostMapping
     @Log(value = "新增方案", module = "方案管理", operationType = OperationType.INSERT)
-    public Result<Void> add(@RequestBody PlanSaveDTO dto) {
+    public Result<Void> add(@RequestBody @Valid PlanSaveDTO dto) {
         planService.addPlan(dto);
         return Result.success();
     }
@@ -69,7 +70,7 @@ public class PlanController {
      */
     @PutMapping
     @Log(value = "修改方案信息", module = "方案管理", operationType = OperationType.UPDATE)
-    public Result<Void> update(@RequestBody PlanSaveDTO dto) {
+    public Result<Void> update(@RequestBody @Valid PlanSaveDTO dto) {
         planService.updatePlan(dto);
         return Result.success();
     }

+ 20 - 34
code/backend/src/main/java/com/aijiuyi/admin/controller/UserCategoryController.java

@@ -13,9 +13,8 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 用户类别管理 Controller
- * 提供用户类别(用户类型字典)的增删改查接口
- * 内置类别(普通用户/专家/管理员)不可删除或修改 code
+ * 用户分类管理 Controller
+ * 提供 app_user.user_type=1 用户分类的增删改查接口
  */
 @RestController
 @RequestMapping("/user/category")
@@ -25,90 +24,77 @@ public class UserCategoryController {
     private UserCategoryService userCategoryService;
 
     /**
-     * 分页查询用户类
+     * 分页查询用户
      *
      * @param queryDTO 查询条件
      * @return 分页结果
      */
     @GetMapping("/page")
-    @Log(value = "查询用户类列表", module = "用户类管理", operationType = OperationType.QUERY)
+    @Log(value = "查询用户类列表", module = "用户类管理", operationType = OperationType.QUERY)
     public Result<IPage<UserCategory>> page(UserCategoryQueryDTO queryDTO) {
         return Result.success(userCategoryService.pageList(queryDTO));
     }
 
     /**
-     * 查询所有启用的用户类(前端下拉选择使用)
+     * 查询所有用户类(前端下拉选择使用)
      *
-     * @return 启用的用户类列表
+     * @return 启用的用户类列表
      */
     @GetMapping("/list")
-    @Log(value = "获取用户类下拉列表", module = "用户类管理", operationType = OperationType.QUERY)
+    @Log(value = "获取用户类下拉列表", module = "用户类管理", operationType = OperationType.QUERY)
     public Result<List<UserCategory>> list() {
         return Result.success(userCategoryService.listEnabled());
     }
 
     /**
-     * 查询用户类详情
+     * 查询用户类详情
      *
-     * @param id 类ID
-     * @return 类详情
+     * @param id 类ID
+     * @return 类详情
      */
     @GetMapping("/{id}")
-    @Log(value = "查询用户类详情", module = "用户类管理", operationType = OperationType.QUERY)
+    @Log(value = "查询用户类详情", module = "用户类管理", operationType = OperationType.QUERY)
     public Result<UserCategory> getById(@PathVariable Long id) {
         return Result.success(userCategoryService.getById(id));
     }
 
     /**
-     * 新增用户类
+     * 新增用户
      *
-     * @param category 类信息
+     * @param category 类信息
      * @return 操作结果
      */
     @PostMapping
-    @Log(value = "新增用户类", module = "用户类管理", operationType = OperationType.INSERT)
+    @Log(value = "新增用户类", module = "用户类管理", operationType = OperationType.INSERT)
     public Result<Void> add(@RequestBody UserCategory category) {
         userCategoryService.addCategory(category);
         return Result.success();
     }
 
     /**
-     * 修改用户类
+     * 修改用户
      *
-     * @param category 类信息
+     * @param category 类信息
      * @return 操作结果
      */
     @PutMapping
-    @Log(value = "修改用户类", module = "用户类管理", operationType = OperationType.UPDATE)
+    @Log(value = "修改用户类", module = "用户类管理", operationType = OperationType.UPDATE)
     public Result<Void> update(@RequestBody UserCategory category) {
         userCategoryService.updateCategory(category);
         return Result.success();
     }
 
     /**
-     * 删除用户类内置类别和已被使用的类不可删除)
+     * 删除用户类(已被用户使用的类不可删除)
      *
-     * @param id 类ID
+     * @param id 类ID
      * @return 操作结果
      */
     @DeleteMapping("/{id}")
-    @Log(value = "删除用户类", module = "用户类管理", operationType = OperationType.DELETE)
+    @Log(value = "删除用户类", module = "用户类管理", operationType = OperationType.DELETE)
     public Result<Void> delete(@PathVariable Long id) {
         userCategoryService.deleteCategory(id);
         return Result.success();
     }
 
-    /**
-     * 修改用户类别状态(启用/停用)
-     *
-     * @param id     类别ID
-     * @param status 目标状态:1=启用,0=停用
-     * @return 操作结果
-     */
-    @PutMapping("/{id}/status")
-    @Log(value = "修改用户类别状态", module = "用户类别管理", operationType = OperationType.UPDATE)
-    public Result<Void> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
-        userCategoryService.updateStatus(id, status);
-        return Result.success();
-    }
 }

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/UserProfileController.java

@@ -28,7 +28,7 @@ public class UserProfileController {
     /**
      * 分页查询用户列表(联表获取设备编号)
      *
-     * @param queryDTO 查询条件(关键字/设备编号/性别/注册时间范围/穴位表状态)
+     * @param queryDTO 查询条件(关键字/设备编号/性别/用户分类/注册时间范围/穴位表状态)
      * @return 分页结果
      */
     @GetMapping("/page")

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AdminQueryDTO.java

@@ -14,7 +14,7 @@ public class AdminQueryDTO {
     /** 姓名/昵称(模糊查询) */
     private String nickname;
 
-    /** 管理员角色:1=超级管理员,2=运营人员,3=只读用户 */
+    /** 管理员角色:1=超级管理员,2=设备管理员,3=圣手管理 */
     private Integer adminRole;
 
     /** 状态:1=启用,0=禁用 */

+ 22 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AppLoginDTO.java

@@ -0,0 +1,22 @@
+package com.aijiuyi.admin.controller.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+/**
+ * App 端手机号验证码登录请求参数 DTO
+ */
+@Data
+public class AppLoginDTO {
+
+    /** 手机号(不能为空,格式:1[3-9]\d{9}) */
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+    private String phone;
+
+    /** 短信验证码(不能为空,4-6位数字) */
+    @NotBlank(message = "验证码不能为空")
+    private String code;
+}

+ 4 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AppUserQueryDTO.java

@@ -14,9 +14,12 @@ public class AppUserQueryDTO {
     /** 昵称(模糊搜索) */
     private String nickname;
 
-    /** 用户类型:1=普通用户,2=专家,3=管理员 */
+    /** 用户类型:1=用户,2=专家,3=管理员 */
     private Integer userType;
 
+    /** 用户分类ID(仅 userType=1 用户有效) */
+    private Long userCategoryId;
+
     /** 状态:1=正常,0=禁用 */
     private Integer status;
 

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/PlanQueryDTO.java

@@ -14,7 +14,7 @@ public class PlanQueryDTO {
     /** 模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手 */
     private Integer modeType;
 
-    /** 功效类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳 */
+    /** 作用类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳 */
     private String effectType;
 
     /** 创作人名称(模糊搜索) */

+ 5 - 3
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/PlanSaveDTO.java

@@ -3,6 +3,7 @@ package com.aijiuyi.admin.controller.dto;
 import com.aijiuyi.admin.entity.PlanStep;
 import lombok.Data;
 
+import javax.validation.constraints.Size;
 import java.util.List;
 
 /**
@@ -23,7 +24,7 @@ public class PlanSaveDTO {
     /** 模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手 */
     private Integer modeType;
 
-    /** 功效类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳 */
+    /** 作用类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳 */
     private String effectType;
 
     /** 适用症状(逗号分隔) */
@@ -38,10 +39,11 @@ public class PlanSaveDTO {
     /** 适用年龄最大值(岁) */
     private Integer ageMax;
 
-    /** 方案描述(最长200字符) */
+    /** 方案描述(最长500字符) */
+    @Size(max = 500, message = "方案描述最多500字")
     private String description;
 
-    /** 创作人类型:1=管理员,2=普通用户,3=专家 */
+    /** 创作人类型:1=管理员,2=用户,3=专家 */
     private Integer authorType;
 
     /** 创作人ID */

+ 18 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/SendCodeDTO.java

@@ -0,0 +1,18 @@
+package com.aijiuyi.admin.controller.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+/**
+ * 发送短信验证码请求参数 DTO
+ */
+@Data
+public class SendCodeDTO {
+
+    /** 手机号(不能为空,格式:1[3-9]\d{9}) */
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
+    private String phone;
+}

+ 1 - 4
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/UserCategoryQueryDTO.java

@@ -3,7 +3,7 @@ package com.aijiuyi.admin.controller.dto;
 import lombok.Data;
 
 /**
- * 用户类分页查询参数 DTO
+ * 用户类分页查询参数 DTO
  */
 @Data
 public class UserCategoryQueryDTO {
@@ -11,9 +11,6 @@ public class UserCategoryQueryDTO {
     /** 关键字(名称/备注模糊查询) */
     private String keyword;
 
-    /** 状态:1=启用,0=停用 */
-    private Integer status;
-
     /** 当前页码,默认第1页 */
     private Integer pageNum = 1;
 

+ 4 - 1
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/UserProfileDTO.java

@@ -38,9 +38,12 @@ public class UserProfileDTO {
     @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
     private String phone;
 
-    /** 用户类型:1=普通用户,2=专家,3=管理员(选填,缺省按普通用户) */
+    /** 用户类型:保留兼容,用户列表统一按 1=用户 处理 */
     private Integer userType;
 
+    /** 用户分类ID(仅 userType=1 用户有效,关联 app_user_category.id) */
+    private Long userCategoryId;
+
     /** 省级行政区划编码(GB/T 2260) */
     private String provinceCode;
 

+ 2 - 2
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/UserProfileQueryDTO.java

@@ -17,8 +17,8 @@ public class UserProfileQueryDTO {
     /** 性别:1=男,2=女,不传则查全部 */
     private Integer gender;
 
-    /** 用户类型:1=普通用户,2=专家,3=管理员,不传则查全部 */
-    private Integer userType;
+    /** 用户分类ID(仅查询身份为用户的数据) */
+    private Long userCategoryId;
 
     /** 省级行政区划编码(选填,可独立查询) */
     private String provinceCode;

+ 32 - 3
code/backend/src/main/java/com/aijiuyi/admin/entity/AppUser.java

@@ -9,7 +9,7 @@ import java.time.LocalDateTime;
 /**
  * 应用用户实体(统一用户表,通过 userType 区分角色)
  * 对应数据库 app_user 表
- * userType: 1=普通用户,2=专家,3=管理员
+ * userType: 1=用户,2=专家,3=管理员
  */
 @Data
 @TableName("app_user")
@@ -35,13 +35,24 @@ public class AppUser {
     /** 头像URL */
     private String avatar;
 
+    /** 地址 */
+    private String address;
+
+    /** 用户AppId(展示用唯一标识,如 AJY00000001) */
+    private String appId;
+
     /**
-     * 用户类型(角色):1=普通用户,2=专家,3=管理员
+     * 用户类型(角色):1=用户,2=专家,3=管理员
      */
     private Integer userType;
 
     /**
-     * 管理员角色:1=超级管理员,2=运营人员,3=只读用户(仅 userType=3 时有效)
+     * 用户分类ID(仅 userType=1 用户有效,关联 app_user_category.id)
+     */
+    private Long userCategoryId;
+
+    /**
+     * 管理员角色:1=超级管理员,2=设备管理员,3=圣手管理(仅 userType=3 时有效)
      */
     private Integer adminRole;
 
@@ -51,6 +62,24 @@ public class AppUser {
     /** 最后登录时间 */
     private LocalDateTime lastLoginTime;
 
+    /**
+     * 登录方式:1=手机号验证码,2=微信,3=苹果
+     * 记录用户最近一次登录所用的方式
+     */
+    private Integer loginType;
+
+    /** 微信 OpenID(同一应用内唯一,微信登录时记录,预留字段) */
+    private String wechatOpenId;
+
+    /** 微信 UnionID(同一主体下跨应用唯一标识,预留字段) */
+    private String wechatUnionId;
+
+    /** Apple 用户 ID(苹果登录时记录,预留字段) */
+    private String appleUserId;
+
+    /** Apple 邮箱(苹果登录时可能提供,含中继邮箱,预留字段) */
+    private String appleEmail;
+
     /** 逻辑删除:0=未删除,1=已删除 */
     @TableLogic
     private Integer deleted;

+ 6 - 0
code/backend/src/main/java/com/aijiuyi/admin/entity/ContentArticle.java

@@ -48,6 +48,12 @@ public class ContentArticle {
     /** 浏览次数 */
     private Integer viewCount;
 
+    /** 水印文本(为NULL或空则不显示水印,有内容则显示对应水印) */
+    private String watermarkText;
+
+    /** 水印图片URL(上传到OSS的图片地址,优先级高于文本水印) */
+    private String watermarkImage;
+
     /** 发布时间 */
     @TableField(updateStrategy = FieldStrategy.IGNORED)
     private LocalDateTime publishTime;

+ 3 - 3
code/backend/src/main/java/com/aijiuyi/admin/entity/Plan.java

@@ -30,7 +30,7 @@ public class Plan {
     private Integer modeType;
 
     /**
-     * 功效类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳
+     * 作用类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳
      */
     private String effectType;
 
@@ -48,11 +48,11 @@ public class Plan {
     /** 适用年龄最大值(岁) */
     private Integer ageMax;
 
-    /** 方案描述(最长200字符) */
+    /** 方案描述(最长500字符) */
     private String description;
 
     /**
-     * 创作人类型:1=管理员,2=普通用户,3=专家
+     * 创作人类型:1=管理员,2=用户,3=专家
      */
     private Integer authorType;
 

+ 7 - 19
code/backend/src/main/java/com/aijiuyi/admin/entity/UserCategory.java

@@ -6,39 +6,27 @@ import lombok.Data;
 import java.time.LocalDateTime;
 
 /**
- * 用户类实体(用户类型字典)
- * 对应数据库 user_category 表
- * code 字段与 app_user.user_type / user_profile.user_type 关联
+ * 用户类实体
+ * 对应数据库 app_user_category 表
+ * 仅用于 app_user.user_type = 1 的用户分类
  */
 @Data
-@TableName("user_category")
+@TableName("app_user_category")
 public class UserCategory {
 
     /** 主键ID */
     @TableId(type = IdType.ASSIGN_ID)
     private Long id;
 
-    /** 类别编码(与 user_type 字段对应,唯一) */
-    private Integer code;
+    /** 归属用户类型,固定为 1=用户 */
+    private Integer userType;
 
-    /** 类名称 */
+    /** 类名称 */
     private String name;
 
     /** 备注说明 */
     private String remark;
 
-    /** 前端标签颜色:primary/success/warning/danger/info */
-    private String tagType;
-
-    /** 排序(升序) */
-    private Integer sort;
-
-    /** 是否内置:1=内置(不可删除/改code),0=自定义 */
-    private Integer builtin;
-
-    /** 状态:1=启用,0=停用 */
-    private Integer status;
-
     /** 逻辑删除:0=未删除,1=已删除 */
     @TableLogic
     private Integer deleted;

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/entity/UserPlan.java

@@ -36,7 +36,7 @@ public class UserPlan {
     private Integer modeType;
 
     /**
-     * 专业模式功效类型(来自 plan.effect_type,用于前端按功效筛选展示)
+     * 专业模式作用类型(来自 plan.effect_type,用于前端按作用筛选展示)
      */
     @TableField(exist = false)
     private String effectType;

+ 9 - 1
code/backend/src/main/java/com/aijiuyi/admin/entity/UserProfile.java

@@ -33,9 +33,17 @@ public class UserProfile {
     /** 手机号(11位,唯一) */
     private String phone;
 
-    /** 用户类型:1=普通用户,2=专家,3=管理员 */
+    /** 用户类型:1=用户,2=专家,3=管理员 */
     private Integer userType;
 
+    /** 用户分类ID(非数据库字段,查询时从 app_user 关联填充) */
+    @TableField(exist = false)
+    private Long userCategoryId;
+
+    /** 用户分类名称(非数据库字段,查询时从 app_user_category 关联填充) */
+    @TableField(exist = false)
+    private String userCategoryName;
+
     /** 省级行政区划编码(GB/T 2260) */
     private String provinceCode;
 

+ 1 - 1
code/backend/src/main/java/com/aijiuyi/admin/mapper/UserCategoryMapper.java

@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
- * 用户类 Mapper 接口
+ * 用户类 Mapper 接口
  */
 @Mapper
 public interface UserCategoryMapper extends BaseMapper<UserCategory> {

+ 20 - 1
code/backend/src/main/java/com/aijiuyi/admin/service/AppUserService.java

@@ -1,6 +1,7 @@
 package com.aijiuyi.admin.service;
 
 import com.aijiuyi.admin.common.entity.Result;
+import com.aijiuyi.admin.controller.dto.AppLoginDTO;
 import com.aijiuyi.admin.controller.dto.AppUserQueryDTO;
 import com.aijiuyi.admin.controller.dto.LoginDTO;
 import com.aijiuyi.admin.entity.AppUser;
@@ -11,7 +12,7 @@ import java.util.Map;
 
 /**
  * 应用用户 Service
- * 统一用户中心:普通用户、专家、管理员均使用此表,通过 userType 区分
+ * 统一用户中心:用户、专家、管理员均使用此表,通过 userType 区分
  */
 public interface AppUserService extends IService<AppUser> {
 
@@ -32,6 +33,24 @@ public interface AppUserService extends IService<AppUser> {
      */
     void logout(String token);
 
+    /**
+     * App 端发送短信验证码
+     * 60 秒内同一手机号不重复发送,验证码 5 分钟有效
+     *
+     * @param phone 手机号
+     */
+    void sendSmsCode(String phone);
+
+    /**
+     * App 端手机号验证码登录
+     * 手机号未注册时自动注册为用户(userType=1)
+     * Token 永久有效(除非后台主动踢下线)
+     *
+     * @param loginDTO 手机号+验证码
+     * @return 包含 token 和用户基本信息的 Result
+     */
+    Result<Map<String, Object>> appLogin(AppLoginDTO loginDTO);
+
     /**
      * 分页查询应用用户列表
      *

+ 11 - 18
code/backend/src/main/java/com/aijiuyi/admin/service/UserCategoryService.java

@@ -8,12 +8,12 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import java.util.List;
 
 /**
- * 用户类 Service 接口
+ * 用户类 Service 接口
  */
 public interface UserCategoryService extends IService<UserCategory> {
 
     /**
-     * 分页查询用户类
+     * 分页查询用户
      *
      * @param queryDTO 查询条件
      * @return 分页结果
@@ -21,39 +21,32 @@ public interface UserCategoryService extends IService<UserCategory> {
     IPage<UserCategory> pageList(UserCategoryQueryDTO queryDTO);
 
     /**
-     * 查询所有启用的用户类(用于前端下拉选择)
+     * 查询所有用户类(用于前端下拉选择)
      *
-     * @return 启用的用户类列表(按 sort 升序)
+     * @return 用户类列表
      */
     List<UserCategory> listEnabled();
 
     /**
-     * 新增用户类
+     * 新增用户
      *
-     * @param category 类信息
+     * @param category 类信息
      */
     void addCategory(UserCategory category);
 
     /**
-     * 修改用户类
+     * 修改用户
      *
-     * @param category 类信息
+     * @param category 类信息
      */
     void updateCategory(UserCategory category);
 
     /**
-     * 删除用户类(逻辑删除)
-     * 内置类别和已被用户使用的类不可删除
+     * 删除用户类(逻辑删除)
+     * 已被用户使用的类不可删除
      *
-     * @param id 类ID
+     * @param id 类ID
      */
     void deleteCategory(Long id);
 
-    /**
-     * 修改用户类别状态(启用/停用)
-     *
-     * @param id     类别ID
-     * @param status 目标状态:1=启用,0=停用
-     */
-    void updateStatus(Long id, Integer status);
 }

+ 68 - 9
code/backend/src/main/java/com/aijiuyi/admin/service/impl/AdminServiceImpl.java

@@ -19,6 +19,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.DigestUtils;
 import org.springframework.util.StringUtils;
 
+import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -29,6 +31,8 @@ import java.util.List;
 @Service
 public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements AdminService {
 
+    private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$";
+
     @Autowired
     private AdminPermissionMapper adminPermissionMapper;
 
@@ -63,6 +67,7 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
      */
     @Override
     public void addAdmin(AppUser admin) {
+        normalizePhone(admin);
         // 校验用户名唯一性
         if (!StringUtils.hasText(admin.getUsername())) {
             throw new BusinessException(ResultCode.PARAM_ERROR);
@@ -71,6 +76,12 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
         if (usernameCount > 0) {
             throw new BusinessException(ResultCode.USERNAME_EXISTS);
         }
+        if (StringUtils.hasText(admin.getPhone())) {
+            long phoneCount = lambdaQuery().eq(AppUser::getPhone, admin.getPhone()).count();
+            if (phoneCount > 0) {
+                throw new BusinessException(ResultCode.PHONE_EXISTS);
+            }
+        }
         // 强制 userType=3(管理员)
         admin.setUserType(3);
         // 密码加密
@@ -93,6 +104,7 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
         if (exist == null || !Integer.valueOf(3).equals(exist.getUserType())) {
             throw new BusinessException(ResultCode.ADMIN_NOT_FOUND);
         }
+        boolean clearPhone = normalizePhone(admin);
         // 若修改了用户名,校验唯一性
         if (StringUtils.hasText(admin.getUsername()) && !admin.getUsername().equals(exist.getUsername())) {
             long count = lambdaQuery().eq(AppUser::getUsername, admin.getUsername()).count();
@@ -100,6 +112,13 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
                 throw new BusinessException(ResultCode.USERNAME_EXISTS);
             }
         }
+        // 若修改了手机号,校验格式与唯一性;传空字符串表示清空手机号
+        if (StringUtils.hasText(admin.getPhone()) && !admin.getPhone().equals(exist.getPhone())) {
+            long count = lambdaQuery().eq(AppUser::getPhone, admin.getPhone()).count();
+            if (count > 0) {
+                throw new BusinessException(ResultCode.PHONE_EXISTS);
+            }
+        }
         // 密码处理
         if (StringUtils.hasText(admin.getPassword())) {
             admin.setPassword(DigestUtils.md5DigestAsHex(admin.getPassword().getBytes()));
@@ -109,9 +128,34 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
         // 强制 userType 不变
         admin.setUserType(null);
         updateById(admin);
+        if (clearPhone) {
+            lambdaUpdate().eq(AppUser::getId, admin.getId()).set(AppUser::getPhone, null).update();
+        }
         LogUtil.info(AdminServiceImpl.class, "修改管理员[{}]", admin.getId());
     }
 
+    /**
+     * 手机号为选填项:有值时按中国大陆手机号校验,无值时规范为 null。
+     *
+     * @param admin 管理员实体
+     * @return true 表示本次请求显式清空手机号
+     */
+    private boolean normalizePhone(AppUser admin) {
+        if (admin.getPhone() == null) {
+            return false;
+        }
+        String phone = admin.getPhone().trim();
+        if (!StringUtils.hasText(phone)) {
+            admin.setPhone(null);
+            return true;
+        }
+        if (!phone.matches(PHONE_PATTERN)) {
+            throw new BusinessException(ResultCode.PHONE_FORMAT_INVALID);
+        }
+        admin.setPhone(phone);
+        return false;
+    }
+
     /**
      * 删除管理员(逻辑删除)
      * adminRole=1 超级管理员不可删除
@@ -127,7 +171,7 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
         if (Integer.valueOf(1).equals(admin.getAdminRole())) {
             throw new BusinessException(ResultCode.ADMIN_CANNOT_DELETE);
         }
-        removeById(id);
+        logicDeleteAdminsAndReleaseUniqueFields(Collections.singletonList(id));
         LogUtil.info(AdminServiceImpl.class, "删除管理员[{}]", id);
     }
 
@@ -139,20 +183,35 @@ public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implem
      */
     @Override
     public void batchDeleteAdmin(List<Long> ids) {
-        for (Long id : ids) {
-            AppUser admin = getById(id);
-            if (admin != null && Integer.valueOf(1).equals(admin.getAdminRole())) {
+        if (ids == null || ids.isEmpty()) {
+            throw new BusinessException(ResultCode.PARAM_ERROR);
+        }
+        List<AppUser> admins = lambdaQuery()
+                .in(AppUser::getId, ids)
+                .eq(AppUser::getUserType, 3)
+                .list();
+        for (AppUser admin : admins) {
+            if (Integer.valueOf(1).equals(admin.getAdminRole())) {
                 throw new BusinessException(ResultCode.ADMIN_CANNOT_DELETE);
             }
         }
-        // 仅删除 userType=3 的记录,防止误删其他类型用户
-        LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<AppUser>()
-                .in(AppUser::getId, ids)
-                .eq(AppUser::getUserType, 3);
-        remove(wrapper);
+        if (!admins.isEmpty()) {
+            logicDeleteAdminsAndReleaseUniqueFields(ids);
+        }
         LogUtil.info(AdminServiceImpl.class, "批量删除管理员,ID列表:{}", ids);
     }
 
+    private void logicDeleteAdminsAndReleaseUniqueFields(List<Long> ids) {
+        lambdaUpdate()
+                .in(AppUser::getId, ids)
+                .eq(AppUser::getUserType, 3)
+                .set(AppUser::getUsername, null)
+                .set(AppUser::getPhone, null)
+                .set(AppUser::getDeleted, 1)
+                .set(AppUser::getUpdateTime, LocalDateTime.now())
+                .update();
+    }
+
     /**
      * 修改管理员状态(禁用/启用)
      * 不允许禁用当前登录账号自身

+ 256 - 5
code/backend/src/main/java/com/aijiuyi/admin/service/impl/AppUserServiceImpl.java

@@ -7,13 +7,17 @@ import com.aijiuyi.admin.common.exception.BusinessException;
 import com.aijiuyi.admin.common.interceptor.TokenInterceptor;
 import com.aijiuyi.admin.common.util.LogUtil;
 import com.aijiuyi.admin.common.util.RedisUtil;
+import com.aijiuyi.admin.common.util.SmsUtil;
 import com.aijiuyi.admin.common.util.TokenUtil;
+import com.aijiuyi.admin.controller.dto.AppLoginDTO;
 import com.aijiuyi.admin.controller.dto.AppUserQueryDTO;
 import com.aijiuyi.admin.controller.dto.LoginDTO;
 import com.aijiuyi.admin.entity.AdminPermission;
 import com.aijiuyi.admin.entity.AppUser;
+import com.aijiuyi.admin.entity.UserCategory;
 import com.aijiuyi.admin.mapper.AdminPermissionMapper;
 import com.aijiuyi.admin.mapper.AppUserMapper;
+import com.aijiuyi.admin.mapper.UserCategoryMapper;
 import com.aijiuyi.admin.service.AppUserService;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -30,23 +34,58 @@ import java.util.Map;
 
 /**
  * 应用用户 Service 实现类
- * 统一用户中心:普通用户、专家、管理员均使用此表
+ * 统一用户中心:用户、专家、管理员均使用此表
  */
 @Service
 public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements AppUserService {
 
+    private static final int NORMAL_USER_TYPE = 1;
+
     /** Token 在 Redis 中的 key 前缀 */
     private static final String TOKEN_REDIS_PREFIX = TokenInterceptor.REDIS_TOKEN_KEY_PREFIX;
 
     /** Token 在 Redis 中的过期时间:3年(秒) */
     private static final long TOKEN_EXPIRE_SECONDS = 3L * 365 * 24 * 60 * 60;
 
+    /** 短信验证码 Redis key 前缀 */
+    private static final String SMS_CODE_PREFIX = "sms:code:";
+
+    /** 短信发送频率限制 Redis key 前缀 */
+    private static final String SMS_LIMIT_PREFIX = "sms:limit:";
+
+    /** 短信每日发送次数 Redis key 前缀 */
+    private static final String SMS_DAILY_COUNT_PREFIX = "sms:daily:";
+
+    /** 每日最大发送次数(阿里云限制同一手机号同一签名每天10条) */
+    private static final int SMS_DAILY_MAX_COUNT = 10;
+
+    /** 短信验证码有效期:5分钟(秒) */
+    private static final long SMS_CODE_EXPIRE_SECONDS = 5 * 60L;
+
+    /** 短信发送限流:60秒内不重复发送 */
+    private static final long SMS_LIMIT_SECONDS = 60L;
+
+    /**
+     * 短信模板 Code(阿里云短信控制台配置的模板)
+     * TODO: 替换为实际申请的验证码模板 Code
+     */
+    private static final String SMS_TEMPLATE_CODE = "SMS_316430242";
+
+    /** 开发调试用万能验证码(仅 dev/test 环境生效) */
+    private static final String DEV_BYPASS_CODE = "666666";
+
+    @org.springframework.beans.factory.annotation.Value("${spring.profiles.active:prod}")
+    private String activeProfile;
+
     @Autowired
     private RedisUtil redisUtil;
 
     @Autowired
     private AdminPermissionMapper adminPermissionMapper;
 
+    @Autowired
+    private UserCategoryMapper userCategoryMapper;
+
     /**
      * 管理员登录
      * 通过用户名+密码校验身份,生成 JWT Token 并存入 Redis
@@ -161,10 +200,15 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
         Page<AppUser> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
         LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<>();
         wrapper.like(StringUtils.hasText(queryDTO.getPhone()), AppUser::getPhone, queryDTO.getPhone())
-               .like(StringUtils.hasText(queryDTO.getNickname()), AppUser::getNickname, queryDTO.getNickname())
-               .eq(queryDTO.getUserType() != null, AppUser::getUserType, queryDTO.getUserType())
-               .eq(queryDTO.getStatus() != null, AppUser::getStatus, queryDTO.getStatus())
-               .orderByDesc(AppUser::getCreateTime);
+                .like(StringUtils.hasText(queryDTO.getNickname()), AppUser::getNickname, queryDTO.getNickname())
+                .eq(queryDTO.getStatus() != null, AppUser::getStatus, queryDTO.getStatus());
+        if (queryDTO.getUserCategoryId() != null) {
+            wrapper.eq(AppUser::getUserType, NORMAL_USER_TYPE)
+                    .eq(AppUser::getUserCategoryId, queryDTO.getUserCategoryId());
+        } else {
+            wrapper.eq(queryDTO.getUserType() != null, AppUser::getUserType, queryDTO.getUserType());
+        }
+        wrapper.orderByDesc(AppUser::getCreateTime);
         return page(page, wrapper);
     }
 
@@ -195,6 +239,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
      */
     @Override
     public void addUser(AppUser appUser) {
+        normalizeUserCategory(appUser.getUserType(), appUser);
         // 手机号唯一性校验(手机号不为空时校验)
         if (StringUtils.hasText(appUser.getPhone())) {
             long phoneCount = lambdaQuery().eq(AppUser::getPhone, appUser.getPhone()).count();
@@ -230,6 +275,9 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
         if (exist == null) {
             throw new BusinessException(ResultCode.APP_USER_NOT_FOUND);
         }
+        Integer targetUserType = appUser.getUserType() != null ? appUser.getUserType() : exist.getUserType();
+        normalizeUserCategory(targetUserType, appUser);
+        boolean shouldClearUserCategory = !Integer.valueOf(NORMAL_USER_TYPE).equals(targetUserType);
         // 如果修改了手机号,校验唯一性
         if (StringUtils.hasText(appUser.getPhone()) && !appUser.getPhone().equals(exist.getPhone())) {
             long count = lambdaQuery().eq(AppUser::getPhone, appUser.getPhone()).count();
@@ -252,9 +300,53 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
             appUser.setPassword(null);
         }
         updateById(appUser);
+        if (shouldClearUserCategory) {
+            lambdaUpdate()
+                    .set(AppUser::getUserCategoryId, null)
+                    .eq(AppUser::getId, appUser.getId())
+                    .update();
+        }
         LogUtil.info(AppUserServiceImpl.class, "修改应用用户[{}]", appUser.getId());
     }
 
+    private void normalizeUserCategory(Integer userType, AppUser appUser) {
+        if (Integer.valueOf(NORMAL_USER_TYPE).equals(userType)) {
+            if (appUser.getUserCategoryId() == null) {
+                appUser.setUserCategoryId(getDefaultUserCategoryId());
+            } else {
+                validateUserCategory(appUser.getUserCategoryId());
+            }
+            return;
+        }
+        appUser.setUserCategoryId(null);
+    }
+
+    private Long getDefaultUserCategoryId() {
+        UserCategory category = userCategoryMapper.selectOne(new LambdaQueryWrapper<UserCategory>()
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .eq(UserCategory::getName, "普通用户")
+                .last("LIMIT 1"));
+        if (category == null) {
+            category = userCategoryMapper.selectOne(new LambdaQueryWrapper<UserCategory>()
+                    .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                    .orderByAsc(UserCategory::getId)
+                    .last("LIMIT 1"));
+        }
+        return category != null ? category.getId() : null;
+    }
+
+    private void validateUserCategory(Long userCategoryId) {
+        if (userCategoryId == null) {
+            return;
+        }
+        long count = userCategoryMapper.selectCount(new LambdaQueryWrapper<UserCategory>()
+                .eq(UserCategory::getId, userCategoryId)
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE));
+        if (count <= 0) {
+            throw new BusinessException(ResultCode.USER_CATEGORY_NOT_FOUND);
+        }
+    }
+
     /**
      * 删除应用用户(逻辑删除)
      *
@@ -269,4 +361,163 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
         removeById(id);
         LogUtil.info(AppUserServiceImpl.class, "删除应用用户[{}]", id);
     }
+
+    /**
+     * App 端发送短信验证码
+     * 处理逻辑:
+     * 1. 检60秒限流(同一手机号内不重复发送)
+     * 2. 生成6位随机验证码
+     * 3. 调用阿里云短信发送
+     * 4. 将验证码存入 Redis,有效期 5 分钟
+     *
+     * @param phone 手机号
+     */
+    @Override
+    public void sendSmsCode(String phone) {
+        // dev/test 环境不真正发短信,直接存固定验证码到 Redis
+        if (isDevMode()) {
+            redisUtil.set(SMS_CODE_PREFIX + phone, DEV_BYPASS_CODE, SMS_CODE_EXPIRE_SECONDS);
+            LogUtil.info(AppUserServiceImpl.class, "[开发模式] 跳过真实短信发送, phone={}, code={}", phone, DEV_BYPASS_CODE);
+            return;
+        }
+    
+        // 检60秒内是否已发送(限流)
+        String limitKey = SMS_LIMIT_PREFIX + phone;
+        if (redisUtil.hasKey(limitKey)) {
+            throw new BusinessException(ResultCode.SMS_SEND_TOO_FREQUENT);
+        }
+    
+        // 检查每日发送次数是否已达上限
+        String dailyKey = SMS_DAILY_COUNT_PREFIX + phone;
+        Object dailyCount = redisUtil.get(dailyKey);
+        int count = dailyCount != null ? Integer.parseInt(String.valueOf(dailyCount)) : 0;
+        if (count >= SMS_DAILY_MAX_COUNT) {
+            throw new BusinessException(ResultCode.SMS_DAILY_LIMIT);
+        }
+    
+        // 生或6位随机验证码
+        String code = String.format("%06d", (int) (Math.random() * 1000000));
+
+        // 发送短信
+        HashMap<String, String> params = new HashMap<>(2);
+        params.put("code", code);
+        boolean sent = SmsUtil.send(phone, SMS_TEMPLATE_CODE, params);
+        if (!sent) {
+            throw new BusinessException(ResultCode.SMS_SEND_FAILED);
+        }
+
+        // 存入 Redis(5 分钟有效)
+        redisUtil.set(SMS_CODE_PREFIX + phone, code, SMS_CODE_EXPIRE_SECONDS);
+        // 设置60秒限流(设置在发送成功后)
+        redisUtil.set(limitKey, "1", SMS_LIMIT_SECONDS);
+        // 累加每日发送次数(过期时间到当天结束)
+        if (redisUtil.hasKey(dailyKey)) {
+            redisUtil.set(dailyKey, String.valueOf(count + 1), redisUtil.getExpire(dailyKey));
+        } else {
+            // 计算到当天23:59:59的剩余秒数
+            long secondsUntilMidnight = java.time.Duration.between(
+                    java.time.LocalDateTime.now(),
+                    java.time.LocalDate.now().plusDays(1).atStartOfDay()
+            ).getSeconds();
+            redisUtil.set(dailyKey, "1", secondsUntilMidnight);
+        }
+
+        LogUtil.info(AppUserServiceImpl.class, "短信验证码发送成功: phone={}", phone);
+    }
+
+    /**
+     * 判断当前是否为开发/测试环境
+     */
+    private boolean isDevMode() {
+        return "dev".equals(activeProfile) || "liyu".equals(activeProfile);
+    }
+
+    /**
+     * App 端手机号验证码登录
+     * 处理逻辑:
+     * 1. 校验验证码(是否存在、是否正确)
+     * 2. 验证成功后删除 Redis 中的验证码(一次性)
+     * 3. 查询用户,不存在则自动注册
+     * 4. 生成 JWT Token 并存入 Redis(3 年有效)
+     * 5. 更新最后登录时间
+     *
+     * @param loginDTO 手机号+验证码
+     * @return 包含 token 和用户信息的 Result
+     */
+    @Override
+    public Result<Map<String, Object>> appLogin(AppLoginDTO loginDTO) {
+        String phone = loginDTO.getPhone();
+        String inputCode = loginDTO.getCode();
+
+        // 检查验证码是否存在(是否已过期)
+        String codeKey = SMS_CODE_PREFIX + phone;
+        Object storedCode = redisUtil.get(codeKey);
+        if (storedCode == null) {
+            throw new BusinessException(ResultCode.SMS_CODE_EXPIRED);
+        }
+
+        // 检查验证码是否正确
+        if (!inputCode.equals(String.valueOf(storedCode))) {
+            throw new BusinessException(ResultCode.SMS_CODE_ERROR);
+        }
+
+        // 验证成功,删除验证码(一次性)
+        redisUtil.del(codeKey);
+
+        // 查询用户
+        AppUser user = lambdaQuery().eq(AppUser::getPhone, phone).one();
+
+        if (user == null) {
+            // 自动注册新用户
+            user = new AppUser();
+            user.setPhone(phone);
+            user.setNickname("用户" + phone.substring(7));
+            user.setUserType(1);
+            user.setUserCategoryId(getDefaultUserCategoryId());
+            user.setStatus(1);
+            user.setLoginType(1);
+            save(user);
+            // 注册成功后生成 AppId(前缀AJY + 8位用户ID)
+            user.setAppId("AJY" + String.format("%08d", user.getId()));
+            updateById(user);
+            LogUtil.info(AppUserServiceImpl.class, "App 新用户自动注册: phone={}, appId={}", phone, user.getAppId());
+        } else {
+            // 校验用户状态
+            if (user.getStatus() != null && user.getStatus() == 0) {
+                throw new BusinessException(ResultCode.APP_USER_DISABLED);
+            }
+        }
+
+        // 生成 JWT Token(username 字段存手机号)
+        String token = TokenUtil.generateToken(user.getId(), phone, user.getUserType());
+
+        // Token 存入 Redis(3 年有效)
+        redisUtil.set(TOKEN_REDIS_PREFIX + token, user.getId(), TOKEN_EXPIRE_SECONDS);
+
+        // 更新最后登录时间和登录方式
+        AppUser updateLogin = new AppUser();
+        updateLogin.setId(user.getId());
+        updateLogin.setLastLoginTime(LocalDateTime.now());
+        updateLogin.setLoginType(1);
+        updateById(updateLogin);
+
+        // 写入请求上下文(供 LogAspect 正确记录操作人)
+        RequestContext.setUserId(user.getId());
+        RequestContext.setUserName(phone);
+        RequestContext.setUserType(user.getUserType());
+
+        LogUtil.info(AppUserServiceImpl.class, "App 用户[{}]登录成功", phone);
+
+        Map<String, Object> resultData = new HashMap<>(8);
+        resultData.put("token", token);
+        resultData.put("userId", user.getId());
+        resultData.put("phone", user.getPhone());
+        resultData.put("nickname", user.getNickname());
+        resultData.put("avatar", user.getAvatar());
+        resultData.put("appId", user.getAppId());
+        resultData.put("userType", user.getUserType());
+        resultData.put("userCategoryId", user.getUserCategoryId());
+
+        return Result.success(resultData);
+    }
 }

+ 11 - 6
code/backend/src/main/java/com/aijiuyi/admin/service/impl/ContentArticleServiceImpl.java

@@ -119,7 +119,13 @@ public class ContentArticleServiceImpl extends ServiceImpl<ContentArticleMapper,
         if (!isSuperAdmin() && !exist.getCreateBy().equals(RequestContext.getUserId())) {
             throw new BusinessException(ResultCode.ARTICLE_PERMISSION_DENIED);
         }
-        if (!STATUS_DRAFT.equals(exist.getStatus()) && !STATUS_REJECTED.equals(exist.getStatus())) {
+        // 已经是待审核状态,无需重复提交
+        if (STATUS_PENDING.equals(exist.getStatus())) {
+            return;
+        }
+        // 允许草稿、已拒绝、已发布、已下架状态提交审核
+        if (!STATUS_DRAFT.equals(exist.getStatus()) && !STATUS_REJECTED.equals(exist.getStatus())
+                && !STATUS_PUBLISHED.equals(exist.getStatus()) && !STATUS_OFFLINE.equals(exist.getStatus())) {
             throw new BusinessException(ResultCode.ARTICLE_STATUS_INVALID);
         }
         ContentArticle update = new ContentArticle();
@@ -239,10 +245,7 @@ public class ContentArticleServiceImpl extends ServiceImpl<ContentArticleMapper,
     private void checkUpdatePermission(ContentArticle exist) {
         boolean superAdmin = isSuperAdmin();
         if (superAdmin) {
-            // 已发布的文章必须先下架才能编辑
-            if (STATUS_PUBLISHED.equals(exist.getStatus())) {
-                throw new BusinessException(ResultCode.ARTICLE_STATUS_INVALID);
-            }
+            // 系统管理员可编辑任何状态的文章
             return;
         }
         // 普通管理员仅可编辑自己的文章
@@ -250,7 +253,9 @@ public class ContentArticleServiceImpl extends ServiceImpl<ContentArticleMapper,
         if (userId == null || !userId.equals(exist.getCreateBy())) {
             throw new BusinessException(ResultCode.ARTICLE_PERMISSION_DENIED);
         }
-        if (!STATUS_DRAFT.equals(exist.getStatus()) && !STATUS_REJECTED.equals(exist.getStatus())) {
+        // 普通管理员可编辑草稿、已拒绝、已发布状态的文章
+        if (!STATUS_DRAFT.equals(exist.getStatus()) && !STATUS_REJECTED.equals(exist.getStatus())
+                && !STATUS_PUBLISHED.equals(exist.getStatus())) {
             throw new BusinessException(ResultCode.ARTICLE_STATUS_INVALID);
         }
     }

+ 131 - 4
code/backend/src/main/java/com/aijiuyi/admin/service/impl/PlanServiceImpl.java

@@ -1,17 +1,19 @@
 package com.aijiuyi.admin.service.impl;
 
 import com.aijiuyi.admin.common.constant.ResultCode;
+import com.aijiuyi.admin.common.context.RequestContext;
 import com.aijiuyi.admin.common.exception.BusinessException;
 import com.aijiuyi.admin.common.util.LogUtil;
 import com.aijiuyi.admin.controller.dto.PlanQueryDTO;
 import com.aijiuyi.admin.controller.dto.PlanSaveDTO;
+import com.aijiuyi.admin.entity.AppUser;
 import com.aijiuyi.admin.entity.Plan;
 import com.aijiuyi.admin.entity.PlanStep;
 import com.aijiuyi.admin.mapper.PlanMapper;
 import com.aijiuyi.admin.mapper.PlanStepMapper;
+import com.aijiuyi.admin.service.AppUserService;
 import com.aijiuyi.admin.service.PlanService;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -32,9 +34,18 @@ import java.util.UUID;
 @Service
 public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements PlanService {
 
+    private static final String SYSTEM_ONE_CLICK_PLAN_CODE = "S001";
+    private static final int MODE_TYPE_ONE_CLICK = 1;
+    private static final int MODE_TYPE_PROFESSIONAL = 2;
+    private static final int MODE_TYPE_CUSTOM = 3;
+    private static final int MODE_TYPE_MASTER = 4;
+
     @Autowired
     private PlanStepMapper planStepMapper;
 
+    @Autowired
+    private AppUserService appUserService;
+
     /**
      * 分页查询方案列表
      *
@@ -106,6 +117,8 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         String planCode = "S" + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
         Plan plan = new Plan();
         BeanUtils.copyProperties(dto, plan);
+        applyAddPlanModeRules(plan);
+        fillCurrentAuthor(plan);
         plan.setPlanCode(planCode);
         plan.setStatus(0); // 草稿
         plan.setUseCount(0);
@@ -127,6 +140,10 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         if (plan == null) {
             throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
         }
+        boolean systemPresetPlan = isSystemPresetPlan(plan);
+        String originalPlanCode = plan.getPlanCode();
+        String originalName = plan.getName();
+        Integer originalStatus = plan.getStatus();
         // 方案名称唯一性校验(排除自身)
         long nameCount = lambdaQuery()
                 .eq(Plan::getName, dto.getName())
@@ -136,6 +153,8 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
             throw new BusinessException(ResultCode.PLAN_NAME_EXISTS);
         }
         BeanUtils.copyProperties(dto, plan);
+        applyUpdatePlanModeRules(plan, originalPlanCode, originalName, originalStatus, systemPresetPlan);
+        fillCurrentAuthor(plan);
         updateById(plan);
         // 删除旧步骤(逻辑删除)
         planStepMapper.delete(new LambdaQueryWrapper<PlanStep>().eq(PlanStep::getPlanId, dto.getId()));
@@ -146,6 +165,107 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         LogUtil.info(PlanServiceImpl.class, "修改方案[{}]", dto.getId());
     }
 
+    /**
+     * 方案创作人固定为当前登录用户,避免前端传入其他用户覆盖。
+     */
+    private void fillCurrentAuthor(Plan plan) {
+        Long currentUserId = RequestContext.getUserId();
+        AppUser currentUser = currentUserId != null ? appUserService.getById(currentUserId) : null;
+        plan.setAuthorId(currentUserId);
+        plan.setAuthorName(resolveAuthorName(currentUser));
+        plan.setAuthorType(resolvePlanAuthorType(currentUser));
+    }
+
+    private String resolveAuthorName(AppUser currentUser) {
+        if (currentUser == null) {
+            String userName = RequestContext.getUserName();
+            return StringUtils.hasText(userName) ? userName : "ADMIN";
+        }
+        if (StringUtils.hasText(currentUser.getNickname())) {
+            return currentUser.getNickname();
+        }
+        if (StringUtils.hasText(currentUser.getUsername())) {
+            return currentUser.getUsername();
+        }
+        return "ADMIN";
+    }
+
+    private Integer resolvePlanAuthorType(AppUser currentUser) {
+        Integer userType = currentUser != null ? currentUser.getUserType() : RequestContext.getUserType();
+        if (Integer.valueOf(1).equals(userType)) {
+            return 2;
+        }
+        if (Integer.valueOf(2).equals(userType)) {
+            return 3;
+        }
+        return 1;
+    }
+
+    private void applyAddPlanModeRules(Plan plan) {
+        if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
+                && !Integer.valueOf(MODE_TYPE_MASTER).equals(plan.getModeType())) {
+            throw new BusinessException(ResultCode.PARAM_ERROR);
+        }
+        plan.setEffectType("");
+    }
+
+    private void applyUpdatePlanModeRules(Plan plan, String originalPlanCode, String originalName, Integer originalStatus, boolean systemPresetPlan) {
+        if (systemPresetPlan) {
+            plan.setPlanCode(originalPlanCode);
+            plan.setName(originalName);
+            plan.setStatus(originalStatus);
+            if (SYSTEM_ONE_CLICK_PLAN_CODE.equals(originalPlanCode)) {
+                plan.setModeType(MODE_TYPE_ONE_CLICK);
+                plan.setEffectType("");
+                return;
+            }
+            plan.setModeType(MODE_TYPE_PROFESSIONAL);
+            plan.setEffectType(getProfessionalPresetEffectType(originalPlanCode));
+            return;
+        }
+        if (!Integer.valueOf(MODE_TYPE_PROFESSIONAL).equals(plan.getModeType())) {
+            plan.setEffectType("");
+        }
+    }
+
+    private boolean isSystemPresetPlan(Plan plan) {
+        return plan != null
+                && StringUtils.hasText(plan.getPlanCode())
+                && plan.getPlanCode().matches("S00[1-9]");
+    }
+
+    private String getProfessionalPresetEffectType(String planCode) {
+        if (!StringUtils.hasText(planCode)) {
+            return "";
+        }
+        switch (planCode) {
+            case "S002":
+                return "驱寒";
+            case "S003":
+                return "祛湿";
+            case "S004":
+                return "祛风";
+            case "S005":
+                return "化瘀";
+            case "S006":
+                return "活血";
+            case "S007":
+                return "化痰";
+            case "S008":
+                return "养颜";
+            case "S009":
+                return "扶阳";
+            default:
+                return "";
+        }
+    }
+
+    private void checkSystemPresetPlanOperation(Plan plan) {
+        if (isSystemPresetPlan(plan)) {
+            throw new BusinessException(ResultCode.PLAN_SYSTEM_PRESET_FORBIDDEN);
+        }
+    }
+
     /**
      * 删除方案(逻辑删除,仅草稿状态可删)
      *
@@ -158,7 +278,8 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         if (plan == null) {
             throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
         }
-        if (plan.getStatus() != 0) {
+        checkSystemPresetPlanOperation(plan);
+        if (!Integer.valueOf(0).equals(plan.getStatus())) {
             throw new BusinessException(ResultCode.PLAN_CANNOT_DELETE);
         }
         removeById(id);
@@ -195,6 +316,7 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         if (plan == null) {
             throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
         }
+        checkSystemPresetPlanOperation(plan);
         lambdaUpdate().eq(Plan::getId, id).set(Plan::getStatus, 1).update();
         LogUtil.info(PlanServiceImpl.class, "发布方案[{}]", id);
     }
@@ -210,7 +332,9 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         if (ids == null || ids.isEmpty()) {
             throw new BusinessException(ResultCode.PARAM_ERROR);
         }
-        update(new LambdaUpdateWrapper<Plan>().in(Plan::getId, ids).set(Plan::getStatus, 1));
+        for (Long id : ids) {
+            publishPlan(id);
+        }
         LogUtil.info(PlanServiceImpl.class, "批量发布方案,共[{}]条", ids.size());
     }
 
@@ -225,6 +349,7 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         if (plan == null) {
             throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
         }
+        checkSystemPresetPlanOperation(plan);
         lambdaUpdate().eq(Plan::getId, id).set(Plan::getStatus, 2).update();
         LogUtil.info(PlanServiceImpl.class, "下架方案[{}]", id);
     }
@@ -240,7 +365,9 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         if (ids == null || ids.isEmpty()) {
             throw new BusinessException(ResultCode.PARAM_ERROR);
         }
-        update(new LambdaUpdateWrapper<Plan>().in(Plan::getId, ids).set(Plan::getStatus, 2));
+        for (Long id : ids) {
+            offshelfPlan(id);
+        }
         LogUtil.info(PlanServiceImpl.class, "批量下架方案,共[{}]条", ids.size());
     }
 

+ 52 - 91
code/backend/src/main/java/com/aijiuyi/admin/service/impl/UserCategoryServiceImpl.java

@@ -6,10 +6,8 @@ import com.aijiuyi.admin.common.util.LogUtil;
 import com.aijiuyi.admin.controller.dto.UserCategoryQueryDTO;
 import com.aijiuyi.admin.entity.AppUser;
 import com.aijiuyi.admin.entity.UserCategory;
-import com.aijiuyi.admin.entity.UserProfile;
 import com.aijiuyi.admin.mapper.AppUserMapper;
 import com.aijiuyi.admin.mapper.UserCategoryMapper;
-import com.aijiuyi.admin.mapper.UserProfileMapper;
 import com.aijiuyi.admin.service.UserCategoryService;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -19,149 +17,112 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
- * 用户类 Service 实现类
+ * 用户类 Service 实现类
  */
 @Service
 public class UserCategoryServiceImpl extends ServiceImpl<UserCategoryMapper, UserCategory>
         implements UserCategoryService {
 
-    @Autowired
-    private AppUserMapper appUserMapper;
+    private static final int NORMAL_USER_TYPE = 1;
 
     @Autowired
-    private UserProfileMapper userProfileMapper;
+    private AppUserMapper appUserMapper;
 
     @Override
     public IPage<UserCategory> pageList(UserCategoryQueryDTO queryDTO) {
         Page<UserCategory> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
         LambdaQueryWrapper<UserCategory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(UserCategory::getUserType, NORMAL_USER_TYPE);
         if (StringUtils.hasText(queryDTO.getKeyword())) {
-            String kw = queryDTO.getKeyword();
-            wrapper.and(w -> w.like(UserCategory::getName, kw).or().like(UserCategory::getRemark, kw));
+            String keyword = queryDTO.getKeyword();
+            wrapper.and(w -> w.like(UserCategory::getName, keyword)
+                    .or()
+                    .like(UserCategory::getRemark, keyword));
         }
-        wrapper.eq(queryDTO.getStatus() != null, UserCategory::getStatus, queryDTO.getStatus())
-               .orderByAsc(UserCategory::getSort)
-               .orderByAsc(UserCategory::getCode);
+        wrapper.orderByDesc(UserCategory::getCreateTime)
+                .orderByDesc(UserCategory::getId);
         return page(page, wrapper);
     }
 
     @Override
     public List<UserCategory> listEnabled() {
         return list(new LambdaQueryWrapper<UserCategory>()
-                .eq(UserCategory::getStatus, 1)
-                .orderByAsc(UserCategory::getSort)
-                .orderByAsc(UserCategory::getCode));
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .orderByDesc(UserCategory::getCreateTime)
+                .orderByDesc(UserCategory::getId));
     }
 
     @Override
     public void addCategory(UserCategory category) {
-        if (category.getCode() == null) {
-            throw new BusinessException(ResultCode.PARAM_ERROR);
-        }
         if (!StringUtils.hasText(category.getName())) {
             throw new BusinessException(ResultCode.PARAM_ERROR);
         }
-        // 编码唯一性
-        long codeCount = lambdaQuery().eq(UserCategory::getCode, category.getCode()).count();
-        if (codeCount > 0) {
-            throw new BusinessException(ResultCode.USER_CATEGORY_CODE_EXISTS);
-        }
-        // 名称唯一性
-        long nameCount = lambdaQuery().eq(UserCategory::getName, category.getName()).count();
+        String name = category.getName().trim();
+        long nameCount = lambdaQuery()
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .eq(UserCategory::getName, name)
+                .count();
         if (nameCount > 0) {
             throw new BusinessException(ResultCode.USER_CATEGORY_NAME_EXISTS);
         }
-        // 自定义类别强制 builtin=0
-        category.setBuiltin(0);
-        if (category.getStatus() == null) {
-            category.setStatus(1);
-        }
-        if (category.getSort() == null) {
-            category.setSort(0);
-        }
-        if (!StringUtils.hasText(category.getTagType())) {
-            category.setTagType("primary");
-        }
+        category.setUserType(NORMAL_USER_TYPE);
+        category.setName(name);
         save(category);
-        LogUtil.info(UserCategoryServiceImpl.class, "新增用户类别 code=[{}] name=[{}]",
-                category.getCode(), category.getName());
+        LogUtil.info(UserCategoryServiceImpl.class, "新增用户分类 name=[{}]", category.getName());
     }
 
     @Override
     public void updateCategory(UserCategory category) {
-        if (category.getId() == null) {
+        if (category.getId() == null || !StringUtils.hasText(category.getName())) {
             throw new BusinessException(ResultCode.PARAM_ERROR);
         }
-        UserCategory exist = getById(category.getId());
+        UserCategory exist = lambdaQuery()
+                .eq(UserCategory::getId, category.getId())
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .one();
         if (exist == null) {
             throw new BusinessException(ResultCode.USER_CATEGORY_NOT_FOUND);
         }
-        // 内置类别不允许修改 code
-        if (Integer.valueOf(1).equals(exist.getBuiltin())
-                && category.getCode() != null
-                && !category.getCode().equals(exist.getCode())) {
-            throw new BusinessException(ResultCode.USER_CATEGORY_BUILTIN_FORBIDDEN);
-        }
-        // 编码唯一性(排除自身)
-        if (category.getCode() != null && !category.getCode().equals(exist.getCode())) {
-            long codeCount = lambdaQuery()
-                    .eq(UserCategory::getCode, category.getCode())
-                    .ne(UserCategory::getId, category.getId())
-                    .count();
-            if (codeCount > 0) {
-                throw new BusinessException(ResultCode.USER_CATEGORY_CODE_EXISTS);
-            }
-        }
-        // 名称唯一性(排除自身)
-        if (StringUtils.hasText(category.getName()) && !category.getName().equals(exist.getName())) {
-            long nameCount = lambdaQuery()
-                    .eq(UserCategory::getName, category.getName())
-                    .ne(UserCategory::getId, category.getId())
-                    .count();
-            if (nameCount > 0) {
-                throw new BusinessException(ResultCode.USER_CATEGORY_NAME_EXISTS);
-            }
+        String name = category.getName().trim();
+        long nameCount = lambdaQuery()
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .eq(UserCategory::getName, name)
+                .ne(UserCategory::getId, category.getId())
+                .count();
+        if (nameCount > 0) {
+            throw new BusinessException(ResultCode.USER_CATEGORY_NAME_EXISTS);
         }
-        // 内置标识不可篡改
-        category.setBuiltin(exist.getBuiltin());
-        updateById(category);
-        LogUtil.info(UserCategoryServiceImpl.class, "修改用户类别[{}]", category.getId());
+        lambdaUpdate()
+                .set(UserCategory::getName, name)
+                .set(UserCategory::getRemark, category.getRemark())
+                .set(UserCategory::getUpdateTime, LocalDateTime.now())
+                .eq(UserCategory::getId, category.getId())
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .update();
+        LogUtil.info(UserCategoryServiceImpl.class, "修改用户分类[{}]", category.getId());
     }
 
     @Override
     public void deleteCategory(Long id) {
-        UserCategory exist = getById(id);
+        UserCategory exist = lambdaQuery()
+                .eq(UserCategory::getId, id)
+                .eq(UserCategory::getUserType, NORMAL_USER_TYPE)
+                .one();
         if (exist == null) {
             throw new BusinessException(ResultCode.USER_CATEGORY_NOT_FOUND);
         }
-        if (Integer.valueOf(1).equals(exist.getBuiltin())) {
-            throw new BusinessException(ResultCode.USER_CATEGORY_BUILTIN_FORBIDDEN);
-        }
-        // 是否仍被使用
         long appUserCount = appUserMapper.selectCount(
-                new LambdaQueryWrapper<AppUser>().eq(AppUser::getUserType, exist.getCode()));
-        long profileCount = userProfileMapper.selectCount(
-                new LambdaQueryWrapper<UserProfile>().eq(UserProfile::getUserType, exist.getCode()));
-        if (appUserCount > 0 || profileCount > 0) {
+                new LambdaQueryWrapper<AppUser>()
+                        .eq(AppUser::getUserType, NORMAL_USER_TYPE)
+                        .eq(AppUser::getUserCategoryId, id));
+        if (appUserCount > 0) {
             throw new BusinessException(ResultCode.USER_CATEGORY_IN_USE);
         }
         removeById(id);
-        LogUtil.info(UserCategoryServiceImpl.class, "删除用户类别[{}] code=[{}]", id, exist.getCode());
-    }
-
-    @Override
-    public void updateStatus(Long id, Integer status) {
-        UserCategory exist = getById(id);
-        if (exist == null) {
-            throw new BusinessException(ResultCode.USER_CATEGORY_NOT_FOUND);
-        }
-        UserCategory update = new UserCategory();
-        update.setId(id);
-        update.setStatus(status);
-        updateById(update);
-        LogUtil.info(UserCategoryServiceImpl.class, "修改用户类别[{}]状态为[{}]", id, status);
+        LogUtil.info(UserCategoryServiceImpl.class, "删除用户分类[{}]", id);
     }
 }

+ 43 - 3
code/backend/src/main/java/com/aijiuyi/admin/service/impl/UserProfileServiceImpl.java

@@ -6,8 +6,10 @@ import com.aijiuyi.admin.common.util.LogUtil;
 import com.aijiuyi.admin.controller.dto.UserProfileDTO;
 import com.aijiuyi.admin.controller.dto.UserProfileQueryDTO;
 import com.aijiuyi.admin.entity.AppUser;
+import com.aijiuyi.admin.entity.UserCategory;
 import com.aijiuyi.admin.entity.UserProfile;
 import com.aijiuyi.admin.mapper.AppUserMapper;
+import com.aijiuyi.admin.mapper.UserCategoryMapper;
 import com.aijiuyi.admin.mapper.UserProfileMapper;
 import com.aijiuyi.admin.service.UserAcupointService;
 import com.aijiuyi.admin.service.UserProfileService;
@@ -30,12 +32,17 @@ import java.util.List;
 @Service
 public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserProfile> implements UserProfileService {
 
+    private static final int NORMAL_USER_TYPE = 1;
+
     @Autowired
     private UserAcupointService userAcupointService;
 
     @Autowired
     private AppUserMapper appUserMapper;
 
+    @Autowired
+    private UserCategoryMapper userCategoryMapper;
+
     @Override
     public IPage<UserProfile> pageList(UserProfileQueryDTO queryDTO) {
         Page<UserProfile> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
@@ -52,7 +59,8 @@ public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserP
         save(profile);
         LogUtil.info(UserProfileServiceImpl.class, "新增子用户[{}],手机号[{}]", dto.getName(), dto.getPhone());
 
-        syncAppUserType(dto.getPhone(), dto.getUserType());
+        syncAppUserType(dto.getPhone(), NORMAL_USER_TYPE);
+        syncAppUserCategory(dto.getPhone(), dto.getUserCategoryId());
 
         regenerateUserAcupoints(profile.getId(), "新增");
     }
@@ -78,7 +86,8 @@ public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserP
 
         // 优先以传入手机号同步;未传入时回退到已存记录的手机号
         String syncPhone = StringUtils.hasText(dto.getPhone()) ? dto.getPhone() : exist.getPhone();
-        syncAppUserType(syncPhone, dto.getUserType());
+        syncAppUserType(syncPhone, NORMAL_USER_TYPE);
+        syncAppUserCategory(syncPhone, dto.getUserCategoryId());
 
         regenerateUserAcupoints(dto.getId(), "修改");
     }
@@ -210,6 +219,37 @@ public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserP
                 "同步 app_user[{}] 用户类型为[{}](手机号[{}])", appUser.getId(), userType, phone);
     }
 
+    /**
+     * 同步用户分类到 app_user:用户列表只维护身份为“用户”的档案,分类存储在 app_user.user_category_id。
+     */
+    private void syncAppUserCategory(String phone, Long userCategoryId) {
+        if (!StringUtils.hasText(phone) || userCategoryId == null) {
+            return;
+        }
+        long categoryCount = userCategoryMapper.selectCount(
+                new LambdaQueryWrapper<UserCategory>()
+                        .eq(UserCategory::getId, userCategoryId)
+                        .eq(UserCategory::getUserType, NORMAL_USER_TYPE));
+        if (categoryCount <= 0) {
+            throw new BusinessException(ResultCode.USER_CATEGORY_NOT_FOUND);
+        }
+
+        AppUser appUser = appUserMapper.selectOne(
+                new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhone, phone));
+        if (appUser == null || !Integer.valueOf(NORMAL_USER_TYPE).equals(appUser.getUserType())) {
+            return;
+        }
+        if (userCategoryId.equals(appUser.getUserCategoryId())) {
+            return;
+        }
+        AppUser update = new AppUser();
+        update.setId(appUser.getId());
+        update.setUserCategoryId(userCategoryId);
+        appUserMapper.updateById(update);
+        LogUtil.info(UserProfileServiceImpl.class,
+                "同步 app_user[{}] 用户分类为[{}](手机号[{}])", appUser.getId(), userCategoryId, phone);
+    }
+
     private UserProfile buildProfileFromDto(UserProfileDTO dto) {
         UserProfile profile = new UserProfile();
         profile.setUserId(dto.getUserId());
@@ -217,7 +257,7 @@ public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserP
         profile.setGender(dto.getGender());
         profile.setAge(dto.getAge());
         profile.setPhone(dto.getPhone());
-        profile.setUserType(dto.getUserType());
+        profile.setUserType(NORMAL_USER_TYPE);
         profile.setProvinceCode(dto.getProvinceCode());
         profile.setCityCode(dto.getCityCode());
         profile.setDistrictCode(dto.getDistrictCode());

+ 67 - 0
code/backend/src/main/resources/application-liyu.yml

@@ -0,0 +1,67 @@
+spring:
+  # ==================== 数据库配置 ====================
+  datasource:
+    url: jdbc:mysql://localhost:3306/aijiuyi_admin?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
+    username: root
+    password: lilishop
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    hikari:
+      # 最大连接池大小
+      maximum-pool-size: 10
+      # 最小空闲连接
+      minimum-idle: 5
+      # 连接超时时间(ms)
+      connection-timeout: 30000
+
+  # ==================== Redis 配置 ====================
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    # 密码(无密码留空,有密码填写)
+    password: lilishop
+    database: 0
+    # 连接超时时间
+    timeout: 10000ms
+    lettuce:
+      pool:
+        # 最大连接数
+        max-active: 8
+        # 最大等待时间
+        max-wait: -1ms
+        # 最大空闲连接
+        max-idle: 8
+        # 最小空闲连接
+        min-idle: 0
+
+# ==================== 阿里云 OSS 配置(内容管理图片/视频上传) ====================
+aliyun:
+  oss:
+    # OSS 服务的 Endpoint(华北2-北京)
+    endpoint: https://oss-cn-beijing.aliyuncs.com
+    # AccessKey ID
+    access-key-id: LTAI5t7NZAFYFAcmh68MvFLK
+    # AccessKey Secret
+    access-key-secret: hEs6C02BCKycfoq5AzHvFSb3KAMIyb
+    # 存储桶名称
+    bucket-name: ajy001
+    # 访问域名(用于拼接文件公网访问 URL)
+    domain: https://ajy001.oss-cn-beijing.aliyuncs.com
+    # 文件在 OSS 中的根目录
+    base-dir: content
+    # 图片最大大小(字节,默认 5MB)
+    max-image-size: 5242880
+    # 视频最大大小(字节,默认 50MB)
+    max-video-size: 52428800
+
+# ==================== 日志配置(开发环境) ====================
+logging:
+  level:
+    root: INFO
+    # 业务代码使用 DEBUG 级别,可看到所有 SQL
+    com.aijiuyi.admin: DEBUG
+  file:
+    name: logs/aijiuyi-admin-dev.log
+  pattern:
+    # 控制台日志格式(包含 MDC 中的 requestId)
+    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{requestId}] %-5level %logger{50} - %msg%n"
+    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{requestId}] %-5level %logger{50} - %msg%n"

+ 4 - 1
code/backend/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 spring:
   profiles:
-    active: dev
+    active: liyu
 
   # Jackson 日期时间格式配置
   jackson:
@@ -46,6 +46,9 @@ auth:
   white-list:
     - /api/auth/login
     - /api/auth/register
+    - /api/app/auth/send-code
+    - /api/app/auth/login
+    - /api/article/view/**
     - /api/actuator/**
     - /api/doc.html
     - /api/swagger-ui.html

+ 12 - 3
code/backend/src/main/resources/mapper/UserProfileMapper.xml

@@ -3,7 +3,8 @@
 <mapper namespace="com.aijiuyi.admin.mapper.UserProfileMapper">
 
     <!--
-        分页查询子用户列表,LEFT JOIN user_device 获取绑定设备编号
+        分页查询用户列表,LEFT JOIN user_device 获取绑定设备编号
+        该列表固定展示身份为“用户”的数据,并关联 app_user_category 获取用户分类
         device_codes: GROUP_CONCAT 逗号拼接已绑定设备编号
         设备编号过滤使用子查询,避免 LEFT JOIN 过滤导致无设备用户丢失
     -->
@@ -32,10 +33,18 @@
             up.deleted,
             up.create_time,
             up.update_time,
+            MAX(COALESCE(au_id.user_category_id, au_phone.user_category_id)) AS user_category_id,
+            MAX(uc.name) AS user_category_name,
             GROUP_CONCAT(ud.device_code ORDER BY ud.id SEPARATOR ',') AS device_codes
         FROM user_profile up
         LEFT JOIN user_device ud ON ud.user_id = up.id AND ud.status = 1
+        LEFT JOIN app_user au_id ON au_id.id = up.user_id AND au_id.deleted = 0
+        LEFT JOIN app_user au_phone ON au_id.id IS NULL AND au_phone.phone = up.phone AND au_phone.deleted = 0
+        LEFT JOIN app_user_category uc
+            ON uc.id = COALESCE(au_id.user_category_id, au_phone.user_category_id)
+            AND uc.deleted = 0
         WHERE up.deleted = 0
+          AND up.user_type = 1
         <if test="q.keyword != null and q.keyword != ''">
             AND (up.name LIKE CONCAT('%', #{q.keyword}, '%')
               OR up.phone LIKE CONCAT('%', #{q.keyword}, '%'))
@@ -43,8 +52,8 @@
         <if test="q.gender != null">
             AND up.gender = #{q.gender}
         </if>
-        <if test="q.userType != null">
-            AND up.user_type = #{q.userType}
+        <if test="q.userCategoryId != null">
+            AND COALESCE(au_id.user_category_id, au_phone.user_category_id) = #{q.userCategoryId}
         </if>
         <if test="q.provinceCode != null and q.provinceCode != ''">
             AND up.province_code = #{q.provinceCode}

+ 0 - 493
code/backend/src/main/resources/sql/acupoint_full_data_import.sql

@@ -1,493 +0,0 @@
--- Full acupoint import generated from 智能穴位识别数据模型.xlsx
--- Source sheet: 穴位数据模型; rows: 413
-SET NAMES utf8mb4;
-SET FOREIGN_KEY_CHECKS = 0;
-
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'excel_serial_no') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `excel_serial_no` VARCHAR(20) DEFAULT NULL COMMENT ''Excel原始序号'' AFTER `acupoint_code`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'single_double') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `single_double` VARCHAR(20) DEFAULT NULL COMMENT ''单/双穴(Excel原始字段)'' AFTER `name`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'body_part') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `body_part` VARCHAR(50) DEFAULT NULL COMMENT ''身体部位(Excel原始字段)'' AFTER `meridian`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'body_region') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `body_region` VARCHAR(50) DEFAULT NULL COMMENT ''部位分区(Excel原始字段)'' AFTER `body_part`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'location_description') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `location_description` VARCHAR(500) DEFAULT NULL COMMENT ''穴位定位原文(Excel原始字段)'' AFTER `body_region`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'relative_position') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `relative_position` VARCHAR(200) DEFAULT NULL COMMENT ''相对位置原文(Excel原始字段)'' AFTER `location_description`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'indications') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `indications` VARCHAR(1000) DEFAULT NULL COMMENT ''主管疾病/主治(Excel原始字段)'' AFTER `relative_position`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'product_symptoms') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `product_symptoms` VARCHAR(500) DEFAULT NULL COMMENT ''产品症状覆盖(Excel原始字段)'' AFTER `indications`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'acupoint_method') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `acupoint_method` VARCHAR(500) DEFAULT NULL COMMENT ''取穴说明(Excel原始字段)'' AFTER `product_symptoms`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'product_acupoint') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `product_acupoint` TINYINT NOT NULL DEFAULT 0 COMMENT ''是否艾灸椅产品穴位:1=是,0=否'' AFTER `acupoint_method`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'y_reference_term') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `y_reference_term` VARCHAR(100) DEFAULT NULL COMMENT ''Y轴医学定位术语'' AFTER `normalized_y`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'parse_status') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `parse_status` VARCHAR(20) DEFAULT NULL COMMENT ''定位描述解析状态:已解析/待复核/暂不支持'' AFTER `y_reference_term`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'parse_confidence') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `parse_confidence` VARCHAR(10) DEFAULT NULL COMMENT ''定位描述解析置信度:高/中/低'' AFTER `parse_status`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'parse_message') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `parse_message` VARCHAR(500) DEFAULT NULL COMMENT ''定位描述解析说明'' AFTER `parse_confidence`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-
-INSERT INTO `acupoint`
-(`acupoint_code`, `excel_serial_no`, `name`, `single_double`, `meridian`, `body_part`, `body_region`, `location_description`, `relative_position`, `indications`, `product_symptoms`, `acupoint_method`, `product_acupoint`, `effects`, `location_type`, `longitude_offset`, `lateral_offset`, `normalized_y`, `y_reference_term`, `side`, `finger_width_type`, `gender`, `status`, `deleted`, `create_time`, `update_time`)
-VALUES
-    ('feijing-01', '01', '中府', '双穴', '手太阴肺经', '胸部', '前胸区', '在胸部,横平第1肋间隙,锁骨下窝外侧,前正中线旁开6寸。', NULL, '①咳嗽、气喘、胸痛等胸肺病证;②肩背痛。', NULL, NULL, 1, NULL, NULL, NULL, 6, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-02', '02', '云门', '双穴', '手太阴肺经', '胸部', '前胸区', '在胸部,锁骨下窝凹陷中,肩胛骨喙突内缘,前正中线旁开6寸', NULL, '①咳嗽、气喘;②胸痛,肩背痛。', NULL, NULL, 0, NULL, NULL, NULL, 6, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-03', '03', '天府', '双穴', '手太阴肺经', '上肢', '上臂前区', '在臂前区,腋前纹头下3寸,肱二头肌桡侧缘处。', NULL, '①咳嗽、气喘、鼻衄等肺系病证;②瘿气;③上臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-04', '04', '侠白', '双穴', '手太阴肺经', '上肢', '上臂前区', '在臂前区,腋前纹头下4寸,肱二头肌桡侧缘处。', NULL, '①咳嗽、气喘等肺系病证;②心痛,干呕;③上臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-05', '05', '尺泽', '双穴', '手太阴肺经', '上肢', '肘部前区', '在肘区,肘横纹上,肱二头肌腱桡侧缘凹陷中。', NULL, '①咳嗽、气喘、咯血、咽喉肿痛等肺系实热病证;②肘臂挛痛;③急性吐泻、中暑、小儿惊风等急症。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-06', '06', '孔最', '双穴', '手太阴肺经', '上肢', '前臂前区', '在前臂前区,腕掌侧远端横纹上7寸,尺泽与太渊连线上。', NULL, '①鼻衄、咯血、咳嗽、气喘、咽喉肿痛等肺系病证;②肘臂挛痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-07', '07', '列缺', '双穴', '手太阴肺经', '上肢', '前臂前区', '在前臂,腕掌侧远端横纹上1.5寸,拇短伸肌腱和拇长展肌腱之间,拇长展肌腱沟的凹陷中。', NULL, '①咳嗽、气喘、咽喉肿痛等肺系病证;②偏正头痛、齿痛、项强痛、口眼歪斜等头面部病证;③手腕痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-08', '08', '经渠', '双穴', '手太阴肺经', '上肢', '前臂前区', '在前臂前区,腕掌侧远端横纹上1寸,桡骨茎突与桡动脉之间。', NULL, '①咳嗽、气喘、胸痛、咽喉肿痛等肺系病证;②手腕痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-09', '09', '太渊', '双穴', '手太阴肺经', '上肢', '手腕前区', '在腕前区,桡骨茎突与舟状骨之间,拇长展肌腱尺侧凹陷中。', NULL, '①咳嗽、气喘等肺系病证;②无脉症;③腕臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-10', '10', '鱼际', '双穴', '手太阴肺经', '上肢', '手外侧', '在手外侧,第1掌骨桡侧中点赤白肉际处。', NULL, '①咳嗽、咯血、咽干、咽喉肿痛、失音等肺系实热病证;②掌中热;③小儿疳积。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('feijing-11', '11', '少商', '双穴', '手太阴肺经', '上肢', '手指', '在手指,拇指末节桡侧,指甲根角侧上方0.1寸(指寸)。', NULL, '①咽喉肿痛、鼻衄、高热等肺系实热病证;②昏迷、癫狂等急症。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-01', '12', '商阳', '双穴', '手阳明大肠经', '上肢', '手指', '在手指,食指末节桡侧,指甲根角侧上方0.1寸(指寸)。', NULL, '①齿痛、咽喉肿痛等五官病;②热病、昏迷等热证、急症。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-02', '13', '二间', '双穴', '手阳明大肠经', '上肢', '手指', '在手指,第2掌指关节桡侧远端赤白肉际处。', NULL, '①鼻衄、齿痛等五官病;②热病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-03', '14', '三间', '双穴', '手阳明大肠经', '上肢', '手背', '在手背,第2掌指关节桡侧近端凹陷中。', NULL, '①齿痛、咽喉肿痛等五官病;②腹胀、肠鸣等肠腑病证;③嗜睡。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-04', '15', '合谷', '双穴', '手阳明大肠经', '上肢', '手背', '在手背,第2掌骨桡侧的中点处。', NULL, '①头痛、目赤肿痛、齿痛、鼻衄、口眼歪斜、耳聋等头面五官病证;②发热恶寒等外感病证;③热病无汗或多汗;④痛经、经闭、滞产等妇产科病证;⑤各种痛证,为牙拔除术、甲状腺手术等五官及颈部手术针麻常用穴。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-05', '16', '阳溪', '双穴', '手阳明大肠经', '上肢', '手腕背区', '在腕区,腕背侧远端横纹桡侧,桡骨茎突远端,解剖学“鼻烟窝”凹陷中。', NULL, '①头痛、目赤肿痛、耳聋等头面五官病证;②手腕痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-06', '17', '偏历', '双穴', '手阳明大肠经', '上肢', '前臂外侧区', '在前臂,腕背侧远端横纹上3寸,阳溪与曲池连线上。', NULL, '①耳鸣,鼻衄;②手臂酸痛;③腹部胀满;④水肿。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-07', '18', '温溜', '双穴', '手阳明大肠经', '上肢', '前臂外侧区', '在前臂,腕背侧远端横纹上5寸,阳溪与曲池连线上。', NULL, '①急性肠鸣、腹痛等肠腑病证;②疔疮;③头痛、面肿、咽喉肿痛等头面病证;④肩背酸痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-08', '19', '下廉', '双穴', '手阳明大肠经', '上肢', '前臂外侧区', '在前臂,肘横纹下4寸,阳溪与曲池连线上。', NULL, '①肘臂痛;②头痛,眩晕,目痛;③腹胀、腹痛等肠腑病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-09', '20', '上廉', '双穴', '手阳明大肠经', '上肢', '前臂外侧区', '在前臂,肘横纹下3寸,阳溪与曲池连线上。', NULL, '①肘臂痛,半身不遂,手臂麻木;②头痛;③肠鸣,腹痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-10', '21', '手三里', '双穴', '手阳明大肠经', '上肢', '前臂外侧区', '在前臂,肘横纹下2寸,阳溪与曲池连线上。', NULL, '①手臂无力,上肢不遂;②腹痛,腹泻;③齿痛,颊肿。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-11', '22', '曲池', '双穴', '手阳明大肠经', '上肢', '肘部外侧区', '在肘区,在尺泽与肱骨外上髁连线中点凹陷处。', NULL, '①手臂痹痛,上肢不遂;②热病;③眩晕;④腹痛、吐泻等肠胃病证;⑤咽喉肿痛、齿痛、目赤肿痛等五官热性病证;⑥瘾疹、湿疹、瘰疬等皮外科病证;⑦癫狂。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-12', '23', '肘髎', '双穴', '手阳明大肠经', '上肢', '肘部外侧区', '在肘区,肱骨外上髁上缘,髁上嵴的前缘。', NULL, '肘臂部疼痛、麻木、挛急等。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-13', '24', '手五里', '双穴', '手阳明大肠经', '上肢', '上臂外侧区', '在臂部,肘横纹上3寸,曲池与肩髃连线上。', NULL, '①肘臂挛痛;②瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-14', '25', '臂臑', '双穴', '手阳明大肠经', '上肢', '上臂外侧区', '在臂部,曲池上7寸,三角肌前缘处。', NULL, '①肩臂疼痛不遂、颈项拘挛等痹证;②瘰疬;③目疾。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-15', '26', '肩髃', '双穴', '手阳明大肠经', '上肢', '肩部', '在三角肌区,肩峰外侧缘前端与肱骨大结节两骨间凹陷中。', NULL, '①肩臂挛痛,上肢不遂;②瘾疹。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-16', '27', '巨骨', '双穴', '手阳明大肠经', '上肢', '肩胛区', '在肩胛区,锁骨肩峰端与肩胛冈之间凹陷中。', NULL, '①肩臂挛痛,臂不举;②瘰疬,瘿气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-17', '28', '天鼎', '双穴', '手阳明大肠经', '颈部', '颈外侧区', '在颈部,横平环状软骨,胸锁乳突肌后缘。', NULL, '①暴喑气哽、咽喉肿痛、吞咽困难等咽喉病证;②瘰疬,瘿气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-18', '29', '扶突', '双穴', '手阳明大肠经', '颈部', '颈外侧区', '在胸锁乳突肌区,横平喉结,胸锁乳突肌前、后缘中间。', NULL, '①咽喉肿痛、暴喑、吞咽困难等咽喉病证;②瘿气,瘰疬;③呃逆;④咳嗽,气喘;⑤颈部手术针麻用穴。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-19', '30', '口禾髎', '双穴', '手阳明大肠经', '头部', '面部', '在面部,横平人中沟上1/3与下2/3交点,鼻孔外缘直下。', NULL, '鼻塞、鼽衄、口歪、口噤等口鼻部病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dachang-20', '31', '迎香', '双穴', '手阳明大肠经', '头部', '面部', '在面部,鼻翼外缘中点旁,鼻唇沟中。', NULL, '①鼻塞、鼽衄等鼻病;②口歪、面痒等口面部病证;③胆道蛔虫症。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-01', '32', '承泣', '双穴', '足阳明胃经', '头部', '面部', '在面部,眼球与眶下缘之间,目正视,瞳孔直下。', NULL, '①眼睑动、迎风流泪、夜盲、近视等目疾;②口眼歪斜,面肌痉挛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-02', '33', '四白', '双穴', '足阳明胃经', '头部', '面部', '在面部,眶下孔处。', NULL, '①目赤痛痒、眼睑动、目翳等眼部病证;②口眼歪斜、面痛、面肌痉挛等面部病证;③头痛,眩晕。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-03', '34', '巨髎', '双穴', '足阳明胃经', '头部', '面部', '在面部,横平鼻翼下缘,目正视,瞳孔直下。', NULL, '口角歪斜、面痛、鼻衄、齿痛、唇颊肿等局部五官病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-04', '35', '地仓', '双穴', '足阳明胃经', '头部', '面部', '在面部,口角旁开0.4寸(指寸)。', NULL, '地仓(抚平口周皱纹),口角歪斜、流涎、面痛、齿痛等局部病证。', NULL, NULL, 0, NULL, NULL, NULL, 0.4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-05', '36', '大迎', '双穴', '足阳明胃经', '头部', '面部', '在面部,下颌角前方,咬肌附着部的前缘凹陷中,面动脉搏动处。', NULL, '口角歪斜、颊肿、齿痛等局部病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-06', '37', '颊车', '双穴', '足阳明胃经', '头部', '面部', '在面部,下颌角前上方一横指(中指),闭口咬紧牙时咬肌隆起,放松时按之有凹陷处。', NULL, '齿痛、牙关不利、颊肿、口角歪斜等局部病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-07', '38', '下关', '双穴', '足阳明胃经', '头部', '面部', '在面部,颧弓下缘中央与下颌切迹之间凹陷中。', NULL, '①牙关不利、面痛、齿痛、口眼歪斜等面口病证;②耳聋、耳鸣、聤耳等耳疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-08', '39', '头维', '双穴', '足阳明胃经', '头部', '头部', '在头部,额角发际直上0.5寸,头正中线旁开4.5寸。', NULL, '头痛、目眩、目痛等头目病证。', NULL, NULL, 0, NULL, NULL, NULL, 4.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-09', '40', '人迎', '双穴', '足阳明胃经', '颈部', '颈部前区', '在颈部,横平喉结,胸锁乳突肌前缘,颈总动脉搏动处。', NULL, '①瘿气,瘰疬;②咽喉肿痛;③高血压;④气喘。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-10', '41', '水突', '双穴', '足阳明胃经', '颈部', '颈部前区', '在颈部,横平环状软骨,胸锁乳突肌前缘。', NULL, '①咽喉肿痛、失音等咽喉局部病证;②咳嗽,气喘。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-11', '42', '气舍', '双穴', '足阳明胃经', '颈部', '前胸区', '在胸锁乳突肌区,锁骨上小窝,锁骨胸骨端上缘,胸锁乳突肌胸骨头与锁骨头中间的凹陷中。', NULL, '①咽喉肿痛;②瘿瘤,瘰疬;③气喘,呃逆;④颈项强痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-12', '43', '缺盆', '双穴', '足阳明胃经', '颈部', '颈外侧区', '在颈外侧区,锁骨上大窝,锁骨上缘凹陷中,前正中线旁开4寸。', NULL, '①咳嗽、气喘、咽喉肿痛、缺盆中痛等肺系病证;②瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-13', '44', '气户', '双穴', '足阳明胃经', '胸部', '前胸区', '在胸部,锁骨下缘,前正中线旁开4寸。', NULL, '咳嗽、气喘、呃逆、胸痛、胸胁支满等胸肺病证。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-14', '45', '库房', '双穴', '足阳明胃经', '胸部', '前胸区', '在胸部,第1肋间隙,前正中线旁开4寸。', NULL, '咳嗽、气喘、咳唾脓血、胸胁胀痛等胸肺病证。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-15', '46', '屋翳', '双穴', '足阳明胃经', '胸部', '前胸区', '在胸部,第2肋间隙,前正中线旁开4寸。', NULL, '①咳嗽、气喘、咳唾脓血、胸胁胀痛等胸肺病证;②乳痈、乳癖等乳疾。', NULL, NULL, 1, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-16', '47', '膺窗', '双穴', '足阳明胃经', '胸部', '前胸区', '在胸部,第3肋间隙,前正中线旁开4寸。', NULL, '①咳嗽、气喘、胸胁胀痛等胸肺病证;②乳痈。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-17', '48', '乳中', '双穴', '足阳明胃经', '胸部', '前胸区', '在胸部,乳头中央。', NULL, '①乳痈;②难产。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-18', '49', '乳根', '双穴', '足阳明胃经', '胸部', '前胸区', '在胸部,第5肋间隙,前正中线旁开4寸。', NULL, '①乳痈、乳癖、乳少等乳部疾患;②咳嗽,气喘,呃逆;③胸痛。', NULL, NULL, 1, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-19', '50', '不容', '双穴', '足阳明胃经', '腹部', '上腹部', '在上腹部,脐中上6寸,前正中线旁开2寸。', NULL, '呕吐、胃痛、纳少、腹胀等胃疾。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-20', '51', '承满', '双穴', '足阳明胃经', '腹部', '上腹部', '在上腹部,脐中上5寸,前正中线旁开2寸。', NULL, '胃痛、吐血、纳少等胃疾。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-21', '52', '梁门', '双穴', '足阳明胃经', '腹部', '上腹部', '在上腹部,脐中上4寸,前正中线旁开2寸。', NULL, '腹胀、纳少、胃痛、呕吐等胃疾。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-22', '53', '关门', '双穴', '足阳明胃经', '腹部', '上腹部', '在上腹部,脐中上3寸,前正中线旁开2寸。', NULL, '腹胀、腹痛、肠鸣、腹泻等胃肠病证。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-23', '54', '太乙', '双穴', '足阳明胃经', '腹部', '上腹部', '在上腹部,脐中上2寸,前正中线旁开2寸。', NULL, '①腹痛,腹胀;②心烦、癫狂等神志疾患。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-24', '55', '滑肉门', '双穴', '足阳明胃经', '腹部', '上腹部', '在上腹部,脐中上1寸,前正中线旁开2寸。', NULL, '①腹痛,腹胀,呕吐;②癫狂。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-25', '56', '天枢', '双穴', '足阳明胃经', '腹部', '上腹部', '在腹部,横平脐中,前正中线旁开2寸。', NULL, '①腹痛、腹胀、便秘、腹泻、痢疾等胃肠病证;②月经不调、痛经等妇科病证。', NULL, NULL, 1, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-26', '57', '外陵', '双穴', '足阳明胃经', '腹部', '下腹部', '在下腹部,脐中下1寸,前正中线旁开2寸。', NULL, '①腹痛,疝气;②痛经。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-27', '58', '大巨', '双穴', '足阳明胃经', '腹部', '下腹部', '在下腹部,脐中下2寸,前正中线旁开2寸。', NULL, '①小腹胀满;②小便不利等水液输布排泄失常性疾患;③疝气;④遗精、早泄等男科疾患。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-28', '59', '水道', '双穴', '足阳明胃经', '腹部', '下腹部', '在下腹部,脐中下3寸,前正中线旁开2寸。', NULL, '①小腹胀满;②小便不利等水液输布排泄失常性疾患;③疝气;④痛经、不孕等妇科疾患。', NULL, NULL, 1, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-29', '60', '归来', '双穴', '足阳明胃经', '腹部', '下腹部', '在下腹部,脐中下4寸,前正中线旁开2寸。', NULL, '①小腹痛,疝气;②月经不调、带下、阴挺等妇科疾患。', NULL, NULL, 1, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-30', '61', '气冲', '双穴', '足阳明胃经', '腹部', '腹股沟', '在腹股沟区,耻骨联合上缘,前正中线旁开2寸,动脉搏动处。', NULL, '①肠鸣,腹痛;②疝气;③月经不调、不孕、阳痿、阴肿等妇科病及男科病证。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-31', '62', '髀关', '双穴', '足阳明胃经', '下肢', '股前区', '在股前区,股直肌近端、缝匠肌与阔筋膜张肌3条肌肉之间凹陷中。', NULL, '下肢痿痹、腰痛、膝冷等腰及下肢病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-32', '63', '伏兔', '双穴', '足阳明胃经', '下肢', '股前区', '在股前区,髌底上6寸,髂前上棘与髌底外侧端的连线上。', NULL, '①下肢痿痹、腰痛、膝冷等腰及下肢病证;②疝气;③脚气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-33', '64', '阴市', '双穴', '足阳明胃经', '下肢', '股前区', '在股前区,髌底上3寸,股直肌肌腱外侧缘。', NULL, '①下肢痿痹,膝关节屈伸不利;②疝气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-34', '65', '梁丘', '双穴', '足阳明胃经', '下肢', '股前区', '在股前区,髌底上2寸,股外侧肌与股直肌肌腱之间。', NULL, '①急性胃痛;②膝肿痛、下肢不遂等下肢病证;③乳痈、乳痛等乳疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-35', '66', '犊鼻', '双穴', '足阳明胃经', '下肢', '膝部前区', '在膝前区,髌韧带外侧凹陷中。', NULL, '膝痛、屈伸不利、下肢麻痹等下肢、膝关节病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-36', '67', '足三里', '双穴', '足阳明胃经', '下肢', '小腿外侧区', '在小腿外侧,犊鼻下3寸,胫骨前嵴外1横指处,犊鼻与解溪连线上。', NULL, '①胃痛、呕吐、噎膈、腹胀、腹泻、痢疾、便秘等胃肠病证;②下肢痿痹;③癫狂等神志病;④乳痈、肠痈等外科疾患;⑤虚劳诸证,为强壮保健要穴。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-37', '68', '上巨虚', '双穴', '足阳明胃经', '下肢', '小腿外侧区', '在小腿外侧,犊鼻下6寸,犊鼻与解溪连线上。', NULL, '①肠鸣、腹痛、腹泻、便秘、肠痈、痢疾等胃肠病证;②下肢痿痹。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-38', '69', '条口', '双穴', '足阳明胃经', '下肢', '小腿外侧区', '在小腿外侧,犊鼻下8寸,犊鼻与解溪连线上。', NULL, '①下肢痿痹,转筋;②肩臂痛;③脘腹疼痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-39', '70', '下巨虚', '双穴', '足阳明胃经', '下肢', '小腿外侧区', '在小腿外侧,犊鼻下9寸,犊鼻与解溪连线上。', NULL, '①腹泻、痢疾、小腹痛等胃肠病证;②下肢痿痹;③乳痈。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-40', '71', '丰隆', '双穴', '足阳明胃经', '下肢', '小腿外侧区', '在小腿外侧,外踝尖上8寸,胫骨前肌外缘;条口外侧一横指处。', NULL, '①头痛,眩晕;②癫狂;③咳嗽、痰多等痰饮病证;④下肢痿痹;⑤腹胀,便秘。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-41', '72', '解溪', '双穴', '足阳明胃经', '下肢', '踝前区', '在踝区,踝关节前面中央凹陷中,拇长伸肌腱与趾长伸肌腱之间。', NULL, '①下肢痿痹、踝关节病、足下垂等下肢、踝关节疾患;②头痛,眩晕;③癫狂;④腹胀,便秘。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-42', '73', '冲阳', '双穴', '足阳明胃经', '下肢', '足背区', '在足背,第2跖骨基底部与中间楔状骨关节处,可触及足背动脉。', NULL, '①胃痛;②口眼歪斜;③癫狂痫;④足痿无力。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-43', '74', '陷谷', '双穴', '足阳明胃经', '下肢', '足背区', '在足背,第2、3跖骨间,第2跖趾关节近端凹陷中。', NULL, '①面肿、水肿等水液输布失常性疾患;②足背肿痛;③肠鸣,腹痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-44', '75', '内庭', '双穴', '足阳明胃经', '下肢', '足背区', '在足背,第2、3趾间,趾蹼缘后方赤白肉际处。', NULL, '①齿痛、咽喉肿痛、鼻衄等五官热性病证;②热病;③吐酸、腹泻、痢疾、便秘等胃肠病证;④足背肿痛,跖趾关节痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('weijing-45', '76', '厉兑', '双穴', '足阳明胃经', '下肢', '足趾区', '在足趾,第2趾末节外侧,趾甲根角侧后方0.1寸。', NULL, '①鼻衄、齿痛、咽喉肿痛等实热性五官病证;②热病;③多梦、癫狂等神志病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-01', '77', '隐白', '双穴', '足太阴脾经', '下肢', '足趾区', '在足趾,大趾末节内侧,趾甲根角侧后方0.1寸(指寸)。', NULL, '①月经过多、崩漏等妇科病;②便血、尿血等慢性出血证;③癫狂,多梦;④惊风;⑤腹满,暴泻。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-02', '78', '大都', '双穴', '足太阴脾经', '下肢', '足趾区', '在足趾,第1跖趾关节远端赤白肉际凹陷中。', NULL, '①腹胀、胃痛、呕吐、腹泻、便秘等脾胃病证;②热病,无汗。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-03', '79', '太白', '双穴', '足太阴脾经', '下肢', '足跖内区', '在跖区,第1跖趾关节近端赤白肉际凹陷中。', NULL, '①肠鸣、腹胀、腹泻、胃痛、便秘等脾胃病证;②体重节痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-04', '80', '公孙', '双穴', '足太阴脾经', '下肢', '足跖内区', '在跖区,第1跖骨底的前下缘赤白肉际处。', NULL, '①胃痛、呕吐、腹痛、腹泻、痢疾等脾胃肠腑病证;②心烦、失眠、狂证等神志病证;③逆气里急、气上冲心(奔豚气)等冲脉病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-05', '81', '商丘', '双穴', '足太阴脾经', '下肢', '踝内侧区', '在踝区,内踝前下方,舟骨粗隆与内踝尖连线中点凹陷中。', NULL, '①腹胀、腹泻、便秘等脾胃病证;②黄疸;③足踝痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-06', '82', '三阴交', '双穴', '足太阴脾经', '下肢', '小腿内侧区', '在小腿内侧,内踝尖上3寸,胫骨内侧缘后际。', NULL, '①肠鸣、腹胀、腹泻等脾胃虚弱诸证;②月经不调、带下、阴挺、不孕、滞产等妇产科病证;③遗精、阳痿、遗尿等生殖泌尿系统疾患;④心悸,失眠,高血压;⑤下肢痿痹;⑥阴虚诸证', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-07', '83', '漏谷', '双穴', '足太阴脾经', '下肢', '小腿内侧区', '在小腿内侧,内踝尖上6寸,胫骨内侧缘后际。', NULL, '①腹胀,肠鸣;②小便不利,遗精;③下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-08', '84', '地机', '双穴', '足太阴脾经', '下肢', '小腿内侧区', '在小腿内侧,阴陵泉下3寸,胫骨内侧缘后际。', NULL, '①痛经、崩漏、月经不调等妇科病;②腹痛、腹泻等肠胃病证;③疝气;④小便不利、水肿等脾不运化水湿病证', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-09', '85', '阴陵泉', '双穴', '足太阴脾经', '下肢', '小腿内侧区', '在小腿内侧,胫骨内侧髁下缘与胫骨内侧缘之间的凹陷中。', NULL, '①腹胀,腹泻,水肿,黄疸;②小便不利,遗尿,尿失禁;③阴部痛,痛经,遗精;④膝痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-10', '86', '血海', '双穴', '足太阴脾经', '下肢', '股前区', '在股前区,髌底内侧端上2寸,股内侧肌隆起处。', NULL, '①月经不调、痛经、经闭等妇科病;②瘾疹、湿疹、丹毒等血热性皮肤病;③膝股内侧痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-11', '87', '箕门', '双穴', '足太阴脾经', '下肢', '股前区', '在股前区,髌底内侧端与冲门的连线上1/3与下2/3交点,长收肌和缝匠肌交角的动脉搏动处。', NULL, '①小便不利,遗尿;②腹股沟肿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-12', '88', '冲门', '双穴', '足太阴脾经', '腹部', '腹股沟', '在腹股沟区,腹股沟斜纹中,髂外动脉搏动处的外侧。', NULL, '①腹痛,疝气;②崩漏、带下、胎气上冲等妇科病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-13', '89', '府舍', '双穴', '足太阴脾经', '腹部', '下腹部', '在下腹部,脐中下4.3寸,前正中线旁开4寸。', NULL, '腹痛、积聚、疝气等下腹部病证。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-14', '90', '腹结', '双穴', '足太阴脾经', '腹部', '下腹部', '在下腹部,脐中下1.3寸,前正中线旁开4寸。', NULL, '①腹痛,腹泻,食积;②疝气。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-15', '91', '大横', '双穴', '足太阴脾经', '腹部', '上腹部', '在腹部,脐中旁开4寸。', NULL, '腹痛、腹泻、便秘等脾胃病证。', NULL, NULL, 1, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-16', '92', '腹哀', '双穴', '足太阴脾经', '腹部', '上腹部', '在上腹部,脐中上3寸,前正中线旁开4寸。', NULL, '消化不良、腹痛、便秘、痢疾等脾胃肠腑病证。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-17', '93', '食窦', '双穴', '足太阴脾经', '胸部', '前胸区', '在胸部,第5肋间隙,前正中线旁开6寸。', NULL, '①胸胁胀痛;②嗳气、反胃、腹胀等胃气失降性病证;③水肿。', NULL, NULL, 0, NULL, NULL, NULL, 6, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-18', '94', '天溪', '双穴', '足太阴脾经', '胸部', '前胸区', '在胸部,第4肋间隙,前正中线旁开6寸。', NULL, '①胸胁疼痛,咳嗽;②乳痈,乳少。', NULL, NULL, 0, NULL, NULL, NULL, 6, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-19', '95', '胸乡', '双穴', '足太阴脾经', '胸部', '前胸区', '在胸部,第3肋间隙,前正中线旁开6寸。', NULL, '胸胁胀痛。', NULL, NULL, 0, NULL, NULL, NULL, 6, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-20', '96', '周荣', '双穴', '足太阴脾经', '胸部', '前胸区', '在胸部,第2肋间隙,前正中线旁开6寸。', NULL, '①咳嗽,气逆;②胸胁胀满。', NULL, NULL, 0, NULL, NULL, NULL, 6, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pijing-21', '97', '大包', '双穴', '足太阴脾经', '胸部', '侧胸区', '在胸外侧区,第6肋间隙,在腋中线上。', NULL, '①气喘;②胸胁痛;③全身疼痛;④四肢无力。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-01', '98', '极泉', '双穴', '手太阴心经', '上肢', '腋区', '在腋区,腋窝中央,腋动脉搏动处。', NULL, '①心痛、心悸等心系病证;②肩臂疼痛、胁肋疼痛、臂丛神经损伤等痛证;③瘰疬;④腋臭;⑤上肢痿痹;⑥上肢针刺麻醉用穴。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-02', '99', '青灵', '双穴', '手太阴心经', '上肢', '上臂内侧区', '在臂前区,肘横纹上3寸,肱二头肌内侧沟中。', NULL, '①头痛,振寒;②胁痛,肩臂疼痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-03', '100', '少海', '双穴', '手太阴心经', '上肢', '肘内侧区', '在肘前区,横平肘横纹,肱骨内上髁前缘。', NULL, '①心痛、癔症等心病、神志病;②肘臂挛痛,臂麻手颤;③头项痛,腋胁部痛;④瘰疬。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-04', '101', '灵道', '双穴', '手太阴心经', '上肢', '前臂内侧区', '在前臂前区,腕掌侧远端横纹上1.5寸,尺侧腕屈肌腱的桡侧缘。', NULL, '①心痛,悲恐善笑;②暴喑;③肘臂挛痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-05', '102', '通里', '双穴', '手太阴心经', '上肢', '前臂内侧区', '在前臂前区,腕掌侧远端横纹上1寸,尺侧腕屈肌腱的桡侧缘。', NULL, '①心悸、怔忡等心系病证;②舌强不语,暴喑;③腕臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-06', '103', '阴郄', '双穴', '手太阴心经', '上肢', '前臂内侧区', '在前臂前区,腕掌侧远端横纹上0.5寸,尺侧腕屈肌腱的桡侧缘。', NULL, '①心痛、惊悸等心系病证;②骨蒸盗汗;③吐血,衄血。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-07', '104', '神门', '双穴', '手太阴心经', '上肢', '手腕前区', '在腕前区,腕掌侧远端横纹尺侧端,尺侧腕屈肌腱的桡侧缘。', NULL, '①心痛、心烦、惊悸、怔忡、健忘、失眠、痴呆、癫狂痫等心与神志病证;②高血压;③胸胁痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-08', '105', '少府', '双穴', '手太阴心经', '上肢', '手掌', '在手掌,横平第5掌指关节近端,第4、5掌骨之间。', NULL, '①心悸、胸痛等心胸病;②阴痒,阴痛;③痈疡;④小指挛痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinjing-09', '106', '少冲', '双穴', '手太阴心经', '上肢', '手指', '在手指,小指末节桡侧,指甲根角侧上方0.1寸(指寸)。', NULL, '①心悸、心痛、癫狂、昏迷等心与神志病证;②热病;③胸胁痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-01', '107', '少泽', '双穴', '手太阳小肠经', '上肢', '手指', '在手指,小指末节尺侧,指甲根角侧上方0.1寸(指寸)。', NULL, '①乳痈、乳少等乳疾;②昏迷、热病等急症、热证;③头痛、目翳、咽喉肿痛等头面五官病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-02', '108', '前谷', '双穴', '手太阳小肠经', '上肢', '手指', '在手指,第5掌指关节尺侧远端赤白肉际凹陷中。', NULL, '①热病;②乳痈,乳少;③头痛、目痛、耳鸣、咽喉肿痛等头面五官病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-03', '109', '后溪', '双穴', '手太阳小肠经', '上肢', '手内侧区', '在手内侧,第5掌指关节尺侧近端赤白肉际凹陷中。', NULL, '①头项强痛、腰背痛、手指及肘臂挛痛等痛证;②耳聋,目赤;③癫狂痫;④疟疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-04', '110', '腕骨', '双穴', '手太阳小肠经', '上肢', '手腕内侧区', '在腕区,第5掌骨底与三角骨之间的赤白肉际凹陷中。', NULL, '①指挛腕痛,头项强痛;②目翳;③黄疸;④热病,疟疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-05', '111', '阳谷', '双穴', '手太阳小肠经', '上肢', '手腕内侧区', '在腕后区,尺骨茎突与三角骨之间的凹陷中。', NULL, '①颈颔肿痛、臂外侧痛、腕痛等痛证;②头痛、目眩、耳鸣、耳聋等头面五官病证;③热病;④癫狂痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-06', '112', '养老', '双穴', '手太阳小肠经', '上肢', '前臂后区', '在前臂后区,腕背横纹上1寸,尺骨头桡侧凹陷中。', NULL, '①目视不明;②肩、背、肘、臂酸痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-07', '113', '支正', '双穴', '手太阳小肠经', '上肢', '前臂后区', '在前臂后区,腕背侧远端横纹上5寸,尺骨尺侧与尺侧腕屈肌之间。', NULL, '①头痛,项强,肘臂酸痛;②热病;③癫狂;④疣症。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-08', '114', '小海', '双穴', '手太阳小肠经', '上肢', '肘内侧区', '在肘后区,尺骨鹰嘴与肱骨内上髁之间凹陷中。', NULL, '①肘臂疼痛,麻木;②癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-09', '115', '肩贞', '双穴', '手太阳小肠经', '上肢', '肩胛区', '在肩胛区,肩关节后下方,腋后纹头直上1寸。', NULL, '①肩臂疼痛,上肢不遂;②瘰疬。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-10', '116', '臑俞', '双穴', '手太阳小肠经', '上肢', '肩胛区', '在肩胛区,腋后纹头直上,肩胛冈下缘凹陷中。', NULL, '①肩臂疼痛,肩不举;②瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-11', '117', '天宗', '双穴', '手太阳小肠经', '上肢', '肩胛区', '在肩胛区,肩胛冈中点与肩胛骨下角连线上1/3与下2/3交点凹陷中。', NULL, '①肩胛疼痛、肩背部损伤等局部病证;②气喘。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-12', '118', '秉风', '双穴', '手太阳小肠经', '上肢', '肩胛区', '在肩胛区,肩胛冈中点上方冈上窝中。', NULL, '肩胛疼痛、上肢酸麻等肩胛、上肢病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-13', '119', '曲垣', '双穴', '手太阳小肠经', '上肢', '肩胛区', '在肩胛区,肩胛冈内侧端上缘凹陷中。', NULL, '肩胛疼痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-14', '120', '肩外俞', '双穴', '手太阳小肠经', '背部', '背部', '在脊柱区,第1胸椎棘突下,后正中线旁开3寸。', NULL, '肩背疼痛、颈项强急等肩背、颈项痹证。', NULL, NULL, 1, NULL, 1, NULL, 3, 0.0340, '第1胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-15', '121', '肩中俞', '双穴', '手太阳小肠经', '背部', '背部', '在脊柱区,第7颈椎棘突下,后正中线旁开2寸。', NULL, '①咳嗽,气喘;②肩背疼痛。', NULL, NULL, 1, NULL, 1, NULL, 2, 0.0000, '第7颈椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-16', '122', '天窗', '双穴', '手太阳小肠经', '颈部', '颈外侧区', '在颈部,横平喉结,胸锁乳突肌后缘。', NULL, '①耳鸣、耳聋、咽喉肿痛、暴喑等五官病证;②颈项强痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-17', '123', '天容', '双穴', '手太阳小肠经', '颈部', '颈外侧区', '在颈部,下颌角后方,胸锁乳突肌的前缘凹陷中。', NULL, '①耳鸣、耳聋、咽喉肿痛等五官病证;②头痛,颈项强痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-18', '124', '颧髎', '双穴', '手太阳小肠经', '头部', '面部', '在面部,颧骨下缘,目外眦直下凹陷中。', NULL, '口眼歪斜、眼睑动、齿痛、面痛等。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xiaochang-19', '125', '听宫', '双穴', '手太阳小肠经', '头部', '面部', '在面部,耳屏正中与下颌骨髁突之间的凹陷中。', NULL, '①耳鸣、耳聋、聤耳等耳疾;②齿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-01', '126', '睛明', '双穴', '足太阳膀胱经', '头部', '面部', '在面部,目内眦内上方眶内侧壁凹陷中。', NULL, '①目赤肿痛、流泪、视物不明、目眩、近视、夜盲、色盲、干眼症等目疾;②急性腰扭伤,坐骨神经痛;③心悸,怔忡。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-02', '127', '攒竹', '双穴', '足太阳膀胱经', '头部', '面部', '在面部,眉头凹陷中,额切迹处。', NULL, '①头痛,眉棱骨痛;②眼睑动、眼睑下垂、口眼歪斜、目视不明、流泪、目赤肿痛等目疾;③呃逆。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-03', '128', '眉冲', '双穴', '足太阳膀胱经', '头部', '头部', '在头部,额切迹直上入发际0.5寸。', NULL, '①头痛,目眩;②鼻塞,鼻衄;③癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-04', '129', '曲差', '双穴', '足太阳膀胱经', '头部', '头部', '在头部,前发际正中直上0.5寸,旁开1.5寸。', NULL, '①头痛,目眩;②鼻塞、鼻衄等鼻病。', NULL, NULL, 0, NULL, NULL, NULL, 1.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-05', '130', '五处', '双穴', '足太阳膀胱经', '头部', '头部', '在头部,前发际正中直上1寸,旁开1.5寸。', NULL, '①头痛,目眩;②癫痫。', NULL, NULL, 0, NULL, NULL, NULL, 1.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-06', '131', '承光', '双穴', '足太阳膀胱经', '头部', '头部', '在头部,前发际正中直上2.5寸,旁开1.5寸。', NULL, '①头痛,目眩;②鼻塞。', NULL, NULL, 0, NULL, NULL, NULL, 1.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-07', '132', '通天', '双穴', '足太阳膀胱经', '头部', '头部', '在头部,前发际正中直上4寸,旁开1.5寸。', NULL, '①头痛,眩晕;②鼻塞、鼻衄、鼻渊等鼻病;③癫痫。', NULL, NULL, 0, NULL, NULL, NULL, 1.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-08', '133', '络却', '双穴', '足太阳膀胱经', '头部', '头后部', '在头部,前发际正中直上5.5寸,旁开1.5寸。', NULL, '①头晕;②目视不明,耳鸣。', NULL, NULL, 0, NULL, NULL, NULL, 1.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-09', '134', '玉枕', '双穴', '足太阳膀胱经', '头部', '头后部', '在头部,横平枕外隆凸上缘,后发际正中旁开1.3寸。', NULL, '①头项痛,目痛;②鼻塞。', NULL, NULL, 0, NULL, NULL, NULL, 1.3, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-10', '135', '天柱', '双穴', '足太阳膀胱经', '颈部', '颈后部', '在颈后区,横平第2颈椎棘突上际,斜方肌外缘凹陷中。', NULL, '①后头痛、项强、肩背腰痛;②鼻塞;③目痛;④癫狂痫;⑤热病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-11', '136', '大杼', '双穴', '足太阳膀胱经', '背部', '颈后部', '在脊柱区,第1胸椎棘突下,后正中线旁开1.5寸。', NULL, '①咳嗽,发热;②项强,肩背痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.0340, '第1胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-12', '137', '风门', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第2胸椎棘突下,后正中线旁开1.5寸。', NULL, '①感冒、咳嗽、发热、头痛等外感病证;②项强,胸背痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.0780, '第2胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-13', '138', '肺俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第3胸椎棘突下,后正中线旁开1.5寸。', NULL, '①咳嗽、气喘、咯血等肺系病证;②骨蒸潮热、盗汗等阴虚病证;③瘙痒、瘾疹等皮肤病。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.1200, '第3胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-14', '139', '厥阴俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第4胸椎棘突下,后正中线旁开1.5寸。', NULL, '①心痛,心悸;②咳嗽,胸闷;③呕吐。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.1620, '第4胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-15', '140', '心俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第5胸椎棘突下,后正中线旁开1.5寸。', NULL, '①心痛、惊悸、失眠、健忘、癫痫等心与神志病证;②咳嗽、咯血等肺系病证;③盗汗,遗精。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.2030, '第5胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-16', '141', '督俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第6胸椎棘突下,后正中线旁开1.5寸。', NULL, '①心痛,胸闷;②寒热,气喘;③腹胀、腹痛、肠鸣、呃逆等胃肠病证。', NULL, NULL, 0, NULL, 1, NULL, 1.5, 0.2430, '第6胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-17', '142', '膈俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第7胸椎棘突下,后正中线旁开1.5寸。', NULL, '①血瘀诸证;②呕吐、呃逆、气喘、吐血等上逆之证;③瘾疹,皮肤瘙痒;④贫血;⑤潮热,盗汗。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.2830, '第7胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-18', '143', '肝俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第9胸椎棘突下,后正中线旁开1.5寸。', NULL, '①胁痛、黄疸等肝胆病证;②目赤、目视不明、目眩、夜盲、迎风流泪等目疾;③癫狂痫;④脊背痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.3610, '第9胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-19', '144', '胆俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第10胸椎棘突下,后正中线旁开1.5寸。', NULL, '①黄疸、口苦、胁痛等肝胆病证;②肺痨,潮热。', NULL, NULL, 0, NULL, 1, NULL, 1.5, 0.4000, '第10胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-20', '145', '脾俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第11胸椎棘突下,后正中线旁开1.5寸', NULL, '①腹胀、纳呆、呕吐、腹泻、痢疾、便血、水肿等脾胃肠腑病证;②多食善饥,身体消瘦;③背痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.4380, '第11胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-21', '146', '胃俞', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第12胸椎棘突下,后正中线旁开1.5寸。', NULL, '①胃脘痛、呕吐、腹胀、肠鸣等胃肠病证;②多食善饥,身体消瘦。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.4760, '第12胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-22', '147', '三焦俞', '双穴', '足太阳膀胱经', '背部', '腰部', '在脊柱区,第1腰椎棘突下,后正中线旁开1.5寸。', NULL, '①肠鸣、腹胀、呕吐、腹泻、痢疾等脾胃肠腑病证;②小便不利、水肿等三焦气化不利病证;③腰背强痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.5140, '第1腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-23', '148', '肾俞', '双穴', '足太阳膀胱经', '背部', '腰部', '在脊柱区,第2腰椎棘突下,后正中线旁开1.5寸。', NULL, '①头晕、耳鸣、耳聋、腰酸痛等肾虚病证;②遗尿、遗精、阳痿、早泄、不育等泌尿生殖系统疾患;③月经不调、带下、不孕等妇科病证;④消渴。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.5520, '第2腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-24', '149', '气海俞', '双穴', '足太阳膀胱经', '背部', '腰部', '在脊柱区,第3腰椎棘突下,后正中线旁开1.5寸。', NULL, '①肠鸣,腹胀;②痛经;③腰痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.5900, '第3腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-25', '150', '大肠俞', '双穴', '足太阳膀胱经', '背部', '腰部', '在脊柱区,第4腰椎棘突下,后正中线旁开1.5寸。', NULL, '①腰腿痛;②腹胀、腹泻、便秘等胃肠病证。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.6280, '第4腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-26', '151', '关元俞', '双穴', '足太阳膀胱经', '背部', '腰部', '在脊柱区,第5腰椎棘突下,后正中线旁开1.5寸。', NULL, '①腹胀,泄泻;②腰骶痛;③小便频数或不利,遗尿。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.6660, '第5腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-27', '152', '小肠俞', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,横平第1骶后孔,骶正中嵴旁开1.5寸。', NULL, '①遗精、遗尿、尿血、尿痛、带下等泌尿生殖系统疾患;②腹泻,痢疾;③疝气;④腰骶痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.7500, '横平第1骶后孔', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-28', '153', '膀胱俞', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,横平第2骶后孔,骶正中嵴旁开1.5寸。', NULL, '①小便不利、遗尿等膀胱气化功能失调病证;②腹泻,便秘;③腰脊强痛。', NULL, NULL, 1, NULL, 1, NULL, 1.5, 0.8340, '横平第2骶后孔', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-29', '154', '中膂俞', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,横平第3骶后孔,骶正中嵴旁开1.5寸。', NULL, '①腹泻;②疝气;③腰骶痛。', NULL, NULL, 0, NULL, 1, NULL, 1.5, 0.9170, '横平第3骶后孔', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-30', '155', '白环俞', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,横平第4骶后孔,骶正中嵴旁开1.5寸。', NULL, '①遗尿,遗精;②月经不调,带下;③疝气;④腰骶痛。', NULL, NULL, 0, NULL, 1, NULL, 1.5, 1.0000, '横平第4骶后孔', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-31', '156', '上髎', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,正对第1骶后孔中。', NULL, '①大小便不利;②月经不调、带下、阴挺等妇科病证;③遗精,阳痿;④腰骶痛。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.7500, '正对第1骶后孔中', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-32', '157', '次髎', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,正对第2骶后孔中。', NULL, '①月经不调、痛经、带下等妇科病证;②小便不利、遗精、阳痿等;③疝气;④腰骶痛,下肢痿痹。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.8340, '正对第2骶后孔中', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-33', '158', '中髎', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,正对第3骶后孔中。', NULL, '①便秘,泄泻;②小便不利;③月经不调,带下;④腰骶痛。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.9170, '正对第3骶后孔中', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-34', '159', '下髎', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,正对第4骶后孔中。', NULL, '①腹痛,便秘;②小便不利;③带下;④腰骶痛。', NULL, NULL, 1, NULL, 1, NULL, NULL, 1.0000, '正对第4骶后孔中', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-35', '160', '会阳', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,尾骨端旁开0.5寸。', NULL, '①痔疾,腹泻,便血;②阳痿;③带下。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-36', '161', '承扶', '双穴', '足太阳膀胱经', '下肢', '股后区', '在股后区,臀沟的中点。', NULL, '①腰、骶、臀、股部疼痛;②痔疾。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-37', '162', '殷门', '双穴', '足太阳膀胱经', '下肢', '股后区', '在股后区,臀沟下6寸,股二头肌与半腱肌之间。', NULL, '腰痛,下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-38', '163', '浮郄', '双穴', '足太阳膀胱经', '下肢', '膝后区', '在膝后区,腘横纹上1寸,股二头肌腱的内侧缘。', NULL, '①股腘部疼痛、麻木;②便秘。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-39', '164', '委阳', '双穴', '足太阳膀胱经', '下肢', '膝后区', '在膝部,腘横纹上,股二头肌腱的内侧缘。', NULL, '①腹满,小便不利;②腰脊强痛,腿足挛痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-40', '165', '委中', '双穴', '足太阳膀胱经', '下肢', '膝后区', '在膝后区,腘横纹中点。', NULL, '①腰背痛、下肢痿痹等腰及下肢病证;②腹痛、急性吐泻等急症;③瘾疹,丹毒;④小便不利,遗尿。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-41', '166', '附分', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第2胸椎棘突下,后正中线旁开3寸。', NULL, '颈项强痛、肩背拘急、肘臂麻木等痹证。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.0780, '第2胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-42', '167', '魄户', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第3胸椎棘突下,后正中线旁开3寸。', NULL, '①咳嗽、气喘、肺痨等肺疾;②项强,肩背痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.1200, '第3胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-43', '168', '膏肓', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第4胸椎棘突下,后正中线旁开3寸。', NULL, '①咳嗽、气喘、肺痨等肺系虚损病证;②健忘、遗精、盗汗、羸瘦等虚劳诸证;③肩胛痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.1620, '第4胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-44', '169', '神堂', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第5胸椎棘突下,后正中线旁开3寸。', NULL, '①咳嗽、气喘、胸闷等肺胸病证;②脊背强痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.2030, '第5胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-45', '170', '譩譆', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第6胸椎棘突下,后正中线旁开3寸。', NULL, '①咳嗽,气喘;②肩背痛;③疟疾,热病。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.2430, '第6胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-46', '171', '膈关', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第7胸椎棘突下,后正中线旁开3寸。', NULL, '①胸闷、嗳气、呕吐等气上逆之病证;②脊背强痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.2830, '第7胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-47', '172', '魂门', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第9胸椎棘突下,后正中线旁开3寸。', NULL, '①胸胁痛,背痛;②呕吐,腹泻。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.3610, '第9胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-48', '173', '阳纲', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第10胸椎棘突下,后正中线旁开3寸。', NULL, '①肠鸣、腹痛、腹泻等胃肠病证;②黄疸;③消渴。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.4000, '第10胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-49', '174', '意舍', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第11胸椎棘突下,后正中线旁开3寸。', NULL, '腹胀、肠鸣、呕吐、腹泻等胃肠病证。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.4380, '第11胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-50', '175', '胃仓', '双穴', '足太阳膀胱经', '背部', '背部', '在脊柱区,第12胸椎棘突下,后正中线旁开3寸。', NULL, '①胃脘痛、腹胀、小儿食积等脾胃病证;②水肿;③背脊痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.4760, '第12胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-51', '176', '盲门', '双穴', '足太阳膀胱经', '背部', '腰部', '在腰区,第1腰椎棘突下,后正中线旁开3寸。', NULL, '①腹痛、胃痛、便秘、痞块等胃肠病证;②乳疾。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.5140, '第1腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-52', '177', '志室', '双穴', '足太阳膀胱经', '背部', '腰部', '在腰区,第2腰椎棘突下,后正中线旁开3寸。', NULL, '①遗精、阳痿等肾虚病证;②小便不利,水肿;③腰脊强痛。', NULL, NULL, 1, NULL, 1, NULL, 3, 0.5520, '第2腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-53', '178', '胞肓', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,横平第2骶后孔,骶正中嵴旁开3寸。', NULL, '①肠鸣、腹胀、便秘等胃肠病证;②癃闭;③腰脊强痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.8340, '横平第2骶后孔', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-54', '179', '秩边', '双穴', '足太阳膀胱经', '背部', '骶区', '在骶区,横平第4骶后孔,骶正中嵴旁开3寸。', NULL, '①腰骶痛、下肢痿痹等腰及下肢病证;②小便不利,癃闭;③便秘,痔疾;④阴痛。', NULL, NULL, 0, NULL, 1, NULL, 3, 1.0000, '横平第4骶后孔', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-55', '180', '合阳', '双穴', '足太阳膀胱经', '下肢', '小腿后区', '在小腿后区,腘横纹下2寸,腓肠肌内、外侧头之间。', NULL, '①腰脊强痛,下肢痿痹;②疝气;③崩漏。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-56', '181', '承筋', '双穴', '足太阳膀胱经', '下肢', '小腿后区', '在小腿后区,腘横纹下5寸,腓肠肌两肌腹之间。', NULL, '①腰腿拘急、疼痛;②痔疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-57', '182', '承山', '双穴', '足太阳膀胱经', '下肢', '小腿后区', '在小腿后区,腓肠肌两肌腹与肌腱交角处。', NULL, '①腰腿拘急、疼痛;②痔疾,便秘;③腹痛,疝气。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-58', '183', '飞扬', '双穴', '足太阳膀胱经', '下肢', '小腿外侧区', '在小腿后区,昆仑直上7寸,腓肠肌外下缘与跟腱移行处。', NULL, '①腰腿疼痛;②头痛,目眩;③鼻塞,鼻衄;④痔疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-59', '184', '跗阳', '双穴', '足太阳膀胱经', '下肢', '小腿外侧区', '在小腿后区,昆仑直上3寸,腓骨与跟腱之间。', NULL, '①腰骶痛、下肢痿痹、外踝肿痛等腰、下肢病证;②头痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-60', '185', '昆仑', '双穴', '足太阳膀胱经', '下肢', '踝外侧区', '在踝区,外踝尖与跟腱之间的凹陷中。', NULL, '①后头痛,项强,目眩;②腰骶疼痛,足踝肿痛;③癫痫;④滞产。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-61', '186', '仆参', '双穴', '足太阳膀胱经', '下肢', '踝外侧区', '在跟区,昆仑直下,跟骨外侧,赤白肉际处。', NULL, '①下肢痿痹,足跟痛;②癫痫。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-62', '187', '申脉', '双穴', '足太阳膀胱经', '下肢', '踝外侧区', '在踝区,外踝尖直下,外踝下缘与跟骨之间凹陷中。', NULL, '①头痛,眩晕;②失眠、癫狂痫等神志病证;③腰腿酸痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-63', '188', '金门', '双穴', '足太阳膀胱经', '下肢', '足背区', '在足背,外踝前缘直下,第5跖骨粗隆后方,骰骨下缘凹陷中。', NULL, '①头痛、腰痛、下肢痿痹、外踝痛等痛证、痹证;②癫痫;③小儿惊风。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-64', '189', '京骨', '双穴', '足太阳膀胱经', '下肢', '足跖外区', '在跖区,第5跖骨粗隆前下方,赤白肉际处。', NULL, '①头痛,项强;②腰腿痛;③癫痫;④目翳。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-65', '190', '束骨', '双穴', '足太阳膀胱经', '下肢', '足跖外区', '在跖区,第5跖趾关节的近端,赤白肉际处。', NULL, '①头痛、项强、目眩等头部疾患;②腰腿痛;③癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-66', '191', '足通骨', '双穴', '足太阳膀胱经', '下肢', '足趾区', '在足趾,第5跖趾关节的远端,赤白肉际处。', NULL, '①头痛,项强;②目眩,鼻衄;③癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('pangguang-67', '192', '至阴', '双穴', '足太阳膀胱经', '下肢', '足趾区', '在足趾,足小趾末节外侧,趾甲根角侧后方0.1寸(指寸)。', NULL, '①胎位不正,滞产;②头痛,目痛;③鼻塞,鼻衄。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-01', '193', '涌泉', '双穴', '足少阴肾经', '下肢', '足底', '在足底,屈足卷趾时足心最凹陷中;约当足底第2、3趾蹼缘与足跟连线的前1/3与后2/3交点凹陷中。', NULL, '①昏厥、中暑、小儿惊风、癫狂痫等急症及神志病证;②头痛,头晕,目眩,失眠;③咯血、咽喉肿痛、喉痹、失音等肺系病证;④大便难,小便不利;⑤奔豚气;⑥足心热。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-02', '194', '然谷', '双穴', '足少阴肾经', '下肢', '足跖内区', '在足内侧,足舟骨粗隆下方,赤白肉际处。', NULL, '①月经不调、阴挺、阴痒、白浊等妇科病证;②遗精、阳痿、小便不利等泌尿生殖系统疾患;③咯血,咽喉肿痛;④消渴;⑤下肢痿痹,足跗痛;⑥小儿脐风,口噤;⑦腹泻。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-03', '195', '太溪', '双穴', '足少阴肾经', '下肢', '踝内侧区', '在足踝区,内踝尖与跟腱之间凹陷中。', NULL, '①头痛、目眩、失眠、健忘、遗精、阳痿等肾虚证;②咽喉肿痛、齿痛、耳鸣、耳聋等阴虚性五官病证;③咳嗽、气喘、咯血、胸痛等肺系疾患;④消渴,小便频数,便秘;⑤月经不调;⑥腰脊痛,下肢厥冷,内踝肿痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-04', '196', '大钟', '双穴', '足少阴肾经', '下肢', '踝内侧区', '在跟区,内踝后下方,跟骨上缘,跟腱附着部前缘凹陷中。', NULL, '①痴呆;②癃闭,遗尿,便秘;③月经不调;④咯血,气喘;⑤腰脊强痛,足跟痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-05', '197', '水泉', '双穴', '足少阴肾经', '下肢', '踝内侧区', '在跟区,太溪直下1寸,跟骨结节内侧凹陷中。', NULL, '①月经不调、痛经、阴挺等妇科病证;②小便不利,淋证,血尿。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-06', '198', '照海', '双穴', '足少阴肾经', '下肢', '踝内侧区', '在踝区,内踝尖下1寸,内踝下缘边际凹陷中。', NULL, '①失眠、癫痫等神志病证;②咽喉干痛、目赤肿痛等五官热性病证;③月经不调、痛经、带下、阴挺等妇科病证;④小便频数,癃闭。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-07', '199', '复溜', '双穴', '足少阴肾经', '下肢', '小腿内侧区', '在小腿内侧,内踝尖上2寸,跟腱的前缘。', NULL, '①水肿、汗证(无汗或多汗)等津液输布失调病证;②腹胀、腹泻、肠鸣等胃肠病证;③腰脊强痛,下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-08', '200', '交信', '双穴', '足少阴肾经', '下肢', '小腿内侧区', '在小腿内侧,在内踝尖上2寸,胫骨内侧缘后际凹陷中;复溜前0.5寸。', NULL, '①月经不调、崩漏、阴挺、阴痒等妇科病证;②腹泻、便秘、痢疾等胃肠病证;③五淋;④疝气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-09', '201', '筑宾', '双穴', '足少阴肾经', '下肢', '小腿内侧区', '在小腿内侧,太溪直上5寸,比目鱼肌与跟腱之间。', NULL, '①癫狂;②疝气;③呕吐涎沫,吐舌;④小腿内侧痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-10', '202', '阴谷', '双穴', '足少阴肾经', '下肢', '膝后区', '在膝后区,腘横纹上,半腱肌肌腱外侧缘。', NULL, '①癫狂;②阳痿、小便不利、月经不调、崩漏等泌尿生殖系统疾患;③膝股内侧痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-11', '203', '横骨', '双穴', '足少阴肾经', '腹部', '下腹部', '在下腹部,脐中下5寸,前正中线旁开0.5寸。', NULL, '①少腹胀痛;②小便不利、遗尿、遗精、阳痿等泌尿生殖系疾患;③疝气。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-12', '204', '大赫', '双穴', '足少阴肾经', '腹部', '下腹部', '在下腹部,脐中下4寸,前正中线旁开0.5寸。', NULL, '①遗精,阳痿;②阴挺、带下、月经不调等妇科病证;③泄泻,痢疾。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-13', '205', '气穴', '双穴', '足少阴肾经', '腹部', '下腹部', '在下腹部,脐中下3寸,前正中线旁开0.5寸。', NULL, '①月经不调,带下,不孕;②小便不利;③腹泻;④奔豚气。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-14', '206', '四满', '双穴', '足少阴肾经', '腹部', '下腹部', '在下腹部,脐中下2寸,前正中线旁开0.5寸。', NULL, '①月经不调、崩漏、带下、产后恶露不尽等妇产科病证;②遗精,遗尿;③小腹痛,脐下积、聚、疝、瘕等腹部疾患;④便秘,水肿。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-15', '207', '中注', '双穴', '足少阴肾经', '腹部', '下腹部', '在下腹部,脐中下1寸,前正中线旁开0.5寸。', NULL, '①月经不调;②腹痛、便秘、腹泻等胃肠病证', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-16', '208', '肓俞', '双穴', '足少阴肾经', '腹部', '上腹部', '在腹部,脐中旁开0.5寸。', NULL, '①腹痛绕脐、腹胀、腹泻、便秘等胃肠病证;②疝气;③月经不调。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-17', '209', '商曲', '双穴', '足少阴肾经', '腹部', '上腹部', '上腹部,脐中上2寸,前正中线旁开0.5寸。', NULL, '①胃痛、腹痛、腹胀、腹泻、便秘等胃肠病证;②腹中积聚。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-18', '210', '石关', '双穴', '足少阴肾经', '腹部', '上腹部', '在上腹部,脐中上3寸,前正中线旁开0.5寸。', NULL, '①胃痛、呕吐、腹痛、便秘等胃肠病证;②产后腹痛,不孕。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-19', '211', '阴都', '双穴', '足少阴肾经', '腹部', '上腹部', '在上腹部,脐中上4寸,前正中线旁开0.5寸。', NULL, '胃痛、腹胀、便秘等胃肠病证。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-20', '212', '腹通谷', '双穴', '足少阴肾经', '腹部', '上腹部', '在上腹部,脐中上5寸,前正中线旁开0.5寸。', NULL, '①腹痛、腹胀、胃痛、呕吐等胃肠病证;②心痛、心悸、胸痛等心胸病证。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-21', '213', '幽门', '双穴', '足少阴肾经', '腹部', '上腹部', '在上腹部,脐中上6寸,前正中线旁开0.5寸。', NULL, '腹痛、善哕、呕吐、腹胀、腹泻等胃肠病证。', NULL, NULL, 0, NULL, NULL, NULL, 0.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-22', '214', '步廊', '双穴', '足少阴肾经', '胸部', '前胸区', '在胸部,第5肋间隙,前正中线旁开2寸。', NULL, '①胸痛、咳嗽、气喘等胸肺病证;②乳痈。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-23', '215', '神封', '双穴', '足少阴肾经', '胸部', '前胸区', '在胸部,第4肋间隙,前正中线旁开2寸。', NULL, '①胸胁支满、咳嗽、气喘等胸肺疾患;②乳痈;③呕吐,不嗜食。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-24', '216', '灵墟', '双穴', '足少阴肾经', '胸部', '前胸区', '在胸部,第3肋间隙,前正中线旁开2寸。', NULL, '①胸胁支满、咳嗽、气喘等胸肺疾患;②乳痈;③呕吐。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-25', '217', '神藏', '双穴', '足少阴肾经', '胸部', '前胸区', '在胸部,第2肋间隙,前正中线旁开2寸。', NULL, '①胸胁支满、咳嗽、气喘等胸肺疾患;②呕吐,不嗜食。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-26', '218', '彧中', '双穴', '足少阴肾经', '胸部', '前胸区', '在胸部,第1肋间隙,前正中线旁开2寸。', NULL, '咳嗽、气喘、胸胁支满、痰涌等肺系病证。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('shenjing-27', '219', '俞府', '双穴', '足少阴肾经', '胸部', '前胸区', '在胸部,锁骨下缘,前正中线旁开2寸。', NULL, '咳嗽、气喘、胸痛等胸肺疾患。', NULL, NULL, 0, NULL, NULL, NULL, 2, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-01', '220', '天池', '双穴', '手厥阴心包经', '胸部', '前胸区', '在胸部,第4肋间隙,前正中线旁开5寸。', NULL, '①咳嗽、痰多、胸闷、气喘、胸痛等心肺病证;②腋肿,乳痈,乳少;③瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, 5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-02', '221', '天泉', '双穴', '手厥阴心包经', '上肢', '上臂前区', '在臂前区,腋前纹头下2寸,肱二头肌的长、短头之间。', NULL, '①心痛、咳嗽、胸胁胀满等心肺病证;②胸背及上臂内侧痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-03', '222', '曲泽', '双穴', '手厥阴心包经', '上肢', '肘部前区', '在肘前区,肘横纹上,肱二头肌腱的尺侧缘凹陷中。', NULL, '①心痛、心悸、善惊等心系病证;②胃痛、呕血、呕吐等胃热病证;③暑热病;④肘臂挛痛,上肢颤动。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-04', '223', '郄门', '双穴', '手厥阴心包经', '上肢', '前臂前区', '在前臂前区,腕掌侧远端横纹上5寸,掌长肌腱与桡侧腕屈肌腱之间。', NULL, '①急性心痛、心悸、心烦、胸痛等心胸病证;②咯血、呕血、衄血等热性出血证;③疔疮;④癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-05', '224', '间使', '双穴', '手厥阴心包经', '上肢', '前臂前区', '在前臂前区,腕掌侧远端横纹上3寸,掌长肌腱与桡侧腕屈肌腱之间。', NULL, '①心痛、心悸等心系病证;②胃痛、呕吐等胃热病证;③热病,疟疾;④癫狂痫;⑤腋肿,肘、臂、腕挛痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-06', '225', '内关', '双穴', '手厥阴心包经', '上肢', '前臂前区', '在前臂前区,腕掌侧远端横纹上2寸,掌长肌腱与桡侧腕屈肌腱之间。', NULL, '①心痛、胸闷、心动过速或过缓等心系病证;②胃痛、呕吐、呃逆等胃腑病证;③中风,偏瘫,眩晕,偏头痛;④失眠、郁证、癫狂痫等神志病证;⑤肘、臂、腕挛痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-07', '226', '大陵', '双穴', '手厥阴心包经', '上肢', '手腕前区', '在腕前区,腕掌侧远端横纹中,掌长肌腱与桡侧腕屈肌腱之间。', NULL, '①心痛,心悸,胸胁满痛;②胃痛、呕吐、口臭等胃腑病证;③喜笑悲恐、癫狂痫等神志疾患;④臂、手挛痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-08', '227', '劳宫', '双穴', '手厥阴心包经', '上肢', '手掌', '在掌区,横平第3掌指关节近端,第2、3掌骨之间偏于第3掌骨。', NULL, '①中风昏迷、中暑等急症;②心痛、烦闷、癫狂痫等心与神志病证;③口疮,口臭;④鹅掌风。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('xinbao-09', '228', '中冲', '双穴', '手厥阴心包经', '上肢', '手指', '在手指,中指末端最高点。', NULL, '①中风昏迷、舌强不语、中暑、昏厥、小儿惊风等急症;②热病,舌下肿痛;③小儿夜啼。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-01', '229', '关冲', '双穴', '手少阴三焦经', '上肢', '手指', '在手指,第4指末节尺侧,指甲根角侧上方0.1寸(指寸)。', NULL, '①头痛、目赤、耳鸣、耳聋、喉痹、舌强等头面五官病证;②热病,中暑。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-02', '230', '液门', '双穴', '手少阴三焦经', '上肢', '手背', '在手背部,当第4、5指间,指蹼缘上方赤白肉际凹陷中。', NULL, '①头痛、目赤、耳鸣、耳聋、喉痹等头面五官热性病证;②疟疾;③手臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-03', '231', '中渚', '双穴', '手少阴三焦经', '上肢', '手背', '在手背,第4、5掌骨间,第4掌指关节近端凹陷中。', NULL, '①头痛、目赤、耳鸣、耳聋、喉痹等头面五官病证;②热病,疟疾;③肩背肘臂酸痛,手指不能屈伸。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-04', '232', '阳池', '双穴', '手少阴三焦经', '上肢', '手腕后区', '在腕后区,腕背侧远端横纹上,指伸肌腱的尺侧缘凹陷中。', NULL, '①目赤肿痛、耳聋、喉痹等五官病证;②消渴,口干;③腕痛,肩臂痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-05', '233', '外关', '双穴', '手少阴三焦经', '上肢', '前臂后区', '在前臂后区,腕背侧远端横纹上2寸,尺骨与桡骨间隙中点。', NULL, '①热病;②头痛、目赤肿痛、耳鸣、耳聋等头面五官病证;③瘰疬;④胁肋痛;⑤上肢痿痹不遂。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-06', '234', '支沟', '双穴', '手少阴三焦经', '上肢', '前臂后区', '在前臂后区,腕背侧远端横纹上3寸,尺骨与桡骨间隙中点。', NULL, '①耳聋,耳鸣,暴喑;②胁肋痛;③便秘;④瘰疬;⑤热病。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-07', '235', '会宗', '双穴', '手少阴三焦经', '上肢', '前臂后区', '在前臂后区,腕背侧远端横纹上3寸,尺骨的桡侧缘。', NULL, '①耳鸣,耳聋;②手臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-08', '236', '三阳络', '双穴', '手少阴三焦经', '上肢', '前臂后区', '在前臂后区,腕背侧远端横纹上4寸,尺骨与桡骨间隙中点。', NULL, '①耳聋、暴喑、齿痛等五官病证;②手臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-09', '237', '四渎', '双穴', '手少阴三焦经', '上肢', '前臂后区', '在前臂后区,肘尖下5寸,尺骨与桡骨间隙中点。', NULL, '①耳聋、暴喑、齿痛、咽喉肿痛等五官病证;②手臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-10', '238', '天井', '双穴', '手少阴三焦经', '上肢', '肘后区', '在肘后区,肘尖上1寸凹陷中。', NULL, '①耳聋;②癫痫;③瘰疬,瘿气;④偏头痛,胁肋痛,颈项肩臂痛;⑤肘劳。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-11', '239', '清泠渊', '双穴', '手少阴三焦经', '上肢', '上臂后区', '在臂后区,肘尖与肩峰角连线上,肘尖上2寸。', NULL, '头痛,目痛,胁痛,肩臂痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-12', '240', '消泺', '双穴', '手少阴三焦经', '上肢', '上臂后区', '在臂后区,肘尖与肩峰角连线上,肘尖上5寸。', NULL, '头痛,齿痛,项背痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-13', '241', '臑会', '双穴', '手少阴三焦经', '上肢', '上臂后区', '在臂后区,肩峰角下3寸,三角肌的后下缘。', NULL, '①瘰疬,瘿气;②上肢痹痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-14', '242', '肩髎', '双穴', '手少阴三焦经', '上肢', '肩部', '在三角肌区,肩峰角与肱骨大结节两骨间凹陷中。', NULL, '臂痛,肩重不能举。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-15', '243', '天髎', '双穴', '手少阴三焦经', '上肢', '肩胛区', '在肩胛区,肩胛骨上角骨际凹陷中。', NULL, '肩臂痛,颈项强急。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-16', '244', '天牖', '双穴', '手少阴三焦经', '颈部', '颈外侧区', '在颈部,横平下颌角,胸锁乳突肌的后缘凹陷中。', NULL, '①头痛、头眩、项强、目不明、暴聋、鼻衄、喉痹等头项、五官病证;②瘰疬;③颈项强痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-17', '245', '翳风', '双穴', '手少阴三焦经', '颈部', '颈外侧区', '在颈部,耳垂后方,乳突下端前方凹陷中。', NULL, '①耳鸣、耳聋等耳疾;②口眼歪斜、面痛、牙关紧闭、颊肿等面、口病证;③瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-18', '246', '瘈脉', '双穴', '手少阴三焦经', '头部', '头部', '在头部,乳突中央,角孙与翳风沿耳轮弧形连线的上2/3与下1/3的交点处。', NULL, '①头痛;②耳鸣,耳聋;③小儿惊风。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-19', '247', '颅息', '双穴', '手少阴三焦经', '头部', '头部', '在头部,角孙与翳风沿耳轮弧形连线的上1/3与下2/3的交点处。', NULL, '①头痛;②耳鸣,耳聋;③小儿惊风。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-20', '248', '角孙', '双穴', '手少阴三焦经', '头部', '头部', '在头部,耳尖正对发际处。', NULL, '①头痛,项强;②痄腮,齿痛;③目翳,目赤肿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-21', '249', '耳门', '双穴', '手少阴三焦经', '头部', '头部', '在耳区,耳屏上切迹与下颌骨髁突之间的凹陷中。', NULL, '①耳鸣、耳聋、聤耳等耳疾;②齿痛,颈颔痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-22', '250', '耳和髎', '双穴', '手少阴三焦经', '头部', '头部', '在头部,鬓发后缘,耳郭根的前方,颞浅动脉的后缘。', NULL, '①头痛,耳鸣;②牙关紧闭,口歪。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('sanjiao-23', '251', '丝竹空', '双穴', '手少阴三焦经', '头部', '面部', '在面部,眉梢凹陷中。', NULL, '①癫痫;②头痛、目眩、目赤肿痛、眼睑动等头目病证;③齿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-01', '252', '瞳子髎', '双穴', '足少阳胆经', '头部', '面部', '在面部,目外眦外侧0.5寸凹陷中。', NULL, '①头痛;②目赤肿痛、羞明流泪、内障、目翳等目疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-02', '253', '听会', '双穴', '足少阳胆经', '头部', '面部', '在面部,耳屏间切迹与下颌骨髁突之间的凹陷中。', NULL, '①耳鸣、耳聋、聤耳等耳疾;②齿痛、面痛、口眼歪斜等面口病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-03', '254', '上关', '双穴', '足少阳胆经', '头部', '面部', '在面部,颧弓上缘中央凹陷中。', NULL, '①耳鸣、耳聋、聤耳等耳疾;②齿痛、面痛、口眼歪斜、口噤等面口病证;③癫狂痫', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-04', '255', '颌厌', '双穴', '足少阳胆经', '头部', '头部', '在头部,从头维至曲鬓的弧形连线(其弧度与鬓发弧度相应)的上1/4与下3/4交点处。', NULL, '①偏头痛,眩晕;②惊痫;③耳鸣、目外眦痛、齿痛等五官病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-05', '256', '悬颅', '双穴', '足少阳胆经', '头部', '头部', '在头部,从头维至曲鬓的弧形连线(其弧度与鬓发弧度相应)的中点处。', NULL, '①偏头痛;②目赤肿痛,齿痛;③鼽衄。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-06', '257', '悬厘', '双穴', '足少阳胆经', '头部', '头部', '在头部,从头维至曲鬓的弧形连线(其弧度与鬓发弧度相应)的上3/4与下1/4交点处。', NULL, '①偏头痛;②目赤肿痛;③耳鸣。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-07', '258', '曲鬓', '双穴', '足少阳胆经', '头部', '头部', '在头部,耳前鬓角发际后缘与耳尖水平线交点处。', NULL, '头痛连齿、颊颔肿、口噤等头面病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-08', '259', '率谷', '双穴', '足少阳胆经', '头部', '头部', '在头部,耳尖直上入发际1.5寸。', NULL, '①偏头痛,眩晕;②小儿急、慢惊风。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-09', '260', '天冲', '双穴', '足少阳胆经', '头部', '头部', '在头部,耳根后缘直上,入发际2寸。', NULL, '①偏头痛;②癫痫;③齿龈肿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-10', '261', '浮白', '双穴', '足少阳胆经', '头部', '头部', '在头部,耳后乳突的后上方,从天冲至完骨的弧形连线(其弧度与耳郭弧度相应)的上1/3与下2/3交点处。', NULL, '①头痛、耳鸣、耳聋、齿痛等头面病证;②瘿气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-11', '262', '头窍阴', '双穴', '足少阳胆经', '头部', '头部', '在头部,耳后乳突的后上方,从天冲至完骨的弧形连线(其弧度与耳郭弧度相应)的上2/3与下1/3交点处。', NULL, '①头痛,眩晕;②耳鸣,耳聋。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-12', '263', '完骨', '双穴', '足少阳胆经', '头部', '头部', '在头部,耳后乳突的后下方凹陷中。', NULL, '①癫痫;②头痛、颈项强痛、喉痹、颊肿、齿痛、口歪等头项五官病证;③中风。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-13', '264', '本神', '双穴', '足少阳胆经', '头部', '头部', '在头部,前发际上0.5寸,头正中线旁开3寸。', NULL, '①癫痫,小儿惊风,中风;②头痛,目眩;③不寐。', NULL, NULL, 0, NULL, NULL, NULL, 3, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-14', '265', '阳白', '双穴', '足少阳胆经', '头部', '头部', '在头部,眉上1寸,瞳孔直上。', NULL, '①前头痛;②眼睑下垂,口眼歪斜;③目赤肿痛、视物模糊、眼睑动等目疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-15', '266', '头临泣', '双穴', '足少阳胆经', '头部', '头部', '在头部,前发际上0.5寸,瞳孔直上。', NULL, '①头痛;②目痛、目眩、流泪、目翳等目疾;③鼻塞,鼻渊;④小儿惊痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-16', '267', '目窗', '双穴', '足少阳胆经', '头部', '头部', '在头部,前发际上1.5寸,瞳孔直上。', NULL, '①头痛;②目痛、目眩、远视、近视等目疾;③小儿惊痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-17', '268', '正营', '双穴', '足少阳胆经', '头部', '头部', '在头部,前发际上2.5寸,瞳孔直上。', NULL, '①头痛、头晕、目眩等头目病证;②齿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-18', '269', '承灵', '双穴', '足少阳胆经', '头部', '头部', '在头部,前发际上4寸,瞳孔直上。', NULL, '①头痛,眩晕;②目痛;③鼻渊、鼻衄、鼻窒、多涕等鼻疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-19', '270', '脑空', '双穴', '足少阳胆经', '头部', '头后部', '在头部,横平枕外隆凸的上缘,风池直上。', NULL, '①热病;②头痛,颈项强痛;③目眩、目赤肿痛、鼻痛、耳聋等五官病证;④惊悸,癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-20', '271', '风池', '双穴', '足少阳胆经', '颈部', '颈后部', '在颈后区,枕骨之下,胸锁乳突肌上端与斜方肌上端之间的凹陷中。注:项部枕骨下两侧,横平风府,胸锁乳突肌与斜方肌之间凹陷中。', NULL, '①中风、癫痫、头痛、眩晕、耳鸣、耳聋等内风所致的病证;②感冒、鼻塞、鼽衄、目赤肿痛、口眼歪斜等外风所致的病证;③颈项强痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-21', '272', '肩井', '双穴', '足少阳胆经', '上肢', '肩胛区', '在肩胛区,第7颈椎棘突与肩峰最外侧点连线的中点。', NULL, '①颈项强痛,肩背疼痛,上肢不遂;②滞产、乳痈、乳汁不下、乳癖等妇产科及乳房疾患;③瘰疬。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.0000, '第7颈椎棘突', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-22', '273', '渊腋', '双穴', '足少阳胆经', '胸部', '胸外侧区', '在胸外侧区,第4肋间隙中,在腋中线上。', NULL, '①胸满,胁痛;②上肢痹痛,腋下肿。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-23', '274', '辄筋', '双穴', '足少阳胆经', '胸部', '胸外侧区', '在胸外侧区,第4肋间隙中,腋中线前1寸。', NULL, '①胸满,气喘;②呕吐,吞酸;③胁痛,腋肿,肩背痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-24', '275', '日月', '双穴', '足少阳胆经', '胸部', '前胸区', '在胸部,第7肋间隙中,前正中线旁开4寸。', NULL, '①黄疸、胁肋疼痛等肝胆病证;②呕吐、吞酸、呃逆等肝胆犯胃病证。', NULL, NULL, 0, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-25', '276', '京门', '双穴', '足少阳胆经', '腹部', '侧腹区', '在上腹部,当第12肋骨游离端的下际。', NULL, '①小便不利、水肿等水液代谢失调病证;②腹胀、肠鸣、腹泻等胃肠病证;③腰痛,胁痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-26', '277', '带脉', '双穴', '足少阳胆经', '腹部', '侧腹区', '在侧腹部,第11肋骨游离端垂线与脐水平线的交点上。', NULL, '①月经不调、闭经、赤白带下等妇科病;②疝气;③腰痛,胁痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-27', '278', '五枢', '双穴', '足少阳胆经', '腹部', '下腹部', '在下腹部,横平脐下3寸,髂前上棘内侧。', NULL, '①赤白带下、月经不调、阴挺、小腹痛等妇科病证;②疝气,少腹痛;③腰胯痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-28', '279', '维道', '双穴', '足少阳胆经', '腹部', '下腹部', '在下腹部,髂前上棘内下0.5寸。', NULL, '①阴挺、赤白带下、月经不调等妇科病证;②疝气,少腹痛;③腰胯痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-29', '280', '居髎', '双穴', '足少阳胆经', '下肢', '臀部', '在臀部,髂前上棘与股骨大转子最凸点连线的中点处。', NULL, '①腰腿痹痛,瘫痪;②疝气,少腹痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-30', '281', '环跳', '双穴', '足少阳胆经', '下肢', '臀部', '在臀区,股骨大转子最凸点与骶管裂孔连线的外1/3与内2/3交点处。', NULL, '腰胯疼痛、下肢痿痹、半身不遂等腰腿疾患。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-31', '282', '风市', '双穴', '足少阳胆经', '下肢', '股外侧区', '在股部,髌底上7寸:直立垂手,掌心贴于大腿时,中指尖所指凹陷中,髂胫束后缘。', NULL, '①下肢痿痹、麻木及半身不遂等下肢疾患;②遍身瘙痒,脚气。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-32', '283', '中渎', '双穴', '足少阳胆经', '下肢', '股外侧区', '在股部,腘横纹上7寸,髂胫束后缘。', NULL, '下肢痿痹、麻木及半身不遂等下肢疾患。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-33', '284', '膝阳关', '双穴', '足少阳胆经', '下肢', '膝外侧区', '在膝部,股骨外上髁后上缘,股二头肌腱与髂胫束之间的凹陷中。', NULL, '①膝腘肿痛、挛急及小腿麻木等下肢、膝关节疾患;②脚气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-34', '285', '阳陵泉', '双穴', '足少阳胆经', '下肢', '小腿外侧区', '在小腿外侧,腓骨头前下方凹陷中。', NULL, '①黄疸、胁痛、口苦、呕吐、吞酸等肝胆犯胃病证;②膝肿痛、下肢痿痹及麻木等下肢、膝关节疾患;③小儿惊风;④肩痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-35', '286', '阳交', '双穴', '足少阳胆经', '下肢', '小腿外侧区', '在小腿外侧,外踝尖上7寸,腓骨后缘。', NULL, '①惊狂、癫痫等神志病证;②瘛疭;③胸胁满痛;④下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-36', '287', '外丘', '双穴', '足少阳胆经', '下肢', '小腿外侧区', '在小腿外侧,外踝尖上7寸,腓骨前缘。', NULL, '①癫狂;②胸胁胀满;③下肢痿痹;④颈项强痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-37', '288', '光明', '双穴', '足少阳胆经', '下肢', '小腿外侧区', '在小腿外侧,外踝尖上5寸,腓骨前缘。', NULL, '①目痛、夜盲、近视、目花等目疾;②胸乳胀痛,乳少;③下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-38', '289', '阳辅', '双穴', '足少阳胆经', '下肢', '小腿外侧区', '在小腿外侧,外踝尖上4寸,腓骨前缘。', NULL, '①偏头痛、目外眦痛、咽喉肿痛、腋下肿痛、胸胁满痛等头面躯体痛证;②瘰疬;③下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-39', '290', '悬钟', '双穴', '足少阳胆经', '下肢', '小腿外侧区', '在小腿外侧,外踝尖上3寸,腓骨前缘。', NULL, '①痴呆、中风等髓海不足疾患;②颈项强痛,胸胁满痛,下肢痿痹。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-40', '291', '丘墟', '双穴', '足少阳胆经', '下肢', '踝外侧区', '在踝区,外踝的前下方,趾长伸肌腱的外侧凹陷中。', NULL, '①目赤肿痛、目翳等目疾;②颈项痛、腋下肿、胸胁痛、外踝肿痛等痛证;③足内翻,足下垂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-41', '292', '足临泣', '双穴', '足少阳胆经', '下肢', '足背区', '在足背,第4、5跖骨底结合部的前方,第5趾长伸肌腱外侧凹陷中。', NULL, '①偏头痛、目赤肿痛、胁肋疼痛、足跗疼痛等痛证;②月经不调,乳少,乳痈;③疟疾;④瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-42', '293', '地五会', '双穴', '足少阳胆经', '下肢', '足背区', '在足背,第4、5跖骨间,第4跖趾关节近端凹陷中。', NULL, '①头痛、目赤肿痛、胁痛、足跗肿痛等痛证;②耳鸣,耳聋;③乳痈。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-43', '294', '侠溪', '双穴', '足少阳胆经', '下肢', '足背区', '在足背,第4、5趾间,趾蹼缘后方赤白肉际处。', NULL, '①惊悸;②头痛、眩晕、颊肿、耳鸣、耳聋、目赤肿痛等头面五官病证;③胁肋疼痛、膝股痛、足跗肿痛等痛证;④乳痈;⑤热病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('danjing-44', '295', '足窍阴', '双穴', '足少阳胆经', '下肢', '足趾区', '在足趾,第4趾末节外侧,趾甲根角侧后方0.1寸(指寸)。', NULL, '①头痛、目赤肿痛、耳鸣、耳聋、喉痹等头面五官病证;②胸胁痛,足跗肿痛;③不寐;④热病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-01', '296', '大敦', '双穴', '足厥阴肝经', '下肢', '足趾区', '在足趾,大趾末节外侧,趾甲根角侧后方0.1寸(指寸)。', NULL, '①疝气,少腹痛;②遗尿、癃闭、五淋、尿血等前阴病;③月经不调、崩漏、阴挺等妇科病;④癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-02', '297', '行间', '双穴', '足厥阴肝经', '下肢', '足背区', '在足背,第1、2趾间,趾蹼缘后方赤白肉际处。', NULL, '①中风、癫痫、头痛、目眩、目赤肿痛、青盲、口歪等肝经风热病证;②月经不调、痛经、闭经、崩漏、带下等妇科病;③阴中痛,疝气;④遗尿、癃闭、五淋等泌尿系病证;⑤胸胁满痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-03', '298', '太冲', '双穴', '足厥阴肝经', '下肢', '足背区', '在足背,第1、2跖骨间,跖骨底结合部前方凹陷中,或触及动脉搏动。', NULL, '①中风、癫狂痫、小儿惊风、头痛、眩晕、耳鸣、目赤肿痛、口歪、咽痛等肝经风热病证;②月经不调、痛经、经闭、崩漏、带下、滞产等妇产科病证;③黄疸、胁痛、口苦、腹胀、呕逆等肝胃病证;④癃闭,遗尿;⑤下肢痿痹,足跗肿痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-04', '299', '中封', '双穴', '足厥阴肝经', '下肢', '踝内侧区', '在踝区,内踝前,胫骨前肌肌腱的内侧缘凹陷中。', NULL, '①疝气;②阴缩,阴茎痛,遗精;③小便不利;④腰痛、少腹痛、内踝肿痛等痛证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-05', '300', '蠡沟', '双穴', '足厥阴肝经', '下肢', '小腿内侧区', '在小腿内侧,内踝尖上5寸,胫骨内侧面的中央。', NULL, '①月经不调、赤白带下、阴挺、阴痒等妇科病证;②小便不利;③疝气,睾丸肿痛;④足胫疼痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-06', '301', '中都', '双穴', '足厥阴肝经', '下肢', '小腿内侧区', '在小腿内侧,内踝尖上7寸,胫骨内侧面的中央。', NULL, '①疝气,小腹痛;②崩漏,恶露不尽;③泄泻;④下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-07', '302', '膝关', '双穴', '足厥阴肝经', '下肢', '膝内侧区', '在膝部,胫骨内侧髁的下方,阴陵泉后1寸。', NULL, '膝髌肿痛,下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-08', '303', '曲泉', '双穴', '足厥阴肝经', '下肢', '膝内侧区', '在膝部,腘横纹内侧端,半腱肌肌腱内缘凹陷中。', NULL, '①月经不调、痛经、带下、阴挺、阴痒、产后腹痛、腹中包块等妇科病;②遗精,阳痿,疝气;③小便不利;④膝髌肿痛,下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-09', '304', '阴包', '双穴', '足厥阴肝经', '下肢', '股内侧区', '在股前区,髌底上4寸,股薄肌与缝匠肌之间。', NULL, '①月经不调;②小便不利,遗尿;③腰骶痛引少腹。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-10', '305', '足五里', '双穴', '足厥阴肝经', '下肢', '股前区', '在股前区,气冲直下3寸,动脉搏动处。', NULL, '①少腹痛;②小便不利,阴挺,睾丸肿痛;③瘰疬。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-11', '306', '阴廉', '双穴', '足厥阴肝经', '下肢', '股前区', '在股前区,气冲直下2寸。', NULL, '①月经不调,带下;②少腹痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-12', '307', '急脉', '双穴', '足厥阴肝经', '腹部', '腹股沟', '在腹股沟区,横平耻骨联合上缘,前正中线旁开2.5寸。', NULL, '①少腹痛,疝气;②阴挺,外阴肿痛。', NULL, NULL, 0, NULL, NULL, NULL, 2.5, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-13', '308', '章门', '双穴', '足厥阴肝经', '腹部', '侧腹区', '在侧腹部,在第11肋游离端的下际。', NULL, '①腹痛、腹胀、肠鸣、腹泻、呕吐等脾胃病证;②胁痛、黄疸、痞块等肝胆病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('ganjing-14', '309', '期门', '双穴', '足厥阴肝经', '胸部', '前胸区', '在胸部,第6肋间隙,前正中线旁开4寸。', NULL, '①胸胁胀痛、呕吐、吞酸、呃逆、腹胀、腹泻等肝胃病证;②郁病,奔豚气;③乳痈。', NULL, NULL, 1, NULL, NULL, NULL, 4, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-01', '310', '会阴', '单穴', '任脉', '会阴', '会阴区', '在会阴区,男性在阴囊根部与肛门连线的中点;女性在大阴唇后联合与肛门连线的中点。', NULL, '①溺水窒息、昏迷、癫狂痫等急危症、神志病;②小便不利、遗尿、遗精、阴痛、阴痒、脱肛、阴挺、痔疮等前后二阴疾患。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-02', '311', '曲骨', '单穴', '任脉', '腹部', '下腹部', '在下腹部,耻骨联合上缘,前正中线上。', NULL, '①小便不利、遗尿等前阴病;②遗精、阳痿、阴囊湿痒等男科病;③月经不调、痛经、赤白带下等妇科病。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-03', '312', '中极', '单穴', '任脉', '腹部', '下腹部', '在下腹部,脐中下4寸,前正中线上。', NULL, '①遗尿、小便不利、癃闭等前阴病;②遗精、阳痿、不育等男科病证;③月经不调、崩漏、阴挺、阴痒、不孕、产后恶露不尽、带下等妇科病。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-04', '313', '关元', '单穴', '任脉', '腹部', '下腹部', '在下腹部,脐中下3寸,前正中线上。', NULL, '①中风脱证、虚劳冷惫、羸瘦无力等元气虚损病证;②少腹疼痛,疝气;③腹泻、痢疾、脱肛、便血等肠腑病证;④五淋、尿血、尿闭、尿频等前阴病;⑤遗精、阳痿、早泄、白浊等男科病;⑥月经不调、痛经、经闭、崩漏、带下、阴挺、恶露不尽、胞衣不下等妇科病;⑦保健灸常用穴。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-05', '314', '石门', '单穴', '任脉', '腹部', '下腹部', '在下腹部,脐中下2寸,前正中线上。', NULL, '①腹胀、腹泻、痢疾、绕脐疼痛等肠腑病证;②奔豚气,疝气;③水肿,小便不利;④遗精、阳痿等男科病;⑤经闭、带下、崩漏、产后恶露不尽等妇科病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-06', '315', '气海', '单穴', '任脉', '腹部', '下腹部', '在下腹部,脐中下1.5寸,前正中线上。', NULL, '①虚脱、形体羸瘦、脏气衰惫、乏力等气虚病证;②水谷不化、绕脐疼痛、腹泻、痢疾、便秘等肠腑病证;③小便不利、遗尿等前阴病;④遗精,阳痿;⑤疝气,少腹痛;⑥月经不调、痛经、经闭、崩漏、带下、阴挺、产后恶露不尽、胞衣不下等妇科病;⑦保健灸常用穴。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-07', '316', '阴交', '单穴', '任脉', '腹部', '下腹部', '在下腹部,脐中下1寸,前正中线上。', NULL, '①腹痛,疝气;②水肿,小便不利;③月经不调、崩漏、带下等妇科病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-08', '317', '神阙', '单穴', '任脉', '腹部', '上腹部', '在脐区,脐中央。', NULL, '①虚脱、中风脱证等元阳暴脱;②腹痛、腹胀、腹泻、痢疾、便秘、脱肛等肠腑病证;③水肿,小便不利;④保健灸常用穴。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-09', '318', '水分', '单穴', '任脉', '腹部', '上腹部', '在上腹部,脐中上1寸,前正中线上。', NULL, '①水肿、小便不利等水液输布失常病证;②腹痛、腹泻、反胃吐食等胃肠病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-10', '319', '下脘', '单穴', '任脉', '腹部', '上腹部', '在上腹部,脐中上2寸,前正中线上。', NULL, '①腹痛、腹胀、腹泻、呕吐、完谷不化、小儿疳积等脾胃病;②痞块。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-11', '320', '建里', '单穴', '任脉', '腹部', '上腹部', '在上腹部,脐中上3寸,前正中线上。', NULL, '①胃痛、呕吐、食欲不振、腹胀、腹痛等脾胃病;②水肿。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-12', '321', '中脘', '单穴', '任脉', '腹部', '上腹部', '在上腹部,脐中上4寸,前正中线上。', NULL, '①胃痛、腹胀、纳呆、呕吐、吞酸、呃逆、小儿疳积等脾胃病;②黄疸;③癫狂,脏躁。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-13', '322', '上脘', '单穴', '任脉', '腹部', '上腹部', '在上腹部,脐中上5寸,前正中线上。', NULL, '①胃痛、呕吐、呃逆、腹胀等胃腑病证;②癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-14', '323', '巨阙', '单穴', '任脉', '腹部', '上腹部', '在上腹部,脐中上6寸,前正中线上。', NULL, '①癫狂痫;②胸痛,心悸;③呕吐,吞酸。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-15', '324', '鸠尾', '单穴', '任脉', '腹部', '上腹部', '在上腹部,剑胸结合下1寸,前正中线上。', NULL, '①癫狂痫;②胸痛;③腹胀,呃逆。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-16', '325', '中庭', '单穴', '任脉', '腹部', '上腹部', '在上腹部,剑胸结合中点处,前正中线上。', NULL, '①胸腹胀满、噎膈、呕吐等胃气上逆病证;②心痛;③梅核气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-17', '326', '膻中', '单穴', '任脉', '胸部', '前胸区', '在胸部,横平第4肋间隙,前正中线上。', NULL, '①咳嗽、气喘、胸闷、心痛、噎膈、呃逆等胸中气机不畅病证;②产后乳少、乳痈、乳癖等胸乳病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-18', '327', '玉堂', '单穴', '任脉', '胸部', '前胸区', '在胸部,横平第3肋间隙,前正中线上。', NULL, '咳嗽、气喘、胸闷、胸痛、乳房胀痛、呕吐等气机不畅病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-19', '328', '紫宫', '单穴', '任脉', '胸部', '前胸区', '在胸部,横平第2肋间隙,前正中线上。', NULL, '咳嗽,气喘,胸痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-20', '329', '华盖', '单穴', '任脉', '胸部', '前胸区', '在胸部,横平第1肋间隙,前正中线上。', NULL, '咳嗽,气喘,胸痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-21', '330', '璇玑', '单穴', '任脉', '胸部', '前胸区', '在胸部,胸骨上窝下1寸,前正中线上。', NULL, '①咳嗽,气喘,胸痛;②咽喉肿痛;③积食。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-22', '331', '天突', '单穴', '任脉', '颈部', '颈部前区', '在颈前区,胸骨上窝中央,前正中线上。', NULL, '①咳嗽、哮喘、胸痛、咽喉肿痛、暴喑等肺系病证;②瘿气、梅核气、噎膈等气机不畅病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-23', '332', '廉泉', '单穴', '任脉', '颈部', '颈部前区', '在颈前区,喉结上方,舌骨上缘凹陷中,前正中线上。', NULL, '中风失语、暴喑、吞咽困难、舌缓流涎、舌下肿痛、口舌生疮、喉痹等咽喉口舌病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('renmai-24', '333', '承浆', '单穴', '任脉', '头部', '面部', '在面部,颏唇沟的正中凹陷处。', NULL, '①口歪、齿龈肿痛、流涎等口部病证;②暴喑;③癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-01', '334', '长强', '单穴', '督脉', '会阴', '会阴区', '在会阴区,尾骨下方,尾骨端与肛门连线的中点处。', NULL, '①腹泻、痢疾、便血、便秘、痔疮、脱肛等肠腑病证;②癫狂痫;③腰脊和尾骶部疼痛。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-02', '335', '腰俞', '单穴', '督脉', '背部', '骶区', '在骶区,正对骶管裂孔,后正中线上。', NULL, '①月经不调、经闭等月经病;②腰脊强痛,下肢痿痹;③痫证;④腹泻、痢疾、便血、便秘、痔疮、脱肛等肠腑病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-03', '336', '腰阳关', '单穴', '督脉', '背部', '腰部', '在脊柱区,第4腰椎棘突下凹陷中,后正中线上。', NULL, '①腰骶疼痛,下肢痿痹;②月经不调、赤白带下等妇科病证;③遗精、阳痿等男科病证。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.6280, '第4腰椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-04', '337', '命门', '单穴', '督脉', '背部', '腰部', '在脊柱区,第2腰椎棘突下凹陷中,后正中线上。', NULL, '①腰脊强痛,下肢痿痹;②月经不调、赤白带下、痛经、经闭、不孕等妇科病证;③遗精、阳痿、精冷不育、小便频数等男子肾阳不足病证;④小腹冷痛,腹泻。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.5520, '第2腰椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-05', '338', '悬枢', '单穴', '督脉', '背部', '腰部', '在脊柱区,第1腰椎棘突下凹陷中,后正中线上。', NULL, '①腰脊强痛;②腹胀、腹痛、完谷不化、腹泻、痢疾等胃肠疾患。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.5140, '第1腰椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-06', '339', '脊中', '单穴', '督脉', '背部', '背部', '在脊柱区,第11胸椎棘突下凹陷中,后正中线上。', NULL, '①癫痫;②黄疸;③腹泻、痢疾、痔疮、脱肛、便血等肠腑病证;④腰脊强痛;⑤小儿疳积。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.4380, '第11胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-07', '340', '中枢', '单穴', '督脉', '背部', '背部', '在脊柱区,第10胸椎棘突下凹陷中,后正中线上。', NULL, '①黄疸;②呕吐、腹满、胃痛、食欲不振等脾胃病证;③腰背疼痛。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.4000, '第10胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-08', '341', '筋缩', '单穴', '督脉', '背部', '背部', '在脊柱区,第9胸椎棘突下凹陷中,后正中线上。', NULL, '①癫狂痫;②抽搐、脊强、四肢不收、筋挛拘急等筋病;③胃痛;④黄疸。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.3610, '第9胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-09', '342', '至阳', '单穴', '督脉', '背部', '背部', '在脊柱区,第7胸椎棘突下凹陷中,后正中线上。', NULL, '①黄疸、胸胁胀满等肝胆病证;②咳嗽,气喘;③腰背疼痛,脊强。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.2830, '第7胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-10', '343', '灵台', '单穴', '督脉', '背部', '背部', '在脊柱区,第6胸椎棘突下凹陷中,后正中线上。', NULL, '①咳嗽,气喘;②脊痛,项强;③疔疮。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.2430, '第6胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-11', '344', '神道', '单穴', '督脉', '背部', '背部', '在脊柱区,第5胸椎棘突下凹陷中,后正中线上。', NULL, '①心痛、心悸、怔忡等心疾;②失眠、健忘、中风不语、痫证等神志病;③咳嗽,气喘;④腰脊强,肩背痛。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.2030, '第5胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-12', '345', '身柱', '单穴', '督脉', '背部', '背部', '在脊柱区,第3胸椎棘突下凹陷中,后正中线上。', NULL, '①身热、头痛、咳嗽、气喘等外感病证;②惊厥、癫狂痫等神志病;③腰脊强痛;④疔疮发背。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.1200, '第3胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-13', '346', '陶道', '单穴', '督脉', '背部', '背部', '在脊柱区,第1胸椎棘突下凹陷中,后正中线上。', NULL, '①热病、疟疾、恶寒发热、咳嗽、气喘等外感病证;②骨蒸潮热;③癫狂;④脊强。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.0340, '第1胸椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-14', '347', '大椎', '单穴', '督脉', '背部', '背部', '在脊柱区,第7颈椎棘突下凹陷中,后正中线上。', NULL, '①热病、疟疾、恶寒发热、咳嗽、气喘等外感病证;②骨蒸潮热;③癫狂痫证、小儿惊风等神志病;④项强,脊痛;⑤风疹,痤疮。', NULL, NULL, 1, NULL, 1, NULL, NULL, 0.0000, '第7颈椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-15', '348', '哑门', '单穴', '督脉', '颈部', '颈后部', '在颈后区,第2颈椎棘突上际凹陷中,后正中线上。', NULL, '①暴喑,舌缓不语;②癫狂痫、癔症等神志病;③头痛,颈项强痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-16', '349', '风府', '单穴', '督脉', '颈部', '颈后部', '在颈后区,枕外隆凸直下,两侧斜方肌之间凹陷中。', NULL, '①中风、癫狂痫、癔症等内风为患的神志病证;②头痛、眩晕、颈项强痛、咽喉肿痛、失音、目痛、鼻衄等头颈、五官病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-17', '350', '脑户', '单穴', '督脉', '头部', '头后部', '在头部,枕外隆凸的上缘凹陷中。', NULL, '①头晕,项强;②失音;③癫痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-18', '351', '强间', '单穴', '督脉', '头部', '头后部', '在头部,后发际正中直上4寸。', NULL, '①头痛,目眩,项强;②癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-19', '352', '后顶', '单穴', '督脉', '头部', '头后部', '在头部,后发际正中直上5.5寸。', NULL, '①头痛,眩晕;②癫狂痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-20', '353', '百会', '单穴', '督脉', '头部', '头部', '在头部,前发际正中直上5寸。', NULL, '①痴呆、中风、失语、瘛疭、失眠、健忘、癫狂痫证、癔症等神志病;②头痛,眩晕,耳鸣;③脱肛、阴挺、胃下垂、肾下垂等气失固摄而致的下陷性病证。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-21', '354', '前顶', '单穴', '督脉', '头部', '头部', '在头部,前发际正中直上3.5寸。', NULL, '①头痛,眩晕;②鼻渊;③癫狂痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-22', '355', '卤会', '单穴', '督脉', '头部', '头部', '在头部,前发际正中直上2寸。', NULL, '①头痛,眩晕;②鼻渊;③癫狂痫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-23', '356', '上星', '单穴', '督脉', '头部', '头部', '在头部,前发际正中直上1寸。', NULL, '①鼻渊、鼻衄、头痛、目痛等头面部病;②热病,疟疾;③癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-24', '357', '神庭', '单穴', '督脉', '头部', '头部', '在头部,前发际正中直上0.5寸。', NULL, '①癫狂痫、失眠、惊悸等神志病;②头痛、目眩、目赤、目翳、鼻渊、鼻衄等头面五官病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-25', '358', '素髎', '单穴', '督脉', '头部', '面部', '在面部,鼻尖的正中央。', NULL, '①昏迷、惊厥、新生儿窒息、休克、呼吸衰竭等急危重症;②鼻渊、鼻衄等鼻病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-26', '359', '水沟', '单穴', '督脉', '头部', '面部', '在面部,人中沟的上1/3与中1/3交点处。', NULL, '①昏迷、晕厥、中风、中暑、休克、呼吸衰竭等急危重症,为急救要穴之一;②癔症、癫狂痫、急慢惊风等神志病;③鼻塞、鼻衄、面肿、口歪、齿痛、牙关紧闭等面鼻口部病证;④闪挫腰痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-27', '360', '兑端', '单穴', '督脉', '头部', '面部', '在面部,上唇结节的中点。', NULL, '①昏迷、晕厥、癫狂、癔症等神志病;②口歪、口噤、口臭、齿痛等口部病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-28', '361', '龈交', '单穴', '督脉', '头部', '面部', '在上唇内,上唇系带与上牙龈的交点。', NULL, '①口歪、口噤、口臭、齿衄、齿痛、鼻衄、面赤颊肿等面口部病证;②痔疮;③癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('dumai-29', '362', '印堂', '单穴', '督脉', '头部', '面部', '在头部,两眉毛内侧端中间的凹陷中。', NULL, '①痴呆、痫证、失眠、健忘等神志病证;②头痛,眩晕;③鼻衄,鼻渊;④小儿惊风,产后血晕,子痫。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-01', '363', '四神聪', '单穴', '经外奇穴', '头部', '头部', '在头部,百会前后左右各旁开1寸,共4穴。', NULL, '①头痛,眩晕;②失眠、健忘、癫痫等神志病;③目疾。', NULL, NULL, 1, NULL, NULL, NULL, 1, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-02', '364', '当阳', '双穴', '经外奇穴', '头部', '头部', '在头部,瞳孔直上,前发际上1寸。', NULL, '失眠、健忘、癫痫、头痛、眩晕。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-03', '365', '鱼腰', '双穴', '经外奇穴', '头部', '头部', '在头部,瞳孔直上,眉毛中。', NULL, '眉棱骨痛、眼睑动、眼睑下垂、目赤肿痛、目翳、口眼歪斜等。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-04', '366', '太阳', '双穴', '经外奇穴', '头部', '头部', '在头部,当眉梢与目外眦之间,向后约一横指的凹陷中。', NULL, '①头痛;②目疾;③面瘫。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-05', '367', '耳尖', '双穴', '经外奇穴', '头部', '头部', '在耳区,在外耳轮的最高点。', NULL, '①目疾;②头痛;③咽喉肿痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-06', '368', '球后', '双穴', '经外奇穴', '头部', '面部', '在面部,眶下缘外1/4与内3/4交界处。', NULL, '目疾。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-07', '369', '上迎香', '双穴', '经外奇穴', '头部', '面部', '在面部,鼻翼软骨与鼻甲的交界处,近鼻唇沟上端处。', NULL, '鼻渊,鼻部疮疖。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-08', '370', '内迎香', '双穴', '经外奇穴', '头部', '面部', '在鼻孔内,鼻翼软骨与鼻甲交界的粘膜处。', NULL, '头痛,目赤肿痛,鼻炎,咽喉炎,中暑。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-09', '371', '聚泉', '单穴', '经外奇穴', '头部', '面部', '在口腔内,舌背正中缝的中点处。', NULL, '咳嗽、哮喘、语言障碍、味觉减退。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-10', '372', '海泉', '单穴', '经外奇穴', '头部', '面部', '在口腔内,舌下系带中点处。', NULL, '口舌生疮、呕吐、腹泻、咽喉炎、糖尿病。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-11', '373', '金津', '单穴', '经外奇穴', '头部', '面部', '在口腔内,舌下系带的静脉上。左侧为金津。', NULL, '①舌强,舌肿,口疮,喉痹,失语;②消渴,呕吐,腹泻。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-12', '374', '玉液', '单穴', '经外奇穴', '头部', '面部', '在口腔内,舌下系带的静脉上。右侧为玉液。', NULL, '①舌强,舌肿,口疮,喉痹,失语;②消渴,呕吐,腹泻。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-13', '375', '夹承浆', '双穴', '经外奇穴', '头部', '面部', '在面部,承浆穴左右各旁开1寸。', NULL, '口歪,齿龈肿痛。', NULL, NULL, 0, NULL, NULL, NULL, 1, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-14', '376', '牵正', '双穴', '经外奇穴', '头部', '面部', '在面部,耳垂前0.5~1寸的压痛处。', NULL, '口歪,口疮。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-15', '377', '翳明', '双穴', '经外奇穴', '颈部', '颈外侧区', '在颈部,翳风后1寸。', NULL, '①头痛,眩晕,失眠;②目疾,耳鸣。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-16', '378', '颈百劳', '双穴', '经外奇穴', '颈部', '颈后部', '在颈部,第7颈椎棘突直上2寸,后正中线旁开1寸。', NULL, '①颈项强痛;②咳嗽,气喘,骨蒸潮热,盗汗,自汗;③瘰疬。', NULL, NULL, 0, NULL, 1, -2.00, 1, NULL, '第7颈椎棘突直上2寸', 4, 1, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-17', '379', '安眠', '双穴', '经外奇穴', '颈部', '颈外侧区', '在项部,在翳风穴与风池穴连线之中点处。', NULL, '①失眠,头痛,眩晕;②心悸;③癫狂。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-18', '380', '子宫', '双穴', '经外奇穴', '腹部', '下腹部', '在下腹部,脐中下4寸,前正中线旁开3寸。', NULL, '阴挺、月经不调、痛经、崩漏、不孕等妇科病。', NULL, NULL, 1, NULL, NULL, NULL, 3, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-19', '381', '三角炙', '单穴', '经外奇穴', '腹部', '下腹部', '在下腹部,以患者两口角之间的长度为一边,做等边三角形,将顶角置于患者脐心,底边呈水平线,两底角处取穴。', NULL, '疝气,腹痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-20', '382', '定喘', '双穴', '经外奇穴', '背部', '背部', '在脊柱区,横平第7颈椎棘突下,后正中线旁开0.5寸。', NULL, '①哮喘,咳嗽;②肩背痛,落枕。', NULL, NULL, 0, NULL, 1, NULL, 0.5, 0.0000, '第7颈椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-21', '383', '夹脊', '双穴', '经外奇穴', '背部', '背部', '在脊柱区,第1胸椎至第5腰椎棘突下两侧,后正中线旁开0.5寸,一侧17穴。', NULL, '适应范围较广,其中上胸部的穴位治疗心肺、上肢疾病;下胸部的穴位治疗脾胃肝胆疾病;腰部的穴位治疗肾病、腰腹及下肢疾病。', NULL, NULL, 0, NULL, 1, NULL, 0.5, 0.6660, '第5腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-22', '384', '胃脘下俞', '双穴', '经外奇穴', '背部', '背部', '在脊柱区,横平第8胸椎棘突下,后正中线旁开1.5寸。', NULL, '①胃痛,腹痛,胸胁痛;②消渴。', NULL, NULL, 0, NULL, 1, NULL, 1.5, 0.3220, '第8胸椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-23', '385', '痞根', '双穴', '经外奇穴', '背部', '腰部', '在腰区,横平第1腰椎棘突下,后正中线旁开3.5寸。', NULL, '痞块,癥瘕,疝气,腰痛。', NULL, NULL, 0, NULL, 1, NULL, 3.5, 0.5140, '第1腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-24', '386', '下极俞', '单穴', '经外奇穴', '背部', '腰部', '在腰区,第3腰椎棘突下。', NULL, '肾炎、遗尿、肠炎、腰肌劳损、阳痿、遗精。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.5900, '第3腰椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-25', '387', '腰宜', '双穴', '经外奇穴', '背部', '腰部', '在腰区,横平第4腰椎棘突下,后正中线旁开3寸。', NULL, '睾丸炎、遗尿、肾炎、腰肌劳损。', NULL, NULL, 0, NULL, 1, NULL, 3, 0.6280, '第4腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-26', '388', '腰眼', '双穴', '经外奇穴', '背部', '腰部', '在腰区,横平第4腰椎棘突下,后正中线旁开约3.5凹陷中。', NULL, '①腰痛;②月经不调,带下;③虚劳。', NULL, NULL, 0, NULL, 1, NULL, 3.5, 0.6280, '第4腰椎棘突下', 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-27', '389', '十七椎', '单穴', '经外奇穴', '背部', '腰部', '在腰区,第5腰椎棘突下凹陷中。', NULL, '①腰腿痛,下肢瘫痪;②崩漏,痛经,月经不调;③小便不利。', NULL, NULL, 0, NULL, 1, NULL, NULL, 0.6660, '第5腰椎棘突下', 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-28', '390', '腰奇', '单穴', '经外奇穴', '背部', '骶区', '在骶区,尾骨端直上2寸,骶角之间凹陷中。', NULL, '①癫痫;②头痛,失眠;③便秘。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-29', '391', '肩前', '双穴', '经外奇穴', '上肢', '肩部', '在肩前区,正坐垂肩,腋前皱襞顶端与肩髃连线的中点。', NULL, '肩臂痛,臂不能举。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-30', '392', '肘尖', '双穴', '经外奇穴', '上肢', '肘后区', '在肘后区,尺骨膺嘴的尖端。', NULL, '①瘰疬;②痈疽;③肠痈。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-31', '393', '二白', '双穴', '经外奇穴', '上肢', '前臂前区', '在前臂前区,腕掌侧远端横纹上4寸,桡侧腕屈肌腱的两侧,一肢3穴。', NULL, '①痔疾,脱肛;②前臂痛,胸胁痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-32', '394', '中泉', '双穴', '经外奇穴', '上肢', '前臂后区', '在前臂后区,腕背侧远端横纹上,指总伸肌腱桡侧的凹陷中。', NULL, '支气管炎、支气管哮喘、胃炎、肠炎。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-33', '395', '中魁', '双穴', '经外奇穴', '上肢', '手指', '在手指,中指背面,近侧指间关节的中点处。', NULL, '噎膈、呕吐、食欲不振、呃逆等脾胃病证。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-34', '396', '大骨空', '双穴', '经外奇穴', '上肢', '手指', '在手指,拇指背面,指间关节的中点处。', NULL, '①目痛,迎风流泪,目翳;②吐泻;③衄血。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-35', '397', '小骨空', '双穴', '经外奇穴', '上肢', '手指', '在手指,小指背面,近侧指间关节的中点处。', NULL, '①目痛,迎风流泪,目翳;②指关节痛。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-36', '398', '腰痛点', '双穴', '经外奇穴', '上肢', '手背', '在手背,第2、3掌骨及第4、5掌骨之间,腕背侧横纹远端与掌指关节中点处,一手2穴。', NULL, '急性腰扭伤。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-37', '399', '外劳宫', '双穴', '经外奇穴', '上肢', '手背', '在手背,第2、3掌骨间,掌指关节后0.5寸(指寸)凹陷中。', NULL, '①落枕;②手臂肿痛;③脐风。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-38', '400', '八邪', '双穴', '经外奇穴', '上肢', '手背', '在手背,第1~5指间,指蹼缘后方赤白肉际处,左右共8穴。', NULL, '①手背肿痛,手指麻木;②烦热;③目痛;④毒蛇咬伤。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-39', '401', '四缝', '双穴', '经外奇穴', '上肢', '手指', '在手指,第2~5指掌面的近侧指间关节横纹的中央,一手4穴。', NULL, '①小儿疳积;②百日咳。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-40', '402', '十宣', '双穴', '经外奇穴', '上肢', '手指', '在手指,十指尖端,距指甲游离缘0.1寸(指寸),左右共10穴。', NULL, '①昏迷;②癫痫;③高热,咽喉肿痛;④手指麻木。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-41', '403', '髋骨', '双穴', '经外奇穴', '下肢', '股前区', '在股前区,梁丘两旁各1.5寸,一肢2穴。', NULL, '腿痛、膝关节炎。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-42', '404', '鹤顶', '双穴', '经外奇穴', '下肢', '膝部前区', '在膝前区,髌底中点的上方凹陷中。', NULL, '膝痛,足胫无力,下肢瘫痪。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-43', '405', '百虫窝', '双穴', '经外奇穴', '下肢', '股前区', '在股前区,髌底内侧端上3寸。', NULL, '①虫积;②风湿痒疹,下部生疮。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-44', '406', '内膝眼', '双穴', '经外奇穴', '下肢', '膝部前区', '在膝部,髌韧带内侧凹陷处的中央。', NULL, '①膝痛,腿痛;②脚气。', NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-45', '407', '胆囊', '双穴', '经外奇穴', '下肢', '小腿外侧区', '在小腿外侧,腓骨小头直下2寸。', NULL, '①胆囊炎,胆石症,胆道蛔虫症,胆绞痛;②下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-46', '408', '阑尾', '双穴', '经外奇穴', '下肢', '小腿外侧区', '在小腿外侧,髌韧带外侧凹陷下5寸,胫骨前嵴外一横指(中指)。', NULL, '①阑尾炎,消化不良;②下肢痿痹。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-47', '409', '内踝尖', '双穴', '经外奇穴', '下肢', '踝内侧区', '在踝区,内踝的最凸起处。', NULL, '①乳蛾,牙痛;②小儿不语;③霍乱转筋。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-48', '410', '外踝尖', '双穴', '经外奇穴', '下肢', '踝内侧区', '在踝区,外踝的最凸起处。', NULL, '牙痛、腓肠肌痉挛、寒热脚气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-49', '411', '八风', '双穴', '经外奇穴', '下肢', '足背区', '在足背,第1~5趾间,趾蹼缘后方赤白肉际处,左右共8穴。', NULL, '①足跗肿痛,趾痛;②毒蛇咬伤;③脚气。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-50', '412', '独阴', '双穴', '经外奇穴', '下肢', '足底', '在足底,第2趾的跖侧远端趾间关节的中点。', NULL, '①胞衣不下,月经不调,疝气;②胸胁痛,卒心痛,呕吐。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW()),
-    ('jingwai-51', '413', '气端', '双穴', '经外奇穴', '下肢', '足趾', '在足趾,十趾端的中央,距趾甲游离缘0.1寸(指寸),左右共10穴。', NULL, '①足趾麻木,脚背红肿疼痛;②卒中。', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, 0, 1, 0, NOW(), NOW())
-ON DUPLICATE KEY UPDATE
-    `excel_serial_no` = VALUES(`excel_serial_no`),
-    `name` = VALUES(`name`),
-    `single_double` = VALUES(`single_double`),
-    `meridian` = VALUES(`meridian`),
-    `body_part` = VALUES(`body_part`),
-    `body_region` = VALUES(`body_region`),
-    `location_description` = VALUES(`location_description`),
-    `relative_position` = VALUES(`relative_position`),
-    `indications` = VALUES(`indications`),
-    `product_symptoms` = VALUES(`product_symptoms`),
-    `acupoint_method` = VALUES(`acupoint_method`),
-    `product_acupoint` = VALUES(`product_acupoint`),
-    `effects` = VALUES(`effects`),
-    `location_type` = VALUES(`location_type`),
-    `longitude_offset` = VALUES(`longitude_offset`),
-    `lateral_offset` = VALUES(`lateral_offset`),
-    `normalized_y` = VALUES(`normalized_y`),
-    `y_reference_term` = VALUES(`y_reference_term`),
-    `side` = VALUES(`side`),
-    `finger_width_type` = VALUES(`finger_width_type`),
-    `gender` = VALUES(`gender`),
-    `status` = VALUES(`status`),
-    `deleted` = VALUES(`deleted`),
-    `update_time` = VALUES(`update_time`);
-
-UPDATE `acupoint` SET `location_type` = 1 WHERE `deleted` = 0;
-
-SET FOREIGN_KEY_CHECKS = 1;

+ 0 - 614
code/backend/src/main/resources/sql/init.sql

@@ -1,614 +0,0 @@
--- ==================== 艾灸椅管理后台初始化 SQL ====================
--- 执行前请确保已创建数据库:CREATE DATABASE aijiuyi_admin DEFAULT CHARACTER SET utf8mb4;
--- 数据库连接:jdbc:mysql://localhost:3306/aijiuyi_admin
-
-SET NAMES utf8mb4;
-SET FOREIGN_KEY_CHECKS = 0;
-
--- ----------------------------
--- 系统操作日志表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `sys_log` (
-    `id`               BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `user_id`          BIGINT                COMMENT '请求用户ID',
-    `user_name`        VARCHAR(100)          COMMENT '请求用户名',
-    `user_type`        TINYINT               COMMENT '操作者类型:1=普通用户,2=专家,3=管理员(统一使用app_user表)',
-    `request_url`      VARCHAR(500)          COMMENT '请求接口路径',
-    `request_params`   TEXT                  COMMENT '请求参数(JSON格式)',
-    `request_result`   TEXT                  COMMENT '请求结果(JSON格式,超2000字符截断)',
-    `api_desc`         VARCHAR(200)          COMMENT '接口说明(来自@Log注解)',
-    `operation_type`   VARCHAR(20)           COMMENT '操作方式(查询/新增/更新/删除/其他)',
-    `request_ip`       VARCHAR(50)           COMMENT '请求客户端IP',
-    `operation_module` VARCHAR(100)          COMMENT '操作模块(来自@Log注解)',
-    `request_id`       VARCHAR(50)           COMMENT '请求唯一ID(同一请求的所有日志共享)',
-    `create_time`      DATETIME              COMMENT '创建时间',
-    PRIMARY KEY (`id`),
-    KEY `idx_request_id` (`request_id`),
-    KEY `idx_user_id` (`user_id`),
-    KEY `idx_create_time` (`create_time`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '系统操作日志表';
-
--- ----------------------------
--- 应用用户表(手机号验证码登录的普通用户/专家)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `app_user` (
-    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `phone`       VARCHAR(20)           COMMENT '手机号(唯一,用于登录)',
-    `username`    VARCHAR(64)           COMMENT '用户名(管理员登录用,唯一)',
-    `password`    VARCHAR(64)           COMMENT '密码(MD5加密,管理员用)',
-    `nickname`    VARCHAR(100)          COMMENT '昵称',
-    `avatar`      VARCHAR(500)          COMMENT '头像URL',
-    `user_type`   TINYINT      NOT NULL DEFAULT 1 COMMENT '用户类型:1=普通用户,2=专家,3=管理员',
-    `status`      TINYINT      NOT NULL DEFAULT 1 COMMENT '状态:1=正常,0=禁用',
-    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time` DATETIME              COMMENT '创建时间',
-    `update_time` DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_phone` (`phone`),
-    UNIQUE KEY `uk_username` (`username`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '应用用户表';
-
--- 初始化管理员账号(密码:admin123,MD5后为:0192023a7bbd73250516f069df18b500)
-INSERT INTO `app_user` (`username`, `password`, `nickname`, `user_type`, `status`, `deleted`, `create_time`, `update_time`)
-VALUES ('admin', MD5('admin123'), '超级管理员', 3, 1, 0, NOW(), NOW());
-
--- ----------------------------
--- 用户类别表(用户类型字典,code 与 app_user.user_type / user_profile.user_type 关联)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `user_category` (
-    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `code`        INT          NOT NULL COMMENT '类别编码(与 user_type 字段对应,唯一)',
-    `name`        VARCHAR(50)  NOT NULL COMMENT '类别名称',
-    `remark`      VARCHAR(255)          COMMENT '备注说明',
-    `tag_type`    VARCHAR(20)  DEFAULT 'primary' COMMENT '前端标签颜色:primary/success/warning/danger/info',
-    `sort`        INT          NOT NULL DEFAULT 0 COMMENT '排序(升序)',
-    `builtin`     TINYINT      NOT NULL DEFAULT 0 COMMENT '是否内置:1=内置(不可删除/改code),0=自定义',
-    `status`      TINYINT      NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=停用',
-    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time` DATETIME              COMMENT '创建时间',
-    `update_time` DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_code` (`code`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '用户类别表';
-
--- 初始化内置用户类别(与原 userType 取值保持一致)
-INSERT INTO `user_category` (`code`, `name`, `remark`, `tag_type`, `sort`, `builtin`, `status`, `deleted`, `create_time`, `update_time`)
-VALUES
-    (1, '普通用户', '默认用户类别', 'primary', 1, 1, 1, 0, NOW(), NOW()),
-    (2, '专家',     '可创作方案的专家用户', 'warning', 2, 1, 1, 0, NOW(), NOW()),
-    (3, '管理员',   '后台管理员(仅 code=3 可登录后台)', 'danger', 3, 1, 1, 0, NOW(), NOW());
-
--- ----------------------------
--- 子用户信息表(属于某个应用用户)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `user_profile` (
-    `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `user_id`           BIGINT                COMMENT '所属应用用户ID(关联 app_user.id)',
-    `name`              VARCHAR(50)  NOT NULL COMMENT '姓名',
-    `gender`            TINYINT               COMMENT '性别:1=男,2=女',
-    `age`               INT                   COMMENT '年龄',
-    `phone`             VARCHAR(20)           COMMENT '手机号(唯一)',
-    `user_type`         TINYINT      NOT NULL DEFAULT 1 COMMENT '用户类型:1=普通用户,2=专家,3=管理员',
-    `province_code`     VARCHAR(12)           COMMENT '省级行政区划编码(GB/T 2260)',
-    `city_code`         VARCHAR(12)           COMMENT '市级行政区划编码',
-    `district_code`     VARCHAR(12)           COMMENT '区/县级行政区划编码',
-    `address`           VARCHAR(255)          COMMENT '详细地址',
-    `shoulder_width`    DECIMAL(5,1)          COMMENT '肩宽(cm)',
-    `body_height`       DECIMAL(5,1)          COMMENT '身长/坐高(cm)',
-    `spine_length`      DECIMAL(5,1)          COMMENT '脊柱长度C7-S4(cm,范围30-60)',
-    `height`            DECIMAL(5,1)          COMMENT '身高(cm)',
-    `weight`            DECIMAL(5,1)          COMMENT '体重(kg)',
-    `finger_width_1`    DECIMAL(4,2)          COMMENT '1寸指宽(cm,选填,提高穴位精度)',
-    `finger_width_15`   DECIMAL(4,2)          COMMENT '1.5寸指宽(cm,选填)',
-    `finger_width_3`    DECIMAL(4,2)          COMMENT '3寸指宽(cm,选填)',
-    `acupoint_table_id` BIGINT                COMMENT '用户穴位表ID',
-    `deleted`           TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time`       DATETIME              COMMENT '创建时间',
-    `update_time`       DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    KEY `idx_user_id` (`user_id`),
-    KEY `idx_phone` (`phone`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '子用户信息表';
-
--- ----------------------------
--- 用户设备绑定表(子用户与设备绑定)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `user_device` (
-    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `user_id`     BIGINT       NOT NULL COMMENT '所属子用户ID(关联 user_profile.id)',
-    `device_code`   VARCHAR(100) NOT NULL COMMENT '设备唯一编码',
-    `device_name`   VARCHAR(100)          COMMENT '设备名称',
-    `device_model`  VARCHAR(100)          COMMENT '设备型号',
-    `is_primary`    TINYINT      NOT NULL DEFAULT 0 COMMENT '是否主设备:1=是,0=否',
-    `online_status` TINYINT      NOT NULL DEFAULT 0 COMMENT '在线状态:1=在线,0=离线',
-    `status`        TINYINT      NOT NULL DEFAULT 1 COMMENT '绑定状态:1=已绑定,0=已解绑',
-    `bind_time`   DATETIME              COMMENT '绑定时间',
-    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time` DATETIME              COMMENT '创建时间',
-    `update_time` DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    KEY `idx_user_id` (`user_id`),
-    KEY `idx_device_code` (`device_code`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '用户设备绑定表(子用户绑定设备)';
-
--- ----------------------------
--- 艾灸椅设备表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `device` (
-    `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `device_code`       VARCHAR(50)  NOT NULL COMMENT '设备编号(唯一,如:AJY001)',
-    `device_name`       VARCHAR(100) NOT NULL COMMENT '设备名称(如:艾灸椅-001)',
-    `device_model`      VARCHAR(50)           COMMENT '设备型号(AJY-2026 / AJY-2025)',
-    `serial_no`         VARCHAR(50)           COMMENT '设备序列号(唯一)',
-    `firmware_version`  VARCHAR(20)           COMMENT '固件版本(如:v1.2.0)',
-    `online_status`     TINYINT      NOT NULL DEFAULT 0 COMMENT '在线状态:1=在线,0=离线',
-    `last_online_time`  DATETIME              COMMENT '最后在线时间',
-    `remark`            VARCHAR(200)          COMMENT '备注',
-    `deleted`           TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time`       DATETIME              COMMENT '创建时间',
-    `update_time`       DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_device_code` (`device_code`),
-    UNIQUE KEY `uk_serial_no` (`serial_no`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '艾灸椅设备表';
-
--- ----------------------------
--- 穴位信息表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `acupoint` (
-    `id`                BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `acupoint_code`     VARCHAR(100)  NOT NULL COMMENT '穴位编码(唯一标识,如 dazhui)',
-    `excel_serial_no`   VARCHAR(20)            COMMENT 'Excel原始序号',
-    `name`              VARCHAR(50)   NOT NULL COMMENT '穴位名称(如:大椎穴)',
-    `single_double`     VARCHAR(20)            COMMENT '单/双穴(Excel原始字段)',
-    `meridian`          VARCHAR(100)           COMMENT '所属经络(如:督脉、足太阳膀胱经)',
-    `body_part`         VARCHAR(50)            COMMENT '身体部位(Excel原始字段)',
-    `body_region`       VARCHAR(50)            COMMENT '部位分区(Excel原始字段)',
-    `location_description` VARCHAR(500)        COMMENT '穴位定位原文(Excel原始字段)',
-    `relative_position` VARCHAR(200)           COMMENT '相对位置原文(Excel原始字段)',
-    `indications`       VARCHAR(1000)          COMMENT '主管疾病/主治(Excel原始字段)',
-    `product_symptoms`  VARCHAR(500)           COMMENT '产品症状覆盖(Excel原始字段)',
-    `acupoint_method`   VARCHAR(500)           COMMENT '取穴说明(Excel原始字段)',
-    `product_acupoint`  TINYINT       NOT NULL DEFAULT 0 COMMENT '是否艾灸椅产品穴位:1=是,0=否',
-    `effects`           VARCHAR(500)           COMMENT '功效描述(逗号分隔,如:驱寒,祛湿)',
-    `location_type`     TINYINT                COMMENT '定位方式:固定为1=相对定位',
-    `longitude_offset`  DECIMAL(6,2)           COMMENT '相对大椎穴纵向偏移(寸)',
-    `lateral_offset`    DECIMAL(6,2)           COMMENT '相对大椎穴横向偏移(寸,双侧穴位填写)',
-    `normalized_y`      DECIMAL(6,4)           COMMENT 'Panjabi归一化Y位置系数(0~1,基于C7-S4脊柱长度)',
-    `y_reference_term`  VARCHAR(100)           COMMENT 'Y轴医学定位术语',
-    `parse_status`      VARCHAR(20)            COMMENT '定位描述解析状态:已解析/待复核/暂不支持',
-    `parse_confidence`  VARCHAR(10)            COMMENT '定位描述解析置信度:高/中/低',
-    `parse_message`     VARCHAR(500)           COMMENT '定位描述解析说明',
-    `side`              TINYINT                COMMENT '侧别:1=中心,2=左侧,3=右侧,4=双侧',
-    `finger_width_type` TINYINT                COMMENT '指寸类型:1=一寸,2=1.5寸,3=三寸',
-    `gender`            TINYINT       NOT NULL DEFAULT 0 COMMENT '适用性别:0=不限,1=仅男,2=仅女',
-    `status`            TINYINT       NOT NULL DEFAULT 1 COMMENT '状态:1=活跃,0=待完善',
-    `deleted`           TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time`       DATETIME               COMMENT '创建时间',
-    `update_time`       DATETIME               COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_acupoint_code` (`acupoint_code`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '穴位信息表';
-
--- 初始化12个常用穴位(含Panjabi归一化Y位置系数)
-INSERT INTO `acupoint` (`acupoint_code`, `name`, `meridian`, `effects`, `location_type`, `longitude_offset`, `lateral_offset`, `normalized_y`, `side`, `finger_width_type`, `gender`, `status`, `deleted`, `create_time`, `update_time`) VALUES
-('dazhui',   '大椎穴', '督脉',         '驱寒,祛湿',   1, 0.00,  NULL, 0.0000, 1, 1, 0, 1, 0, NOW(), NOW()),
-('fengmen',  '风门穴', '足太阳膀胱经', '祛风,解表',   1, 2.00,  1.50, 0.0780, 4, 1, 0, 1, 0, NOW(), NOW()),
-('feishu',   '肺俞穴', '足太阳膀胱经', '宣肺,止咳',   1, 3.00,  1.50, 0.1200, 4, 1, 0, 1, 0, NOW(), NOW()),
-('xinshu',   '心俞穴', '足太阳膀胱经', '养心,安神',   1, 5.00,  1.50, 0.2030, 4, 1, 0, 1, 0, NOW(), NOW()),
-('ganshu',   '肝俞穴', '足太阳膀胱经', '疏肝,理气',   1, 9.00,  1.50, 0.3610, 4, 1, 0, 1, 0, NOW(), NOW()),
-('pishu',    '脾俞穴', '足太阳膀胱经', '健脾,祛湿',   1, 11.00, 1.50, 0.4380, 4, 1, 0, 1, 0, NOW(), NOW()),
-('weishu',   '胃俞穴', '足太阳膀胱经', '和胃,消食',   1, 12.00, 1.50, 0.4760, 4, 1, 0, 1, 0, NOW(), NOW()),
-('shenshu',  '肾俞穴', '足太阳膀胱经', '补肾,扶阳',   1, 14.00, 1.50, 0.5520, 4, 1, 0, 1, 0, NOW(), NOW()),
-('mingmen',  '命门穴', '督脉',         '扶阳,固本',   1, 14.00, NULL, 0.5520, 1, 1, 0, 1, 0, NOW(), NOW()),
-('zhongwan', '中脘穴', '任脉',         '健脾,和胃',   1, NULL,  NULL, NULL,   1, NULL, 0, 1, 0, NOW(), NOW()),
-('qihai',    '气海穴', '任脉',         '扶阳,益气',   1, NULL,  NULL, NULL,   1, NULL, 0, 1, 0, NOW(), NOW()),
-('guanyuan', '关元穴', '任脉',         '温肾,扶阳',   1, NULL,  NULL, NULL,   1, NULL, 0, 1, 0, NOW(), NOW());
-
--- ----------------------------
--- 用户穴位坐标表(根据用户体型数据和穴位信息计算生成)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `user_acupoint` (
-    `id`             BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `user_id`        BIGINT        NOT NULL COMMENT '子用户ID(关联 user_profile.id)',
-    `acupoint_id`    BIGINT        NOT NULL COMMENT '穴位ID(关联 acupoint.id)',
-    `acupoint_name`  VARCHAR(50)   NOT NULL COMMENT '穴位名称(冗余存储,加速查询)',
-    `acupoint_side`  VARCHAR(10)   NOT NULL DEFAULT 'center' COMMENT '侧别:center=中心,left=左侧,right=右侧',
-    `cun_value`      DECIMAL(6,2)           COMMENT '相对大椎穴纵向偏移(寸)',
-    `cun_type`       VARCHAR(10)            COMMENT '指寸类型:1寸/1.5寸/3寸',
-    `coordinate_x`   DECIMAL(8,2)           COMMENT '计算后的X坐标(mm,相对大椎穴,向右为正)',
-    `coordinate_y`   DECIMAL(8,2)           COMMENT '计算后的Y坐标(mm,相对大椎穴,向下为正)',
-    `coordinate_z`   DECIMAL(8,2)           COMMENT 'Z坐标(mm,体表=0)',
-    `confidence`     VARCHAR(10)            COMMENT '置信度:high=高/medium=中/low=低',
-    `create_time`    DATETIME               COMMENT '首次生成时间',
-    `update_time`    DATETIME               COMMENT '最后更新时间',
-    PRIMARY KEY (`id`),
-    KEY `idx_user_id` (`user_id`),
-    KEY `idx_acupoint_id` (`acupoint_id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '用户穴位坐标表(按用户体型计算的个性化穴位坐标)';
-
--- ----------------------------
--- 艾灸手法表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `moxibustion_technique` (
-    `id`               BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `technique_code`   VARCHAR(50)   NOT NULL COMMENT '手法编码(唯一标识,如 oneclick)',
-    `name`             VARCHAR(50)   NOT NULL COMMENT '手法名称(如:一键艾灸)',
-    `motion_mode`      VARCHAR(20)   NOT NULL COMMENT '运动模式:default=默认工艺,fixed=固定不动,updown=上下移动,circular=圆周运动,dotted=点状移动',
-    `temp_min`         DECIMAL(4,1)  NOT NULL COMMENT '温度范围最小值(°C,35-50)',
-    `temp_max`         DECIMAL(4,1)  NOT NULL COMMENT '温度范围最大值(°C,35-50)',
-    `time_min`         INT           NOT NULL COMMENT '时间范围最小值(分钟,1-30)',
-    `time_max`         INT           NOT NULL COMMENT '时间范围最大值(分钟,1-30)',
-    `motion_amplitude` DECIMAL(4,1)           COMMENT '运动幅度(mm,1-20,运动模式非固定不动时必填)',
-    `motion_speed`     DECIMAL(4,1)           COMMENT '运动速度(mm/s,1-10,运动模式非固定不动时必填)',
-    `status`           TINYINT       NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=禁用',
-    `remark`           VARCHAR(200)           COMMENT '备注(最长200字符)',
-    `deleted`          TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time`      DATETIME               COMMENT '创建时间',
-    `update_time`      DATETIME               COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_technique_code` (`technique_code`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '艾灸手法表';
-
--- 初始化5个内置手法
-INSERT INTO `moxibustion_technique` (`technique_code`, `name`, `motion_mode`, `temp_min`, `temp_max`, `time_min`, `time_max`, `motion_amplitude`, `motion_speed`, `status`, `deleted`, `create_time`, `update_time`) VALUES
-('oneclick', '一键艾灸', 'default',   42.0, 45.0,  30, 30,  NULL, NULL, 1, 0, NOW(), NOW()),
-('gentle',   '温和灸',   'fixed',     35.0, 50.0,   1, 30,  NULL, NULL, 1, 0, NOW(), NOW()),
-('pecking',  '雀啄灸',   'updown',    35.0, 50.0,   1, 30,  10.0,  5.0, 1, 0, NOW(), NOW()),
-('circular', '回旋灸',   'circular',  35.0, 50.0,   1, 30,  15.0,  3.0, 1, 0, NOW(), NOW()),
-('sparrow',  '麻雀灸',   'dotted',    35.0, 50.0,   1, 30,   8.0,  4.0, 1, 0, NOW(), NOW());
-
--- ----------------------------
--- 方案表(艾灸方案)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `plan` (
-    `id`               BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `plan_code`        VARCHAR(50)   NOT NULL COMMENT '方案编码(唯一标识,如 S001)',
-    `name`             VARCHAR(100)  NOT NULL COMMENT '方案名称(最长100字符)',
-    `mode_type`        TINYINT       NOT NULL COMMENT '模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手',
-    `effect_type`      VARCHAR(20)            COMMENT '功效类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳',
-    `symptoms`         VARCHAR(500)           COMMENT '适用症状(逗号分隔:怕冷,感冒,咳嗽,...)',
-    `applicable_gender` TINYINT      NOT NULL DEFAULT 0 COMMENT '适用人群:0=不限,1=仅男,2=仅女',
-    `age_min`          INT           NOT NULL DEFAULT 1 COMMENT '适用年龄最小值(岁)',
-    `age_max`          INT           NOT NULL DEFAULT 120 COMMENT '适用年龄最大值(岁)',
-    `description`      VARCHAR(200)           COMMENT '方案描述(最长200字符)',
-    `author_type`      TINYINT       NOT NULL DEFAULT 1 COMMENT '创作人类型:1=管理员,2=普通用户,3=专家',
-    `author_id`        BIGINT                 COMMENT '创作人ID(对应 app_user.id)',
-    `author_name`      VARCHAR(100)           COMMENT '创作人名称(冗余存储,加速查询)',
-    `status`           TINYINT       NOT NULL DEFAULT 0 COMMENT '状态:0=草稿,1=已发布,2=已下架',
-    `use_count`        INT           NOT NULL DEFAULT 0 COMMENT '使用人数',
-    `avg_rating`       DECIMAL(3,1)           COMMENT '平均评分',
-    `last_use_time`    DATETIME               COMMENT '最后使用时间',
-    `deleted`          TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time`      DATETIME               COMMENT '创建时间',
-    `update_time`      DATETIME               COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_plan_code` (`plan_code`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '方案表(艾灸方案)';
-
--- ----------------------------
--- 方案步骤表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `plan_step` (
-    `id`             BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `plan_id`        BIGINT        NOT NULL COMMENT '方案ID(关联 plan.id)',
-    `step_order`     INT           NOT NULL COMMENT '步骤序号(从1开始)',
-    `acupoint_id`    BIGINT                 COMMENT '穴位ID(关联 acupoint.id)',
-    `acupoint_name`  VARCHAR(50)   NOT NULL COMMENT '穴位名称(冗余存储)',
-    `side`           TINYINT       NOT NULL COMMENT '侧别:1=中心,2=左侧,3=右侧,4=双侧',
-    `temperature`    DECIMAL(4,1)  NOT NULL COMMENT '温度(°C,35-50)',
-    `duration`       INT           NOT NULL COMMENT '时间(分钟,1-30)',
-    `technique_id`   BIGINT                 COMMENT '艾灸手法ID(关联 moxibustion_technique.id)',
-    `technique_name` VARCHAR(50)            COMMENT '艾灸手法名称(冗余存储)',
-    `remark`         VARCHAR(100)           COMMENT '备注(最长100字符)',
-    `deleted`        TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time`    DATETIME               COMMENT '创建时间',
-    `update_time`    DATETIME               COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    KEY `idx_plan_id` (`plan_id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '方案步骤表';
-
--- ----------------------------
--- 用户方案表(用户个性化工艺参数方案)
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `user_plan` (
-    `id`          BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `user_id`     BIGINT        NOT NULL COMMENT '子用户ID(关联 user_profile.id)',
-    `plan_id`     BIGINT        NOT NULL COMMENT '方案ID(关联 plan.id)',
-    `plan_code`   VARCHAR(50)   NOT NULL COMMENT '方案编码(冗余存储)',
-    `plan_name`   VARCHAR(100)  NOT NULL COMMENT '方案名称(冗余存储)',
-    `mode_type`   TINYINT       NOT NULL COMMENT '模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手',
-    `author_name` VARCHAR(100)           COMMENT '创作人名称(冗余存储)',
-    `step_count`  INT           NOT NULL DEFAULT 0 COMMENT '步骤数量',
-    `use_count`   INT           NOT NULL DEFAULT 0 COMMENT '使用次数',
-    `steps_json`  TEXT                   COMMENT '个性化工艺步骤(JSON格式,含坐标/温度/时间等)',
-    `deleted`     TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time` DATETIME               COMMENT '订阅/生成时间',
-    `update_time` DATETIME               COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    KEY `idx_user_id` (`user_id`),
-    KEY `idx_plan_id` (`plan_id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '用户方案表(用户个性化工艺参数方案)';
-
--- ----------------------------
--- 初始化系统默认方案:1个一键艾灸 + 8个专业方案
--- ----------------------------
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S001', '一键艾灸(默认)', 1, NULL, '日常调理,疲劳,手脚冰凉', 0, 1, 120, '系统默认一键艾灸方案,覆盖基础督脉与膀胱经调理。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S001');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S002', '专业驱寒方案', 2, '驱寒', '怕冷,手脚冰凉,畏寒', 0, 1, 120, '针对阳虚畏寒人群的基础驱寒方案。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S002');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S003', '专业祛湿方案', 2, '祛湿', '困倦,湿重,食欲差', 0, 1, 120, '围绕脾胃与膀胱经调理的祛湿方案。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S003');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S004', '专业祛风方案', 2, '祛风', '感冒,怕风,鼻塞', 0, 1, 120, '针对风寒风邪引发不适的基础调理方案。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S004');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S005', '专业化瘀方案', 2, '化瘀', '僵硬,酸痛,局部不适', 0, 1, 120, '用于改善局部瘀滞与经络不畅。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S005');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S006', '专业活血方案', 2, '活血', '疲劳,循环差,肩背紧张', 0, 1, 120, '帮助提升背部循环与整体活力。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S006');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S007', '专业化痰方案', 2, '化痰', '咳嗽,痰多,胸闷', 0, 1, 120, '偏肺俞与中焦调理的化痰方案。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S007');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S008', '专业养颜方案', 2, '养颜', '气色差,疲劳,睡眠一般', 0, 1, 120, '通过脾胃与气海关元综合调理气色。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S008');
-
-INSERT INTO `plan` (`plan_code`, `name`, `mode_type`, `effect_type`, `symptoms`, `applicable_gender`, `age_min`, `age_max`, `description`, `author_type`, `author_name`, `status`, `use_count`, `deleted`, `create_time`, `update_time`)
-SELECT 'S009', '专业扶阳方案', 2, '扶阳', '乏力,腰酸,手脚冰凉', 0, 1, 120, '以命门肾俞关元为主的扶阳固本方案。', 1, 'ADMIN', 1, 0, 0, NOW(), NOW()
-WHERE NOT EXISTS (SELECT 1 FROM `plan` WHERE `plan_code` = 'S009');
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 1, '大椎穴', 1, 42.0, 6, 2, '温和灸', '默认基准穴位', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S001'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 2, '风门穴', 4, 43.0, 6, 2, '温和灸', '双侧温养', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S001'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 3, '肺俞穴', 4, 43.0, 6, 2, '温和灸', '提升背部暖感', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S001'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 8, '肾俞穴', 4, 44.0, 6, 2, '温和灸', '补益下焦', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S001'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 5, 9, '命门穴', 1, 45.0, 6, 2, '温和灸', '固本收尾', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S001'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 5);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 1, '大椎穴', 1, 44.0, 8, 3, '雀啄灸', '驱寒起始', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S002'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 2, '风门穴', 4, 44.0, 8, 3, '雀啄灸', '祛寒散表', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S002'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 8, '肾俞穴', 4, 45.0, 8, 2, '温和灸', '温补肾阳', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S002'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 9, '命门穴', 1, 45.0, 8, 2, '温和灸', '扶阳收尾', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S002'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 6, '脾俞穴', 4, 43.0, 8, 2, '温和灸', '健脾化湿', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S003'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 7, '胃俞穴', 4, 43.0, 8, 2, '温和灸', '和胃助运化', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S003'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 10, '中脘穴', 1, 42.0, 8, 4, '回旋灸', '中焦祛湿', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S003'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 11, '气海穴', 1, 42.0, 6, 2, '温和灸', '益气固本', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S003'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 1, '大椎穴', 1, 42.0, 7, 2, '温和灸', '祛风首穴', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S004'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 2, '风门穴', 4, 43.0, 7, 3, '雀啄灸', '散风解表', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S004'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 3, '肺俞穴', 4, 42.0, 7, 2, '温和灸', '协同宣肺', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S004'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 4, '心俞穴', 4, 42.0, 6, 2, '温和灸', '安神扶正', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S004'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 4, '心俞穴', 4, 43.0, 8, 4, '回旋灸', '活络起始', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S005'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 5, '肝俞穴', 4, 44.0, 8, 4, '回旋灸', '疏肝化瘀', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S005'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 6, '脾俞穴', 4, 43.0, 6, 2, '温和灸', '调和中焦', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S005'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 9, '命门穴', 1, 44.0, 6, 2, '温和灸', '温阳通络', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S005'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 3, '肺俞穴', 4, 42.0, 7, 4, '回旋灸', '活血舒展', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S006'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 4, '心俞穴', 4, 43.0, 7, 4, '回旋灸', '促进循环', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S006'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 5, '肝俞穴', 4, 43.0, 7, 4, '回旋灸', '疏通经络', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S006'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 11, '气海穴', 1, 42.0, 6, 2, '温和灸', '补气收束', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S006'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 3, '肺俞穴', 4, 42.0, 8, 2, '温和灸', '宣肺止咳', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S007'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 2, '风门穴', 4, 42.0, 8, 2, '温和灸', '协同宣散', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S007'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 10, '中脘穴', 1, 41.0, 7, 4, '回旋灸', '中焦运化', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S007'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 11, '气海穴', 1, 41.0, 7, 2, '温和灸', '益气化痰', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S007'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 6, '脾俞穴', 4, 42.0, 8, 2, '温和灸', '健脾养颜', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S008'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 7, '胃俞穴', 4, 42.0, 8, 2, '温和灸', '和胃生化', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S008'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 11, '气海穴', 1, 42.0, 7, 4, '回旋灸', '补气养神', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S008'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 12, '关元穴', 1, 42.0, 7, 2, '温和灸', '养元收尾', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S008'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 1, 8, '肾俞穴', 4, 44.0, 8, 2, '温和灸', '补肾扶阳', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S009'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 1);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 2, 9, '命门穴', 1, 45.0, 8, 2, '温和灸', '培补命门火', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S009'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 2);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 3, 11, '气海穴', 1, 44.0, 7, 2, '温和灸', '益气助阳', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S009'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 3);
-INSERT INTO `plan_step` (`plan_id`, `step_order`, `acupoint_id`, `acupoint_name`, `side`, `temperature`, `duration`, `technique_id`, `technique_name`, `remark`, `deleted`, `create_time`, `update_time`)
-SELECT p.id, 4, 12, '关元穴', 1, 44.0, 7, 2, '温和灸', '固本培元', 0, NOW(), NOW()
-FROM `plan` p
-WHERE p.plan_code = 'S009'
-  AND NOT EXISTS (SELECT 1 FROM `plan_step` ps WHERE ps.plan_id = p.id AND ps.step_order = 4);
-
--- ----------------------------
--- 管理员权限配置表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `admin_permission` (
-    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `admin_id`    BIGINT       NOT NULL COMMENT '管理员ID(关联 app_user.id)',
-    `route_paths` TEXT                  COMMENT '可访问的路由路径(JSON数组,如 ["/dashboard","/device"])',
-    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time` DATETIME              COMMENT '创建时间',
-    `update_time` DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_admin_id` (`admin_id`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '管理员权限配置表';
-
--- app_user 表追加管理员相关字段(首次执行时添加,若字段已存在请注释掉此段)
-ALTER TABLE `app_user`
-    ADD COLUMN `admin_role`      TINYINT  DEFAULT NULL COMMENT '管理员角色:1=超级管理员,2=运营人员,3=只读用户(仅 userType=3 时有效)',
-    ADD COLUMN `last_login_time` DATETIME DEFAULT NULL COMMENT '最后登录时间';
-
--- 更新初始管理员为超级管理员角色
-UPDATE `app_user` SET `admin_role` = 1 WHERE `username` = 'admin' AND `admin_role` IS NULL;
-
-SET FOREIGN_KEY_CHECKS = 1;

+ 0 - 30
code/backend/src/main/resources/sql/migration_acupoint_auto_parse.sql

@@ -1,30 +0,0 @@
--- 穴位定位描述自动解析与相对坐标迁移
--- 说明:旧版固定坐标字段不再参与业务;为兼容已有库,本迁移不删除旧列。
-
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'parse_status') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `parse_status` VARCHAR(20) DEFAULT NULL COMMENT ''定位描述解析状态:已解析/待复核/暂不支持'' AFTER `y_reference_term`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'parse_confidence') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `parse_confidence` VARCHAR(10) DEFAULT NULL COMMENT ''定位描述解析置信度:高/中/低'' AFTER `parse_status`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-
-SET @ddl = IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'acupoint' AND COLUMN_NAME = 'parse_message') = 0,
-    'ALTER TABLE `acupoint` ADD COLUMN `parse_message` VARCHAR(500) DEFAULT NULL COMMENT ''定位描述解析说明'' AFTER `parse_confidence`', 'SELECT 1');
-PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-
--- 新算法全部按相对大椎坐标处理;历史绝对定位记录进入待重新解析/复核流程。
-UPDATE `acupoint`
-SET `location_type` = 1,
-    `status` = CASE WHEN (`y_reference_term` IS NOT NULL OR `normalized_y` IS NOT NULL OR `longitude_offset` IS NOT NULL)
-        AND (`side` IN (1) OR `lateral_offset` IS NOT NULL OR `acupoint_code` IN ('pangguang-31','pangguang-32','pangguang-33','pangguang-34') OR `name` IN ('上髎','次髎','中髎','下髎')) THEN `status` ELSE 0 END,
-    `parse_status` = CASE WHEN (`y_reference_term` IS NOT NULL OR `normalized_y` IS NOT NULL OR `longitude_offset` IS NOT NULL)
-        AND (`side` IN (1) OR `lateral_offset` IS NOT NULL OR `acupoint_code` IN ('pangguang-31','pangguang-32','pangguang-33','pangguang-34') OR `name` IN ('上髎','次髎','中髎','下髎')) THEN '已解析' ELSE '待复核' END,
-    `parse_confidence` = CASE WHEN (`y_reference_term` IS NOT NULL OR `normalized_y` IS NOT NULL OR `longitude_offset` IS NOT NULL)
-        AND (`side` IN (1) OR `lateral_offset` IS NOT NULL OR `acupoint_code` IN ('pangguang-31','pangguang-32','pangguang-33','pangguang-34') OR `name` IN ('上髎','次髎','中髎','下髎')) THEN '中' ELSE '低' END,
-    `parse_message` = CASE WHEN (`y_reference_term` IS NOT NULL OR `normalized_y` IS NOT NULL OR `longitude_offset` IS NOT NULL)
-        AND (`side` IN (1) OR `lateral_offset` IS NOT NULL OR `acupoint_code` IN ('pangguang-31','pangguang-32','pangguang-33','pangguang-34') OR `name` IN ('上髎','次髎','中髎','下髎'))
-        THEN '历史数据已有Y轴参数,按相对坐标继续使用'
-        ELSE '历史数据缺少完整相对坐标参数,请重新保存或重新导入以触发定位描述自动解析'
-    END
-WHERE `deleted` = 0;

+ 0 - 31
code/backend/src/main/resources/sql/migration_algorithm_v3.sql

@@ -1,31 +0,0 @@
--- ========== 算法3.0 数据库迁移脚本 ==========
--- 请在已有数据库上执行此脚本,新建库请直接使用更新后的 init.sql
-
-SET NAMES utf8mb4;
-
--- 1. user_profile 表新增字段
-ALTER TABLE `user_profile`
-    ADD COLUMN `spine_length` DECIMAL(5,1) DEFAULT NULL COMMENT '脊柱长度C7-S4(cm,范围30-60)' AFTER `body_height`,
-    ADD COLUMN `height`       DECIMAL(5,1) DEFAULT NULL COMMENT '身高(cm)' AFTER `spine_length`,
-    ADD COLUMN `weight`       DECIMAL(5,1) DEFAULT NULL COMMENT '体重(kg)' AFTER `height`;
-
--- 2. acupoint 表新增字段
-ALTER TABLE `acupoint`
-    ADD COLUMN `normalized_y` DECIMAL(6,4) DEFAULT NULL COMMENT 'Panjabi归一化Y位置系数(0~1,基于C7-S4脊柱长度)' AFTER `lateral_offset`;
-
--- 3. user_acupoint 表新增字段
-ALTER TABLE `user_acupoint`
-    ADD COLUMN `coordinate_z` DECIMAL(8,2) DEFAULT NULL COMMENT 'Z坐标(mm,体表=0)' AFTER `coordinate_y`,
-    ADD COLUMN `confidence`   VARCHAR(10)  DEFAULT NULL COMMENT '置信度:high=高/medium=中/low=低' AFTER `coordinate_z`;
-
--- 4. 更新现有穴位的 Panjabi 归一化 Y 位置系数
-UPDATE `acupoint` SET `normalized_y` = 0.0000 WHERE `acupoint_code` = 'dazhui';
-UPDATE `acupoint` SET `normalized_y` = 0.0780 WHERE `acupoint_code` = 'fengmen';
-UPDATE `acupoint` SET `normalized_y` = 0.1200 WHERE `acupoint_code` = 'feishu';
-UPDATE `acupoint` SET `normalized_y` = 0.2030 WHERE `acupoint_code` = 'xinshu';
-UPDATE `acupoint` SET `normalized_y` = 0.3610 WHERE `acupoint_code` = 'ganshu';
-UPDATE `acupoint` SET `normalized_y` = 0.4380 WHERE `acupoint_code` = 'pishu';
-UPDATE `acupoint` SET `normalized_y` = 0.4760 WHERE `acupoint_code` = 'weishu';
-UPDATE `acupoint` SET `normalized_y` = 0.5520 WHERE `acupoint_code` = 'shenshu';
-UPDATE `acupoint` SET `normalized_y` = 0.5520 WHERE `acupoint_code` = 'mingmen';
--- zhongwan / qihai / guanyuan 缺少可换算为大椎相对Y轴的参数,normalized_y 保持 NULL

+ 6 - 0
code/backend/src/main/resources/sql/migration_article_watermark_image.sql

@@ -0,0 +1,6 @@
+-- ==================== 内容文章表:添加水印图片字段 ====================
+-- 执行数据库:aijiuyi_admin
+-- 作用:为文章添加水印图片URL字段,上传图片到OSS后存储URL,图片水印优先级高于文本水印
+
+ALTER TABLE `content_article`
+    ADD COLUMN `watermark_image` VARCHAR(500) DEFAULT NULL COMMENT '水印图片URL(OSS地址,优先级高于文本水印)' AFTER `watermark_text`;

+ 0 - 33
code/backend/src/main/resources/sql/migration_content_article.sql

@@ -1,33 +0,0 @@
--- ==================== 内容管理模块建表 SQL ====================
--- 执行数据库:aijiuyi_admin
--- 作用:创建文章内容表(content_article),支持草稿/待审核/已发布/已拒绝/已下架状态流转
--- 管理员在 app_user 表中,通过 admin_role 字段区分权限:1=超级管理员(可审核),2/3=普通管理员(可创建编辑)
-
-SET NAMES utf8mb4;
-
--- ----------------------------
--- 内容文章表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS `content_article` (
-    `id`             BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `title`          VARCHAR(200)  NOT NULL COMMENT '文章标题',
-    `content`        LONGTEXT               COMMENT '富文本内容(含HTML标签、图片、视频)',
-    `cover_image`    VARCHAR(500)           COMMENT '封面图URL',
-    `author`         VARCHAR(50)            COMMENT '作者名称',
-    `summary`        VARCHAR(500)           COMMENT '文章摘要',
-    `status`         VARCHAR(20)   NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT=草稿,PENDING=待审核,PUBLISHED=已发布,REJECTED=已拒绝,OFFLINE=已下架',
-    `reject_reason`  VARCHAR(500)           COMMENT '审核拒绝原因',
-    `view_count`     INT           NOT NULL DEFAULT 0 COMMENT '浏览次数',
-    `publish_time`   DATETIME               COMMENT '发布时间',
-    `create_by`      BIGINT                 COMMENT '创建人ID(关联 app_user.id)',
-    `create_time`    DATETIME               COMMENT '创建时间',
-    `update_by`      BIGINT                 COMMENT '更新人ID(关联 app_user.id)',
-    `update_time`    DATETIME               COMMENT '更新时间',
-    `deleted`        TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    PRIMARY KEY (`id`),
-    KEY `idx_status` (`status`),
-    KEY `idx_create_by` (`create_by`),
-    KEY `idx_create_time` (`create_time`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '内容文章表';

+ 0 - 29
code/backend/src/main/resources/sql/migration_user_category.sql

@@ -1,29 +0,0 @@
--- =====================================================
--- 迁移脚本:新增 user_category 用户类别表(用户类型字典)
--- 适用:已部署数据库(首次部署使用 init.sql 即可,无需执行此脚本)
--- =====================================================
-
-CREATE TABLE IF NOT EXISTS `user_category` (
-    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-    `code`        INT          NOT NULL COMMENT '类别编码(与 user_type 字段对应,唯一)',
-    `name`        VARCHAR(50)  NOT NULL COMMENT '类别名称',
-    `remark`      VARCHAR(255)          COMMENT '备注说明',
-    `tag_type`    VARCHAR(20)  DEFAULT 'primary' COMMENT '前端标签颜色:primary/success/warning/danger/info',
-    `sort`        INT          NOT NULL DEFAULT 0 COMMENT '排序(升序)',
-    `builtin`     TINYINT      NOT NULL DEFAULT 0 COMMENT '是否内置:1=内置(不可删除/改code),0=自定义',
-    `status`      TINYINT      NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=停用',
-    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
-    `create_time` DATETIME              COMMENT '创建时间',
-    `update_time` DATETIME              COMMENT '更新时间',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_code` (`code`)
-) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4
-  COMMENT = '用户类别表';
-
--- 初始化内置用户类别(与原 userType 取值保持一致)
-INSERT INTO `user_category` (`code`, `name`, `remark`, `tag_type`, `sort`, `builtin`, `status`, `deleted`, `create_time`, `update_time`)
-VALUES
-    (1, '普通用户', '默认用户类别', 'primary', 1, 1, 1, 0, NOW(), NOW()),
-    (2, '专家',     '可创作方案的专家用户', 'warning', 2, 1, 1, 0, NOW(), NOW()),
-    (3, '管理员',   '后台管理员(仅 code=3 可登录后台)', 'danger', 3, 1, 1, 0, NOW(), NOW());

+ 0 - 23
code/backend/src/main/resources/sql/migration_user_profile_address.sql

@@ -1,23 +0,0 @@
--- =====================================================
--- 迁移脚本:user_profile 新增 用户类型 / 省市区行政区划编码 / 详细地址
--- 适用:已部署数据库(首次部署使用 init.sql 即可,无需执行此脚本)
--- 执行后将自动同步 user_profile.user_type 与 app_user.user_type(按手机号匹配)
--- =====================================================
-
-ALTER TABLE `user_profile`
-    ADD COLUMN `user_type`     TINYINT     NOT NULL DEFAULT 1 COMMENT '用户类型:1=普通用户,2=专家,3=管理员' AFTER `phone`,
-    ADD COLUMN `province_code` VARCHAR(12) NULL COMMENT '省级行政区划编码(GB/T 2260)' AFTER `user_type`,
-    ADD COLUMN `city_code`     VARCHAR(12) NULL COMMENT '市级行政区划编码' AFTER `province_code`,
-    ADD COLUMN `district_code` VARCHAR(12) NULL COMMENT '区/县级行政区划编码' AFTER `city_code`,
-    ADD COLUMN `address`       VARCHAR(255) NULL COMMENT '详细地址' AFTER `district_code`;
-
--- 用 app_user 已有用户类型回填 user_profile(按手机号匹配)
-UPDATE `user_profile` up
-JOIN `app_user` au ON au.phone = up.phone AND au.deleted = 0
-SET up.user_type = au.user_type
-WHERE up.deleted = 0;
-ALTER TABLE device
-    ADD COLUMN province_code VARCHAR(20) DEFAULT '' COMMENT '省级行政区划编码',
-  ADD COLUMN city_code     VARCHAR(20) DEFAULT '' COMMENT '市级行政区划编码',
-  ADD COLUMN district_code VARCHAR(20) DEFAULT '' COMMENT '区/县级行政区划编码',
-  ADD COLUMN address       VARCHAR(200) DEFAULT '' COMMENT '详细地址';

+ 40 - 159
code/frontend/package-lock.json

@@ -9,13 +9,16 @@
       "version": "0.0.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.3.2",
+        "@tinymce/tinymce-vue": "^6.3.0",
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^5.1.12",
         "axios": "^1.14.0",
+        "dompurify": "^3.2.4",
         "element-china-area-data": "^6.1.0",
         "element-plus": "^2.13.6",
         "pinia": "^3.0.4",
         "sass": "^1.99.0",
+        "tinymce": "^8.5.1",
         "vue": "^3.5.32",
         "vue-router": "^4.6.4",
         "xlsx": "^0.18.5"
@@ -763,6 +766,21 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@tinymce/tinymce-vue": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@tinymce/tinymce-vue/-/tinymce-vue-6.3.0.tgz",
+      "integrity": "sha512-DSP8Jhd3XqCCliTnusfbmz3D8GqQ4iRzkc4aadYHDcJPVjkaqopJ61McOdH82CSy599vGLkPjGzqJYWJkRMiUA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "tinymce": "^8.0.0 || ^7.0.0 || ^6.0.0 || ^5.5.1",
+        "vue": "^3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "tinymce": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@transloadit/prettier-bytes": {
       "version": "0.0.7",
       "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz",
@@ -801,6 +819,13 @@
         "@types/lodash": "*"
       }
     },
+    "node_modules/@types/trusted-types": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/@types/web-bluetooth": {
       "version": "0.0.21",
       "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
@@ -1209,165 +1234,6 @@
         "snabbdom": "^3.1.0"
       }
     },
-    "node_modules/@wangeditor/basic-modules": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz",
-      "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==",
-      "license": "MIT",
-      "dependencies": {
-        "is-url": "^1.2.4"
-      },
-      "peerDependencies": {
-        "@wangeditor/core": "1.x",
-        "dom7": "^3.0.0",
-        "lodash.throttle": "^4.1.1",
-        "nanoid": "^3.2.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/code-highlight": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz",
-      "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==",
-      "license": "MIT",
-      "dependencies": {
-        "prismjs": "^1.23.0"
-      },
-      "peerDependencies": {
-        "@wangeditor/core": "1.x",
-        "dom7": "^3.0.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/core": {
-      "version": "1.1.19",
-      "resolved": "https://registry.npmjs.org/@wangeditor/core/-/core-1.1.19.tgz",
-      "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/event-emitter": "^0.3.3",
-        "event-emitter": "^0.3.5",
-        "html-void-elements": "^2.0.0",
-        "i18next": "^20.4.0",
-        "scroll-into-view-if-needed": "^2.2.28",
-        "slate-history": "^0.66.0"
-      },
-      "peerDependencies": {
-        "@uppy/core": "^2.1.1",
-        "@uppy/xhr-upload": "^2.0.3",
-        "dom7": "^3.0.0",
-        "is-hotkey": "^0.2.0",
-        "lodash.camelcase": "^4.3.0",
-        "lodash.clonedeep": "^4.5.0",
-        "lodash.debounce": "^4.0.8",
-        "lodash.foreach": "^4.5.0",
-        "lodash.isequal": "^4.5.0",
-        "lodash.throttle": "^4.1.1",
-        "lodash.toarray": "^4.4.0",
-        "nanoid": "^3.2.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/editor": {
-      "version": "5.1.23",
-      "resolved": "https://registry.npmjs.org/@wangeditor/editor/-/editor-5.1.23.tgz",
-      "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@uppy/core": "^2.1.1",
-        "@uppy/xhr-upload": "^2.0.3",
-        "@wangeditor/basic-modules": "^1.1.7",
-        "@wangeditor/code-highlight": "^1.0.3",
-        "@wangeditor/core": "^1.1.19",
-        "@wangeditor/list-module": "^1.0.5",
-        "@wangeditor/table-module": "^1.1.4",
-        "@wangeditor/upload-image-module": "^1.0.2",
-        "@wangeditor/video-module": "^1.1.4",
-        "dom7": "^3.0.0",
-        "is-hotkey": "^0.2.0",
-        "lodash.camelcase": "^4.3.0",
-        "lodash.clonedeep": "^4.5.0",
-        "lodash.debounce": "^4.0.8",
-        "lodash.foreach": "^4.5.0",
-        "lodash.isequal": "^4.5.0",
-        "lodash.throttle": "^4.1.1",
-        "lodash.toarray": "^4.4.0",
-        "nanoid": "^3.2.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/editor-for-vue": {
-      "version": "5.1.12",
-      "resolved": "https://registry.npmjs.org/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz",
-      "integrity": "sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@wangeditor/editor": ">=5.1.0",
-        "vue": "^3.0.5"
-      }
-    },
-    "node_modules/@wangeditor/list-module": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@wangeditor/list-module/-/list-module-1.0.5.tgz",
-      "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@wangeditor/core": "1.x",
-        "dom7": "^3.0.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/table-module": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/@wangeditor/table-module/-/table-module-1.1.4.tgz",
-      "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@wangeditor/core": "1.x",
-        "dom7": "^3.0.0",
-        "lodash.isequal": "^4.5.0",
-        "lodash.throttle": "^4.1.1",
-        "nanoid": "^3.2.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/upload-image-module": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz",
-      "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@uppy/core": "^2.0.3",
-        "@uppy/xhr-upload": "^2.0.3",
-        "@wangeditor/basic-modules": "1.x",
-        "@wangeditor/core": "1.x",
-        "dom7": "^3.0.0",
-        "lodash.foreach": "^4.5.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
-    "node_modules/@wangeditor/video-module": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/@wangeditor/video-module/-/video-module-1.1.4.tgz",
-      "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@uppy/core": "^2.1.4",
-        "@uppy/xhr-upload": "^2.0.7",
-        "@wangeditor/core": "1.x",
-        "dom7": "^3.0.0",
-        "nanoid": "^3.2.0",
-        "slate": "^0.72.0",
-        "snabbdom": "^3.1.0"
-      }
-    },
     "node_modules/adler-32": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
@@ -1563,6 +1429,15 @@
         "ssr-window": "^3.0.0-alpha.1"
       }
     },
+    "node_modules/dompurify": {
+      "version": "3.4.7",
+      "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.7.tgz",
+      "integrity": "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==",
+      "license": "(MPL-2.0 OR Apache-2.0)",
+      "optionalDependencies": {
+        "@types/trusted-types": "^2.0.7"
+      }
+    },
     "node_modules/dunder-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2745,6 +2620,12 @@
         "url": "https://github.com/sponsors/SuperchupuDev"
       }
     },
+    "node_modules/tinymce": {
+      "version": "8.5.1",
+      "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-8.5.1.tgz",
+      "integrity": "sha512-Wd4OrokDEwlXppSR+ijXngLa2+s3v8NZax9WCvAHeQhqvFsj9tGt19PmrnAdGLiQFkBBYzdOLW23tsBTPMiJYw==",
+      "license": "SEE LICENSE IN license.md"
+    },
     "node_modules/tslib": {
       "version": "2.8.1",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",

+ 2 - 0
code/frontend/package.json

@@ -10,6 +10,7 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.2",
+    "@tinymce/tinymce-vue": "^6.3.0",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.12",
     "axios": "^1.14.0",
@@ -18,6 +19,7 @@
     "element-plus": "^2.13.6",
     "pinia": "^3.0.4",
     "sass": "^1.99.0",
+    "tinymce": "^8.5.1",
     "vue": "^3.5.32",
     "vue-router": "^4.6.4",
     "xlsx": "^0.18.5"

TEMPAT SAMPAH
code/frontend/public/default-avatar.png


+ 362 - 0
code/frontend/public/tinymce/langs/zh_CN.js

@@ -0,0 +1,362 @@
+tinymce.addI18n('zh_CN', {
+  "Redo": "\u91cd\u505a",
+  "Undo": "\u64a4\u9500",
+  "Cut": "\u526a\u5207",
+  "Copy": "\u590d\u5236",
+  "Paste": "\u7c98\u8d34",
+  "Select all": "\u5168\u9009",
+  "New document": "\u65b0\u6587\u6863",
+  "Ok": "\u786e\u5b9a",
+  "Cancel": "\u53d6\u6d88",
+  "Visual aids": "\u7f51\u683c\u7ebf",
+  "Bold": "\u52a0\u7c97",
+  "Italic": "\u659c\u4f53",
+  "Underline": "\u4e0b\u5212\u7ebf",
+  "Strikethrough": "\u5220\u9664\u7ebf",
+  "Superscript": "\u4e0a\u6807",
+  "Subscript": "\u4e0b\u6807",
+  "Clear formatting": "\u6e05\u9664\u683c\u5f0f",
+  "Remove": "\u5220\u9664",
+  "Align left": "\u5de6\u5bf9\u9f50",
+  "Align center": "\u5c45\u4e2d\u5bf9\u9f50",
+  "Align right": "\u53f3\u5bf9\u9f50",
+  "No alignment": "\u65e0\u5bf9\u9f50",
+  "Justify": "\u4e24\u7aef\u5bf9\u9f50",
+  "Bullet list": "\u65e0\u5e8f\u5217\u8868",
+  "Numbered list": "\u6709\u5e8f\u5217\u8868",
+  "Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
+  "Increase indent": "\u589e\u52a0\u7f29\u8fdb",
+  "Close": "\u5173\u95ed",
+  "Formats": "\u683c\u5f0f",
+  "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u5bf9\u526a\u8d34\u677f\u7684\u8bbf\u95ee\uff0c\u8bf7\u4f7f\u7528Ctrl+X/C/V\u952e\u8fdb\u884c\u590d\u5236\u7c98\u8d34\u3002",
+  "Headings": "\u6807\u9898",
+  "Heading 1": "\u6807\u98981",
+  "Heading 2": "\u6807\u98982",
+  "Heading 3": "\u6807\u98983",
+  "Heading 4": "\u6807\u98984",
+  "Heading 5": "\u6807\u98985",
+  "Heading 6": "\u6807\u98986",
+  "Preformatted": "\u9884\u683c\u5f0f\u5316",
+  "Div": "Div",
+  "Pre": "\u9884\u683c\u5f0f",
+  "Code": "\u4ee3\u7801",
+  "Paragraph": "\u6bb5\u843d",
+  "Blockquote": "\u5f15\u7528",
+  "Inline": "\u884c\u5185",
+  "Blocks": "\u533a\u5757",
+  "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
+  "Fonts": "\u5b57\u4f53",
+  "Font sizes": "\u5b57\u53f7",
+  "Class": "\u7c7b\u578b",
+  "Browse for an image": "\u6d4f\u89c8\u56fe\u7247",
+  "OR": "\u6216",
+  "Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u7247\u81f3\u6b64",
+  "Upload": "\u4e0a\u4f20",
+  "Uploading image": "\u4e0a\u4f20\u56fe\u7247\u4e2d",
+  "Block": "\u5757",
+  "Align": "\u5bf9\u9f50",
+  "Default": "\u9ed8\u8ba4",
+  "Circle": "\u7a7a\u5fc3\u5706",
+  "Disc": "\u5b9e\u5fc3\u5706",
+  "Square": "\u65b9\u5757",
+  "Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
+  "Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
+  "Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
+  "Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
+  "Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
+  "Anchor...": "\u951a\u70b9...",
+  "Anchor": "\u951a\u70b9",
+  "Name": "\u540d\u79f0",
+  "ID": "ID",
+  "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
+  "You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
+  "Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
+  "Special character...": "\u7279\u6b8a\u5b57\u7b26...",
+  "Special Character": "\u7279\u6b8a\u5b57\u7b26",
+  "Source code": "\u6e90\u4ee3\u7801",
+  "Insert/Edit code sample": "\u63d2\u5165/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
+  "Language": "\u8bed\u8a00",
+  "Code sample...": "\u4ee3\u7801\u793a\u4f8b...",
+  "Left to right": "\u4ece\u5de6\u5230\u53f3",
+  "Right to left": "\u4ece\u53f3\u5230\u5de6",
+  "Title": "\u6807\u9898",
+  "Fullscreen": "\u5168\u5c4f",
+  "Action": "\u64cd\u4f5c",
+  "Shortcut": "\u5feb\u6377\u952e",
+  "Help": "\u5e2e\u52a9",
+  "Address": "\u5730\u5740",
+  "Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
+  "Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
+  "Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
+  "Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
+  "Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5",
+  "Save (if save plugin activated)": "\u4fdd\u5b58",
+  "Find (if searchreplace plugin activated)": "\u67e5\u627e",
+  "Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
+  "Premium plugins:": "\u4ed8\u8d39\u63d2\u4ef6\uff1a",
+  "Learn more...": "\u4e86\u89e3\u66f4\u591a...",
+  "You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
+  "Plugins": "\u63d2\u4ef6",
+  "Handy Shortcuts": "\u5feb\u6377\u952e",
+  "Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
+  "Insert/edit image": "\u63d2\u5165/\u7f16\u8f91\u56fe\u7247",
+  "Alternative description": "\u66ff\u4ee3\u63cf\u8ff0",
+  "Accessibility": "\u8f85\u52a9\u529f\u80fd",
+  "Image is decorative": "\u56fe\u7247\u662f\u88c5\u9970\u6027\u7684",
+  "Source": "\u5730\u5740",
+  "Dimensions": "\u5927\u5c0f",
+  "Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
+  "General": "\u666e\u901a",
+  "Advanced": "\u9ad8\u7ea7",
+  "Style": "\u6837\u5f0f",
+  "Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
+  "Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
+  "Border": "\u8fb9\u6846",
+  "Insert image": "\u63d2\u5165\u56fe\u7247",
+  "Image...": "\u56fe\u7247...",
+  "Image list": "\u56fe\u7247\u5217\u8868",
+  "Resize": "\u8c03\u6574\u5927\u5c0f",
+  "Insert date/time": "\u63d2\u5165\u65e5\u671f/\u65f6\u95f4",
+  "Date/time": "\u65e5\u671f/\u65f6\u95f4",
+  "Insert/edit link": "\u63d2\u5165/\u7f16\u8f91\u94fe\u63a5",
+  "Text to display": "\u663e\u793a\u6587\u5b57",
+  "Url": "\u5730\u5740",
+  "Open link in...": "\u94fe\u63a5\u6253\u5f00\u65b9\u5f0f...",
+  "Current window": "\u5f53\u524d\u7a97\u53e3",
+  "None": "\u65e0",
+  "New window": "\u65b0\u7a97\u53e3",
+  "Open link": "\u6253\u5f00\u94fe\u63a5",
+  "Remove link": "\u5220\u9664\u94fe\u63a5",
+  "Anchors": "\u951a\u70b9",
+  "Link...": "\u94fe\u63a5...",
+  "Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
+  "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
+  "The URL you entered seems to be an external link. Do you want to add the required http:// prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp://\u524d\u7f00\u5417\uff1f",
+  "The URL you entered seems to be an external link. Do you want to add the required https:// prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttps://\u524d\u7f00\u5417\uff1f",
+  "Link list": "\u94fe\u63a5\u5217\u8868",
+  "Insert video": "\u63d2\u5165\u89c6\u9891",
+  "Insert/edit video": "\u63d2\u5165/\u7f16\u8f91\u89c6\u9891",
+  "Insert/edit media": "\u63d2\u5165/\u7f16\u8f91\u5a92\u4f53",
+  "Alternative source": "\u955c\u50cf",
+  "Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
+  "Media poster (Image URL)": "\u5c01\u9762\uff08\u56fe\u7247\u5730\u5740\uff09",
+  "Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
+  "Embed": "\u5185\u5d4c",
+  "Media...": "\u591a\u5a92\u4f53...",
+  "Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
+  "Page break": "\u5206\u9875\u7b26",
+  "Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
+  "Preview": "\u9884\u89c8",
+  "Print": "\u6253\u5370",
+  "Print...": "\u6253\u5370...",
+  "Save": "\u4fdd\u5b58",
+  "Find": "\u67e5\u627e",
+  "Replace with": "\u66ff\u6362\u4e3a",
+  "Replace": "\u66ff\u6362",
+  "Replace all": "\u5168\u90e8\u66ff\u6362",
+  "Previous": "\u4e0a\u4e00\u4e2a",
+  "Next": "\u4e0b\u4e00\u4e2a",
+  "Find and Replace": "\u67e5\u627e\u548c\u66ff\u6362",
+  "Find and replace...": "\u67e5\u627e\u548c\u66ff\u6362...",
+  "Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
+  "Match case": "\u533a\u5206\u5927\u5c0f\u5199",
+  "Find whole words only": "\u5168\u5b57\u5339\u914d",
+  "Find in selection": "\u5728\u9009\u533a\u4e2d\u67e5\u627e",
+  "Insert table": "\u63d2\u5165\u8868\u683c",
+  "Table properties": "\u8868\u683c\u5c5e\u6027",
+  "Delete table": "\u5220\u9664\u8868\u683c",
+  "Cell": "\u5355\u5143\u683c",
+  "Row": "\u884c",
+  "Column": "\u5217",
+  "Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
+  "Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
+  "Split cell": "\u62c6\u5206\u5355\u5143\u683c",
+  "Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
+  "Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
+  "Delete row": "\u5220\u9664\u884c",
+  "Row properties": "\u884c\u5c5e\u6027",
+  "Cut row": "\u526a\u5207\u884c",
+  "Cut column": "\u526a\u5207\u5217",
+  "Copy row": "\u590d\u5236\u884c",
+  "Copy column": "\u590d\u5236\u5217",
+  "Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
+  "Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
+  "Paste column before": "\u7c98\u8d34\u5230\u524d\u65b9",
+  "Paste column after": "\u7c98\u8d34\u5230\u540e\u65b9",
+  "Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
+  "Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
+  "Delete column": "\u5220\u9664\u5217",
+  "Cols": "\u5217",
+  "Rows": "\u884c",
+  "Width": "\u5bbd",
+  "Height": "\u9ad8",
+  "Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
+  "Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
+  "Row clipboard actions": "\u884c\u526a\u8d34\u677f\u64cd\u4f5c",
+  "Column clipboard actions": "\u5217\u526a\u8d34\u677f\u64cd\u4f5c",
+  "Table styles": "\u8868\u683c\u6837\u5f0f",
+  "Cell styles": "\u5355\u5143\u683c\u6837\u5f0f",
+  "Column header": "\u5217\u5934",
+  "Row header": "\u884c\u5934",
+  "Table caption": "\u8868\u683c\u6807\u9898",
+  "Caption": "\u6807\u9898",
+  "Show caption": "\u663e\u793a\u6807\u9898",
+  "Left": "\u5de6\u5bf9\u9f50",
+  "Center": "\u5c45\u4e2d",
+  "Right": "\u53f3\u5bf9\u9f50",
+  "Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
+  "Scope": "\u8303\u56f4",
+  "Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
+  "Horizontal align": "\u6c34\u5e73\u5bf9\u9f50",
+  "Vertical align": "\u5782\u76f4\u5bf9\u9f50",
+  "Top": "\u9876\u90e8\u5bf9\u9f50",
+  "Middle": "\u5782\u76f4\u5c45\u4e2d",
+  "Bottom": "\u5e95\u90e8\u5bf9\u9f50",
+  "Header cell": "\u8868\u5934\u5355\u5143\u683c",
+  "Row group": "\u884c\u7ec4",
+  "Column group": "\u5217\u7ec4",
+  "Row type": "\u884c\u7c7b\u578b",
+  "Header": "\u8868\u5934",
+  "Body": "\u8868\u4f53",
+  "Footer": "\u8868\u5c3e",
+  "Border color": "\u8fb9\u6846\u989c\u8272",
+  "Solid": "\u5b9e\u7ebf",
+  "Dotted": "\u70b9\u7ebf",
+  "Dashed": "\u865a\u7ebf",
+  "Double": "\u53cc\u7ebf",
+  "Groove": "\u51f9\u69fd",
+  "Ridge": "\u5782\u810a",
+  "Inset": "\u5185\u51f9",
+  "Outset": "\u5916\u51f8",
+  "Hidden": "\u9690\u85cf",
+  "Insert template...": "\u63d2\u5165\u6a21\u677f...",
+  "Templates": "\u6a21\u677f",
+  "Template": "\u6a21\u677f",
+  "Insert Template": "\u63d2\u5165\u6a21\u677f",
+  "Text color": "\u6587\u5b57\u989c\u8272",
+  "Background color": "\u80cc\u666f\u8272",
+  "Custom...": "\u81ea\u5b9a\u4e49...",
+  "Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
+  "No color": "\u65e0",
+  "Remove color": "\u79fb\u9664\u989c\u8272",
+  "Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
+  "Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
+  "Word count": "\u5b57\u6570\u7edf\u8ba1",
+  "Count": "\u8ba1\u6570",
+  "Document": "\u6587\u6863",
+  "Selection": "\u9009\u62e9",
+  "Words": "\u5355\u8bcd",
+  "Words: {0}": "\u5b57\u6570\uff1a{0}",
+  "{0} words": "{0} \u4e2a\u5b57",
+  "File": "\u6587\u4ef6",
+  "Edit": "\u7f16\u8f91",
+  "Insert": "\u63d2\u5165",
+  "View": "\u89c6\u56fe",
+  "Format": "\u683c\u5f0f",
+  "Table": "\u8868\u683c",
+  "Tools": "\u5de5\u5177",
+  "Powered by {0}": "\u7531 {0} \u9a71\u52a8",
+  "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
+  "Image title": "\u56fe\u7247\u6807\u9898",
+  "Border width": "\u8fb9\u6846\u5bbd\u5ea6",
+  "Border style": "\u8fb9\u6846\u6837\u5f0f",
+  "Error": "\u9519\u8bef",
+  "Warn": "\u8b66\u544a",
+  "Valid": "\u6709\u6548",
+  "To open the popup, press Shift+Enter": "\u6309Shift+Enter\u6253\u5f00\u5bf9\u8bdd\u6846",
+  "Rich Text Area": "\u5bcc\u6587\u672c\u7f16\u8f91\u533a",
+  "Rich Text Area. Press ALT-0 for help.": "\u5bcc\u6587\u672c\u7f16\u8f91\u533a\u3002\u6309ALT-0\u83b7\u53d6\u5e2e\u52a9\u3002",
+  "System Font": "\u7cfb\u7edf\u5b57\u4f53",
+  "Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
+  "Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u4ece {1}",
+  "Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0}",
+  "Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
+  "example": "\u793a\u4f8b",
+  "Search": "\u641c\u7d22",
+  "All": "\u5168\u90e8",
+  "Currency": "\u8d27\u5e01",
+  "Text": "\u6587\u672c",
+  "Quotations": "\u5f15\u53f7",
+  "Mathematical": "\u6570\u5b66",
+  "Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5c55",
+  "Symbols": "\u7b26\u53f7",
+  "Arrows": "\u7bad\u5934",
+  "User Defined": "\u81ea\u5b9a\u4e49",
+  "dollar sign": "\u7f8e\u5143\u7b26\u53f7",
+  "currency sign": "\u8d27\u5e01\u7b26\u53f7",
+  "euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
+  "colon sign": "\u5192\u53f7",
+  "cruzeiro sign": "\u514b\u9c81\u585e\u7f57\u5e01\u7b26\u53f7",
+  "french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
+  "lira sign": "\u91cc\u62c9\u7b26\u53f7",
+  "mill sign": "\u5bc6\u5c14\u7b26\u53f7",
+  "naira sign": "\u5948\u62c9\u7b26\u53f7",
+  "peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
+  "rupee sign": "\u5362\u6bd4\u7b26\u53f7",
+  "won sign": "\u97e9\u5143\u7b26\u53f7",
+  "new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
+  "dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
+  "kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
+  "tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
+  "drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
+  "german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
+  "peso sign": "\u6bd4\u7d22\u7b26\u53f7",
+  "guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
+  "austral sign": "\u6fb3\u5143\u7b26\u53f7",
+  "hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
+  "cedi sign": "\u585e\u5730\u7b26\u53f7",
+  "livre tournois sign": "\u91cc\u5f17\u5c14\u7b26\u53f7",
+  "spesmilo sign": "\u65af\u4f69\u65af\u7c73\u6d1b\u7b26\u53f7",
+  "tenge sign": "\u575a\u6208\u7b26\u53f7",
+  "indian rupee sign": "\u5370\u5ea6\u5362\u6bd4\u7b26\u53f7",
+  "turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9\u7b26\u53f7",
+  "nordic mark sign": "\u5317\u6b27\u9a6c\u514b\u7b26\u53f7",
+  "manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
+  "ruble sign": "\u5362\u5e03\u7b26\u53f7",
+  "yen character": "\u65e5\u5143\u5b57\u7b26",
+  "yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u7b26",
+  "yuan character, in Hong Kong and Taiwan": "\u5143\u5b57\u7b26\uff08\u6e2f\u53f0\u5730\u533a\uff09",
+  "yen/yuan character variant one": "\u5143\u5b57\u7b26\uff08\u5927\u5199\uff09",
+  "Embed URL": "\u5d4c\u5165\u5730\u5740",
+  "Paste the embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
+  "Insert/Edit embedded media": "\u63d2\u5165/\u7f16\u8f91\u5a92\u4f53",
+  "Document properties": "\u6587\u6863\u5c5e\u6027",
+  "Robots": "\u673a\u5668\u4eba",
+  "Description": "\u63cf\u8ff0",
+  "Encoding": "\u7f16\u7801",
+  "Author": "\u4f5c\u8005",
+  "Fullscreen": "\u5168\u5c4f",
+  "Horizontal Rule": "\u6c34\u5e73\u5206\u5272\u7ebf",
+  "Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
+  "Insert/edit anchor": "\u63d2\u5165/\u7f16\u8f91\u951a\u70b9",
+  "Open in new tab": "\u5728\u65b0\u6807\u7b7e\u9875\u4e2d\u6253\u5f00",
+  "Target": "\u6253\u5f00\u65b9\u5f0f",
+  "Rel": "\u5173\u7cfb",
+  "Media URL": "\u5a92\u4f53\u5730\u5740",
+  "Media type": "\u5a92\u4f53\u7c7b\u578b",
+  "Upload file": "\u4e0a\u4f20\u6587\u4ef6",
+  "You have clicked a link in read-only mode": "\u5728\u53ea\u8bfb\u6a21\u5f0f\u4e0b\u70b9\u51fb\u4e86\u94fe\u63a5",
+  "The current editor ({0}) mode is readonly. To apply the requested action, switch the editor to edit mode first.": "\u5f53\u524d\u7f16\u8f91\u5668 ({0}) \u6a21\u5f0f\u4e3a\u53ea\u8bfb\u3002\u8981\u5e94\u7528\u8bf7\u6c42\u7684\u64cd\u4f5c\uff0c\u8bf7\u5148\u5c06\u7f16\u8f91\u5668\u5207\u6362\u5230\u7f16\u8f91\u6a21\u5f0f\u3002",
+  "Version": "\u7248\u672c",
+  "Keyboard Navigation": "\u952e\u76d8\u5bfc\u822a",
+  "Color Picker": "\u989c\u8272\u9009\u62e9\u5668",
+  "Color swatch": "\u989c\u8272\u6837\u672c",
+  "Color value": "\u989c\u8272\u503c",
+  "R": "R",
+  "G": "G",
+  "B": "B",
+  "Hex": "Hex",
+  "#": "#",
+  "No color": "\u65e0\u989c\u8272",
+  "Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
+  "Invalid hex color code: {0}": "\u65e0\u6548\u7684\u5341\u516d\u8fdb\u5236\u989c\u8272\u4ee3\u7801: {0}",
+  "Invalid input": "\u65e0\u6548\u8f93\u5165",
+  "Basic formatting": "\u57fa\u672c\u683c\u5f0f",
+  "Increase font size": "\u589e\u5927\u5b57\u53f7",
+  "Decrease font size": "\u51cf\u5c0f\u5b57\u53f7",
+  "Font size {0}": "\u5b57\u53f7 {0}",
+  "Formatting": "\u683c\u5f0f\u5316",
+  "More...": "\u66f4\u591a...",
+  "History": "\u5386\u53f2",
+  "Resize": "\u8c03\u6574\u5927\u5c0f"
+});

+ 75 - 0
code/frontend/public/tinymce/skins/content/dark/content.css

@@ -0,0 +1,75 @@
+/* This file is bundled with the code from the following third party libraries */
+
+/**
+ * http://prismjs.com/
+ * Dracula Theme originally by Zeno Rocha [@zenorocha]
+ * https://draculatheme.com/
+ *
+ * Ported for PrismJS by Albert Vallverdu [@byverdu]
+ */
+body {
+  background-color: #222f3e;
+  color: #fff;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  line-height: 1.4;
+  margin: 1rem;
+}
+a {
+  color: #4099ff;
+}
+table {
+  border-collapse: collapse;
+}
+/* Apply a default padding if legacy cellpadding attribute is missing */
+table:not([cellpadding]) th,
+table:not([cellpadding]) td {
+  padding: 0.4rem;
+}
+/* Set default table styles if a table has a positive border attribute
+   and no inline css */
+table[border]:not([border="0"]):not([style*="border-width"]) th,
+table[border]:not([border="0"]):not([style*="border-width"]) td {
+  border-width: 1px;
+}
+/* Set default table styles if a table has a positive border attribute
+   and no inline css */
+table[border]:not([border="0"]):not([style*="border-style"]) th,
+table[border]:not([border="0"]):not([style*="border-style"]) td {
+  border-style: solid;
+}
+/* Set default table styles if a table has a positive border attribute
+   and no inline css */
+table[border]:not([border="0"]):not([style*="border-color"]) th,
+table[border]:not([border="0"]):not([style*="border-color"]) td {
+  border-color: #6d737b;
+}
+figure {
+  display: table;
+  margin: 1rem auto;
+}
+figure figcaption {
+  color: #8a8f97;
+  display: block;
+  margin-top: 0.25rem;
+  text-align: center;
+}
+hr {
+  border-color: #6d737b;
+  border-style: solid;
+  border-width: 1px 0 0 0;
+}
+code {
+  background-color: #6d737b;
+  border-radius: 3px;
+  padding: 0.1rem 0.2rem;
+}
+.mce-content-body:not([dir=rtl]) blockquote {
+  border-left: 2px solid #6d737b;
+  margin-left: 1.5rem;
+  padding-left: 1rem;
+}
+.mce-content-body[dir=rtl] blockquote {
+  border-right: 2px solid #6d737b;
+  margin-right: 1.5rem;
+  padding-right: 1rem;
+}

+ 10 - 0
code/frontend/public/tinymce/skins/content/dark/content.js

@@ -0,0 +1,10 @@
+/* This file is bundled with the code from the following third party libraries */
+
+/**
+ * http://prismjs.com/
+ * Dracula Theme originally by Zeno Rocha [@zenorocha]
+ * https://draculatheme.com/
+ *
+ * Ported for PrismJS by Albert Vallverdu [@byverdu]
+ */
+tinymce.Resource.add('content/dark/content.css', `body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}`)

+ 10 - 0
code/frontend/public/tinymce/skins/content/dark/content.min.css

@@ -0,0 +1,10 @@
+/* This file is bundled with the code from the following third party libraries */
+
+/**
+ * http://prismjs.com/
+ * Dracula Theme originally by Zeno Rocha [@zenorocha]
+ * https://draculatheme.com/
+ *
+ * Ported for PrismJS by Albert Vallverdu [@byverdu]
+ */
+body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}

+ 3 - 0
code/frontend/public/tinymce/skins/content/dark/content.min.ts

@@ -0,0 +1,3 @@
+export interface Classes {
+  "mce-content-body": string;
+};

+ 3 - 0
code/frontend/public/tinymce/skins/content/dark/content.ts

@@ -0,0 +1,3 @@
+export interface Classes {
+  "mce-content-body": string;
+};

+ 70 - 0
code/frontend/public/tinymce/skins/content/default/content.css

@@ -0,0 +1,70 @@
+/* This file is bundled with the code from the following third party libraries */
+
+/**
+ * http://prismjs.com/
+ * Dracula Theme originally by Zeno Rocha [@zenorocha]
+ * https://draculatheme.com/
+ *
+ * Ported for PrismJS by Albert Vallverdu [@byverdu]
+ */
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  line-height: 1.4;
+  margin: 1rem;
+}
+table {
+  border-collapse: collapse;
+}
+/* Apply a default padding if legacy cellpadding attribute is missing */
+table:not([cellpadding]) th,
+table:not([cellpadding]) td {
+  padding: 0.4rem;
+}
+/* Set default table styles if a table has a positive border attribute
+   and no inline css */
+table[border]:not([border="0"]):not([style*="border-width"]) th,
+table[border]:not([border="0"]):not([style*="border-width"]) td {
+  border-width: 1px;
+}
+/* Set default table styles if a table has a positive border attribute
+   and no inline css */
+table[border]:not([border="0"]):not([style*="border-style"]) th,
+table[border]:not([border="0"]):not([style*="border-style"]) td {
+  border-style: solid;
+}
+/* Set default table styles if a table has a positive border attribute
+   and no inline css */
+table[border]:not([border="0"]):not([style*="border-color"]) th,
+table[border]:not([border="0"]):not([style*="border-color"]) td {
+  border-color: #ccc;
+}
+figure {
+  display: table;
+  margin: 1rem auto;
+}
+figure figcaption {
+  color: #999;
+  display: block;
+  margin-top: 0.25rem;
+  text-align: center;
+}
+hr {
+  border-color: #ccc;
+  border-style: solid;
+  border-width: 1px 0 0 0;
+}
+code {
+  background-color: #e8e8e8;
+  border-radius: 3px;
+  padding: 0.1rem 0.2rem;
+}
+.mce-content-body:not([dir=rtl]) blockquote {
+  border-left: 2px solid #ccc;
+  margin-left: 1.5rem;
+  padding-left: 1rem;
+}
+.mce-content-body[dir=rtl] blockquote {
+  border-right: 2px solid #ccc;
+  margin-right: 1.5rem;
+  padding-right: 1rem;
+}

+ 10 - 0
code/frontend/public/tinymce/skins/content/default/content.js

@@ -0,0 +1,10 @@
+/* This file is bundled with the code from the following third party libraries */
+
+/**
+ * http://prismjs.com/
+ * Dracula Theme originally by Zeno Rocha [@zenorocha]
+ * https://draculatheme.com/
+ *
+ * Ported for PrismJS by Albert Vallverdu [@byverdu]
+ */
+tinymce.Resource.add('content/default/content.css', `body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}`)

+ 10 - 0
code/frontend/public/tinymce/skins/content/default/content.min.css

@@ -0,0 +1,10 @@
+/* This file is bundled with the code from the following third party libraries */
+
+/**
+ * http://prismjs.com/
+ * Dracula Theme originally by Zeno Rocha [@zenorocha]
+ * https://draculatheme.com/
+ *
+ * Ported for PrismJS by Albert Vallverdu [@byverdu]
+ */
+body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini