Browse Source

first commit

liuhairui 3 months ago
parent
commit
c3f9289f1b
54 changed files with 8770 additions and 0 deletions
  1. 207 0
      App.vue
  2. 21 0
      api/index.js
  3. 184 0
      components/colorui/animation.css
  4. 65 0
      components/colorui/components/cu-custom.vue
  5. 36 0
      components/colorui/icon.css
  6. 3912 0
      components/colorui/main.css
  7. 99 0
      components/luch-request/adapters/index.js
  8. 51 0
      components/luch-request/core/InterceptorManager.js
  9. 200 0
      components/luch-request/core/Request.js
  10. 20 0
      components/luch-request/core/buildFullPath.js
  11. 30 0
      components/luch-request/core/defaults.js
  12. 6 0
      components/luch-request/core/dispatchRequest.js
  13. 103 0
      components/luch-request/core/mergeConfig.js
  14. 16 0
      components/luch-request/core/settle.js
  15. 155 0
      components/luch-request/custom/api.js
  16. 148 0
      components/luch-request/custom/service.js
  17. 69 0
      components/luch-request/helpers/buildURL.js
  18. 14 0
      components/luch-request/helpers/combineURLs.js
  19. 14 0
      components/luch-request/helpers/isAbsoluteURL.js
  20. 116 0
      components/luch-request/index.d.ts
  21. 2 0
      components/luch-request/index.js
  22. 135 0
      components/luch-request/utils.js
  23. 264 0
      components/luch-request/utils/clone.js
  24. 36 0
      components/watch-login/css/icon.css
  25. 164 0
      components/watch-login/watch-button.vue
  26. 203 0
      components/watch-login/watch-input.vue
  27. 20 0
      index.html
  28. 21 0
      main.js
  29. 82 0
      manifest.json
  30. BIN
      nativeplugins/Alvin-CBZUhfModule/android/UhfChengBangZi-release.aar
  31. BIN
      nativeplugins/Alvin-CBZUhfModule/android/libs/armeabi-v7a/libserial_port.so
  32. 25 0
      nativeplugins/Alvin-CBZUhfModule/package.json
  33. 25 0
      nativeplugins/Alvin-CBZUhfModule/使用说明.md
  34. 49 0
      pages.json
  35. 1168 0
      pages/index/index.vue
  36. 94 0
      pages/login/css/main.css
  37. 29 0
      pages/login/login.vue
  38. 911 0
      pages/mine/manage.vue
  39. BIN
      static/empty.png
  40. BIN
      static/logo.png
  41. BIN
      static/menu_mine.png
  42. BIN
      static/menu_mine_selected.png
  43. BIN
      static/menu_search.png
  44. BIN
      static/menu_search_selected.png
  45. BIN
      static/nologin.png
  46. BIN
      static/scan.png
  47. BIN
      static/workbench/archive.png
  48. BIN
      static/workbench/inspection.png
  49. BIN
      static/workbench/maintenance.png
  50. BIN
      static/workbench/repair.png
  51. BIN
      static/workbench/repair1.png
  52. BIN
      static/workbench/repair_pool.png
  53. BIN
      static/workbench/scrapped.png
  54. 76 0
      uni.scss

+ 207 - 0
App.vue

@@ -0,0 +1,207 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.log('App Launch');
+			// 检查登录状态
+			this.checkLoginStatus();
+		},
+		onShow: function() {
+			console.log('App Show');
+			// 每次显示应用时检查登录状态
+			this.checkLoginStatus();
+		},
+		onHide: function() {
+			console.log('App Hide');
+			// 应用进入后台时释放资源
+			this.releaseGlobalResources();
+		},
+		
+		// 全局错误捕获
+		onError: function(error) {
+			this.handleGlobalError(error);
+		},
+		methods: {
+			// 释放全局资源
+			releaseGlobalResources: function() {
+				console.log('Releasing global resources');
+				// 清除可能存在的定时器
+				try {
+					// 这里可以添加更多的资源释放逻辑
+				} catch (e) {
+					console.error('Error releasing resources:', e);
+				}
+			},
+			
+			// 全局错误处理
+			handleGlobalError: function(error) {
+				console.error('Global error captured:', error);
+				
+				// 处理特定类型的错误
+				if (error.message && error.message.includes('Failed to receiveTasks')) {
+					// 提取实例ID
+					const instanceIdMatch = error.message.match(/instance \((\d+)\)/);
+					const instanceId = instanceIdMatch ? instanceIdMatch[1] : 'unknown';
+					
+					// 记录详细错误信息
+					console.error(`UHF plugin instance (${instanceId}) error: Failed to receiveTasks`);
+					
+					// 显示错误提示
+					uni.showToast({
+						title: `设备通信错误(实例${instanceId}),请重试`,
+						icon: 'none',
+						duration: 3000
+					});
+						
+					// 尝试重置插件实例
+					setTimeout(() => {
+						this.resetPluginInstance();
+					}, 1000);
+				}
+			},
+			
+			// 重置应用状态
+			resetAppState: function() {
+				console.log('Resetting app state due to plugin error');
+				try {
+					// 清除所有相关缓存数据
+					uni.removeStorageSync('uhf_plugin_state');
+					uni.removeStorageSync('equipment_token');
+					
+					// 重新加载当前页面
+					const pages = getCurrentPages();
+					if (pages && pages.length > 0) {
+						const currentPage = pages[pages.length - 1];
+						// 如果当前是首页,则刷新页面
+						if (currentPage.route === 'pages/index/index') {
+							currentPage.$vm.$destroy();
+							uni.redirectTo({
+								url: '/pages/index/index'
+							});
+						} else {
+							// 否则跳转到首页
+							uni.redirectTo({
+								url: '/pages/index/index'
+							});
+						}
+					} else {
+						// 如果没有页面,跳转到首页
+						uni.redirectTo({
+							url: '/pages/index/index'
+						});
+					}
+				} catch (e) {
+					console.error('Error resetting app state:', e);
+				}
+			},
+			
+			// 检查并清理插件实例
+			checkAndCleanupPluginInstance: function() {
+				console.log('Checking and cleaning up plugin instance');
+				try {
+					// 如果存在页面实例
+					const pages = getCurrentPages();
+					if (pages && pages.length > 0) {
+						const currentPage = pages[pages.length - 1];
+						if (currentPage && currentPage.$vm) {
+							// 如果页面有uhfSFHelper实例,尝试释放设备
+							if (currentPage.$vm.uhfSFHelper) {
+								try {
+									console.log('Releasing device from current page');
+									currentPage.$vm.releaseDevice();
+								} catch (e) {
+									console.error('Error releasing device:', e);
+								}
+								// 清除插件实例
+								currentPage.$vm.uhfSFHelper = null;
+								console.log('Plugin instance cleared from current page');
+							}
+						}
+					}
+					
+					// 清除全局插件相关缓存
+					uni.removeStorageSync('uhf_plugin_instance');
+					console.log('Plugin related storage cleared');
+				} catch (e) {
+					console.error('Error checking and cleaning up plugin instance:', e);
+				}
+			},
+			
+
+			checkLoginStatus: function() {
+				try {
+					const token = uni.getStorageSync('equipment_token');
+					// 获取当前页面路径
+					const pages = getCurrentPages();
+					let currentPath = '';
+					if (pages && pages.length > 0) {
+						const currentPage = pages[pages.length - 1];
+						currentPath = currentPage.route;
+					}
+
+					if (!token) {
+						// 未登录,跳转到登录页面
+						// 检查当前是否已经在登录页面,避免重复跳转
+						if (currentPath !== 'pages/login/login') {
+							uni.redirectTo({
+								url: '/pages/login/login'
+							});
+						}
+					} else {
+						// 已登录,设置全局登录状态
+						const equipmentManage = uni.getStorageSync('equipment_manage');
+						const userInfo = uni.getStorageSync('user_info') || {};
+						this.globalData.isLoggedIn = true;
+						this.globalData.token = token;
+						this.globalData.equipmentManage = equipmentManage;
+						this.globalData.userInfo = userInfo;
+					}
+				} catch (e) {
+					// 错误处理
+					console.error('检查登录状态失败:', e);
+					// 跳转到登录页面
+					// 获取当前页面路径
+					const pages = getCurrentPages();
+					let currentPath = '';
+					if (pages && pages.length > 0) {
+						const currentPage = pages[pages.length - 1];
+						currentPath = currentPage.route;
+					}
+
+					// 检查当前是否已经在登录页面,避免重复跳转
+					if (currentPath !== 'pages/login/login') {
+						uni.redirectTo({
+							url: '/pages/login/login'
+						});
+					}
+				}
+			},
+			
+			// 重置插件实例
+			resetPluginInstance: function() {
+				console.log('Resetting plugin instance globally');
+				try {
+					this.checkAndCleanupPluginInstance();
+					
+					// 延迟一段时间后重置应用状态
+					setTimeout(() => {
+						this.resetAppState();
+					}, 1000);
+				} catch (e) {
+					console.error('Error resetting plugin instance:', e);
+				}
+			}
+		},
+		globalData: {
+			isLoggedIn: false,
+			token: '',
+			equipmentManage: 0,
+			userInfo: {}
+		}
+	}
+</script>
+
+<style>
+	/*每个页面公共css */
+	@import "components/colorui/main.css";
+	@import "components/colorui/icon.css";
+</style>

+ 21 - 0
api/index.js

@@ -0,0 +1,21 @@
+// api/index.js - 集中管理API接口
+
+// 基础URL
+const BASE_URL = getApp().globalData.apiUrl || 'http://dev-rfid.7in6.com/index.php/api/index';
+
+// API接口集合
+const API = {
+  // 获取栋舍列表
+  getBuilding: `${BASE_URL}/get_building`,
+  
+  // 获取房间列表
+  getRoom: `${BASE_URL}/get_room`,
+  
+  // 获取栏位列表
+  getField: `${BASE_URL}/get_field`,
+  
+  // 提交数据
+  postListAdd: `${BASE_URL}/post_listadd`,
+};
+
+export default API;

+ 184 - 0
components/colorui/animation.css

@@ -0,0 +1,184 @@
+/* 
+  Animation 微动画  
+  基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
+ */
+
+/* css 滤镜 控制黑白底色gif的 */
+.gif-black{  
+  mix-blend-mode: screen;  
+}
+.gif-white{  
+  mix-blend-mode: multiply; 
+}
+
+
+/* Animation css */
+[class*=animation-] {
+    animation-duration: .5s;
+    animation-timing-function: ease-out;
+    animation-fill-mode: both
+}
+
+.animation-fade {
+    animation-name: fade;
+    animation-duration: .8s;
+    animation-timing-function: linear
+}
+
+.animation-scale-up {
+    animation-name: scale-up
+}
+
+.animation-scale-down {
+    animation-name: scale-down
+}
+
+.animation-slide-top {
+    animation-name: slide-top
+}
+
+.animation-slide-bottom {
+    animation-name: slide-bottom
+}
+
+.animation-slide-left {
+    animation-name: slide-left
+}
+
+.animation-slide-right {
+    animation-name: slide-right
+}
+
+.animation-shake {
+    animation-name: shake
+}
+
+.animation-reverse {
+    animation-direction: reverse
+}
+
+@keyframes fade {
+    0% {
+        opacity: 0
+    }
+
+    100% {
+        opacity: 1
+    }
+}
+
+@keyframes scale-up {
+    0% {
+        opacity: 0;
+        transform: scale(.2)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes scale-down {
+    0% {
+        opacity: 0;
+        transform: scale(1.8)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes slide-top {
+    0% {
+        opacity: 0;
+        transform: translateY(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes slide-bottom {
+    0% {
+        opacity: 0;
+        transform: translateY(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes shake {
+
+    0%,
+    100% {
+        transform: translateX(0)
+    }
+
+    10% {
+        transform: translateX(-9px)
+    }
+
+    20% {
+        transform: translateX(8px)
+    }
+
+    30% {
+        transform: translateX(-7px)
+    }
+
+    40% {
+        transform: translateX(6px)
+    }
+
+    50% {
+        transform: translateX(-5px)
+    }
+
+    60% {
+        transform: translateX(4px)
+    }
+
+    70% {
+        transform: translateX(-3px)
+    }
+
+    80% {
+        transform: translateX(2px)
+    }
+
+    90% {
+        transform: translateX(-1px)
+    }
+}
+
+@keyframes slide-left {
+    0% {
+        opacity: 0;
+        transform: translateX(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}
+
+@keyframes slide-right {
+    0% {
+        opacity: 0;
+        transform: translateX(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}

+ 65 - 0
components/colorui/components/cu-custom.vue

@@ -0,0 +1,65 @@
+<template>
+	<view>
+		<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
+			<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
+				<view class="action" @tap="BackPage" v-if="isBack">
+					<text class="cuIcon-back"></text>
+					<slot name="backText"></slot>
+				</view>
+				<view class="content" :style="[{top:StatusBar + 'px'}]">
+					<slot name="content"></slot>
+				</view>
+				<slot name="right"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				StatusBar: this.StatusBar,
+				CustomBar: this.CustomBar
+			};
+		},
+		name: 'cu-custom',
+		computed: {
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var bgImage = this.bgImage;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				if (this.bgImage) {
+					style = `${style}background-image:url(${bgImage});`;
+				}
+				return style
+			}
+		},
+		props: {
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			isBack: {
+				type: [Boolean, String],
+				default: false
+			},
+			bgImage: {
+				type: String,
+				default: ''
+			},
+		},
+		methods: {
+			BackPage() {
+				uni.navigateBack({
+					delta: 1
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

File diff suppressed because it is too large
+ 36 - 0
components/colorui/icon.css


+ 3912 - 0
components/colorui/main.css

@@ -0,0 +1,3912 @@
+/*
+  ColorUi for uniApp  v2.1.6 | by 文晓港 2019-05-31 10:44:24
+  仅供学习交流,如作它用所承受的法律责任一概与作者无关  
+  
+  *使用ColorUi开发扩展与插件时,请注明基于ColorUi开发 
+  
+  (QQ交流群:240787041)
+*/
+
+/* ==================
+        初始化
+ ==================== */
+body {
+	background-color: #f1f1f1;
+	font-size: 28upx;
+	color: #333333;
+	font-family: Helvetica Neue, Helvetica, sans-serif;
+}
+
+view,
+scroll-view,
+swiper,
+button,
+input,
+textarea,
+label,
+navigator,
+image {
+	box-sizing: border-box;
+}
+
+.round {
+	border-radius: 5000upx;
+}
+
+.radius {
+	border-radius: 6upx;
+}
+
+/* ==================
+          图片
+ ==================== */
+
+image {
+	max-width: 100%;
+	display: inline-block;
+	position: relative;
+	z-index: 0;
+}
+
+image.loading::before {
+	content: "";
+	background-color: #f5f5f5;
+	display: block;
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	z-index: -2;
+}
+
+image.loading::after {
+	content: "\e7f1";
+	font-family: "cuIcon";
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 32upx;
+	height: 32upx;
+	line-height: 32upx;
+	right: 0;
+	bottom: 0;
+	z-index: -1;
+	font-size: 32upx;
+	margin: auto;
+	color: #ccc;
+	-webkit-animation: cuIcon-spin 2s infinite linear;
+	animation: cuIcon-spin 2s infinite linear;
+	display: block;
+}
+
+.response {
+	width: 100%;
+}
+
+/* ==================
+         开关
+ ==================== */
+
+switch,
+checkbox,
+radio {
+	position: relative;
+}
+
+switch::after,
+switch::before {
+	font-family: "cuIcon";
+	content: "\e645";
+	position: absolute;
+	color: #ffffff !important;
+	top: 0%;
+	left: 0upx;
+	font-size: 26upx;
+	line-height: 26px;
+	width: 50%;
+	text-align: center;
+	pointer-events: none;
+	transform: scale(0, 0);
+	transition: all 0.3s ease-in-out 0s;
+	z-index: 9;
+	bottom: 0;
+	height: 26px;
+	margin: auto;
+}
+
+switch::before {
+	content: "\e646";
+	right: 0;
+	transform: scale(1, 1);
+	left: auto;
+}
+
+switch[checked]::after,
+switch.checked::after {
+	transform: scale(1, 1);
+}
+
+switch[checked]::before,
+switch.checked::before {
+	transform: scale(0, 0);
+}
+
+/* #ifndef MP-ALIPAY */
+radio::before,
+checkbox::before {
+	font-family: "cuIcon";
+	content: "\e645";
+	position: absolute;
+	color: #ffffff !important;
+	top: 50%;
+	margin-top: -8px;
+	right: 5px;
+	font-size: 32upx;
+	line-height: 16px;
+	pointer-events: none;
+	transform: scale(1, 1);
+	transition: all 0.3s ease-in-out 0s;
+	z-index: 9;
+}
+
+radio .wx-radio-input,
+checkbox .wx-checkbox-input,
+radio .uni-radio-input,
+checkbox .uni-checkbox-input {
+	margin: 0;
+	width: 24px;
+	height: 24px;
+}
+
+checkbox.round .wx-checkbox-input,
+checkbox.round .uni-checkbox-input {
+	border-radius: 100upx;
+}
+
+/* #endif */
+
+switch[checked]::before {
+	transform: scale(0, 0);
+}
+
+switch .wx-switch-input,
+switch .uni-switch-input {
+	border: none;
+	padding: 0 24px;
+	width: 48px;
+	height: 26px;
+	margin: 0;
+	border-radius: 100upx;
+}
+
+switch .wx-switch-input:not([class*="bg-"]),
+switch .uni-switch-input:not([class*="bg-"]) {
+	background: #8799a3 !important;
+}
+
+switch .wx-switch-input::after,
+switch .uni-switch-input::after {
+	margin: auto;
+	width: 26px;
+	height: 26px;
+	border-radius: 100upx;
+	left: 0upx;
+	top: 0upx;
+	bottom: 0upx;
+	position: absolute;
+	transform: scale(0.9, 0.9);
+	transition: all 0.1s ease-in-out 0s;
+}
+
+switch .wx-switch-input.wx-switch-input-checked::after,
+switch .uni-switch-input.uni-switch-input-checked::after {
+	margin: auto;
+	left: 22px;
+	box-shadow: none;
+	transform: scale(0.9, 0.9);
+}
+
+radio-group {
+	display: inline-block;
+}
+
+
+
+switch.radius .wx-switch-input::after,
+switch.radius .wx-switch-input,
+switch.radius .wx-switch-input::before,
+switch.radius .uni-switch-input::after,
+switch.radius .uni-switch-input,
+switch.radius .uni-switch-input::before {
+	border-radius: 10upx;
+}
+
+switch .wx-switch-input::before,
+radio.radio::before,
+checkbox .wx-checkbox-input::before,
+radio .wx-radio-input::before,
+switch .uni-switch-input::before,
+radio.radio::before,
+checkbox .uni-checkbox-input::before,
+radio .uni-radio-input::before {
+	display: none;
+}
+
+radio.radio[checked]::after,
+radio.radio .uni-radio-input-checked::after {
+	content: "";
+	background-color: transparent;
+	display: block;
+	position: absolute;
+	width: 8px;
+	height: 8px;
+	z-index: 999;
+	top: 0upx;
+	left: 0upx;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	border-radius: 200upx;
+	/* #ifndef MP */
+	border: 7px solid #ffffff !important;
+	/* #endif */
+
+	/* #ifdef MP */
+	border: 8px solid #ffffff !important;
+	/* #endif */
+}
+
+.switch-sex::after {
+	content: "\e71c";
+}
+
+.switch-sex::before {
+	content: "\e71a";
+}
+
+.switch-sex .wx-switch-input,
+.switch-sex .uni-switch-input {
+	background: #e54d42 !important;
+	border-color: #e54d42 !important;
+}
+
+.switch-sex[checked] .wx-switch-input,
+.switch-sex.checked .uni-switch-input {
+	background: #0081ff !important;
+	border-color: #0081ff !important;
+}
+
+switch.red[checked] .wx-switch-input.wx-switch-input-checked,
+checkbox.red[checked] .wx-checkbox-input,
+radio.red[checked] .wx-radio-input,
+switch.red.checked .uni-switch-input.uni-switch-input-checked,
+checkbox.red.checked .uni-checkbox-input,
+radio.red.checked .uni-radio-input {
+	background-color: #e54d42 !important;
+	border-color: #e54d42 !important;
+	color: #ffffff !important;
+}
+
+switch.orange[checked] .wx-switch-input,
+checkbox.orange[checked] .wx-checkbox-input,
+radio.orange[checked] .wx-radio-input,
+switch.orange.checked .uni-switch-input,
+checkbox.orange.checked .uni-checkbox-input,
+radio.orange.checked .uni-radio-input {
+	background-color: #f37b1d !important;
+	border-color: #f37b1d !important;
+	color: #ffffff !important;
+}
+
+switch.yellow[checked] .wx-switch-input,
+checkbox.yellow[checked] .wx-checkbox-input,
+radio.yellow[checked] .wx-radio-input,
+switch.yellow.checked .uni-switch-input,
+checkbox.yellow.checked .uni-checkbox-input,
+radio.yellow.checked .uni-radio-input {
+	background-color: #fbbd08 !important;
+	border-color: #fbbd08 !important;
+	color: #333333 !important;
+}
+
+switch.olive[checked] .wx-switch-input,
+checkbox.olive[checked] .wx-checkbox-input,
+radio.olive[checked] .wx-radio-input,
+switch.olive.checked .uni-switch-input,
+checkbox.olive.checked .uni-checkbox-input,
+radio.olive.checked .uni-radio-input {
+	background-color: #8dc63f !important;
+	border-color: #8dc63f !important;
+	color: #ffffff !important;
+}
+
+switch.green[checked] .wx-switch-input,
+switch[checked] .wx-switch-input,
+checkbox.green[checked] .wx-checkbox-input,
+checkbox[checked] .wx-checkbox-input,
+radio.green[checked] .wx-radio-input,
+radio[checked] .wx-radio-input,
+switch.green.checked .uni-switch-input,
+switch.checked .uni-switch-input,
+checkbox.green.checked .uni-checkbox-input,
+checkbox.checked .uni-checkbox-input,
+radio.green.checked .uni-radio-input,
+radio.checked .uni-radio-input {
+	background-color: #39b54a !important;
+	border-color: #39b54a !important;
+	color: #ffffff !important;
+	border-color: #39B54A !important;
+}
+
+switch.cyan[checked] .wx-switch-input,
+checkbox.cyan[checked] .wx-checkbox-input,
+radio.cyan[checked] .wx-radio-input,
+switch.cyan.checked .uni-switch-input,
+checkbox.cyan.checked .uni-checkbox-input,
+radio.cyan.checked .uni-radio-input {
+	background-color: #1cbbb4 !important;
+	border-color: #1cbbb4 !important;
+	color: #ffffff !important;
+}
+
+switch.blue[checked] .wx-switch-input,
+checkbox.blue[checked] .wx-checkbox-input,
+radio.blue[checked] .wx-radio-input,
+switch.blue.checked .uni-switch-input,
+checkbox.blue.checked .uni-checkbox-input,
+radio.blue.checked .uni-radio-input {
+	background-color: #0081ff !important;
+	border-color: #0081ff !important;
+	color: #ffffff !important;
+}
+
+switch.purple[checked] .wx-switch-input,
+checkbox.purple[checked] .wx-checkbox-input,
+radio.purple[checked] .wx-radio-input,
+switch.purple.checked .uni-switch-input,
+checkbox.purple.checked .uni-checkbox-input,
+radio.purple.checked .uni-radio-input {
+	background-color: #6739b6 !important;
+	border-color: #6739b6 !important;
+	color: #ffffff !important;
+}
+
+switch.mauve[checked] .wx-switch-input,
+checkbox.mauve[checked] .wx-checkbox-input,
+radio.mauve[checked] .wx-radio-input,
+switch.mauve.checked .uni-switch-input,
+checkbox.mauve.checked .uni-checkbox-input,
+radio.mauve.checked .uni-radio-input {
+	background-color: #9c26b0 !important;
+	border-color: #9c26b0 !important;
+	color: #ffffff !important;
+}
+
+switch.pink[checked] .wx-switch-input,
+checkbox.pink[checked] .wx-checkbox-input,
+radio.pink[checked] .wx-radio-input,
+switch.pink.checked .uni-switch-input,
+checkbox.pink.checked .uni-checkbox-input,
+radio.pink.checked .uni-radio-input {
+	background-color: #e03997 !important;
+	border-color: #e03997 !important;
+	color: #ffffff !important;
+}
+
+switch.brown[checked] .wx-switch-input,
+checkbox.brown[checked] .wx-checkbox-input,
+radio.brown[checked] .wx-radio-input,
+switch.brown.checked .uni-switch-input,
+checkbox.brown.checked .uni-checkbox-input,
+radio.brown.checked .uni-radio-input {
+	background-color: #a5673f !important;
+	border-color: #a5673f !important;
+	color: #ffffff !important;
+}
+
+switch.grey[checked] .wx-switch-input,
+checkbox.grey[checked] .wx-checkbox-input,
+radio.grey[checked] .wx-radio-input,
+switch.grey.checked .uni-switch-input,
+checkbox.grey.checked .uni-checkbox-input,
+radio.grey.checked .uni-radio-input {
+	background-color: #8799a3 !important;
+	border-color: #8799a3 !important;
+	color: #ffffff !important;
+}
+
+switch.gray[checked] .wx-switch-input,
+checkbox.gray[checked] .wx-checkbox-input,
+radio.gray[checked] .wx-radio-input,
+switch.gray.checked .uni-switch-input,
+checkbox.gray.checked .uni-checkbox-input,
+radio.gray.checked .uni-radio-input {
+	background-color: #f0f0f0 !important;
+	border-color: #f0f0f0 !important;
+	color: #333333 !important;
+}
+
+switch.black[checked] .wx-switch-input,
+checkbox.black[checked] .wx-checkbox-input,
+radio.black[checked] .wx-radio-input,
+switch.black.checked .uni-switch-input,
+checkbox.black.checked .uni-checkbox-input,
+radio.black.checked .uni-radio-input {
+	background-color: #333333 !important;
+	border-color: #333333 !important;
+	color: #ffffff !important;
+}
+
+switch.white[checked] .wx-switch-input,
+checkbox.white[checked] .wx-checkbox-input,
+radio.white[checked] .wx-radio-input,
+switch.white.checked .uni-switch-input,
+checkbox.white.checked .uni-checkbox-input,
+radio.white.checked .uni-radio-input {
+	background-color: #ffffff !important;
+	border-color: #ffffff !important;
+	color: #333333 !important;
+}
+
+/* ==================
+          边框
+ ==================== */
+
+/* -- 实线 -- */
+
+.solid,
+.solid-top,
+.solid-right,
+.solid-bottom,
+.solid-left,
+.solids,
+.solids-top,
+.solids-right,
+.solids-bottom,
+.solids-left,
+.dashed,
+.dashed-top,
+.dashed-right,
+.dashed-bottom,
+.dashed-left {
+	position: relative;
+}
+
+.solid::after,
+.solid-top::after,
+.solid-right::after,
+.solid-bottom::after,
+.solid-left::after,
+.solids::after,
+.solids-top::after,
+.solids-right::after,
+.solids-bottom::after,
+.solids-left::after,
+.dashed::after,
+.dashed-top::after,
+.dashed-right::after,
+.dashed-bottom::after,
+.dashed-left::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border-radius: inherit;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+}
+
+.solid::after {
+	border: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-top::after {
+	border-top: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-right::after {
+	border-right: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-bottom::after {
+	border-bottom: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-left::after {
+	border-left: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solids::after {
+	border: 8upx solid #eee;
+}
+
+.solids-top::after {
+	border-top: 8upx solid #eee;
+}
+
+.solids-right::after {
+	border-right: 8upx solid #eee;
+}
+
+.solids-bottom::after {
+	border-bottom: 8upx solid #eee;
+}
+
+.solids-left::after {
+	border-left: 8upx solid #eee;
+}
+
+/* -- 虚线 -- */
+
+.dashed::after {
+	border: 1upx dashed #ddd;
+}
+
+.dashed-top::after {
+	border-top: 1upx dashed #ddd;
+}
+
+.dashed-right::after {
+	border-right: 1upx dashed #ddd;
+}
+
+.dashed-bottom::after {
+	border-bottom: 1upx dashed #ddd;
+}
+
+.dashed-left::after {
+	border-left: 1upx dashed #ddd;
+}
+
+/* -- 阴影 -- */
+
+.shadow[class*='white'] {
+	--ShadowSize: 0 1upx 6upx;
+}
+
+.shadow-lg {
+	--ShadowSize: 0upx 40upx 100upx 0upx;
+}
+
+.shadow-warp {
+	position: relative;
+	box-shadow: 0 0 10upx rgba(0, 0, 0, 0.1);
+}
+
+.shadow-warp:before,
+.shadow-warp:after {
+	position: absolute;
+	content: "";
+	top: 20upx;
+	bottom: 30upx;
+	left: 20upx;
+	width: 50%;
+	box-shadow: 0 30upx 20upx rgba(0, 0, 0, 0.2);
+	transform: rotate(-3deg);
+	z-index: -1;
+}
+
+.shadow-warp:after {
+	right: 20upx;
+	left: auto;
+	transform: rotate(3deg);
+}
+
+.shadow-blur {
+	position: relative;
+}
+
+.shadow-blur::before {
+	content: "";
+	display: block;
+	background: inherit;
+	filter: blur(10upx);
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	top: 10upx;
+	left: 10upx;
+	z-index: -1;
+	opacity: 0.4;
+	transform-origin: 0 0;
+	border-radius: inherit;
+	transform: scale(1, 1);
+}
+
+/* ==================
+          按钮
+ ==================== */
+
+.cu-btn {
+	position: relative;
+	border: 0upx;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	box-sizing: border-box;
+	padding: 0 30upx;
+	font-size: 28upx;
+	height: 64upx;
+	line-height: 1;
+	text-align: center;
+	text-decoration: none;
+	overflow: visible;
+	margin-left: initial;
+	transform: translate(0upx, 0upx);
+	margin-right: initial;
+}
+
+.cu-btn::after {
+	display: none;
+}
+
+.cu-btn:not([class*="bg-"]) {
+	background-color: #f0f0f0;
+}
+
+.cu-btn[class*="line"] {
+	background-color: transparent;
+}
+
+.cu-btn[class*="line"]::after {
+	content: " ";
+	display: block;
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border: 1upx solid currentColor;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	box-sizing: border-box;
+	border-radius: 12upx;
+	z-index: 1;
+	pointer-events: none;
+}
+
+.cu-btn.round[class*="line"]::after {
+	border-radius: 1000upx;
+}
+
+.cu-btn[class*="lines"]::after {
+	border: 6upx solid currentColor;
+}
+
+.cu-btn[class*="bg-"]::after {
+	display: none;
+}
+
+.cu-btn.sm {
+	padding: 0 20upx;
+	font-size: 20upx;
+	height: 48upx;
+}
+
+.cu-btn.lg {
+	padding: 0 40upx;
+	font-size: 32upx;
+	height: 80upx;
+}
+
+.cu-btn.cuIcon.sm {
+	width: 48upx;
+	height: 48upx;
+}
+
+.cu-btn.cuIcon {
+	width: 64upx;
+	height: 64upx;
+	border-radius: 500upx;
+	padding: 0;
+}
+
+button.cuIcon.lg {
+	width: 80upx;
+	height: 80upx;
+}
+
+.cu-btn.shadow-blur::before {
+	top: 4upx;
+	left: 4upx;
+	filter: blur(6upx);
+	opacity: 0.6;
+}
+
+.cu-btn.button-hover {
+	transform: translate(1upx, 1upx);
+}
+
+.block {
+	display: block;
+}
+
+.cu-btn.block {
+	display: flex;
+}
+
+.cu-btn[disabled] {
+	opacity: 0.6;
+	color: #ffffff;
+}
+
+/* ==================
+          徽章
+ ==================== */
+
+.cu-tag {
+	font-size: 24upx;
+	vertical-align: middle;
+	position: relative;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	box-sizing: border-box;
+	padding: 0upx 16upx;
+	height: 48upx;
+	font-family: Helvetica Neue, Helvetica, sans-serif;
+	white-space: nowrap;
+}
+
+.cu-tag:not([class*="bg"]):not([class*="line"]) {
+	background-color: #f1f1f1;
+}
+
+.cu-tag[class*="line-"]::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border: 1upx solid currentColor;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	box-sizing: border-box;
+	border-radius: inherit;
+	z-index: 1;
+	pointer-events: none;
+}
+
+.cu-tag.radius[class*="line"]::after {
+	border-radius: 12upx;
+}
+
+.cu-tag.round[class*="line"]::after {
+	border-radius: 1000upx;
+}
+
+.cu-tag[class*="line-"]::after {
+	border-radius: 0;
+}
+
+.cu-tag+.cu-tag {
+	margin-left: 10upx;
+}
+
+.cu-tag.sm {
+	font-size: 20upx;
+	padding: 0upx 12upx;
+	height: 32upx;
+}
+
+.cu-capsule {
+	display: inline-flex;
+	vertical-align: middle;
+}
+
+.cu-capsule+.cu-capsule {
+	margin-left: 10upx;
+}
+
+.cu-capsule .cu-tag {
+	margin: 0;
+}
+
+.cu-capsule .cu-tag[class*="line-"]:last-child::after {
+	border-left: 0upx solid transparent;
+}
+
+.cu-capsule .cu-tag[class*="line-"]:first-child::after {
+	border-right: 0upx solid transparent;
+}
+
+.cu-capsule.radius .cu-tag:first-child {
+	border-top-left-radius: 6upx;
+	border-bottom-left-radius: 6upx;
+}
+
+.cu-capsule.radius .cu-tag:last-child::after,
+.cu-capsule.radius .cu-tag[class*="line-"] {
+	border-top-right-radius: 12upx;
+	border-bottom-right-radius: 12upx;
+}
+
+.cu-capsule.round .cu-tag:first-child {
+	border-top-left-radius: 200upx;
+	border-bottom-left-radius: 200upx;
+	text-indent: 4upx;
+}
+
+.cu-capsule.round .cu-tag:last-child::after,
+.cu-capsule.round .cu-tag:last-child {
+	border-top-right-radius: 200upx;
+	border-bottom-right-radius: 200upx;
+	text-indent: -4upx;
+}
+
+.cu-tag.badge {
+	border-radius: 200upx;
+	position: absolute;
+	top: -10upx;
+	right: -10upx;
+	font-size: 20upx;
+	padding: 0upx 10upx;
+	height: 28upx;
+	color: #ffffff;
+}
+
+.cu-tag.badge:not([class*="bg-"]) {
+	background-color: #dd514c;
+}
+
+.cu-tag:empty:not([class*="cuIcon-"]) {
+	padding: 0upx;
+	width: 16upx;
+	height: 16upx;
+	top: -4upx;
+	right: -4upx;
+}
+
+.cu-tag[class*="cuIcon-"] {
+	width: 32upx;
+	height: 32upx;
+	top: -4upx;
+	right: -4upx;
+}
+
+/* ==================
+          头像
+ ==================== */
+
+.cu-avatar {
+	font-variant: small-caps;
+	margin: 0;
+	padding: 0;
+	display: inline-flex;
+	text-align: center;
+	justify-content: center;
+	align-items: center;
+	background-color: #ccc;
+	color: #ffffff;
+	white-space: nowrap;
+	position: relative;
+	width: 64upx;
+	height: 64upx;
+	background-size: cover;
+	background-position: center;
+	vertical-align: middle;
+	font-size: 1.5em;
+}
+
+.cu-avatar.sm {
+	width: 48upx;
+	height: 48upx;
+	font-size: 1em;
+}
+
+.cu-avatar.lg {
+	width: 96upx;
+	height: 96upx;
+	font-size: 2em;
+}
+
+.cu-avatar.xl {
+	width: 128upx;
+	height: 128upx;
+	font-size: 2.5em;
+}
+
+.cu-avatar .avatar-text {
+	font-size: 0.4em;
+}
+
+.cu-avatar-group {
+	direction: rtl;
+	unicode-bidi: bidi-override;
+	padding: 0 10upx 0 40upx;
+	display: inline-block;
+}
+
+.cu-avatar-group .cu-avatar {
+	margin-left: -30upx;
+	border: 4upx solid #f1f1f1;
+	vertical-align: middle;
+}
+
+.cu-avatar-group .cu-avatar.sm {
+	margin-left: -20upx;
+	border: 1upx solid #f1f1f1;
+}
+
+/* ==================
+         进度条
+ ==================== */
+
+.cu-progress {
+	overflow: hidden;
+	height: 28upx;
+	background-color: #ebeef5;
+	display: inline-flex;
+	align-items: center;
+	width: 100%;
+}
+
+.cu-progress+view,
+.cu-progress+text {
+	line-height: 1;
+}
+
+.cu-progress.xs {
+	height: 10upx;
+}
+
+.cu-progress.sm {
+	height: 20upx;
+}
+
+.cu-progress view {
+	width: 0;
+	height: 100%;
+	align-items: center;
+	display: flex;
+	justify-items: flex-end;
+	justify-content: space-around;
+	font-size: 20upx;
+	color: #ffffff;
+	transition: width 0.6s ease;
+}
+
+.cu-progress text {
+	align-items: center;
+	display: flex;
+	font-size: 20upx;
+	color: #333333;
+	text-indent: 10upx;
+}
+
+.cu-progress.text-progress {
+	padding-right: 60upx;
+}
+
+.cu-progress.striped view {
+	background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+	background-size: 72upx 72upx;
+}
+
+.cu-progress.active view {
+	animation: progress-stripes 2s linear infinite;
+}
+
+@keyframes progress-stripes {
+	from {
+		background-position: 72upx 0;
+	}
+
+	to {
+		background-position: 0 0;
+	}
+}
+
+/* ==================
+          加载
+ ==================== */
+
+.cu-load {
+	display: block;
+	line-height: 3em;
+	text-align: center;
+}
+
+.cu-load::before {
+	font-family: "cuIcon";
+	display: inline-block;
+	margin-right: 6upx;
+}
+
+.cu-load.loading::before {
+	content: "\e67a";
+	animation: cuIcon-spin 2s infinite linear;
+}
+
+.cu-load.loading::after {
+	content: "加载中...";
+}
+
+.cu-load.over::before {
+	content: "\e64a";
+}
+
+.cu-load.over::after {
+	content: "没有更多了";
+}
+
+.cu-load.erro::before {
+	content: "\e658";
+}
+
+.cu-load.erro::after {
+	content: "加载失败";
+}
+
+.cu-load.load-cuIcon::before {
+	font-size: 32upx;
+}
+
+.cu-load.load-cuIcon::after {
+	display: none;
+}
+
+.cu-load.load-cuIcon.over {
+	display: none;
+}
+
+.cu-load.load-modal {
+	position: fixed;
+	top: 0;
+	right: 0;
+	bottom: 140upx;
+	left: 0;
+	margin: auto;
+	width: 260upx;
+	height: 260upx;
+	background-color: #ffffff;
+	border-radius: 10upx;
+	box-shadow: 0 0 0upx 2000upx rgba(0, 0, 0, 0.5);
+	display: flex;
+	align-items: center;
+	flex-direction: column;
+	justify-content: center;
+	font-size: 28upx;
+	z-index: 9999;
+	line-height: 2.4em;
+}
+
+.cu-load.load-modal [class*="cuIcon-"] {
+	font-size: 60upx;
+}
+
+.cu-load.load-modal image {
+	width: 70upx;
+	height: 70upx;
+}
+
+.cu-load.load-modal::after {
+	content: "";
+	position: absolute;
+	background-color: #ffffff;
+	border-radius: 50%;
+	width: 200upx;
+	height: 200upx;
+	font-size: 10px;
+	border-top: 6upx solid rgba(0, 0, 0, 0.05);
+	border-right: 6upx solid rgba(0, 0, 0, 0.05);
+	border-bottom: 6upx solid rgba(0, 0, 0, 0.05);
+	border-left: 6upx solid #f37b1d;
+	animation: cuIcon-spin 1s infinite linear;
+	z-index: -1;
+}
+
+.load-progress {
+	pointer-events: none;
+	top: 0;
+	position: fixed;
+	width: 100%;
+	left: 0;
+	z-index: 2000;
+}
+
+.load-progress.hide {
+	display: none;
+}
+
+.load-progress .load-progress-bar {
+	position: relative;
+	width: 100%;
+	height: 4upx;
+	overflow: hidden;
+	transition: all 200ms ease 0s;
+}
+
+.load-progress .load-progress-spinner {
+	position: absolute;
+	top: 10upx;
+	right: 10upx;
+	z-index: 2000;
+	display: block;
+}
+
+.load-progress .load-progress-spinner::after {
+	content: "";
+	display: block;
+	width: 24upx;
+	height: 24upx;
+	-webkit-box-sizing: border-box;
+	box-sizing: border-box;
+	border: solid 4upx transparent;
+	border-top-color: inherit;
+	border-left-color: inherit;
+	border-radius: 50%;
+	-webkit-animation: load-progress-spinner 0.4s linear infinite;
+	animation: load-progress-spinner 0.4s linear infinite;
+}
+
+@-webkit-keyframes load-progress-spinner {
+	0% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+
+	100% {
+		-webkit-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+@keyframes load-progress-spinner {
+	0% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+
+	100% {
+		-webkit-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+/* ==================
+          列表
+ ==================== */
+.grayscale {
+	filter: grayscale(1);
+}
+
+.cu-list+.cu-list {
+	margin-top: 30upx
+}
+
+.cu-list>.cu-item {
+	transition: all .6s ease-in-out 0s;
+	transform: translateX(0upx)
+}
+
+.cu-list>.cu-item.move-cur {
+	transform: translateX(-260upx)
+}
+
+.cu-list>.cu-item .move {
+	position: absolute;
+	right: 0;
+	display: flex;
+	width: 260upx;
+	height: 100%;
+	transform: translateX(100%)
+}
+
+.cu-list>.cu-item .move view {
+	display: flex;
+	flex: 1;
+	justify-content: center;
+	align-items: center
+}
+
+.cu-list.menu-avatar {
+	overflow: hidden;
+}
+
+.cu-list.menu-avatar>.cu-item {
+	position: relative;
+	display: flex;
+	padding-right: 10upx;
+	height: 140upx;
+	background-color: #ffffff;
+	justify-content: flex-end;
+	align-items: center
+}
+
+.cu-list.menu-avatar>.cu-item>.cu-avatar {
+	position: absolute;
+	left: 30upx
+}
+
+.cu-list.menu-avatar>.cu-item .flex .text-cut {
+	max-width: 510upx
+}
+
+.cu-list.menu-avatar>.cu-item .content {
+	position: absolute;
+	left: 146upx;
+	width: calc(100% - 96upx - 60upx - 120upx - 20upx);
+	line-height: 1.6em;
+}
+
+.cu-list.menu-avatar>.cu-item .content.flex-sub {
+	width: calc(100% - 96upx - 60upx - 20upx);
+}
+
+.cu-list.menu-avatar>.cu-item .content>view:first-child {
+	font-size: 30upx;
+	display: flex;
+	align-items: center
+}
+
+.cu-list.menu-avatar>.cu-item .content .cu-tag.sm {
+	display: inline-block;
+	margin-left: 10upx;
+	height: 28upx;
+	font-size: 16upx;
+	line-height: 32upx
+}
+
+.cu-list.menu-avatar>.cu-item .action {
+	width: 100upx;
+	text-align: center
+}
+
+.cu-list.menu-avatar>.cu-item .action view+view {
+	margin-top: 10upx
+}
+
+.cu-list.menu-avatar.comment>.cu-item .content {
+	position: relative;
+	left: 0;
+	width: auto;
+	flex: 1;
+}
+
+.cu-list.menu-avatar.comment>.cu-item {
+	padding: 30upx 30upx 30upx 120upx;
+	height: auto
+}
+
+.cu-list.menu-avatar.comment .cu-avatar {
+	align-self: flex-start
+}
+
+.cu-list.menu>.cu-item {
+	position: relative;
+	display: flex;
+	padding: 0 30upx;
+	min-height: 100upx;
+	background-color: #ffffff;
+	justify-content: space-between;
+	align-items: center
+}
+
+.cu-list.menu>.cu-item:last-child:after {
+	border: none
+}
+
+.cu-list.menu-avatar>.cu-item:after,
+.cu-list.menu>.cu-item:after {
+	position: absolute;
+	top: 0;
+	left: 0;
+	box-sizing: border-box;
+	width: 200%;
+	height: 200%;
+	border-bottom: 1upx solid #ddd;
+	border-radius: inherit;
+	content: " ";
+	transform: scale(.5);
+	transform-origin: 0 0;
+	pointer-events: none
+}
+
+.cu-list.menu>.cu-item.grayscale {
+	background-color: #f5f5f5
+}
+
+.cu-list.menu>.cu-item.cur {
+	background-color: #fcf7e9
+}
+
+.cu-list.menu>.cu-item.arrow {
+	padding-right: 90upx
+}
+
+.cu-list.menu>.cu-item.arrow:before {
+	position: absolute;
+	top: 0;
+	right: 30upx;
+	bottom: 0;
+	display: block;
+	margin: auto;
+	width: 30upx;
+	height: 30upx;
+	color: #333333;
+	content: "\e6a3";
+	text-align: center;
+	font-size: 34upx;
+	font-family: cuIcon;
+	line-height: 30upx
+}
+
+.cu-list.menu>.cu-item button.content {
+	padding: 0;
+	background-color: transparent;
+	justify-content: flex-start
+}
+
+.cu-list.menu>.cu-item button.content:after {
+	display: none
+}
+
+.cu-list.menu>.cu-item .cu-avatar-group .cu-avatar {
+	border-color: #ffffff
+}
+
+.cu-list.menu>.cu-item .content>view:first-child {
+	display: flex;
+	align-items: center
+}
+
+.cu-list.menu>.cu-item .content>text[class*=cuIcon] {
+	display: inline-block;
+	margin-right: 10upx;
+	width: 1.6em;
+	text-align: center
+}
+
+.cu-list.menu>.cu-item .content>image {
+	display: inline-block;
+	margin-right: 10upx;
+	width: 1.6em;
+	height: 1.6em;
+	vertical-align: middle
+}
+
+.cu-list.menu>.cu-item .content {
+	font-size: 30upx;
+	line-height: 1.6em;
+	flex: 1
+}
+
+.cu-list.menu>.cu-item .content .cu-tag.sm {
+	display: inline-block;
+	margin-left: 10upx;
+	height: 28upx;
+	font-size: 16upx;
+	line-height: 32upx
+}
+
+.cu-list.menu>.cu-item .action .cu-tag:empty {
+	right: 10upx
+}
+
+.cu-list.menu {
+	display: block;
+	overflow: hidden
+}
+
+.cu-list.menu.sm-border>.cu-item:after {
+	left: 30upx;
+	width: calc(200% - 120upx)
+}
+
+.cu-list.grid>.cu-item {
+	position: relative;
+	display: flex;
+	padding: 20upx 0 30upx;
+	transition-duration: 0s;
+	flex-direction: column
+}
+
+.cu-list.grid>.cu-item:after {
+	position: absolute;
+	top: 0;
+	left: 0;
+	box-sizing: border-box;
+	width: 200%;
+	height: 200%;
+	border-right: 1px solid rgba(0, 0, 0, .1);
+	border-bottom: 1px solid rgba(0, 0, 0, .1);
+	border-radius: inherit;
+	content: " ";
+	transform: scale(.5);
+	transform-origin: 0 0;
+	pointer-events: none
+}
+
+.cu-list.grid>.cu-item text {
+	display: block;
+	margin-top: 10upx;
+	color: #888;
+	font-size: 26upx;
+	line-height: 40upx
+}
+
+.cu-list.grid>.cu-item [class*=cuIcon] {
+	position: relative;
+	display: block;
+	margin-top: 20upx;
+	width: 100%;
+	font-size: 48upx
+}
+
+.cu-list.grid>.cu-item .cu-tag {
+	right: auto;
+	left: 50%;
+	margin-left: 20upx
+}
+
+.cu-list.grid {
+	background-color: #ffffff;
+	text-align: center
+}
+
+.cu-list.grid.no-border>.cu-item {
+	padding-top: 10upx;
+	padding-bottom: 20upx
+}
+
+.cu-list.grid.no-border>.cu-item:after {
+	border: none
+}
+
+.cu-list.grid.no-border {
+	padding: 20upx 10upx
+}
+
+.cu-list.grid.col-3>.cu-item:nth-child(3n):after,
+.cu-list.grid.col-4>.cu-item:nth-child(4n):after,
+.cu-list.grid.col-5>.cu-item:nth-child(5n):after {
+	border-right-width: 0
+}
+
+.cu-list.card-menu {
+	overflow: hidden;
+	margin-right: 30upx;
+	margin-left: 30upx;
+	border-radius: 20upx
+}
+
+
+/* ==================
+          操作条
+ ==================== */
+
+.cu-bar {
+	display: flex;
+	position: relative;
+	align-items: center;
+	min-height: 100upx;
+	justify-content: space-between;
+}
+
+.cu-bar .action {
+	display: flex;
+	align-items: center;
+	height: 100%;
+	justify-content: center;
+	max-width: 100%;
+}
+
+.cu-bar .action.border-title {
+	position: relative;
+	top: -10upx;
+}
+
+.cu-bar .action.border-title text[class*="bg-"]:last-child {
+	position: absolute;
+	bottom: -0.5rem;
+	min-width: 2rem;
+	height: 6upx;
+	left: 0;
+}
+
+.cu-bar .action.sub-title {
+	position: relative;
+	top: -0.2rem;
+}
+
+.cu-bar .action.sub-title text {
+	position: relative;
+	z-index: 1;
+}
+
+.cu-bar .action.sub-title text[class*="bg-"]:last-child {
+	position: absolute;
+	display: inline-block;
+	bottom: -0.2rem;
+	border-radius: 6upx;
+	width: 100%;
+	height: 0.6rem;
+	left: 0.6rem;
+	opacity: 0.3;
+	z-index: 0;
+}
+
+.cu-bar .action.sub-title text[class*="text-"]:last-child {
+	position: absolute;
+	display: inline-block;
+	bottom: -0.7rem;
+	left: 0.5rem;
+	opacity: 0.2;
+	z-index: 0;
+	text-align: right;
+	font-weight: 900;
+	font-size: 36upx;
+}
+
+.cu-bar.justify-center .action.border-title text:last-child,
+.cu-bar.justify-center .action.sub-title text:last-child {
+	left: 0;
+	right: 0;
+	margin: auto;
+	text-align: center;
+}
+
+.cu-bar .action:first-child {
+	margin-left: 30upx;
+	font-size: 30upx;
+}
+
+.cu-bar .action text.text-cut {
+	text-align: left;
+	width: 100%;
+}
+
+.cu-bar .cu-avatar:first-child {
+	margin-left: 20upx;
+}
+
+.cu-bar .action:first-child>text[class*="cuIcon-"] {
+	margin-left: -0.3em;
+	margin-right: 0.3em;
+}
+
+.cu-bar .action:last-child {
+	margin-right: 30upx;
+}
+
+.cu-bar .action>text[class*="cuIcon-"],
+.cu-bar .action>view[class*="cuIcon-"] {
+	font-size: 36upx;
+}
+
+.cu-bar .action>text[class*="cuIcon-"]+text[class*="cuIcon-"] {
+	margin-left: 0.5em;
+}
+
+.cu-bar .content {
+	position: absolute;
+	text-align: center;
+	width: calc(100% - 340upx);
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+	margin: auto;
+	height: 60upx;
+	font-size: 32upx;
+	line-height: 60upx;
+	cursor: none;
+	pointer-events: none;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+}
+
+.cu-bar.ios .content {
+	bottom: 7px;
+	height: 30px;
+	font-size: 32upx;
+	line-height: 30px;
+}
+
+.cu-bar.btn-group {
+	justify-content: space-around;
+}
+
+.cu-bar.btn-group button {
+	padding: 20upx 32upx;
+}
+
+.cu-bar.btn-group button {
+	flex: 1;
+	margin: 0 20upx;
+	max-width: 50%;
+}
+
+.cu-bar .search-form {
+	background-color: #f5f5f5;
+	line-height: 64upx;
+	height: 64upx;
+	font-size: 24upx;
+	color: #333333;
+	flex: 1;
+	display: flex;
+	align-items: center;
+	margin: 0 30upx;
+}
+
+.cu-bar .search-form+.action {
+	margin-right: 30upx;
+}
+
+.cu-bar .search-form input {
+	flex: 1;
+	padding-right: 30upx;
+	height: 64upx;
+	line-height: 64upx;
+	font-size: 26upx;
+	background-color: transparent;
+}
+
+.cu-bar .search-form [class*="cuIcon-"] {
+	margin: 0 0.5em 0 0.8em;
+}
+
+.cu-bar .search-form [class*="cuIcon-"]::before {
+	top: 0upx;
+}
+
+.cu-bar.fixed,
+.nav.fixed {
+	position: fixed;
+	width: 100%;
+	top: 0;
+	z-index: 1024;
+	box-shadow: 0 1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.foot {
+	position: fixed;
+	width: 100%;
+	bottom: 0;
+	z-index: 1024;
+	box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.tabbar {
+	padding: 0;
+	height: calc(100upx + env(safe-area-inset-bottom) / 2);
+	padding-bottom: calc(env(safe-area-inset-bottom) / 2);
+}
+
+.cu-tabbar-height {
+	min-height: 100upx;
+	height: calc(100upx + env(safe-area-inset-bottom) / 2);
+}
+
+.cu-bar.tabbar.shadow {
+	box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.tabbar .action {
+	font-size: 22upx;
+	position: relative;
+	flex: 1;
+	text-align: center;
+	padding: 0;
+	display: block;
+	height: auto;
+	line-height: 1;
+	margin: 0;
+	background-color: inherit;
+	overflow: initial;
+}
+
+.cu-bar.tabbar.shop .action {
+	width: 140upx;
+	flex: initial;
+}
+
+.cu-bar.tabbar .action.add-action {
+	position: relative;
+	z-index: 2;
+	padding-top: 50upx;
+}
+
+.cu-bar.tabbar .action.add-action [class*="cuIcon-"] {
+	position: absolute;
+	width: 70upx;
+	z-index: 2;
+	height: 70upx;
+	border-radius: 50%;
+	line-height: 70upx;
+	font-size: 50upx;
+	top: -35upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	padding: 0;
+}
+
+.cu-bar.tabbar .action.add-action::after {
+	content: "";
+	position: absolute;
+	width: 100upx;
+	height: 100upx;
+	top: -50upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	box-shadow: 0 -3upx 8upx rgba(0, 0, 0, 0.08);
+	border-radius: 50upx;
+	background-color: inherit;
+	z-index: 0;
+}
+
+.cu-bar.tabbar .action.add-action::before {
+	content: "";
+	position: absolute;
+	width: 100upx;
+	height: 30upx;
+	bottom: 30upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	background-color: inherit;
+	z-index: 1;
+}
+
+.cu-bar.tabbar .btn-group {
+	flex: 1;
+	display: flex;
+	justify-content: space-around;
+	align-items: center;
+	padding: 0 10upx;
+}
+
+.cu-bar.tabbar button.action::after {
+	border: 0;
+}
+
+.cu-bar.tabbar .action [class*="cuIcon-"] {
+	width: 100upx;
+	position: relative;
+	display: block;
+	height: auto;
+	margin: 0 auto 10upx;
+	text-align: center;
+	font-size: 40upx;
+}
+
+.cu-bar.tabbar .action .cuIcon-cu-image {
+	margin: 0 auto;
+}
+
+.cu-bar.tabbar .action .cuIcon-cu-image image {
+	width: 50upx;
+	height: 50upx;
+	display: inline-block;
+}
+
+.cu-bar.tabbar .submit {
+	align-items: center;
+	display: flex;
+	justify-content: center;
+	text-align: center;
+	position: relative;
+	flex: 2;
+	align-self: stretch;
+}
+
+.cu-bar.tabbar .submit:last-child {
+	flex: 2.6;
+}
+
+.cu-bar.tabbar .submit+.submit {
+	flex: 2;
+}
+
+.cu-bar.tabbar.border .action::before {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	border-right: 1upx solid rgba(0, 0, 0, 0.1);
+	z-index: 3;
+}
+
+.cu-bar.tabbar.border .action:last-child:before {
+	display: none;
+}
+
+.cu-bar.input {
+	padding-right: 20upx;
+	background-color: #ffffff;
+}
+
+.cu-bar.input input {
+	overflow: initial;
+	line-height: 64upx;
+	height: 64upx;
+	min-height: 64upx;
+	flex: 1;
+	font-size: 30upx;
+	margin: 0 20upx;
+}
+
+.cu-bar.input .action {
+	margin-left: 20upx;
+}
+
+.cu-bar.input .action [class*="cuIcon-"] {
+	font-size: 48upx;
+}
+
+.cu-bar.input input+.action {
+	margin-right: 20upx;
+	margin-left: 0upx;
+}
+
+.cu-bar.input .action:first-child [class*="cuIcon-"] {
+	margin-left: 0upx;
+}
+
+.cu-custom {
+	display: block;
+	position: relative;
+}
+
+.cu-custom .cu-bar .content {
+	width: calc(100% - 440upx);
+}
+
+/* #ifdef MP-ALIPAY */
+.cu-custom .cu-bar .action .cuIcon-back {
+	opacity: 0;
+}
+
+/* #endif */
+
+.cu-custom .cu-bar .content image {
+	height: 60upx;
+	width: 240upx;
+}
+
+.cu-custom .cu-bar {
+	min-height: 0px;
+	/* #ifdef MP-WEIXIN */
+	padding-right: 220upx;
+	/* #endif */
+	/* #ifdef MP-ALIPAY */
+	padding-right: 150upx;
+	/* #endif */
+	box-shadow: 0upx 0upx 0upx;
+	z-index: 9999;
+}
+
+.cu-custom .cu-bar .border-custom {
+	position: relative;
+	background: rgba(0, 0, 0, 0.15);
+	border-radius: 1000upx;
+	height: 30px;
+}
+
+.cu-custom .cu-bar .border-custom::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border-radius: inherit;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	border: 1upx solid #ffffff;
+	opacity: 0.5;
+}
+
+.cu-custom .cu-bar .border-custom::before {
+	content: " ";
+	width: 1upx;
+	height: 110%;
+	position: absolute;
+	top: 22.5%;
+	left: 0;
+	right: 0;
+	margin: auto;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	opacity: 0.6;
+	background-color: #ffffff;
+}
+
+.cu-custom .cu-bar .border-custom text {
+	display: block;
+	flex: 1;
+	margin: auto !important;
+	text-align: center;
+	font-size: 34upx;
+}
+
+/* ==================
+         导航栏
+ ==================== */
+
+.nav {
+	white-space: nowrap;
+}
+
+::-webkit-scrollbar {
+	display: none;
+}
+
+.nav .cu-item {
+	height: 90upx;
+	display: inline-block;
+	line-height: 90upx;
+	margin: 0 10upx;
+	padding: 0 20upx;
+}
+
+.nav .cu-item.cur {
+	border-bottom: 4upx solid;
+}
+
+/* ==================
+         时间轴
+ ==================== */
+
+.cu-timeline {
+	display: block;
+	background-color: #ffffff;
+}
+
+.cu-timeline .cu-time {
+	width: 120upx;
+	text-align: center;
+	padding: 20upx 0;
+	font-size: 26upx;
+	color: #888;
+	display: block;
+}
+
+.cu-timeline>.cu-item {
+	padding: 30upx 30upx 30upx 120upx;
+	position: relative;
+	display: block;
+	z-index: 0;
+}
+
+.cu-timeline>.cu-item:not([class*="text-"]) {
+	color: #ccc;
+}
+
+.cu-timeline>.cu-item::after {
+	content: "";
+	display: block;
+	position: absolute;
+	width: 1upx;
+	background-color: #ddd;
+	left: 60upx;
+	height: 100%;
+	top: 0;
+	z-index: 8;
+}
+
+.cu-timeline>.cu-item::before {
+	font-family: "cuIcon";
+	display: block;
+	position: absolute;
+	top: 36upx;
+	z-index: 9;
+	background-color: #ffffff;
+	width: 50upx;
+	height: 50upx;
+	text-align: center;
+	border: none;
+	line-height: 50upx;
+	left: 36upx;
+}
+
+.cu-timeline>.cu-item:not([class*="cuIcon-"])::before {
+	content: "\e763";
+}
+
+.cu-timeline>.cu-item[class*="cuIcon-"]::before {
+	background-color: #ffffff;
+	width: 50upx;
+	height: 50upx;
+	text-align: center;
+	border: none;
+	line-height: 50upx;
+	left: 36upx;
+}
+
+.cu-timeline>.cu-item>.content {
+	padding: 30upx;
+	border-radius: 6upx;
+	display: block;
+	line-height: 1.6;
+}
+
+.cu-timeline>.cu-item>.content:not([class*="bg-"]) {
+	background-color: #f1f1f1;
+	color: #333333;
+}
+
+.cu-timeline>.cu-item>.content+.content {
+	margin-top: 20upx;
+}
+
+/* ==================
+         聊天
+ ==================== */
+
+.cu-chat {
+	display: flex;
+	flex-direction: column;
+}
+
+.cu-chat .cu-item {
+	display: flex;
+	padding: 30upx 30upx 70upx;
+	position: relative;
+}
+
+.cu-chat .cu-item>.cu-avatar {
+	width: 80upx;
+	height: 80upx;
+}
+
+.cu-chat .cu-item>.main {
+	max-width: calc(100% - 260upx);
+	margin: 0 40upx;
+	display: flex;
+	align-items: center;
+}
+
+.cu-chat .cu-item>image {
+	height: 320upx;
+}
+
+.cu-chat .cu-item>.main .content {
+	padding: 20upx;
+	border-radius: 6upx;
+	display: inline-flex;
+	max-width: 100%;
+	align-items: center;
+	font-size: 30upx;
+	position: relative;
+	min-height: 80upx;
+	line-height: 40upx;
+	text-align: left;
+}
+
+.cu-chat .cu-item>.main .content:not([class*="bg-"]) {
+	background-color: #ffffff;
+	color: #333333;
+}
+
+.cu-chat .cu-item .date {
+	position: absolute;
+	font-size: 24upx;
+	color: #8799a3;
+	width: calc(100% - 320upx);
+	bottom: 20upx;
+	left: 160upx;
+}
+
+.cu-chat .cu-item .action {
+	padding: 0 30upx;
+	display: flex;
+	align-items: center;
+}
+
+.cu-chat .cu-item>.main .content::after {
+	content: "";
+	top: 27upx;
+	transform: rotate(45deg);
+	position: absolute;
+	z-index: 100;
+	display: inline-block;
+	overflow: hidden;
+	width: 24upx;
+	height: 24upx;
+	left: -12upx;
+	right: initial;
+	background-color: inherit;
+}
+
+.cu-chat .cu-item.self>.main .content::after {
+	left: auto;
+	right: -12upx;
+}
+
+.cu-chat .cu-item>.main .content::before {
+	content: "";
+	top: 30upx;
+	transform: rotate(45deg);
+	position: absolute;
+	z-index: -1;
+	display: inline-block;
+	overflow: hidden;
+	width: 24upx;
+	height: 24upx;
+	left: -12upx;
+	right: initial;
+	background-color: inherit;
+	filter: blur(5upx);
+	opacity: 0.3;
+}
+
+.cu-chat .cu-item>.main .content:not([class*="bg-"])::before {
+	background-color: #333333;
+	opacity: 0.1;
+}
+
+.cu-chat .cu-item.self>.main .content::before {
+	left: auto;
+	right: -12upx;
+}
+
+.cu-chat .cu-item.self {
+	justify-content: flex-end;
+	text-align: right;
+}
+
+.cu-chat .cu-info {
+	display: inline-block;
+	margin: 20upx auto;
+	font-size: 24upx;
+	padding: 8upx 12upx;
+	background-color: rgba(0, 0, 0, 0.2);
+	border-radius: 6upx;
+	color: #ffffff;
+	max-width: 400upx;
+	line-height: 1.4;
+}
+
+/* ==================
+         卡片
+ ==================== */
+
+.cu-card {
+	display: block;
+	overflow: hidden;
+}
+
+.cu-card>.cu-item {
+	display: block;
+	background-color: #ffffff;
+	overflow: hidden;
+	border-radius: 10upx;
+	margin: 30upx;
+}
+
+.cu-card>.cu-item.shadow-blur {
+	overflow: initial;
+}
+
+.cu-card.no-card>.cu-item {
+	margin: 0upx;
+	border-radius: 0upx;
+}
+
+.cu-card .grid.grid-square {
+	margin-bottom: -20upx;
+}
+
+.cu-card.case .image {
+	position: relative;
+}
+
+.cu-card.case .image image {
+	width: 100%;
+}
+
+.cu-card.case .image .cu-tag {
+	position: absolute;
+	right: 0;
+	top: 0;
+}
+
+.cu-card.case .image .cu-bar {
+	position: absolute;
+	bottom: 0;
+	width: 100%;
+	background-color: transparent;
+	padding: 0upx 30upx;
+}
+
+.cu-card.case.no-card .image {
+	margin: 30upx 30upx 0;
+	overflow: hidden;
+	border-radius: 10upx;
+}
+
+.cu-card.dynamic {
+	display: block;
+}
+
+.cu-card.dynamic>.cu-item {
+	display: block;
+	background-color: #ffffff;
+	overflow: hidden;
+}
+
+.cu-card.dynamic>.cu-item>.text-content {
+	padding: 0 30upx 0;
+	max-height: 6.4em;
+	overflow: hidden;
+	font-size: 30upx;
+	margin-bottom: 20upx;
+}
+
+.cu-card.dynamic>.cu-item .square-img {
+	width: 100%;
+	height: 200upx;
+	border-radius: 6upx;
+}
+
+.cu-card.dynamic>.cu-item .only-img {
+	width: 100%;
+	height: 320upx;
+	border-radius: 6upx;
+}
+
+/* card.dynamic>.cu-item .comment {
+  padding: 20upx;
+  background-color: #f1f1f1;
+  margin: 0 30upx 30upx;
+  border-radius: 6upx;
+} */
+
+.cu-card.article {
+	display: block;
+}
+
+.cu-card.article>.cu-item {
+	padding-bottom: 30upx;
+}
+
+.cu-card.article>.cu-item .title {
+	font-size: 30upx;
+	font-weight: 900;
+	color: #333333;
+	line-height: 100upx;
+	padding: 0 30upx;
+}
+
+.cu-card.article>.cu-item .content {
+	display: flex;
+	padding: 0 30upx;
+}
+
+.cu-card.article>.cu-item .content>image {
+	width: 240upx;
+	height: 6.4em;
+	margin-right: 20upx;
+	border-radius: 6upx;
+}
+
+.cu-card.article>.cu-item .content .desc {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+
+.cu-card.article>.cu-item .content .text-content {
+	font-size: 28upx;
+	color: #888;
+	height: 4.8em;
+	overflow: hidden;
+}
+
+/* ==================
+         表单
+ ==================== */
+
+.cu-form-group {
+	background-color: #ffffff;
+	padding: 1upx 30upx;
+	display: flex;
+	align-items: center;
+	min-height: 100upx;
+	justify-content: space-between;
+}
+
+.cu-form-group+.cu-form-group {
+	border-top: 1upx solid #eee;
+}
+
+.cu-form-group .title {
+	text-align: justify;
+	padding-right: 30upx;
+	font-size: 30upx;
+	position: relative;
+	height: 60upx;
+	line-height: 60upx;
+}
+
+.cu-form-group input {
+	flex: 1;
+	font-size: 30upx;
+	color: #555;
+	padding-right: 20upx;
+}
+
+.cu-form-group>text[class*="cuIcon-"] {
+	font-size: 36upx;
+	padding: 0;
+	box-sizing: border-box;
+}
+
+.cu-form-group textarea {
+	margin: 32upx 0 30upx;
+	height: 4.6em;
+	width: 100%;
+	line-height: 1.2em;
+	flex: 1;
+	font-size: 28upx;
+	padding: 0;
+}
+
+.cu-form-group.align-start .title {
+	height: 1em;
+	margin-top: 32upx;
+	line-height: 1em;
+}
+
+.cu-form-group picker {
+	flex: 1;
+	padding-right: 40upx;
+	overflow: hidden;
+	position: relative;
+}
+
+.cu-form-group picker .picker {
+	line-height: 100upx;
+	font-size: 28upx;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+	width: 100%;
+	text-align: right;
+}
+
+.cu-form-group picker::after {
+	font-family: cuIcon;
+	display: block;
+	content: "\e6a3";
+	position: absolute;
+	font-size: 34upx;
+	color: #333333;
+	line-height: 100upx;
+	width: 60upx;
+	text-align: center;
+	top: 0;
+	bottom: 0;
+	right: -20upx;
+	margin: auto;
+}
+
+.cu-form-group textarea[disabled],
+.cu-form-group textarea[disabled] .placeholder {
+	color: transparent;
+}
+
+/* ==================
+         模态窗口
+ ==================== */
+
+.cu-modal {
+	position: fixed;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	z-index: 1110;
+	opacity: 0;
+	outline: 0;
+	text-align: center;
+	-ms-transform: scale(1.185);
+	transform: scale(1.185);
+	backface-visibility: hidden;
+	perspective: 2000upx;
+	background: rgba(0, 0, 0, 0.6);
+	transition: all 0.3s ease-in-out 0s;
+	pointer-events: none;
+}
+
+.cu-modal::before {
+	content: "\200B";
+	display: inline-block;
+	height: 100%;
+	vertical-align: middle;
+}
+
+.cu-modal.show {
+	opacity: 1;
+	transition-duration: 0.3s;
+	-ms-transform: scale(1);
+	transform: scale(1);
+	overflow-x: hidden;
+	overflow-y: auto;
+	pointer-events: auto;
+}
+
+.cu-dialog {
+	position: relative;
+	display: inline-block;
+	vertical-align: middle;
+	margin-left: auto;
+	margin-right: auto;
+	width: 680upx;
+	max-width: 100%;
+	background-color: #f8f8f8;
+	border-radius: 10upx;
+	overflow: hidden;
+}
+
+.cu-modal.bottom-modal::before {
+	vertical-align: bottom;
+}
+
+.cu-modal.bottom-modal .cu-dialog {
+	width: 100%;
+	border-radius: 0;
+}
+
+.cu-modal.bottom-modal {
+	margin-bottom: -1000upx;
+}
+
+.cu-modal.bottom-modal.show {
+	margin-bottom: 0;
+}
+
+.cu-modal.drawer-modal {
+	transform: scale(1);
+	display: flex;
+}
+
+.cu-modal.drawer-modal .cu-dialog {
+	height: 100%;
+	min-width: 200upx;
+	border-radius: 0;
+	margin: initial;
+	transition-duration: 0.3s;
+}
+
+.cu-modal.drawer-modal.justify-start .cu-dialog {
+	transform: translateX(-100%);
+}
+
+.cu-modal.drawer-modal.justify-end .cu-dialog {
+	transform: translateX(100%);
+}
+
+.cu-modal.drawer-modal.show .cu-dialog {
+	transform: translateX(0%);
+}
+.cu-modal .cu-dialog>.cu-bar:first-child .action{
+  min-width: 100rpx;
+  margin-right: 0;
+  min-height: 100rpx;
+}
+/* ==================
+         轮播
+ ==================== */
+swiper .a-swiper-dot {
+	display: inline-block;
+	width: 16upx;
+	height: 16upx;
+	background: rgba(0, 0, 0, .3);
+	border-radius: 50%;
+	vertical-align: middle;
+}
+
+swiper[class*="-dot"] .wx-swiper-dots,
+swiper[class*="-dot"] .a-swiper-dots,
+swiper[class*="-dot"] .uni-swiper-dots {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	justify-content: center;
+}
+
+swiper.square-dot .wx-swiper-dot,
+swiper.square-dot .a-swiper-dot,
+swiper.square-dot .uni-swiper-dot {
+	background-color: #ffffff;
+	opacity: 0.4;
+	width: 10upx;
+	height: 10upx;
+	border-radius: 20upx;
+	margin: 0 8upx !important;
+}
+
+swiper.square-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.square-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.square-dot .uni-swiper-dot.uni-swiper-dot-active {
+	opacity: 1;
+	width: 30upx;
+}
+
+swiper.round-dot .wx-swiper-dot,
+swiper.round-dot .a-swiper-dot,
+swiper.round-dot .uni-swiper-dot {
+	width: 10upx;
+	height: 10upx;
+	position: relative;
+	margin: 4upx 8upx !important;
+}
+
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active::after,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active::after,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active::after {
+	content: "";
+	position: absolute;
+	width: 10upx;
+	height: 10upx;
+	top: 0upx;
+	left: 0upx;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	background-color: #ffffff;
+	border-radius: 20upx;
+}
+
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active {
+	width: 18upx;
+	height: 18upx;
+}
+
+.screen-swiper {
+	min-height: 375upx;
+}
+
+.screen-swiper image,
+.screen-swiper video,
+.swiper-item image,
+.swiper-item video {
+	width: 100%;
+	display: block;
+	height: 100%;
+	margin: 0;
+	pointer-events: none;
+}
+
+.card-swiper {
+	height: 420upx !important;
+}
+
+.card-swiper swiper-item {
+	width: 610upx !important;
+	left: 70upx;
+	box-sizing: border-box;
+	padding: 40upx 0upx 70upx;
+	overflow: initial;
+}
+
+.card-swiper swiper-item .swiper-item {
+	width: 100%;
+	display: block;
+	height: 100%;
+	border-radius: 10upx;
+	transform: scale(0.9);
+	transition: all 0.2s ease-in 0s;
+	overflow: hidden;
+}
+
+.card-swiper swiper-item.cur .swiper-item {
+	transform: none;
+	transition: all 0.2s ease-in 0s;
+}
+
+
+.tower-swiper {
+	height: 420upx;
+	position: relative;
+	max-width: 750upx;
+	overflow: hidden;
+}
+
+.tower-swiper .tower-item {
+	position: absolute;
+	width: 300upx;
+	height: 380upx;
+	top: 0;
+	bottom: 0;
+	left: 50%;
+	margin: auto;
+	transition: all 0.2s ease-in 0s;
+	opacity: 1;
+}
+
+.tower-swiper .tower-item.none {
+	opacity: 0;
+}
+
+.tower-swiper .tower-item .swiper-item {
+	width: 100%;
+	height: 100%;
+	border-radius: 6upx;
+	overflow: hidden;
+}
+
+/* ==================
+          步骤条
+ ==================== */
+
+.cu-steps {
+	display: flex;
+}
+
+scroll-view.cu-steps {
+	display: block;
+	white-space: nowrap;
+}
+
+scroll-view.cu-steps .cu-item {
+	display: inline-block;
+}
+
+.cu-steps .cu-item {
+	flex: 1;
+	text-align: center;
+	position: relative;
+	min-width: 100upx;
+}
+
+.cu-steps .cu-item:not([class*="text-"]) {
+	color: #8799a3;
+}
+
+.cu-steps .cu-item [class*="cuIcon-"],
+.cu-steps .cu-item .num {
+	display: block;
+	font-size: 40upx;
+	line-height: 80upx;
+}
+
+.cu-steps .cu-item::before,
+.cu-steps .cu-item::after,
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+	content: "";
+	display: block;
+	position: absolute;
+	height: 0px;
+	width: calc(100% - 80upx);
+	border-bottom: 1px solid #ccc;
+	left: calc(0px - (100% - 80upx) / 2);
+	top: 40upx;
+	z-index: 0;
+}
+
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+	content: "\e6a3";
+	font-family: 'cuIcon';
+	height: 30upx;
+	border-bottom-width: 0px;
+	line-height: 30upx;
+	top: 0;
+	bottom: 0;
+	margin: auto;
+	color: #ccc;
+}
+
+.cu-steps.steps-bottom .cu-item::before,
+.cu-steps.steps-bottom .cu-item::after {
+	bottom: 40upx;
+	top: initial;
+}
+
+.cu-steps .cu-item::after {
+	border-bottom: 1px solid currentColor;
+	width: 0px;
+	transition: all 0.3s ease-in-out 0s;
+}
+
+.cu-steps .cu-item[class*="text-"]::after {
+	width: calc(100% - 80upx);
+	color: currentColor;
+}
+
+.cu-steps .cu-item:first-child::before,
+.cu-steps .cu-item:first-child::after {
+	display: none;
+}
+
+.cu-steps .cu-item .num {
+	width: 40upx;
+	height: 40upx;
+	border-radius: 50%;
+	line-height: 40upx;
+	margin: 20upx auto;
+	font-size: 24upx;
+	border: 1px solid currentColor;
+	position: relative;
+	overflow: hidden;
+}
+
+.cu-steps .cu-item[class*="text-"] .num {
+	background-color: currentColor;
+}
+
+.cu-steps .cu-item .num::before,
+.cu-steps .cu-item .num::after {
+	content: attr(data-index);
+	position: absolute;
+	left: 0;
+	right: 0;
+	top: 0;
+	bottom: 0;
+	margin: auto;
+	transition: all 0.3s ease-in-out 0s;
+	transform: translateY(0upx);
+}
+
+.cu-steps .cu-item[class*="text-"] .num::before {
+	transform: translateY(-40upx);
+	color: #ffffff;
+}
+
+.cu-steps .cu-item .num::after {
+	transform: translateY(40upx);
+	color: #ffffff;
+	transition: all 0.3s ease-in-out 0s;
+}
+
+.cu-steps .cu-item[class*="text-"] .num::after {
+	content: "\e645";
+	font-family: 'cuIcon';
+	color: #ffffff;
+	transform: translateY(0upx);
+}
+
+.cu-steps .cu-item[class*="text-"] .num.err::after {
+	content: "\e646";
+}
+
+/* ==================
+          布局
+ ==================== */
+
+/*  -- flex弹性布局 -- */
+
+.flex {
+	display: flex;
+}
+
+.basis-xs {
+	flex-basis: 20%;
+}
+
+.basis-sm {
+	flex-basis: 40%;
+}
+
+.basis-df {
+	flex-basis: 50%;
+}
+
+.basis-lg {
+	flex-basis: 60%;
+}
+
+.basis-xl {
+	flex-basis: 80%;
+}
+
+.flex-sub {
+	flex: 1;
+}
+
+.flex-twice {
+	flex: 2;
+}
+
+.flex-treble {
+	flex: 3;
+}
+
+.flex-direction {
+	flex-direction: column;
+}
+
+.flex-wrap {
+	flex-wrap: wrap;
+}
+
+.align-start {
+	align-items: flex-start;
+}
+
+.align-end {
+	align-items: flex-end;
+}
+
+.align-center {
+	align-items: center;
+}
+
+.align-stretch {
+	align-items: stretch;
+}
+
+.self-start {
+	align-self: flex-start;
+}
+
+.self-center {
+	align-self: flex-center;
+}
+
+.self-end {
+	align-self: flex-end;
+}
+
+.self-stretch {
+	align-self: stretch;
+}
+
+.align-stretch {
+	align-items: stretch;
+}
+
+.justify-start {
+	justify-content: flex-start;
+}
+
+.justify-end {
+	justify-content: flex-end;
+}
+
+.justify-center {
+	justify-content: center;
+}
+
+.justify-between {
+	justify-content: space-between;
+}
+
+.justify-around {
+	justify-content: space-around;
+}
+
+/* grid布局 */
+
+.grid {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.grid.grid-square {
+	overflow: hidden;
+}
+
+.grid.grid-square .cu-tag {
+	position: absolute;
+	right: 0;
+	top: 0;
+	border-bottom-left-radius: 6upx;
+	padding: 6upx 12upx;
+	height: auto;
+	background-color: rgba(0, 0, 0, 0.5);
+}
+
+.grid.grid-square>view>text[class*="cuIcon-"] {
+	font-size: 52upx;
+	position: absolute;
+	color: #8799a3;
+	margin: auto;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	flex-direction: column;
+}
+
+.grid.grid-square>view {
+	margin-right: 20upx;
+	margin-bottom: 20upx;
+	border-radius: 6upx;
+	position: relative;
+	overflow: hidden;
+}
+.grid.grid-square>view.bg-img image {
+	width: 100%;
+	height: 100%;
+	position: absolute;
+}
+.grid.col-1.grid-square>view {
+	padding-bottom: 100%;
+	height: 0;
+	margin-right: 0;
+}
+
+.grid.col-2.grid-square>view {
+	padding-bottom: calc((100% - 20upx)/2);
+	height: 0;
+	width: calc((100% - 20upx)/2);
+}
+
+.grid.col-3.grid-square>view {
+	padding-bottom: calc((100% - 40upx)/3);
+	height: 0;
+	width: calc((100% - 40upx)/3);
+}
+
+.grid.col-4.grid-square>view {
+	padding-bottom: calc((100% - 60upx)/4);
+	height: 0;
+	width: calc((100% - 60upx)/4);
+}
+
+.grid.col-5.grid-square>view {
+	padding-bottom: calc((100% - 80upx)/5);
+	height: 0;
+	width: calc((100% - 80upx)/5);
+}
+
+.grid.col-2.grid-square>view:nth-child(2n),
+.grid.col-3.grid-square>view:nth-child(3n),
+.grid.col-4.grid-square>view:nth-child(4n),
+.grid.col-5.grid-square>view:nth-child(5n) {
+	margin-right: 0;
+}
+
+.grid.col-1>view {
+	width: 100%;
+}
+
+.grid.col-2>view {
+	width: 50%;
+}
+
+.grid.col-3>view {
+	width: 33.33%;
+}
+
+.grid.col-4>view {
+	width: 25%;
+}
+
+.grid.col-5>view {
+	width: 20%;
+}
+
+/*  -- 内外边距 -- */
+
+.margin-0 {
+	margin: 0;
+}
+
+.margin-xs {
+	margin: 10upx;
+}
+
+.margin-sm {
+	margin: 20upx;
+}
+
+.margin {
+	margin: 30upx;
+}
+
+.margin-lg {
+	margin: 40upx;
+}
+
+.margin-xl {
+	margin: 50upx;
+}
+
+.margin-top-xs {
+	margin-top: 10upx;
+}
+
+.margin-top-sm {
+	margin-top: 20upx;
+}
+
+.margin-top {
+	margin-top: 30upx;
+}
+
+.margin-top-lg {
+	margin-top: 40upx;
+}
+
+.margin-top-xl {
+	margin-top: 50upx;
+}
+
+.margin-right-xs {
+	margin-right: 10upx;
+}
+
+.margin-right-sm {
+	margin-right: 20upx;
+}
+
+.margin-right {
+	margin-right: 30upx;
+}
+
+.margin-right-lg {
+	margin-right: 40upx;
+}
+
+.margin-right-xl {
+	margin-right: 50upx;
+}
+
+.margin-bottom-xs {
+	margin-bottom: 10upx;
+}
+
+.margin-bottom-sm {
+	margin-bottom: 20upx;
+}
+
+.margin-bottom {
+	margin-bottom: 30upx;
+}
+
+.margin-bottom-lg {
+	margin-bottom: 40upx;
+}
+
+.margin-bottom-xl {
+	margin-bottom: 50upx;
+}
+
+.margin-left-xs {
+	margin-left: 10upx;
+}
+
+.margin-left-sm {
+	margin-left: 20upx;
+}
+
+.margin-left {
+	margin-left: 30upx;
+}
+
+.margin-left-lg {
+	margin-left: 40upx;
+}
+
+.margin-left-xl {
+	margin-left: 50upx;
+}
+
+.margin-lr-xs {
+	margin-left: 10upx;
+	margin-right: 10upx;
+}
+
+.margin-lr-sm {
+	margin-left: 20upx;
+	margin-right: 20upx;
+}
+
+.margin-lr {
+	margin-left: 30upx;
+	margin-right: 30upx;
+}
+
+.margin-lr-lg {
+	margin-left: 40upx;
+	margin-right: 40upx;
+}
+
+.margin-lr-xl {
+	margin-left: 50upx;
+	margin-right: 50upx;
+}
+
+.margin-tb-xs {
+	margin-top: 10upx;
+	margin-bottom: 10upx;
+}
+
+.margin-tb-sm {
+	margin-top: 20upx;
+	margin-bottom: 20upx;
+}
+
+.margin-tb {
+	margin-top: 30upx;
+	margin-bottom: 30upx;
+}
+
+.margin-tb-lg {
+	margin-top: 40upx;
+	margin-bottom: 40upx;
+}
+
+.margin-tb-xl {
+	margin-top: 50upx;
+	margin-bottom: 50upx;
+}
+
+.padding-0 {
+	padding: 0;
+}
+
+.padding-xs {
+	padding: 10upx;
+}
+
+.padding-sm {
+	padding: 20upx;
+}
+
+.padding {
+	padding: 30upx;
+}
+
+.padding-lg {
+	padding: 40upx;
+}
+
+.padding-xl {
+	padding: 50upx;
+}
+
+.padding-top-xs {
+	padding-top: 10upx;
+}
+
+.padding-top-sm {
+	padding-top: 20upx;
+}
+
+.padding-top {
+	padding-top: 30upx;
+}
+
+.padding-top-lg {
+	padding-top: 40upx;
+}
+
+.padding-top-xl {
+	padding-top: 50upx;
+}
+
+.padding-right-xs {
+	padding-right: 10upx;
+}
+
+.padding-right-sm {
+	padding-right: 20upx;
+}
+
+.padding-right {
+	padding-right: 30upx;
+}
+
+.padding-right-lg {
+	padding-right: 40upx;
+}
+
+.padding-right-xl {
+	padding-right: 50upx;
+}
+
+.padding-bottom-xs {
+	padding-bottom: 10upx;
+}
+
+.padding-bottom-sm {
+	padding-bottom: 20upx;
+}
+
+.padding-bottom {
+	padding-bottom: 30upx;
+}
+
+.padding-bottom-lg {
+	padding-bottom: 40upx;
+}
+
+.padding-bottom-xl {
+	padding-bottom: 50upx;
+}
+
+.padding-left-xs {
+	padding-left: 10upx;
+}
+
+.padding-left-sm {
+	padding-left: 20upx;
+}
+
+.padding-left {
+	padding-left: 30upx;
+}
+
+.padding-left-lg {
+	padding-left: 40upx;
+}
+
+.padding-left-xl {
+	padding-left: 50upx;
+}
+
+.padding-lr-xs {
+	padding-left: 10upx;
+	padding-right: 10upx;
+}
+
+.padding-lr-sm {
+	padding-left: 20upx;
+	padding-right: 20upx;
+}
+
+.padding-lr {
+	padding-left: 30upx;
+	padding-right: 30upx;
+}
+
+.padding-lr-lg {
+	padding-left: 40upx;
+	padding-right: 40upx;
+}
+
+.padding-lr-xl {
+	padding-left: 50upx;
+	padding-right: 50upx;
+}
+
+.padding-tb-xs {
+	padding-top: 10upx;
+	padding-bottom: 10upx;
+}
+
+.padding-tb-sm {
+	padding-top: 20upx;
+	padding-bottom: 20upx;
+}
+
+.padding-tb {
+	padding-top: 30upx;
+	padding-bottom: 30upx;
+}
+
+.padding-tb-lg {
+	padding-top: 40upx;
+	padding-bottom: 40upx;
+}
+
+.padding-tb-xl {
+	padding-top: 50upx;
+	padding-bottom: 50upx;
+}
+
+/* -- 浮动 --  */
+
+.cf::after,
+.cf::before {
+	content: " ";
+	display: table;
+}
+
+.cf::after {
+	clear: both;
+}
+
+.fl {
+	float: left;
+}
+
+.fr {
+	float: right;
+}
+
+/* ==================
+          背景
+ ==================== */
+
+.line-red::after,
+.lines-red::after {
+	border-color: #e54d42;
+}
+
+.line-orange::after,
+.lines-orange::after {
+	border-color: #f37b1d;
+}
+
+.line-yellow::after,
+.lines-yellow::after {
+	border-color: #fbbd08;
+}
+
+.line-olive::after,
+.lines-olive::after {
+	border-color: #8dc63f;
+}
+
+.line-green::after,
+.lines-green::after {
+	border-color: #39b54a;
+}
+
+.line-cyan::after,
+.lines-cyan::after {
+	border-color: #1cbbb4;
+}
+
+.line-blue::after,
+.lines-blue::after {
+	border-color: #0081ff;
+}
+
+.line-purple::after,
+.lines-purple::after {
+	border-color: #6739b6;
+}
+
+.line-mauve::after,
+.lines-mauve::after {
+	border-color: #9c26b0;
+}
+
+.line-pink::after,
+.lines-pink::after {
+	border-color: #e03997;
+}
+
+.line-brown::after,
+.lines-brown::after {
+	border-color: #a5673f;
+}
+
+.line-grey::after,
+.lines-grey::after {
+	border-color: #8799a3;
+}
+
+.line-gray::after,
+.lines-gray::after {
+	border-color: #aaaaaa;
+}
+
+.line-black::after,
+.lines-black::after {
+	border-color: #333333;
+}
+
+.line-white::after,
+.lines-white::after {
+	border-color: #ffffff;
+}
+
+.bg-red {
+	background-color: #e54d42;
+	color: #ffffff;
+}
+
+.bg-orange {
+	background-color: #f37b1d;
+	color: #ffffff;
+}
+
+.bg-yellow {
+	background-color: #fbbd08;
+	color: #333333;
+}
+
+.bg-olive {
+	background-color: #8dc63f;
+	color: #ffffff;
+}
+
+.bg-green {
+	background-color: #39b54a;
+	color: #ffffff;
+}
+
+.bg-cyan {
+	background-color: #1cbbb4;
+	color: #ffffff;
+}
+
+.bg-blue {
+	background-color: #0081ff;
+	color: #ffffff;
+}
+
+.bg-purple {
+	background-color: #6739b6;
+	color: #ffffff;
+}
+
+.bg-mauve {
+	background-color: #9c26b0;
+	color: #ffffff;
+}
+
+.bg-pink {
+	background-color: #e03997;
+	color: #ffffff;
+}
+
+.bg-brown {
+	background-color: #a5673f;
+	color: #ffffff;
+}
+
+.bg-grey {
+	background-color: #8799a3;
+	color: #ffffff;
+}
+
+.bg-gray {
+	background-color: #f0f0f0;
+	color: #333333;
+}
+
+.bg-black {
+	background-color: #333333;
+	color: #ffffff;
+}
+
+.bg-white {
+	background-color: #ffffff;
+	color: #666666;
+}
+
+.bg-shadeTop {
+	background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.01));
+	color: #ffffff;
+}
+
+.bg-shadeBottom {
+	background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 1));
+	color: #ffffff;
+}
+
+.bg-red.light {
+	color: #e54d42;
+	background-color: #fadbd9;
+}
+
+.bg-orange.light {
+	color: #f37b1d;
+	background-color: #fde6d2;
+}
+
+.bg-yellow.light {
+	color: #fbbd08;
+	background-color: #fef2ced2;
+}
+
+.bg-olive.light {
+	color: #8dc63f;
+	background-color: #e8f4d9;
+}
+
+.bg-green.light {
+	color: #39b54a;
+	background-color: #d7f0dbff;
+}
+
+.bg-cyan.light {
+	color: #1cbbb4;
+	background-color: #d2f1f0;
+}
+
+.bg-blue.light {
+	color: #0081ff;
+	background-color: #cce6ff;
+}
+
+.bg-purple.light {
+	color: #6739b6;
+	background-color: #e1d7f0;
+}
+
+.bg-mauve.light {
+	color: #9c26b0;
+	background-color: #ebd4ef;
+}
+
+.bg-pink.light {
+	color: #e03997;
+	background-color: #f9d7ea;
+}
+
+.bg-brown.light {
+	color: #a5673f;
+	background-color: #ede1d9;
+}
+
+.bg-grey.light {
+	color: #8799a3;
+	background-color: #e7ebed;
+}
+
+.bg-gradual-red {
+	background-image: linear-gradient(45deg, #f43f3b, #ec008c);
+	color: #ffffff;
+}
+
+.bg-gradual-orange {
+	background-image: linear-gradient(45deg, #ff9700, #ed1c24);
+	color: #ffffff;
+}
+
+.bg-gradual-green {
+	background-image: linear-gradient(45deg, #39b54a, #8dc63f);
+	color: #ffffff;
+}
+
+.bg-gradual-purple {
+	background-image: linear-gradient(45deg, #9000ff, #5e00ff);
+	color: #ffffff;
+}
+
+.bg-gradual-pink {
+	background-image: linear-gradient(45deg, #ec008c, #6739b6);
+	color: #ffffff;
+}
+
+.bg-gradual-blue {
+	background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
+	color: #ffffff;
+}
+
+.shadow[class*="-red"] {
+	box-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+}
+
+.shadow[class*="-orange"] {
+	box-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+}
+
+.shadow[class*="-yellow"] {
+	box-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+}
+
+.shadow[class*="-olive"] {
+	box-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+}
+
+.shadow[class*="-green"] {
+	box-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+}
+
+.shadow[class*="-cyan"] {
+	box-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+}
+
+.shadow[class*="-blue"] {
+	box-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+}
+
+.shadow[class*="-purple"] {
+	box-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+}
+
+.shadow[class*="-mauve"] {
+	box-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+}
+
+.shadow[class*="-pink"] {
+	box-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+}
+
+.shadow[class*="-brown"] {
+	box-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+}
+
+.shadow[class*="-grey"] {
+	box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.shadow[class*="-gray"] {
+	box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.shadow[class*="-black"] {
+	box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.shadow[class*="-white"] {
+	box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.text-shadow[class*="-red"] {
+	text-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+}
+
+.text-shadow[class*="-orange"] {
+	text-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+}
+
+.text-shadow[class*="-yellow"] {
+	text-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+}
+
+.text-shadow[class*="-olive"] {
+	text-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+}
+
+.text-shadow[class*="-green"] {
+	text-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+}
+
+.text-shadow[class*="-cyan"] {
+	text-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+}
+
+.text-shadow[class*="-blue"] {
+	text-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+}
+
+.text-shadow[class*="-purple"] {
+	text-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+}
+
+.text-shadow[class*="-mauve"] {
+	text-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+}
+
+.text-shadow[class*="-pink"] {
+	text-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+}
+
+.text-shadow[class*="-brown"] {
+	text-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+}
+
+.text-shadow[class*="-grey"] {
+	text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.text-shadow[class*="-gray"] {
+	text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.text-shadow[class*="-black"] {
+	text-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.bg-img {
+	background-size: cover;
+	background-position: center;
+	background-repeat: no-repeat;
+}
+
+.bg-mask {
+	background-color: #333333;
+	position: relative;
+}
+
+.bg-mask::after {
+	content: "";
+	border-radius: inherit;
+	width: 100%;
+	height: 100%;
+	display: block;
+	background-color: rgba(0, 0, 0, 0.4);
+	position: absolute;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+}
+
+.bg-mask view,
+.bg-mask cover-view {
+	z-index: 5;
+	position: relative;
+}
+
+.bg-video {
+	position: relative;
+}
+
+.bg-video video {
+	display: block;
+	height: 100%;
+	width: 100%;
+	-o-object-fit: cover;
+	object-fit: cover;
+	position: absolute;
+	top: 0;
+	z-index: 0;
+	pointer-events: none;
+}
+
+/* ==================
+          文本
+ ==================== */
+
+.text-xs {
+	font-size: 20upx;
+}
+
+.text-sm {
+	font-size: 24upx;
+}
+
+.text-df {
+	font-size: 28upx;
+}
+
+.text-lg {
+	font-size: 32upx;
+}
+
+.text-xl {
+	font-size: 36upx;
+}
+
+.text-xxl {
+	font-size: 44upx;
+}
+
+.text-sl {
+	font-size: 80upx;
+}
+
+.text-xsl {
+	font-size: 120upx;
+}
+
+.text-Abc {
+	text-transform: Capitalize;
+}
+
+.text-ABC {
+	text-transform: Uppercase;
+}
+
+.text-abc {
+	text-transform: Lowercase;
+}
+
+.text-price::before {
+	content: "¥";
+	font-size: 80%;
+	margin-right: 4upx;
+}
+
+.text-cut {
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+}
+
+.text-bold {
+	font-weight: bold;
+}
+
+.text-center {
+	text-align: center;
+}
+
+.text-content {
+	line-height: 1.6;
+}
+
+.text-left {
+	text-align: left;
+}
+
+.text-right {
+	text-align: right;
+}
+
+.text-red,
+.line-red,
+.lines-red {
+	color: #e54d42;
+}
+
+.text-orange,
+.line-orange,
+.lines-orange {
+	color: #f37b1d;
+}
+
+.text-yellow,
+.line-yellow,
+.lines-yellow {
+	color: #fbbd08;
+}
+
+.text-olive,
+.line-olive,
+.lines-olive {
+	color: #8dc63f;
+}
+
+.text-green,
+.line-green,
+.lines-green {
+	color: #39b54a;
+}
+
+.text-cyan,
+.line-cyan,
+.lines-cyan {
+	color: #1cbbb4;
+}
+
+.text-blue,
+.line-blue,
+.lines-blue {
+	color: #0081ff;
+}
+
+.text-purple,
+.line-purple,
+.lines-purple {
+	color: #6739b6;
+}
+
+.text-mauve,
+.line-mauve,
+.lines-mauve {
+	color: #9c26b0;
+}
+
+.text-pink,
+.line-pink,
+.lines-pink {
+	color: #e03997;
+}
+
+.text-brown,
+.line-brown,
+.lines-brown {
+	color: #a5673f;
+}
+
+.text-grey,
+.line-grey,
+.lines-grey {
+	color: #8799a3;
+}
+
+.text-gray,
+.line-gray,
+.lines-gray {
+	color: #aaaaaa;
+}
+
+.text-black,
+.line-black,
+.lines-black {
+	color: #333333;
+}
+
+.text-white,
+.line-white,
+.lines-white {
+	color: #ffffff;
+}

+ 99 - 0
components/luch-request/adapters/index.js

@@ -0,0 +1,99 @@
+import buildURL from '../helpers/buildURL'
+import buildFullPath from '../core/buildFullPath'
+import settle from '../core/settle'
+import { isUndefined } from "../utils"
+
+/**
+ * 返回可选值存在的配置
+ * @param {Array} keys - 可选值数组
+ * @param {Object} config2 - 配置
+ * @return {{}} - 存在的配置项
+ */
+const mergeKeys = (keys, config2) => {
+  let config = {}
+  keys.forEach(prop => {
+    if (!isUndefined(config2[prop])) {
+      config[prop] = config2[prop]
+    }
+  })
+  return config
+}
+export default (config) => {
+  return new Promise((resolve, reject) => {
+    let fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)
+    const _config = {
+      url: fullPath,
+      header: config.header,
+      complete: (response) => {
+        config.fullPath = fullPath
+        response.config = config
+        try {
+          // 对可能字符串不是json 的情况容错
+          if (typeof response.data === 'string') {
+            response.data = JSON.parse(response.data)
+          }
+          // eslint-disable-next-line no-empty
+        } catch (e) {
+        }
+        settle(resolve, reject, response)
+      }
+    }
+    let requestTask
+    if (config.method === 'UPLOAD') {
+      delete _config.header['content-type']
+      delete _config.header['Content-Type']
+      let otherConfig = {
+        // #ifdef MP-ALIPAY
+        fileType: config.fileType,
+        // #endif
+        filePath: config.filePath,
+        name: config.name
+      }
+      const optionalKeys = [
+        // #ifdef APP-PLUS || H5
+        'files',
+        // #endif
+        // #ifdef H5
+        'file',
+        // #endif
+        // #ifdef H5 || APP-PLUS
+        'timeout',
+        // #endif
+        'formData'
+      ]
+      requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)})
+    } else if (config.method === 'DOWNLOAD') {
+      // #ifdef H5 || APP-PLUS
+      if (!isUndefined(config['timeout'])) {
+        _config['timeout'] = config['timeout']
+      }
+      // #endif
+      requestTask = uni.downloadFile(_config)
+    } else {
+      const optionalKeys = [
+        'data',
+        'method',
+        // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+        'timeout',
+        // #endif
+        'dataType',
+        // #ifndef MP-ALIPAY
+        'responseType',
+        // #endif
+        // #ifdef APP-PLUS
+        'sslVerify',
+        // #endif
+        // #ifdef H5
+        'withCredentials',
+        // #endif
+        // #ifdef APP-PLUS
+        'firstIpv4',
+        // #endif
+      ]
+      requestTask = uni.request({..._config,...mergeKeys(optionalKeys, config)})
+    }
+    if (config.getTask) {
+      config.getTask(requestTask, config)
+    }
+  })
+}

+ 51 - 0
components/luch-request/core/InterceptorManager.js

@@ -0,0 +1,51 @@
+'use strict'
+
+
+function InterceptorManager() {
+  this.handlers = []
+}
+
+/**
+ * Add a new interceptor to the stack
+ *
+ * @param {Function} fulfilled The function to handle `then` for a `Promise`
+ * @param {Function} rejected The function to handle `reject` for a `Promise`
+ *
+ * @return {Number} An ID used to remove interceptor later
+ */
+InterceptorManager.prototype.use = function use(fulfilled, rejected) {
+  this.handlers.push({
+    fulfilled: fulfilled,
+    rejected: rejected
+  })
+  return this.handlers.length - 1
+}
+
+/**
+ * Remove an interceptor from the stack
+ *
+ * @param {Number} id The ID that was returned by `use`
+ */
+InterceptorManager.prototype.eject = function eject(id) {
+  if (this.handlers[id]) {
+    this.handlers[id] = null
+  }
+}
+
+/**
+ * Iterate over all the registered interceptors
+ *
+ * This method is particularly useful for skipping over any
+ * interceptors that may have become `null` calling `eject`.
+ *
+ * @param {Function} fn The function to call for each interceptor
+ */
+InterceptorManager.prototype.forEach = function forEach(fn) {
+  this.handlers.forEach(h => {
+    if (h !== null) {
+      fn(h)
+    }
+  })
+}
+
+export default InterceptorManager

+ 200 - 0
components/luch-request/core/Request.js

@@ -0,0 +1,200 @@
+/**
+ * @Class Request
+ * @description luch-request http请求插件
+ * @version 3.0.7
+ * @Author lu-ch
+ * @Date 2021-09-04
+ * @Email webwork.s@qq.com
+ * 文档: https://www.quanzhan.co/luch-request/
+ * github: https://github.com/lei-mu/luch-request
+ * DCloud: http://ext.dcloud.net.cn/plugin?id=392
+ * HBuilderX: beat-3.0.4 alpha-3.0.4
+ */
+
+
+import dispatchRequest from './dispatchRequest'
+import InterceptorManager from './InterceptorManager'
+import mergeConfig from './mergeConfig'
+import defaults from './defaults'
+import { isPlainObject } from '../utils'
+import clone from '../utils/clone'
+
+export default class Request {
+  /**
+   * @param {Object} arg - 全局配置
+   * @param {String} arg.baseURL - 全局根路径
+   * @param {Object} arg.header - 全局header
+   * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
+   * @param {String} arg.dataType = [json] - 全局默认的dataType
+   * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持
+   * @param {Object} arg.custom - 全局默认的自定义参数
+   * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序
+   * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+)
+   * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+)
+   * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)
+   * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
+   */
+  constructor(arg = {}) {
+    if (!isPlainObject(arg)) {
+      arg = {}
+      console.warn('设置全局参数必须接收一个Object')
+    }
+    this.config = clone({...defaults, ...arg})
+    this.interceptors = {
+      request: new InterceptorManager(),
+      response: new InterceptorManager()
+    }
+  }
+
+  /**
+   * @Function
+   * @param {Request~setConfigCallback} f - 设置全局默认配置
+   */
+  setConfig(f) {
+    this.config = f(this.config)
+  }
+
+  middleware(config) {
+    config = mergeConfig(this.config, config)
+    let chain = [dispatchRequest, undefined]
+    let promise = Promise.resolve(config)
+
+    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
+      chain.unshift(interceptor.fulfilled, interceptor.rejected)
+    })
+
+    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
+      chain.push(interceptor.fulfilled, interceptor.rejected)
+    })
+
+    while (chain.length) {
+      promise = promise.then(chain.shift(), chain.shift())
+    }
+
+    return promise
+  }
+
+  /**
+   * @Function
+   * @param {Object} config - 请求配置项
+   * @prop {String} options.url - 请求路径
+   * @prop {Object} options.data - 请求参数
+   * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
+   * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
+   * @prop {Object} [options.header = config.header] - 请求header
+   * @prop {Object} [options.method = config.method] - 请求方法
+   * @returns {Promise<unknown>}
+   */
+  request(config = {}) {
+    return this.middleware(config)
+  }
+
+  get(url, options = {}) {
+    return this.middleware({
+      url,
+      method: 'GET',
+      ...options
+    })
+  }
+
+  post(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'POST',
+      ...options
+    })
+  }
+
+  // #ifndef MP-ALIPAY
+  put(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'PUT',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+  delete(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'DELETE',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef H5 || MP-WEIXIN
+  connect(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'CONNECT',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef  H5 || MP-WEIXIN || MP-BAIDU
+  head(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'HEAD',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+  options(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'OPTIONS',
+      ...options
+    })
+  }
+
+  // #endif
+
+  // #ifdef H5 || MP-WEIXIN
+  trace(url, data, options = {}) {
+    return this.middleware({
+      url,
+      data,
+      method: 'TRACE',
+      ...options
+    })
+  }
+
+  // #endif
+
+  upload(url, config = {}) {
+    config.url = url
+    config.method = 'UPLOAD'
+    return this.middleware(config)
+  }
+
+  download(url, config = {}) {
+    config.url = url
+    config.method = 'DOWNLOAD'
+    return this.middleware(config)
+  }
+}
+
+
+/**
+ * setConfig回调
+ * @return {Object} - 返回操作后的config
+ * @callback Request~setConfigCallback
+ * @param {Object} config - 全局默认config
+ */

+ 20 - 0
components/luch-request/core/buildFullPath.js

@@ -0,0 +1,20 @@
+'use strict'
+
+import isAbsoluteURL from '../helpers/isAbsoluteURL'
+import combineURLs from '../helpers/combineURLs'
+
+/**
+ * Creates a new URL by combining the baseURL with the requestedURL,
+ * only when the requestedURL is not already an absolute URL.
+ * If the requestURL is absolute, this function returns the requestedURL untouched.
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} requestedURL Absolute or relative URL to combine
+ * @returns {string} The combined full path
+ */
+export default function buildFullPath(baseURL, requestedURL) {
+  if (baseURL && !isAbsoluteURL(requestedURL)) {
+    return combineURLs(baseURL, requestedURL)
+  }
+  return requestedURL
+}

+ 30 - 0
components/luch-request/core/defaults.js

@@ -0,0 +1,30 @@
+/**
+ * 默认的全局配置
+ */
+
+
+export default {
+  baseURL: '',
+  header: {},
+  method: 'GET',
+  dataType: 'json',
+  // #ifndef MP-ALIPAY
+  responseType: 'text',
+  // #endif
+  custom: {},
+  // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+  timeout: 60000,
+  // #endif
+  // #ifdef APP-PLUS
+  sslVerify: true,
+  // #endif
+  // #ifdef H5
+  withCredentials: false,
+  // #endif
+  // #ifdef APP-PLUS
+  firstIpv4: false,
+  // #endif
+  validateStatus: function validateStatus(status) {
+    return status >= 200 && status < 300
+  }
+}

+ 6 - 0
components/luch-request/core/dispatchRequest.js

@@ -0,0 +1,6 @@
+import adapter from '../adapters/index'
+
+
+export default (config) => {
+  return adapter(config)
+}

+ 103 - 0
components/luch-request/core/mergeConfig.js

@@ -0,0 +1,103 @@
+import {deepMerge, isUndefined} from '../utils'
+
+/**
+ * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局
+ * @param {Array} keys - 配置项
+ * @param {Object} globalsConfig - 当前的全局配置
+ * @param {Object} config2 - 局部配置
+ * @return {{}}
+ */
+const mergeKeys = (keys, globalsConfig, config2) => {
+  let config = {}
+  keys.forEach(prop => {
+    if (!isUndefined(config2[prop])) {
+      config[prop] = config2[prop]
+    } else if (!isUndefined(globalsConfig[prop])) {
+      config[prop] = globalsConfig[prop]
+    }
+  })
+  return config
+}
+/**
+ *
+ * @param globalsConfig - 当前实例的全局配置
+ * @param config2 - 当前的局部配置
+ * @return - 合并后的配置
+ */
+export default (globalsConfig, config2 = {}) => {
+  const method = config2.method || globalsConfig.method || 'GET'
+  let config = {
+    baseURL: globalsConfig.baseURL || '',
+    method: method,
+    url: config2.url || '',
+    params: config2.params || {},
+    custom: {...(globalsConfig.custom || {}), ...(config2.custom || {})},
+    header: deepMerge(globalsConfig.header || {}, config2.header || {})
+  }
+  const defaultToConfig2Keys = ['getTask', 'validateStatus']
+  config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)}
+
+  // eslint-disable-next-line no-empty
+  if (method === 'DOWNLOAD') {
+    // #ifdef H5 || APP-PLUS
+    if (!isUndefined(config2.timeout)) {
+      config['timeout'] = config2['timeout']
+    } else if (!isUndefined(globalsConfig.timeout)) {
+      config['timeout'] = globalsConfig['timeout']
+    }
+    // #endif
+  } else if (method === 'UPLOAD') {
+    delete config.header['content-type']
+    delete config.header['Content-Type']
+    const uploadKeys = [
+      // #ifdef APP-PLUS || H5
+      'files',
+      // #endif
+      // #ifdef MP-ALIPAY
+      'fileType',
+      // #endif
+      // #ifdef H5
+      'file',
+      // #endif
+      'filePath',
+      'name',
+      // #ifdef H5 || APP-PLUS
+      'timeout',
+      // #endif
+      'formData',
+    ]
+    uploadKeys.forEach(prop => {
+      if (!isUndefined(config2[prop])) {
+        config[prop] = config2[prop]
+      }
+    })
+    // #ifdef H5 || APP-PLUS
+    if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {
+      config['timeout'] = globalsConfig['timeout']
+    }
+    // #endif
+  } else {
+    const defaultsKeys = [
+      'data',
+      // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+      'timeout',
+      // #endif
+      'dataType',
+      // #ifndef MP-ALIPAY
+      'responseType',
+      // #endif
+      // #ifdef APP-PLUS
+      'sslVerify',
+      // #endif
+      // #ifdef H5
+      'withCredentials',
+      // #endif
+      // #ifdef APP-PLUS
+      'firstIpv4',
+      // #endif
+    ]
+    config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)}
+  }
+
+  return config
+}

+ 16 - 0
components/luch-request/core/settle.js

@@ -0,0 +1,16 @@
+/**
+ * Resolve or reject a Promise based on response status.
+ *
+ * @param {Function} resolve A function that resolves the promise.
+ * @param {Function} reject A function that rejects the promise.
+ * @param {object} response The response.
+ */
+export default function settle(resolve, reject, response) {
+  const validateStatus = response.config.validateStatus
+  const status = response.statusCode
+  if (status && (!validateStatus || validateStatus(status))) {
+    resolve(response)
+  } else {
+    reject(response)
+  }
+}

+ 155 - 0
components/luch-request/custom/api.js

@@ -0,0 +1,155 @@
+import {
+	http
+} from './service.js'
+
+/**
+ * 授权
+ * @param {Object} params
+ */
+export const auth = (params) => {
+	// 若使用code, 刷新code待后续使用
+	if (params.code) {
+		wx.login({
+			success(res) {
+				if (res.code) {
+					uni.setStorageSync('equipment_code', res.code)
+				}
+			}
+		})
+	}
+
+	return http.post('user/weapplogin', params)
+}
+
+export default {
+	/**
+	 * 系统数据
+	 */
+	getSystemInfo() {
+		return http.get('index/getSystemInfo')
+	},
+	/**
+	 * 登录
+	 * @param {Object} params
+	 */
+	login(params) {
+		return http.post('user/login', params)
+	},
+	/**
+	 * 微信授权登录
+	 * @param {Object} params
+	 */
+	welogin(params) {
+		return http.post('user/weapplogin', params)
+	},
+	/**
+	 * 退出登录
+	 * @param {Object} params
+	 */
+	logout(params) {
+		return http.post('user/logout', params)
+	},
+	/**
+	 * 工作台
+	 */
+	workbench() {
+		return http.get('manage/workbench')
+	},
+	/**
+	 * 设备档案列表
+	 * @param {Object} params
+	 */
+	archives(params) {
+		return http.post('manage/archives', params)
+	},
+	/**
+	 * 设备列表
+	 * @param {Object} params
+	 */
+	equipments(params) {
+		return http.post('manage/equipments', params)
+	},
+	/**
+	 * 设备信息
+	 * @param {Object} params
+	 */
+	equipment(params) {
+		return http.get('index/equipments', {
+			params
+		})
+	},
+	/**
+	 * 维修工单列表
+	 * @param {Object} params
+	 */
+	repairs(params) {
+		return http.post('manage/repairs', params)
+	},
+	/**
+	 * 报修详情
+	 * @param {Object} params
+	 */
+	repairInfo(params) {
+		return http.get('index/repairInfos', {
+			params
+		})
+	},
+	/**
+	 * 设备报修
+	 * @param {Object} params
+	 */
+	repair(params) {
+		return http.post('index/repairs', params)
+	},
+	/**
+	 * 维修接单
+	 * @param {Object} params
+	 */
+	receiveRepairs(params) {
+		return http.post('manage/receiveRepairs', params)
+	},
+	/**
+	 * 维修登记
+	 * @param {Object} params
+	 */
+	register(params) {
+		return http.post('index/registers', params)
+	},
+	/**
+	 * 计划任务字段
+	 * @param {Object} params
+	 */
+	planTaskField(params) {
+		return http.get('index/planTaskFields', {
+			params
+		})
+	},
+	/**
+	 * 完成计划任务
+	 * @param {Object} params
+	 */
+	submitPlanTask(params) {
+		return http.post('index/submitPlanTasks', params)
+	},
+	/**
+	 * 记录详情
+	 * @param {Object} params
+	 */
+	getRecordInfo(params) {
+		return http.post('index/getRecordInfo', params)
+	},
+	/**
+	 * 故障原因
+	 * @param {Object} params
+	 */
+	failureCause(params) {
+		return http.post('index/getFailureCause', params)
+	},
+	/**
+	 * 员工信息
+	 * @param {Object} params
+	 */
+	getStaffInfo(params) {
+		return http.post('user/getStaffInfo', params)
+	},
+}

+ 148 - 0
components/luch-request/custom/service.js

@@ -0,0 +1,148 @@
+/**
+ * @version 3.0.7
+ * @Author lu-ch
+ * @Date 2021-09-04
+ * @Email webwork.s@qq.com
+ * 文档: https://www.quanzhan.co/luch-request/
+ * github: https://github.com/lei-mu/luch-request
+ * DCloud: http://ext.dcloud.net.cn/plugin?id=392
+ * HBuilderX: beat-3.0.4 alpha-3.0.4
+ */
+
+import { // 局部引入
+	auth
+} from './api.js'
+import Config from '@/utils/config.js'
+import Request from '../index.js'
+
+const http = new Request({
+	validateStatus: (statusCode) => { // statusCode 必存在。此处示例为全局默认配置
+		return (statusCode >= 200 && statusCode < 300) || statusCode == 401
+	}
+})
+http.setConfig((config) => { /* 设置全局配置 */
+	config.baseURL = Config.apiUrl
+	config.header = {
+		...config.header,
+		ContentType: 'application/json;charset=UTF-8',
+		ContentType: 'application/x-www-form-urlencoded',
+		'Accept-Language': 'zh-CN,zh',
+		'X-Requested-With': 'XMLHttpRequest'
+	}
+	return config
+})
+
+http.interceptors.request.use((config) => { /* 请求之前拦截器。可以使用async await 做异步操作 */
+	config.header = {
+		...config.header,
+		'token': getTokenStorage()
+	}
+	/*
+	 if (!token) { // 如果token不存在,return Promise.reject(config) 会取消本次请求
+	   return Promise.reject(config)
+	 }
+	 */
+	return config
+}, (config) => {
+	return Promise.reject(config)
+})
+
+http.interceptors.response.use(async (response) => { /* 请求之后拦截器。可以使用async await 做异步操作  */
+	// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
+	//   return Promise.reject(response)
+	// }
+
+	const {
+		statusCode,
+		config
+	} = response
+	try {
+		return await handleCode(response, statusCode, config)
+	} catch (err) {
+		return Promise.reject(err)
+	}
+
+	return response
+}, (response) => { // 请求错误做点什么。可以使用async await 做异步操作
+	console.log(response)
+	return Promise.reject(response)
+})
+
+
+/**
+ * 获取 token
+ * @return {string}
+ */
+const getTokenStorage = () => {
+	return uni.getStorageSync('equipment_token') || ''
+}
+
+/**
+ * 重新请求更新获取 `token`
+ * @param {number} uid
+ * @return {Promise}
+ */
+const getApiToken = () => {
+	let params = { // 重新授权登录
+		code: uni.getStorageSync('equipment_code') || '',
+		openid: uni.getStorageSync('equipment_openid') || '',
+		token: uni.getStorageSync('equipment_token') || '', 
+	}
+	return auth(params).then((res) => {
+		return res.data
+	})
+}
+
+/**
+ * 保存 token 到 localStorage
+ * @param {object} data
+ */
+const saveTokenOpenid = (data) => {
+	uni.setStorageSync('equipment_token', data.token)
+	if (data.openid != '') {
+		uni.setStorageSync('equipment_openid', data.openid)
+	}
+}
+
+/**
+ * 处理 http状态码
+ * @param {object} o
+ * @param {object} o.res 请求返回的数据
+ * @param {object} o.config 本次请求的config数据
+ * @param {string|number} o.statusCode http状态码
+ * @return {object|Promise<reject>}
+ */
+const handleCode = (res, statusCode, config) => {
+	const STATUS = {
+		'200'() {
+			if (res.data.code == 1) {
+				return res.data
+			} else {
+				return Promise.reject(res.data)
+			}
+		},
+		'401'() {
+			// 定义实例发送次数
+			if (!config.hasOwnProperty("count")) {
+				config.count = 0
+			}
+
+			// 只让这个实例发送一次请求,如果code还是401则抛出错误
+			if (config.count === 1) {
+				return Promise.reject(res)
+			}
+
+			config.count++; // count字段自增,可以用来判断请求次数,避免多次发送重复的请求
+
+			return getApiToken()
+				.then(saveTokenOpenid)
+				.then(() => http.request(config))
+		}
+	}
+
+	return STATUS[statusCode] ? STATUS[statusCode]() : Promise.reject(res) // 有状态码但不在这个封装的配置里,就直接进入 `fail`
+}
+
+export {
+	http
+}

+ 69 - 0
components/luch-request/helpers/buildURL.js

@@ -0,0 +1,69 @@
+'use strict'
+
+import * as utils from './../utils'
+
+function encode(val) {
+  return encodeURIComponent(val).
+    replace(/%40/gi, '@').
+    replace(/%3A/gi, ':').
+    replace(/%24/g, '$').
+    replace(/%2C/gi, ',').
+    replace(/%20/g, '+').
+    replace(/%5B/gi, '[').
+    replace(/%5D/gi, ']')
+}
+
+/**
+ * Build a URL by appending params to the end
+ *
+ * @param {string} url The base of the url (e.g., http://www.google.com)
+ * @param {object} [params] The params to be appended
+ * @returns {string} The formatted url
+ */
+export default function buildURL(url, params) {
+  /*eslint no-param-reassign:0*/
+  if (!params) {
+    return url
+  }
+
+  var serializedParams
+  if (utils.isURLSearchParams(params)) {
+    serializedParams = params.toString()
+  } else {
+    var parts = []
+
+    utils.forEach(params, function serialize(val, key) {
+      if (val === null || typeof val === 'undefined') {
+        return
+      }
+
+      if (utils.isArray(val)) {
+        key = key + '[]'
+      } else {
+        val = [val]
+      }
+
+      utils.forEach(val, function parseValue(v) {
+        if (utils.isDate(v)) {
+          v = v.toISOString()
+        } else if (utils.isObject(v)) {
+          v = JSON.stringify(v)
+        }
+        parts.push(encode(key) + '=' + encode(v))
+      })
+    })
+
+    serializedParams = parts.join('&')
+  }
+
+  if (serializedParams) {
+    var hashmarkIndex = url.indexOf('#')
+    if (hashmarkIndex !== -1) {
+      url = url.slice(0, hashmarkIndex)
+    }
+
+    url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
+  }
+
+  return url
+}

+ 14 - 0
components/luch-request/helpers/combineURLs.js

@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Creates a new URL by combining the specified URLs
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} relativeURL The relative URL
+ * @returns {string} The combined URL
+ */
+export default function combineURLs(baseURL, relativeURL) {
+  return relativeURL
+    ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
+    : baseURL
+}

+ 14 - 0
components/luch-request/helpers/isAbsoluteURL.js

@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Determines whether the specified URL is absolute
+ *
+ * @param {string} url The URL to test
+ * @returns {boolean} True if the specified URL is absolute, otherwise false
+ */
+export default function isAbsoluteURL(url) {
+  // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
+  // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
+  // by any combination of letters, digits, plus, period, or hyphen.
+  return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
+}

+ 116 - 0
components/luch-request/index.d.ts

@@ -0,0 +1,116 @@
+type AnyObject = Record<string | number | symbol, any>
+type HttpPromise<T> = Promise<HttpResponse<T>>;
+type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask
+export interface RequestTask {
+  abort: () => void;
+  offHeadersReceived: () => void;
+  onHeadersReceived: () => void;
+}
+export interface HttpRequestConfig<T = Tasks> {
+  /** 请求基地址 */
+  baseURL?: string;
+  /** 请求服务器接口地址 */
+  url?: string;
+
+  /** 请求查询参数,自动拼接为查询字符串 */
+  params?: AnyObject;
+  /** 请求体参数 */
+  data?: AnyObject;
+
+  /** 文件对应的 key */
+  name?: string;
+  /** HTTP 请求中其他额外的 form data */
+  formData?: AnyObject;
+  /** 要上传文件资源的路径。 */
+  filePath?: string;
+  /** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5( 2.6.15+) */
+  files?: Array<{
+    name?: string;
+    file?: File;
+    uri: string;
+  }>;
+  /** 要上传的文件对象,仅H5(2.6.15+)支持 */
+  file?: File;
+
+  /** 请求头信息 */
+  header?: AnyObject;
+  /** 请求方式 */
+  method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD";
+  /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
+  dataType?: string;
+  /** 设置响应的数据类型,支付宝小程序不支持 */
+  responseType?: "text" | "arraybuffer";
+  /** 自定义参数 */
+  custom?: AnyObject;
+  /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
+  timeout?: number;
+  /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
+  firstIpv4?: boolean;
+  /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */
+  sslVerify?: boolean;
+  /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */
+  withCredentials?: boolean;
+
+  /** 返回当前请求的task, options。请勿在此处修改options。 */
+  getTask?: (task: T, options: HttpRequestConfig<T>) => void;
+  /**  全局自定义验证器 */
+  validateStatus?: (statusCode: number) => boolean | void;
+}
+export interface HttpResponse<T = any> {
+  config: HttpRequestConfig;
+  statusCode: number;
+  cookies: Array<string>;
+  data: T;
+  errMsg: string;
+  header: AnyObject;
+}
+export interface HttpUploadResponse<T = any> {
+  config: HttpRequestConfig;
+  statusCode: number;
+  data: T;
+  errMsg: string;
+}
+export interface HttpDownloadResponse extends HttpResponse {
+  tempFilePath: string;
+}
+export interface HttpError {
+  config: HttpRequestConfig;
+  statusCode?: number;
+  cookies?: Array<string>;
+  data?: any;
+  errMsg: string;
+  header?: AnyObject;
+}
+export interface HttpInterceptorManager<V, E = V> {
+  use(
+    onFulfilled?: (config: V) => Promise<V> | V,
+    onRejected?: (config: E) => Promise<E> | E
+  ): void;
+  eject(id: number): void;
+}
+export abstract class HttpRequestAbstract {
+  constructor(config?: HttpRequestConfig);
+  config: HttpRequestConfig;
+  interceptors: {
+    request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>;
+    response: HttpInterceptorManager<HttpResponse, HttpError>;
+  }
+  middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>;
+  request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>;
+  delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+
+  download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>;
+
+  setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;
+}
+
+declare class HttpRequest extends HttpRequestAbstract { }
+export default HttpRequest;

+ 2 - 0
components/luch-request/index.js

@@ -0,0 +1,2 @@
+import Request from './core/Request'
+export default Request

+ 135 - 0
components/luch-request/utils.js

@@ -0,0 +1,135 @@
+'use strict'
+
+// utils is a library of generic helper functions non-specific to axios
+
+var toString = Object.prototype.toString
+
+/**
+ * Determine if a value is an Array
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Array, otherwise false
+ */
+export function isArray (val) {
+  return toString.call(val) === '[object Array]'
+}
+
+
+/**
+ * Determine if a value is an Object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Object, otherwise false
+ */
+export function isObject (val) {
+  return val !== null && typeof val === 'object'
+}
+
+/**
+ * Determine if a value is a Date
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a Date, otherwise false
+ */
+export function isDate (val) {
+  return toString.call(val) === '[object Date]'
+}
+
+/**
+ * Determine if a value is a URLSearchParams object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a URLSearchParams object, otherwise false
+ */
+export function isURLSearchParams (val) {
+  return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
+}
+
+
+/**
+ * Iterate over an Array or an Object invoking a function for each item.
+ *
+ * If `obj` is an Array callback will be called passing
+ * the value, index, and complete array for each item.
+ *
+ * If 'obj' is an Object callback will be called passing
+ * the value, key, and complete object for each property.
+ *
+ * @param {Object|Array} obj The object to iterate
+ * @param {Function} fn The callback to invoke for each item
+ */
+export function forEach (obj, fn) {
+  // Don't bother if no value provided
+  if (obj === null || typeof obj === 'undefined') {
+    return
+  }
+
+  // Force an array if not already something iterable
+  if (typeof obj !== 'object') {
+    /*eslint no-param-reassign:0*/
+    obj = [obj]
+  }
+
+  if (isArray(obj)) {
+    // Iterate over array values
+    for (var i = 0, l = obj.length; i < l; i++) {
+      fn.call(null, obj[i], i, obj)
+    }
+  } else {
+    // Iterate over object keys
+    for (var key in obj) {
+      if (Object.prototype.hasOwnProperty.call(obj, key)) {
+        fn.call(null, obj[key], key, obj)
+      }
+    }
+  }
+}
+
+/**
+ * 是否为boolean 值
+ * @param val
+ * @returns {boolean}
+ */
+export function isBoolean(val) {
+  return typeof val === 'boolean'
+}
+
+/**
+ * 是否为真正的对象{} new Object
+ * @param {any} obj - 检测的对象
+ * @returns {boolean}
+ */
+export function isPlainObject(obj) {
+  return Object.prototype.toString.call(obj) === '[object Object]'
+}
+
+
+
+/**
+ * Function equal to merge with the difference being that no reference
+ * to original objects is kept.
+ *
+ * @see merge
+ * @param {Object} obj1 Object to merge
+ * @returns {Object} Result of all merge properties
+ */
+export function deepMerge(/* obj1, obj2, obj3, ... */) {
+  let result = {}
+  function assignValue(val, key) {
+    if (typeof result[key] === 'object' && typeof val === 'object') {
+      result[key] = deepMerge(result[key], val)
+    } else if (typeof val === 'object') {
+      result[key] = deepMerge({}, val)
+    } else {
+      result[key] = val
+    }
+  }
+  for (let i = 0, l = arguments.length; i < l; i++) {
+    forEach(arguments[i], assignValue)
+  }
+  return result
+}
+
+export function isUndefined (val) {
+  return typeof val === 'undefined'
+}

+ 264 - 0
components/luch-request/utils/clone.js

@@ -0,0 +1,264 @@
+/* eslint-disable */
+var clone = (function() {
+  'use strict';
+
+  function _instanceof(obj, type) {
+    return type != null && obj instanceof type;
+  }
+
+  var nativeMap;
+  try {
+    nativeMap = Map;
+  } catch(_) {
+    // maybe a reference error because no `Map`. Give it a dummy value that no
+    // value will ever be an instanceof.
+    nativeMap = function() {};
+  }
+
+  var nativeSet;
+  try {
+    nativeSet = Set;
+  } catch(_) {
+    nativeSet = function() {};
+  }
+
+  var nativePromise;
+  try {
+    nativePromise = Promise;
+  } catch(_) {
+    nativePromise = function() {};
+  }
+
+  /**
+   * Clones (copies) an Object using deep copying.
+   *
+   * This function supports circular references by default, but if you are certain
+   * there are no circular references in your object, you can save some CPU time
+   * by calling clone(obj, false).
+   *
+   * Caution: if `circular` is false and `parent` contains circular references,
+   * your program may enter an infinite loop and crash.
+   *
+   * @param `parent` - the object to be cloned
+   * @param `circular` - set to true if the object to be cloned may contain
+   *    circular references. (optional - true by default)
+   * @param `depth` - set to a number if the object is only to be cloned to
+   *    a particular depth. (optional - defaults to Infinity)
+   * @param `prototype` - sets the prototype to be used when cloning an object.
+   *    (optional - defaults to parent prototype).
+   * @param `includeNonEnumerable` - set to true if the non-enumerable properties
+   *    should be cloned as well. Non-enumerable properties on the prototype
+   *    chain will be ignored. (optional - false by default)
+   */
+  function clone(parent, circular, depth, prototype, includeNonEnumerable) {
+    if (typeof circular === 'object') {
+      depth = circular.depth;
+      prototype = circular.prototype;
+      includeNonEnumerable = circular.includeNonEnumerable;
+      circular = circular.circular;
+    }
+    // maintain two arrays for circular references, where corresponding parents
+    // and children have the same index
+    var allParents = [];
+    var allChildren = [];
+
+    var useBuffer = typeof Buffer != 'undefined';
+
+    if (typeof circular == 'undefined')
+      circular = true;
+
+    if (typeof depth == 'undefined')
+      depth = Infinity;
+
+    // recurse this function so we don't reset allParents and allChildren
+    function _clone(parent, depth) {
+      // cloning null always returns null
+      if (parent === null)
+        return null;
+
+      if (depth === 0)
+        return parent;
+
+      var child;
+      var proto;
+      if (typeof parent != 'object') {
+        return parent;
+      }
+
+      if (_instanceof(parent, nativeMap)) {
+        child = new nativeMap();
+      } else if (_instanceof(parent, nativeSet)) {
+        child = new nativeSet();
+      } else if (_instanceof(parent, nativePromise)) {
+        child = new nativePromise(function (resolve, reject) {
+          parent.then(function(value) {
+            resolve(_clone(value, depth - 1));
+          }, function(err) {
+            reject(_clone(err, depth - 1));
+          });
+        });
+      } else if (clone.__isArray(parent)) {
+        child = [];
+      } else if (clone.__isRegExp(parent)) {
+        child = new RegExp(parent.source, __getRegExpFlags(parent));
+        if (parent.lastIndex) child.lastIndex = parent.lastIndex;
+      } else if (clone.__isDate(parent)) {
+        child = new Date(parent.getTime());
+      } else if (useBuffer && Buffer.isBuffer(parent)) {
+        if (Buffer.from) {
+          // Node.js >= 5.10.0
+          child = Buffer.from(parent);
+        } else {
+          // Older Node.js versions
+          child = new Buffer(parent.length);
+          parent.copy(child);
+        }
+        return child;
+      } else if (_instanceof(parent, Error)) {
+        child = Object.create(parent);
+      } else {
+        if (typeof prototype == 'undefined') {
+          proto = Object.getPrototypeOf(parent);
+          child = Object.create(proto);
+        }
+        else {
+          child = Object.create(prototype);
+          proto = prototype;
+        }
+      }
+
+      if (circular) {
+        var index = allParents.indexOf(parent);
+
+        if (index != -1) {
+          return allChildren[index];
+        }
+        allParents.push(parent);
+        allChildren.push(child);
+      }
+
+      if (_instanceof(parent, nativeMap)) {
+        parent.forEach(function(value, key) {
+          var keyChild = _clone(key, depth - 1);
+          var valueChild = _clone(value, depth - 1);
+          child.set(keyChild, valueChild);
+        });
+      }
+      if (_instanceof(parent, nativeSet)) {
+        parent.forEach(function(value) {
+          var entryChild = _clone(value, depth - 1);
+          child.add(entryChild);
+        });
+      }
+
+      for (var i in parent) {
+        var attrs = Object.getOwnPropertyDescriptor(parent, i);
+        if (attrs) {
+          child[i] = _clone(parent[i], depth - 1);
+        }
+
+        try {
+          var objProperty = Object.getOwnPropertyDescriptor(parent, i);
+          if (objProperty.set === 'undefined') {
+            // no setter defined. Skip cloning this property
+            continue;
+          }
+          child[i] = _clone(parent[i], depth - 1);
+        } catch(e){
+          if (e instanceof TypeError) {
+            // when in strict mode, TypeError will be thrown if child[i] property only has a getter
+            // we can't do anything about this, other than inform the user that this property cannot be set.
+            continue
+          } else if (e instanceof ReferenceError) {
+            //this may happen in non strict mode
+            continue
+          }
+        }
+
+      }
+
+      if (Object.getOwnPropertySymbols) {
+        var symbols = Object.getOwnPropertySymbols(parent);
+        for (var i = 0; i < symbols.length; i++) {
+          // Don't need to worry about cloning a symbol because it is a primitive,
+          // like a number or string.
+          var symbol = symbols[i];
+          var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
+          if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
+            continue;
+          }
+          child[symbol] = _clone(parent[symbol], depth - 1);
+          Object.defineProperty(child, symbol, descriptor);
+        }
+      }
+
+      if (includeNonEnumerable) {
+        var allPropertyNames = Object.getOwnPropertyNames(parent);
+        for (var i = 0; i < allPropertyNames.length; i++) {
+          var propertyName = allPropertyNames[i];
+          var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
+          if (descriptor && descriptor.enumerable) {
+            continue;
+          }
+          child[propertyName] = _clone(parent[propertyName], depth - 1);
+          Object.defineProperty(child, propertyName, descriptor);
+        }
+      }
+
+      return child;
+    }
+
+    return _clone(parent, depth);
+  }
+
+  /**
+   * Simple flat clone using prototype, accepts only objects, usefull for property
+   * override on FLAT configuration object (no nested props).
+   *
+   * USE WITH CAUTION! This may not behave as you wish if you do not know how this
+   * works.
+   */
+  clone.clonePrototype = function clonePrototype(parent) {
+    if (parent === null)
+      return null;
+
+    var c = function () {};
+    c.prototype = parent;
+    return new c();
+  };
+
+// private utility functions
+
+  function __objToStr(o) {
+    return Object.prototype.toString.call(o);
+  }
+  clone.__objToStr = __objToStr;
+
+  function __isDate(o) {
+    return typeof o === 'object' && __objToStr(o) === '[object Date]';
+  }
+  clone.__isDate = __isDate;
+
+  function __isArray(o) {
+    return typeof o === 'object' && __objToStr(o) === '[object Array]';
+  }
+  clone.__isArray = __isArray;
+
+  function __isRegExp(o) {
+    return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
+  }
+  clone.__isRegExp = __isRegExp;
+
+  function __getRegExpFlags(re) {
+    var flags = '';
+    if (re.global) flags += 'g';
+    if (re.ignoreCase) flags += 'i';
+    if (re.multiline) flags += 'm';
+    return flags;
+  }
+  clone.__getRegExpFlags = __getRegExpFlags;
+
+  return clone;
+})();
+
+export default clone

File diff suppressed because it is too large
+ 36 - 0
components/watch-login/css/icon.css


+ 164 - 0
components/watch-login/watch-button.vue

@@ -0,0 +1,164 @@
+<template>
+	<view>
+		<!-- 按钮 -->
+		<button :class="['buttonBorder',!_rotate?'dlbutton':'dlbutton_loading']"
+			:style="{'background':bgColor, 'color': fontColor}" @click="$emit('click', $event)"
+			@contact="$emit('contact', $event)" @error="$emit('error', $event)"
+			@getphonenumber="$emit('getphonenumber', $event)" @getuserinfo="$emit('getuserinfo', $event)"
+			@launchapp="$emit('launchapp', $event)" @longtap="$emit('longtap', $event)"
+			@opensetting="$emit('opensetting', $event)" @touchcancel="$emit('touchcancel', $event)"
+			@touchend="$emit('touchend', $event)" @touchmove="$emit('touchmove', $event)"
+			@touchstart="$emit('touchstart', $event)">
+			<view :class="_rotate?'rotate_loop':''">
+				<text v-if="_rotate" class="cuIcon cuIcon-loading1 "></text>
+				<view v-if="!_rotate">
+					<slot name="text">{{ text }}</slot>
+				</view>
+			</view>
+		</button>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			text: String, //显示文本
+			rotate: {
+				//是否启动加载
+				type: [Boolean, String],
+				default: false,
+			},
+			bgColor: {
+				//按钮背景颜色
+				type: String,
+				default: "#333333",
+			},
+			fontColor: {
+				//按钮字体颜色
+				type: String,
+				default: "#FFFFFF",
+			},
+		},
+		computed: {
+			_rotate() {
+				//处理值
+				return String(this.rotate) !== 'false'
+			},
+		}
+	}
+</script>
+
+<style>
+	@import url("./css/icon.css");
+
+	button {
+		outline: none;
+		/* 或者 outline: 0 */
+	}
+
+	button:after {
+		border: none;
+	}
+
+	button:focus {
+		outline: none;
+		/* 或者 outline: 0 */
+	}
+
+	.dlbutton {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: #FFFFFF;
+		font-size: 30rpx;
+		white-space: nowrap;
+		overflow: hidden;
+		/* width: 601rpx; */
+		height: 100rpx;
+		background: #333333;
+		box-shadow:0rpx 0rpx 13rpx 0rpx rgba(164,217,228,0.4);
+		border-radius: 2.5rem;
+		margin: 0 70rpx;
+	}
+
+	.dlbutton_loading {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: #FFFFFF;
+		font-size: 30rpx;
+		width: 100rpx;
+		height: 100rpx;
+		background: #333333;
+		box-shadow: 0rpx 0rpx 13rpx 0rpx rgba(164,217,228,0.4);
+		border-radius: 2.5rem;
+		margin-top: 0rpx;
+	}
+
+	.buttonBorder {
+		padding: 0 40rpx;
+		font-size: 32rpx;
+		border-radius: 10rpx;
+		border: none;
+		border-radius: 2.5rem;
+		-webkit-box-shadow: 0 0 60rpx 0 rgba(0, 0, 0, .2);
+		box-shadow: 0 0 60rpx 0 rgba(0,0,0,.2) ;
+		-webkit-transition: all 0.4s cubic-bezier(.57, .19, .51, .95);
+		-moz-transition: all 0.4s cubic-bezier(.57, .19, .51, .95);
+		-ms-transition: all 0.4s cubic-bezier(.57, .19, .51, .95);
+		-o-transition: all 0.4s cubic-bezier(.57, .19, .51, .95);
+		transition: all 0.4s cubic-bezier(.57, .19, .51, .95);
+	}
+
+	/* 旋转动画 */
+	.rotate_loop {
+		-webkit-transition-property: -webkit-transform;
+		-webkit-transition-duration: 1s;
+		-moz-transition-property: -moz-transform;
+		-moz-transition-duration: 1s;
+		-webkit-animation: rotate 1s linear infinite;
+		-moz-animation: rotate 1s linear infinite;
+		-o-animation: rotate 1s linear infinite;
+		animation: rotate 1s linear infinite;
+	}
+
+	@-webkit-keyframes rotate {
+		from {
+			-webkit-transform: rotate(0deg)
+		}
+
+		to {
+			-webkit-transform: rotate(360deg)
+		}
+	}
+
+	@-moz-keyframes rotate {
+		from {
+			-moz-transform: rotate(0deg)
+		}
+
+		to {
+			-moz-transform: rotate(359deg)
+		}
+	}
+
+	@-o-keyframes rotate {
+		from {
+			-o-transform: rotate(0deg)
+		}
+
+		to {
+			-o-transform: rotate(359deg)
+		}
+	}
+
+	@keyframes rotate {
+		from {
+			transform: rotate(0deg)
+		}
+
+		to {
+			transform: rotate(359deg)
+		}
+	}
+</style>

+ 203 - 0
components/watch-login/watch-input.vue

@@ -0,0 +1,203 @@
+<template>
+	<view class="main-list oBorder">
+		<!-- 文本框 -->
+		<input class="main-input" :value="value" :type="_type" :focus="_focus" :maxlength="maxlength"
+			:placeholder="placeholder" :password="type==='password'&&!showPassword"
+			@input="$emit('input', $event.detail.value)" @blur="$emit('blur', $event)" @focus="$emit('focus', $event)"
+			@longpress="$emit('longpress', $event)" @confirm="$emit('confirm', $event)" @click="$emit('click', $event)"
+			@longtap="$emit('longtap', $event)" @touchcancel="$emit('touchcancel', $event)"
+			@touchend="$emit('touchend', $event)" @touchmove="$emit('touchmove', $event)"
+			@touchstart="$emit('touchstart', $event)" />
+		<!-- 是否可见密码 -->
+		<image v-if="_isShowPass&&type==='password'&&!_isShowCode" class="img cuIcon"
+			:class="showPassword?'cuIcon-attention':'cuIcon-attentionforbid'" @tap="showPass"></image>
+		<!-- 倒计时 -->
+		<view v-if="_isShowCode&&!_isShowPass" :class="['vercode',{'vercode-run': second>0}]" @click="setCode">
+			{{ getVerCodeSecond }}</view>
+
+	</view>
+</template>
+
+<script>
+	let _this, countDown;
+	export default {
+		data() {
+			return {
+				showPassword: false, //是否显示明文
+				second: 0, //倒计时
+				isRunCode: false, //是否开始倒计时
+			}
+		},
+		props: {
+			type: String, //类型
+			value: String, //值
+			placeholder: String, //框内提示
+			maxlength: {
+				//最大长度
+				type: [Number, String],
+				default: 20,
+			},
+			isShowPass: {
+				//是否显示密码图标(二选一)
+				type: [Boolean, String],
+				default: false,
+			},
+			isShowCode: {
+				//是否显示获取验证码(二选一)
+				type: [Boolean, String],
+				default: false,
+			},
+			codeText: {
+				type: String,
+				default: "获取验证码",
+			},
+			setTime: {
+				//倒计时时间设置
+				type: [Number, String],
+				default: 60,
+			},
+			focus: {
+				//是否聚焦  
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		model: {
+			prop: 'value',
+			event: 'input'
+		},
+		mounted() {
+			_this = this
+			//准备触发
+			this.$on('runCode', (val) => {
+				this.runCode(val);
+			});
+			clearInterval(countDown); //先清理一次循环,避免缓存
+		},
+		methods: {
+			showPass() {
+				//是否显示密码
+				this.showPassword = !this.showPassword
+			},
+			setCode() {
+				//设置获取验证码的事件
+				if (this.isRunCode) {
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.$emit('setCode')
+			},
+			runCode(val) {
+				//开始倒计时
+				if (String(val) == "0") {
+
+					//判断是否需要终止循环
+					this.second = 0; //初始倒计时
+					clearInterval(countDown); //清理循环
+					this.isRunCode = false; //关闭循环状态
+					return false;
+				}
+				if (this.isRunCode) {
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.isRunCode = true
+				this.second = this._setTime //倒数秒数
+
+				let _this = this;
+				countDown = setInterval(function() {
+					_this.second--
+					if (_this.second == 0) {
+						_this.isRunCode = false
+						clearInterval(countDown)
+					}
+				}, 1000)
+			}
+		},
+		computed: {
+			_type() {
+				//处理值
+				const type = this.type
+				return type == 'password' ? 'text' : type
+			},
+			_isShowPass() {
+				//处理值
+				return String(this.isShowPass) !== 'false'
+			},
+			_isShowCode() {
+				//处理值
+				return String(this.isShowCode) !== 'false'
+			},
+			_setTime() {
+				//处理值
+				const setTime = Number(this.setTime)
+				return setTime > 0 ? setTime : 60
+			},
+			_focus() {
+				//处理值  
+				return String(this.focus) !== 'false'
+			},
+			getVerCodeSecond() {
+				//验证码倒计时计算
+				if (this.second <= 0) {
+					return this.codeText;
+				} else {
+					if (this.second < 10) {
+						return '0' + this.second;
+					} else {
+						return this.second;
+					}
+				}
+
+			}
+		}
+	}
+</script>
+
+<style>
+	@import url("./css/icon.css");
+
+	.main-list {
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		/* height: 36rpx; */
+		/* Input 高度 */
+		color: #333333;
+		padding: 30rpx 32rpx;
+		margin: 32rpx 0;
+	}
+
+	.img {
+		width: 32rpx;
+		height: 32rpx;
+		font-size: 32rpx;
+	}
+
+	.main-input {
+		flex: 1;
+		text-align: left;
+		font-size: 28rpx;
+		/* line-height: 100rpx; */
+		padding-right: 10rpx;
+		margin-left: 20rpx;
+	}
+
+	.vercode {
+		color: rgba(0, 0, 0, 0.7);
+		font-size: 24rpx;
+		/* line-height: 100rpx; */
+	}
+
+	.vercode-run {
+		color: rgba(0, 0, 0, 0.4) !important;
+	}
+
+	.oBorder {
+		border: none;
+		border-radius: 2.5rem;
+		-webkit-box-shadow: 0 0 60rpx 0 rgba(43, 86, 112, .1);
+		box-shadow: 0 0 60rpx 0 rgba(43, 86, 112, .1);
+	}
+</style>

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 21 - 0
main.js

@@ -0,0 +1,21 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+    ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif

+ 82 - 0
manifest.json

@@ -0,0 +1,82 @@
+{
+    "name" : "UhfChengBangZi",
+    "appid" : "__UNI__9F8B139",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "modules" : {},
+        "distribute" : {
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a" ]
+            },
+            "ios" : {
+                "dSYMs" : false
+            },
+            "sdkConfigs" : {
+                "ad" : {}
+            }
+        },
+        "nativePlugins" : {
+            "Alvin-CBZUhfModule" : {
+                "__plugin_info__" : {
+                    "name" : "Alvin-CBZUhfModule",
+                    "description" : "Alvin-CBZUhfModule",
+                    "platforms" : "Android",
+                    "isCloud" : false,
+                    "bought" : -1,
+                    "pid" : "Alvin-CBZUhfModule",
+                    "parameters" : {}
+                }
+            }
+        }
+    },
+    "quickapp" : {},
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2"
+}

BIN
nativeplugins/Alvin-CBZUhfModule/android/UhfChengBangZi-release.aar


BIN
nativeplugins/Alvin-CBZUhfModule/android/libs/armeabi-v7a/libserial_port.so


+ 25 - 0
nativeplugins/Alvin-CBZUhfModule/package.json

@@ -0,0 +1,25 @@
+{
+	"name": "Alvin-CBZUhfModule",
+	"id": "Alvin-CBZUhfModule",
+	"version": "1.0.0",
+	"description": "Alvin-CBZUhfModule",
+	"_dp_type": "nativeplugin",
+	"_dp_nativeplugin": {
+		"android": {
+			"plugins": [{
+				"type": "module",
+				"name": "Alvin-CBZUhfModule",
+				"class": "cn.wm.chengbangzi.UhfModule"
+			}],
+			"compileOptions": {
+				"sourceCompatibility": "1.8",
+				"targetCompatibility": "1.8"
+			},
+			"abis": [
+				"armeabi-v7a"
+			],
+			"integrateType": "aar",
+			"minSdkVersion": 21
+		}
+	}
+}

+ 25 - 0
nativeplugins/Alvin-CBZUhfModule/使用说明.md

@@ -0,0 +1,25 @@
+**使用方法**
+
+1. 引用原生插件:
+	const uhfModule = uni.requireNativePlugin("Alvin-CBZUhfModule");
+
+2. 具体使用api:
+	/**
+	 * 初始化设备;
+	 * result:返回值,初始化成功或失败
+	 */
+	(1).init:uhfModule.doInitDevice(result => {})
+	
+	/**
+	 * 开始扫描
+	 * result:扫描到的结果
+	 */
+	(2).扫描:uhfModule.doStartScan(result => {});
+	
+	/**
+	 * 释放
+	 */
+	(3).扫描:uhfModule.doReleaseDevice();
+	
+
+	

+ 49 - 0
pages.json

@@ -0,0 +1,49 @@
+{
+	"pages": [ 
+		{
+			"path": "pages/login/login",
+			"style": {
+				"navigationBarTitleText": "登录",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "养猪场数据采集系统"
+			}
+		},
+		{
+			"path": "pages/mine/manage",
+			"style": {
+				"navigationBarTitleText": "个人中心"
+			}
+		}
+	],
+
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"tabBar": {
+		"color": "#999999",
+		"selectedColor": "#ff0000",
+		"backgroundColor": "#ffffff",
+		"list": [
+			{
+				"pagePath": "pages/index/index",
+				"text": "首页",
+				"iconPath": "static/menu_search.png",
+				"selectedIconPath": "static/menu_search_selected.png"
+			},
+			{
+				"pagePath": "pages/mine/manage",
+				"text": "我的",
+				"iconPath": "static/menu_mine.png",
+				"selectedIconPath": "static/menu_mine_selected.png"
+			}
+		]
+	}
+}

+ 1168 - 0
pages/index/index.vue

@@ -0,0 +1,1168 @@
+<template>
+  <view class="page">
+    <!-- 导航按钮 -->
+    <button @click="logout" class="nav-btn">
+      <text class="icon fa fa-sign-out"></text>
+    </button>
+    <button @click="toggleServerConfig" class="nav-btn">
+      <text class="icon fa fa-cog"></text>
+    </button>
+
+    <scroll-view scroll-y class="scroll-area">
+      <view v-if="!showServerConfig" class="form-section">
+        <view class="section-title">
+          <text class="title"></text>
+        </view>
+
+        <!-- RFID扫描区域 -->
+        <view class="rfid-card">
+          <text class="icon fa fa-qrcode scan-icon"></text>
+          <view class="btn-group">
+            <button class="manual-btn" @click="init" :disabled="isDisable">
+              <text class="fa fa-power-off"></text> {{ isDeviceReady ? '已开启' : '开启设备' }}
+            </button>
+			<!-- <button class="btn" type="primary" @click="release" :disabled="isReDisabled">release</button> -->
+            <button class="scan-btn" @click="scan" :disabled="!isDisable">
+              <text class="fa fa-camera"></text> 扫描耳标签
+            </button>
+          </view>
+          <input class="input-box" v-model="form.earId" disabled placeholder="当前FID标签号码将显示在这里" style="height: 30px;width: 95%;" />
+          <!-- 存储的FID标签数据 -->
+          <scroll-view scroll-y class="sv">
+            <view v-for="(item, index) in dataList" :key="index" class="data-item">
+              <p style="margin-left: 10px;">{{ item.id }}</p>
+              <picker @change="onTypeChange($event, index)" :value="item.typeIndex" :range="types">
+                <view class="picker">
+                  {{ types[item.typeIndex] }}
+                </view>
+              </picker>
+              <button class="delete-btn" @click="deleteItem(index)">删除</button>
+            </view>
+          </scroll-view>
+        </view>
+
+        <!-- 操作按钮组 -->
+        <view class="btn-group">
+          <button class="manual-btn" @click="resetForm">重置</button>
+          <button class="scan-btn" @click="submitForm">提交数据</button>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+// 导入API配置
+import API from '@/api/index.js'
+
+export default {
+  data() {
+      return {
+        dataList: [],           // 存储扫描的耳标数据
+        types: ['正常', '淘汰', '死亡'],  // 耳标类型选项
+        scanProgress: 0,     // 当前扫描进度
+        scanTotalAttempts: 0, // 总扫描尝试次数
+        isDisable: false,       // 设备禁用状态
+        isDeviceReady: false,   // 设备就绪状态
+        isInitializing: false,  // 设备初始化中状态
+        currentDate: '',        // 当前日期
+        showServerConfig: false,// 是否显示服务器配置
+        uhfSFHelper: null,      // UHF插件实例
+        scanTimeout: null,      // 扫描超时计时器
+        retryTimeout: null,     // 重试计时器
+        maxScanTimer: null,     // 最大扫描时间计时器
+        settingChangeListener: null, // 设置变化监听器
+        
+        // 列表数据
+        buildingList: [],       // 栋舍列表
+        roomList: [],           // 房间列表
+        Fieldnumber: [],        // 栏位列表
+        
+        // 表单数据
+        form: {
+          earId: '',            // 耳标ID
+          buildingName: '',     // 栋舍名称
+          roomName: '',         // 房间名称
+          penNo: '',            // 栏位编号
+          status: 'healthy',    // 状态
+          note: ''              // 备注
+        },
+        
+        // 提交状态
+        isSubmitting: false,
+      
+    }
+  },
+  
+  mounted() {
+    // 标记组件已挂载
+    this._isMounted = true;
+    
+    // 初始化日期
+    const now = new Date()
+    this.currentDate = now.toISOString().split('T')[0]
+    
+    // 加载已保存的设置
+    this.loadSavedSettings();
+    
+    // 获取列表数据(即使插件未初始化也可以加载)
+    this.fetchBuildingList();
+    
+    // 监听登录成功事件,重新加载用户设置并清空数据
+    this.reloadUserSettingsListener = uni.$on('reloadUserSettings', () => {
+      // 清空数据
+      this.resetForm();
+      
+      this.loadSavedSettings();
+      // 重新加载栋舍列表,确保使用最新的用户信息
+      this.fetchBuildingList();
+    });
+    
+    // 初始化插件实例
+    this.initializePluginWithRetry(0);
+    
+    // 监听全局设置变化
+    this.settingChangeListener = uni.$on('settingsUpdated', (settings) => {
+      this.loadSavedSettings(settings);
+    });
+  },
+  
+  /**
+   * 带重试的插件初始化
+   * @param {number} retryCount - 当前重试次数
+   */
+  initializePluginWithRetry(retryCount = 0) {
+	
+	console.log('设备初始化成功');
+      this.isDisable = false;
+      uni.showToast({
+        title: '设备初始化成功',
+        icon: 'success',
+        duration: 2000
+      });
+
+	//如果开启设备 初始化失败,重试,请打开下面代码,上方代码可以删除掉
+    const MAX_RETRIES = 1;
+    const RETRY_DELAY = 2000; // 2秒
+    const isPluginInitialized = this.initPluginInstance();
+    // if (!isPluginInitialized) {
+    //   console.warn(`Plugin initialization failed (attempt ${retryCount + 1}/${MAX_RETRIES})`);
+    //   this.isDisable = true; // 禁用依赖插件的按钮
+      
+    //   // 如果未达到最大重试次数,继续重试
+    //   if (retryCount < MAX_RETRIES) {
+    //     console.log(`Retrying plugin initialization after ${RETRY_DELAY}ms`);
+    //     setTimeout(() => {
+    //       if (this._isMounted) {
+    //         this.initializePluginWithRetry(retryCount + 1);
+    //       }
+    //     }, RETRY_DELAY);
+    //   } else {
+    //     console.error('Max retries reached for plugin initialization');
+    //     uni.showToast({
+    //       title: '设备初始化失败,请重启应用',
+    //       icon: 'none',
+    //       duration: 3000
+    //     });
+    //   }
+    // } else {
+    //   console.log('Plugin initialized successfully');
+    //   this.isDisable = false;
+    //   uni.showToast({
+    //     title: '设备初始化成功',
+    //     icon: 'success',
+    //     duration: 2000
+    //   });
+    // }
+  },
+
+  beforeUnmount() {
+    // 标记组件已卸载
+    this._isMounted = false;
+    
+    // 清除所有计时器
+    this.cancelScan();
+    
+    // 释放设备
+    this.releaseDevice();
+    
+    // 清理插件实例
+    this.uhfSFHelper = null;
+    
+    // 移除事件监听
+    if (this.settingChangeListener) {
+      uni.$off('settingsUpdated', this.settingChangeListener);
+    }
+    
+    // 移除登录成功事件监听
+    if (this.reloadUserSettingsListener) {
+      uni.$off('reloadUserSettings', this.reloadUserSettingsListener);
+    }
+  },
+  
+  /**
+   * 页面显示时触发,确保获取最新的编号信息
+   */
+  onShow() {
+    this.loadSavedSettings();
+  },
+  
+  methods: {
+    /**
+   * 带重试的插件初始化
+   * @param {number} retryCount - 当前重试次数
+   */
+  initializePluginWithRetry(retryCount = 0) {
+    const MAX_RETRIES = 1;
+    const RETRY_DELAY = 2000; // 2秒
+    const isPluginInitialized = this.initPluginInstance();
+    if (!isPluginInitialized) {
+      console.warn(`Plugin initialization failed (attempt ${retryCount + 1}/${MAX_RETRIES})`);
+      this.isDisable = true; // 禁用依赖插件的按钮
+      
+      // 如果未达到最大重试次数,继续重试
+      if (retryCount < MAX_RETRIES) {
+        console.log(`Retrying plugin initialization after ${RETRY_DELAY}ms`);
+        setTimeout(() => {
+          if (this._isMounted) {
+            this.initializePluginWithRetry(retryCount + 1);
+          }
+        }, RETRY_DELAY);
+      } else {
+        console.error('Max retries reached for plugin initialization');
+        uni.showToast({
+          title: '设备初始化失败,请重启应用',
+          icon: 'none',
+          duration: 3000
+        });
+      }
+    } else {
+      console.log('插件初始化成功');
+      this.isDisable = false;
+      uni.showToast({
+        title: '设备初始化成功',
+        icon: 'success',
+        duration: 2000
+      });
+    }
+  },
+    /**
+     * 获取栋舍列表(带重试机制)
+     * @param {number} retryCount - 当前重试次数(默认0)
+     */
+    fetchBuildingList(retryCount = 0) {
+      // 显示加载提示
+      if (retryCount === 0) {
+        uni.showLoading({ title: '加载栋舍列表...', mask: true });
+      }
+      
+      // 发送请求到API获取栋舍列表
+      uni.request({
+        url: API.getBuilding,
+        method: 'GET',
+        timeout: 10000, // 增加超时时间到10秒
+        success: (res) => {
+          // 隐藏加载提示
+          if (retryCount === 0) {
+            uni.hideLoading();
+          }
+		// 更新栋舍列表
+		this.buildingList = res.data.data || [];
+        }
+      });
+    },
+
+    /**
+     * 加载已保存的设置
+     * @param {object} settings - 可选的设置参数
+     */
+    loadSavedSettings(settings = null) {
+      const app = getApp();
+      
+      let buildingName, roomName, penNo;
+      
+      // 如果提供了设置参数,优先使用参数值
+      if (settings && typeof settings === 'object') {
+        buildingName = settings.building || '';
+        roomName = settings.room || '';
+        penNo = settings.pen || '';
+        
+        // 更新全局数据
+        app.globalData.buildingName = buildingName;
+        app.globalData.roomName = roomName;
+        app.globalData.penNo = penNo;
+        
+        // 更新本地存储
+        uni.setStorageSync('buildingName', buildingName);
+        uni.setStorageSync('roomName', roomName);
+        uni.setStorageSync('penNo', penNo);
+      } else {
+        // 从全局数据或本地存储获取编号信息
+        buildingName = app.globalData.buildingName || uni.getStorageSync('buildingName') || '';
+        roomName = app.globalData.roomName || uni.getStorageSync('roomName') || '';
+        penNo = app.globalData.penNo || uni.getStorageSync('penNo') || '';
+      }
+      
+      // 更新表单数据
+      this.form.buildingName = buildingName;
+      this.form.roomName = roomName;
+      this.form.penNo = penNo;
+      
+      // 如果栋舍不为空且房间列表为空,加载房间列表
+      if (buildingName && this.roomList.length === 0) {
+        this.fetchRoomList(buildingName);
+      } else if (buildingName && this.roomList.length > 0 && roomName) {
+        // 如果房间已变更,更新房间列表
+        this.fetchRoomList(buildingName);
+      }
+      
+      // 如果房间不为空且栏位列表为空,加载栏位列表
+      if (roomName && this.Fieldnumber.length === 0) {
+        this.fetchFieldList(roomName);
+      } else if (roomName && this.Fieldnumber.length > 0 && penNo) {
+        // 如果栏位已变更,更新栏位列表
+        this.fetchFieldList(roomName);
+      }
+    },
+    
+    /**
+     * 根据栋舍获取房间列表(带重试机制)
+     * @param {string} buildingName - 栋舍名称
+     * @param {number} retryCount - 当前重试次数
+     */
+    fetchRoomList(buildingName, retryCount = 0) {
+      // 显示加载提示
+      if (retryCount === 0) {
+        uni.showLoading({ title: '加载房间列表...', mask: true });
+      }
+      
+      // 发送请求到API获取房间列表
+      uni.request({
+        url: API.getRoom,
+        method: 'GET',
+        data: { building: buildingName },
+        timeout: 10000, // 增加超时时间到10秒
+        success: (res) => {
+          // 隐藏加载提示
+          if (retryCount === 0) {
+            uni.hideLoading();
+          }
+			// 更新房间列表
+			this.roomList = res.data.data || [];
+			// 重置房间和栏位选择
+			this.form.roomName = '';
+			this.Fieldnumber = [];
+			this.form.penNo = '';
+        }
+      });
+    },
+    
+    /**
+     * 根据房间名称获取栏位列表(带重试机制)
+     * @param {string} roomName - 房间名称
+     * @param {number} retryCount - 当前重试次数(默认0)
+     */
+    fetchFieldList(roomName, retryCount = 0) {
+      // 显示加载提示
+      if (retryCount === 0) {
+        uni.showLoading({ title: '加载栏位列表...', mask: true });
+      }
+
+      const MAX_RETRIES = 1;
+      const RETRY_DELAY = 2000; // 2秒
+
+      // 发送请求到API获取栏位列表
+      uni.request({
+        url: API.getField,
+        method: 'GET',
+        data: { room: roomName },
+        timeout: 10000, // 增加超时时间到10秒
+        success: (res) => {
+          // 隐藏加载提示
+          if (retryCount === 0) {
+            uni.hideLoading();
+          }
+		// 更新栏位列表
+		this.Fieldnumber = res.data.data || [];
+		// 重置栏位选择
+		this.form.penNo = '';
+        },
+        fail: (err) => {
+          // 隐藏加载提示
+          if (retryCount === 0) {
+            uni.hideLoading();
+          }
+          // 如果未达到最大重试次数,尝试重试
+          if (retryCount < MAX_RETRIES) {
+            console.log(`Retrying request (${retryCount + 1}/${MAX_RETRIES})`);
+            setTimeout(() => {
+              this.fetchFieldList(roomName, retryCount + 1);
+            }, RETRY_DELAY);
+          } else {
+            
+          }
+        }
+      });
+    },
+
+    /**
+     * 初始化UHF插件实例
+     * @returns {boolean} 初始化是否成功
+     */
+    initPluginInstance() {
+      if (this.uhfSFHelper) {
+        console.log('插件实例已存在');
+        return true;
+      }
+      
+      try {
+        console.log('初始化UHF插件实例');
+        
+        // 检查运行环境是否支持原生插件
+        if (typeof uni.requireNativePlugin !== 'function') {
+          console.error('当前环境不支持uni.requireNativePlugin');
+          return false;
+        }
+        
+        // 尝试加载插件
+        this.uhfSFHelper = uni.requireNativePlugin('Alvin-CBZUhfModule');
+        
+        // 验证插件实例是否有效
+        if (!this.uhfSFHelper) {
+          console.error('插件实例为空或未定义');
+          // 检查插件是否存在于项目中
+          console.log('检查插件是否存在于项目中...');
+          // 提示可能的解决方案
+          console.warn('可能的解决方案:');
+          console.warn('1. 验证插件名称是否正确:Alvin-CBZUhfModule');
+          console.warn('2. 检查插件是否正确安装在nativeplugins目录中');
+          console.warn('3. 确保您的开发环境支持原生插件');
+          return false;
+        }
+        
+        if (typeof this.uhfSFHelper.doInitDevice !== 'function') {
+          console.error('插件实例缺少必要方法: doInitDevice');
+          // 输出插件实例的所有方法,帮助调试
+          console.log('插件实例可用方法:', Object.keys(this.uhfSFHelper));
+          this.uhfSFHelper = null;
+          return false;
+        }
+        
+        console.log('UHF插件实例创建成功');
+        return true;
+      } catch (e) {
+        console.error('加载UHF插件失败:', e.message);
+          console.error('错误栈:', e.stack);
+        this.uhfSFHelper = null;
+        uni.showToast({
+          title: '设备功能不可用: ' + e.message,
+          icon: 'none',
+          duration: 3000
+        });
+        return false;
+      }
+    },
+    
+    /**
+     * 检查并恢复插件实例
+     * @param {number} retryCount - 当前重试次数(默认0)
+     * @returns {boolean} 插件实例是否有效
+     */
+    checkAndRestorePluginInstance(retryCount = 0) {
+      // 设置最大重试次数和超时时间
+      const MAX_RETRIES = 1;
+      const RETRY_DELAY = 1000; // 1秒
+      
+      // 如果实例不存在,尝试初始化
+      if (!this.uhfSFHelper) {
+        console.log(`没有插件实例,尝试初始化 (重试: ${retryCount})`);
+        const result = this.initPluginInstance();
+        
+        // 如果初始化失败且未达到最大重试次数,递归重试
+        if (!result && retryCount < MAX_RETRIES) {
+          console.log(`初始化失败,重试 (${retryCount + 1}/${MAX_RETRIES}) 后 ${RETRY_DELAY}ms`);
+          // 延迟后重试
+          setTimeout(() => {
+            this.checkAndRestorePluginInstance(retryCount + 1);
+          }, RETRY_DELAY);
+          return false;
+        }
+        
+        return result;
+      }
+      
+      // 检查实例方法是否存在且有效
+      const requiredMethods = ['doInitDevice', 'doStartScan', 'doReleaseDevice'];
+      const missingMethods = requiredMethods.filter(method => typeof this.uhfSFHelper[method] !== 'function');
+      
+      if (missingMethods.length > 0) {
+        console.error(`插件实例缺少必要方法: ${missingMethods.join(', ')}`);
+        console.log('插件实例可用方法:', Object.keys(this.uhfSFHelper));
+        this.uhfSFHelper = null;
+        
+        // 如果未达到最大重试次数,尝试重新初始化
+        if (retryCount < MAX_RETRIES) {
+          console.log(`Attempting to reinitialize plugin (${retryCount + 1}/${MAX_RETRIES}) after ${RETRY_DELAY}ms`);
+          setTimeout(() => {
+            this.checkAndRestorePluginInstance(retryCount + 1);
+          }, RETRY_DELAY);
+          return false;
+        }
+        
+        return false;
+      }
+      
+      console.log('插件实例有效且准备使用');
+      return true;
+    },
+    
+    /**
+     * 取消当前扫描操作
+     */
+    cancelScan() {
+      // 清除所有相关计时器
+      if (this.scanTimeout) {
+        clearTimeout(this.scanTimeout);
+        this.scanTimeout = null;
+      }
+      
+      if (this.retryTimeout) {
+        clearTimeout(this.retryTimeout);
+        this.retryTimeout = null;
+      }
+      
+      if (this.maxScanTimer) {
+        clearTimeout(this.maxScanTimer);
+        this.maxScanTimer = null;
+      }
+      
+      // 隐藏加载提示
+      uni.hideLoading();
+      
+      console.log('扫描已取消');
+      uni.showToast({ title: '扫描已取消', icon: 'none' });
+    },
+    
+    /**
+     * 释放设备资源
+     */
+    releaseDevice() {
+      if (this.isDeviceReady && this.uhfSFHelper) {
+        try {
+          this.uhfSFHelper.doReleaseDevice()
+        } catch (e) {
+          console.error('释放设备失败', e)
+        }
+        this.isDeviceReady = false
+        this.isDisable = false
+      }
+    },
+    
+    /**
+     * 初始化设备
+     */
+    init() {
+      // 确保插件实例已初始化且有效
+      if (!this.checkAndRestorePluginInstance()) {
+        return;
+      }
+      
+      if (this.isInitializing) {
+        console.log('设备初始化已在进行中');
+        return;
+      }
+      
+      if (this.isDeviceReady) {
+        console.log('设备已初始化');
+        return uni.showToast({ title: '设备已开启', icon: 'none' });
+      }
+      
+      this.isDisable = false;
+      this.isInitializing = true;
+      
+      try {
+        console.log('开始初始化设备');
+        // 再次检查插件实例是否有效
+        if (!this.checkAndRestorePluginInstance()) {
+          this.isInitializing = false;
+          return;
+        }
+        
+        this.uhfSFHelper.doInitDevice(res => {
+          this.isInitializing = false;
+          
+          if (res === true) {
+            this.isDeviceReady = true;
+            this.isDisable = true;
+            console.log('Device initialized successfully');
+            uni.showToast({ title: '设备已开启', icon: 'success' });
+          } else {
+            this.isDisable = false;
+            console.error('设备初始化失败');
+            uni.showToast({ title: '初始化失败', icon: 'none' });
+          }
+        });
+      } catch (e) {
+        this.isInitializing = false;
+        console.error('Error during device initialization:', e);
+        this.isDisable = false;
+        // 清除插件实例,以便下次初始化尝试
+        this.uhfSFHelper = null;
+        uni.showToast({ title: '初始化异常', icon: 'none' });
+      }
+    },
+    
+    /**
+     * 开始扫描耳标
+     */
+    scan() {
+      // 确保插件实例已初始化且有效
+      if (!this.checkAndRestorePluginInstance()) {
+        return;
+      }
+      
+      if (!this.isDeviceReady) {
+        return uni.showToast({ title: '请先开启设备', icon: 'none' })
+      }
+      
+      // 设置扫描参数 - 优化参数以提高成功率
+      const scanConfig = {
+        retryCount: 3,  // 增加重试次数
+        currentRetry: 0,
+        timeout: 2000,  // 增加超时时间
+        interval: 400,  // 增加间隔,给设备恢复时间
+        signalThreshold: 0.5  // 降低信号阈值,接受更多信号
+      };
+      
+      // 初始化扫描进度变量
+      this.scanProgress = 0;
+      this.scanTotalAttempts = scanConfig.retryCount;
+      
+      // 显示扫描中提示,添加mask以禁止背景操作
+      uni.showLoading({
+        title: '正在扫描耳标...',
+        mask: true
+      });
+      
+      // 执行扫描函数
+      this.performScan(scanConfig);
+    },
+	// release() {
+	// 	this.isDeviceReady = false;
+	// 	this.isDisable = false;
+	// },
+    
+    /**
+     * 执行扫描(带重试机制)
+     * @param {object} config - 扫描配置参数
+     * @param {number} config.retryCount - 最大重试次数
+     * @param {number} config.currentRetry - 当前重试次数
+     * @param {number} config.timeout - 单次扫描超时时间(毫秒)
+     * @param {number} config.interval - 重试间隔(毫秒)
+     */
+    /**
+     * 处理类型选择变化
+     */
+    onTypeChange(e, index) {
+      if (index >= 0 && index < this.dataList.length) {
+        this.dataList[index].typeIndex = e.detail.value;
+        console.log(`耳标 ${this.dataList[index].id} 类型变更为: ${this.types[e.detail.value]}`);
+      }
+    },
+
+    /**
+     * 删除耳标条目
+     */
+    deleteItem(index) {
+      if (index >= 0 && index < this.dataList.length) {
+        const deletedItem = this.dataList.splice(index, 1);
+        console.log(`删除耳标: ${deletedItem[0].id}`);
+        uni.showToast({ title: '删除成功', icon: 'success' });
+      }
+    },
+
+    performScan(config) {
+      // 检查组件是否已卸载或设备是否已准备好
+      if (!this._isMounted || !this.isDeviceReady) {
+        uni.hideLoading();
+        console.log('Scan aborted: component not mounted or device not ready');
+        return;
+      }
+      
+      if (config.currentRetry >= config.retryCount) {
+        uni.hideLoading();
+        console.log('Scan failed after maximum retries');
+        return uni.showToast({ title: '扫描失败,请调整位置重试', icon: 'none' });
+      }
+      
+      // 添加最大扫描时间限制 (总时间 = 超时时间 * 重试次数)
+      const maxScanTime = config.timeout * config.retryCount;
+      if (!this.maxScanTimer) {
+        this.maxScanTimer = setTimeout(() => {
+          console.log('Maximum scan time exceeded');
+          this.cancelScan();
+        }, maxScanTime);
+      }
+      
+      // 增加重试计数
+      config.currentRetry++;
+      this.scanProgress = config.currentRetry;
+      
+      // 更新加载提示(简化提示,避免频繁更新)
+      if (config.currentRetry % 2 === 0 || config.currentRetry === config.retryCount) {
+        uni.hideLoading();
+        uni.showLoading({
+          title: `扫描中 (${Math.round(config.currentRetry/config.retryCount*100)}%)...`,
+          mask: true
+        });
+      }
+      
+      console.log(`开始扫描尝试 ${config.currentRetry}/${config.retryCount},超时时间: ${config.timeout}ms`);
+      
+      // 清除之前的超时
+      if (this.scanTimeout) {
+        clearTimeout(this.scanTimeout);
+        this.scanTimeout = null;
+      }
+      
+      // 设置超时机制
+      this.scanTimeout = setTimeout(() => {
+        console.log(`Scan timeout, retrying (${config.currentRetry}/${config.retryCount})`);
+        // 重试扫描前先检查实例
+        if (this._isMounted) {
+          // 检查并恢复插件实例
+          if (this.checkAndRestorePluginInstance()) {
+            this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
+          } else {
+            console.error('Failed to restore plugin instance before retry');
+            uni.hideLoading();
+            return uni.showToast({ title: '设备功能异常,无法重试', icon: 'none' });
+          }
+        }
+      }, config.timeout);
+      
+      // 执行扫描
+      try {
+        // 清除之前可能存在的重试计时器
+        if (this.retryTimeout) {
+          clearTimeout(this.retryTimeout);
+          this.retryTimeout = null;
+        }
+
+        // 检查插件实例是否有效
+        if (!this.checkAndRestorePluginInstance()) {
+          // 清除超时计时器
+          if (this.scanTimeout) {
+            clearTimeout(this.scanTimeout);
+            this.scanTimeout = null;
+          }
+          
+          console.error('Invalid plugin instance for scanning after restore attempt');
+          // 尝试重新初始化设备
+          this.initDevice();
+          uni.hideLoading();
+          return uni.showToast({ title: '设备功能异常,正在重新初始化', icon: 'none' });
+        }
+        
+        // 保存当前实例引用,防止闭包中实例变化
+        const currentPluginInstance = this.uhfSFHelper;
+        
+        // 执行扫描命令
+        try {
+          currentPluginInstance.doStartScan(result => {
+            // 再次检查插件实例是否与执行扫描时相同
+            if (this.uhfSFHelper !== currentPluginInstance) {
+              console.warn('Plugin instance changed during scan, ignoring result');
+              // 尝试重新扫描
+              if (this._isMounted) {
+                this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
+              }
+              return;
+            }
+            
+            // 检查组件是否已卸载
+            if (!this._isMounted) return;
+            
+            // 清除超时计时器
+            if (this.scanTimeout) {
+              clearTimeout(this.scanTimeout);
+              this.scanTimeout = null;
+            }
+            
+            if (result) {
+              // 假设result是一个包含id和signalStrength的对象
+              // 如果result只是ID字符串,我们将其包装成对象
+              const scanResult = typeof result === 'string' ? { id: result, signalStrength: 1 } : result;
+              
+              // 检查信号强度是否足够
+              if (scanResult.signalStrength >= config.signalThreshold) {
+                // 清除所有计时器
+                if (this.maxScanTimer) {
+                  clearTimeout(this.maxScanTimer);
+                  this.maxScanTimer = null;
+                }
+                if (this.retryTimeout) {
+                  clearTimeout(this.retryTimeout);
+                  this.retryTimeout = null;
+                }
+                
+                uni.hideLoading();
+                console.log('扫描成功:', scanResult);
+                this.form.earId = scanResult.id;
+                
+                // 检查是否重复扫描
+                const isDuplicate = this.dataList.some(item => item.id === scanResult.id);
+                
+                if (isDuplicate) {
+                  console.log('耳标已存在:', scanResult.id);
+                  uni.showToast({ title: '该耳标已扫描过', icon: 'none' });
+                } else {
+                  // 默认添加为'正常'类型
+                  this.dataList.push({ id: scanResult.id, typeIndex: 0 });
+                  uni.showToast({ title: '扫描成功', icon: 'success' });
+                }
+              } else {
+                console.log(`Scan result with weak signal (${scanResult.signalStrength}), retrying`);
+                // 信号太弱,继续重试前检查实例
+                if (this._isMounted) {
+                  if (this.checkAndRestorePluginInstance()) {
+                    this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
+                  } else {
+                    console.error('Failed to restore plugin instance for retry');
+                    uni.hideLoading();
+                    return uni.showToast({ title: '设备功能异常', icon: 'none' });
+                  }
+                }
+              }
+            } else {
+              console.log(`Scan failed, retrying (${config.currentRetry}/${config.retryCount})`);
+              // 重试扫描前检查实例
+              if (this._isMounted) {
+                if (this.checkAndRestorePluginInstance()) {
+                  this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
+                } else {
+                  console.error('Failed to restore plugin instance for retry');
+                  uni.hideLoading();
+                  return uni.showToast({ title: '设备功能异常', icon: 'none' });
+                }
+              }
+            }
+          });
+        } catch (e) {
+          console.error('Exception during scan execution:', e);
+          // 清除超时计时器
+          if (this.scanTimeout) {
+            clearTimeout(this.scanTimeout);
+            this.scanTimeout = null;
+          }
+          
+          // 检查是否是实例不可用错误
+          if (e.message && e.message.includes('instance is not available')) {
+            console.error('Plugin instance not available during scan');
+            // 尝试重新初始化
+            this.uhfSFHelper = null;
+            if (this.checkAndRestorePluginInstance()) {
+              // 重新执行扫描
+              if (this._isMounted) {
+                this.retryTimeout = setTimeout(() => this.performScan(config), config.interval * 2);
+              }
+            } else {
+              uni.hideLoading();
+              return uni.showToast({ title: '设备功能异常,无法扫描', icon: 'none' });
+            }
+          } else {
+            // 其他错误
+            if (this._isMounted) {
+              this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
+            }
+          }
+        }
+      } catch (e) {
+        // 清除超时计时器
+        if (this.scanTimeout) {
+          clearTimeout(this.scanTimeout);
+          this.scanTimeout = null;
+        }
+        
+        console.error('Error during scan setup:', e);
+        // 检查错误是否与实例不可用相关
+        if (e.message && e.message.includes('instance is not available')) {
+          console.error('Plugin instance not available during scan setup');
+          // 清除当前实例
+          this.uhfSFHelper = null;
+          // 尝试重新初始化
+          if (this.checkAndRestorePluginInstance()) {
+            // 重新执行扫描
+            if (this._isMounted) {
+              this.retryTimeout = setTimeout(() => this.performScan(config), config.interval * 2); // 增加间隔
+            }
+          } else {
+            uni.hideLoading();
+            return uni.showToast({ title: '设备功能异常', icon: 'none' });
+          }
+        } else {
+          // 其他错误,继续重试
+          if (this._isMounted) {
+            this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
+          }
+        }
+      }
+    },
+    
+    /**
+     * 提交表单数据
+     */
+    submitForm() {
+      // 确保加载最新的设置
+      this.loadSavedSettings();
+
+      // 验证编号是否已选择
+      let missingField = '';
+      if (!this.form.buildingName) missingField = '栋舍';
+      else if (!this.form.roomName) missingField = '房间';
+      else if (!this.form.penNo) missingField = '栏位';
+
+      if (missingField) {
+        return uni.showToast({ title: `未选择${missingField}编号`, icon: 'none', duration: 3000 })
+      }
+
+      // 验证耳标是否已扫描
+      if (this.dataList.length === 0) {
+        return uni.showToast({ title: '请先扫描耳标', icon: 'none', duration: 3000 })
+      }
+
+      // 验证用户ID是否存在
+      const app = getApp();
+      const userInfo = app.globalData.userInfo || uni.getStorageSync('user_info') || {};
+      const userId = userInfo.id || '';
+      if (!userId) {
+        return uni.showToast({ title: '用户未登录', icon: 'none', duration: 3000 })
+      }
+
+      // 显示加载提示
+      uni.showLoading({ title: '提交中...', mask: true });
+
+      // 将dataList数组转换为包含类型的逗号分隔字符串
+      // 格式: id:type,id:type,...
+      const rfidString = this.dataList.map(item => {
+        return `${item.id}:${this.types[item.typeIndex]}`;
+      }).join(',');
+
+      // 准备提交数据
+      const submitData = {
+        rfid: rfidString,
+        buildingName: this.form.buildingName,
+        roomName: this.form.roomName,
+        penNo: this.form.penNo,
+        userId: userId,
+        username: userInfo.username || '',
+        time: new Date().toISOString()
+      };
+
+      console.log('准备提交的数据:', submitData);
+
+      // 发送请求到API
+      this.submitData(submitData);
+    },
+    
+    /**
+     * 提交数据
+     * @param {object} data - 要提交的数据
+     */
+    submitData(data) {
+      // 设置独立的提交加载状态标志
+      this.isSubmitting = true;
+      
+      uni.request({
+        url: API.postListAdd,
+        method: 'POST',
+        data: data,
+        timeout: 10000, // 设置10秒超时
+        method: 'POST',
+        data: data,
+        timeout: 10000, // 设置10秒超时
+        success: (res) => {
+          console.log('API响应:', res);
+          
+          // 先隐藏加载提示
+          if (this.isSubmitting) {
+            uni.hideLoading();
+            this.isSubmitting = false;
+          }
+          
+          if (res.data) {
+            console.log('服务器返回数据:', res.data);
+            // 显示结果提示
+            const message = res.data.msg || '提交成功';
+            console.log('显示提示:', message);
+            
+            uni.showToast({
+              title: message,
+              icon: res.data.code === 0 ? 'success' : 'none',
+              duration: 3000
+            });
+            
+            if (res.data.code === 0) {
+              // 添加本地记录
+              this.records.unshift({
+                rfid: data.rfid,
+                building: data.buildingName,
+                roomName: data.roomName,
+                pen: data.penNo,
+                userId: data.userId,
+                time: new Date().toLocaleTimeString()
+              });
+              this.resetForm();
+            }
+          } else {
+            console.error('提交失败: 响应数据格式不正确', res);
+            // 显示错误提示
+            uni.showToast({
+              title: '提交失败,请重试',
+              icon: 'none',
+              duration: 3000
+            });
+          }
+        },
+        fail: (err) => {
+          console.error('网络请求失败:', err);
+          // 隐藏加载提示并显示错误提示
+          if (this.isSubmitting) {
+            uni.hideLoading();
+            this.isSubmitting = false;
+          }
+          uni.showToast({
+            title: '网络请求失败,请重试',
+            icon: 'none',
+            duration: 3000
+          });
+          // 接口失败不影响耳标扫描,无需额外处理
+        },
+        complete: () => {
+          // 确保加载提示被隐藏
+          setTimeout(() => {
+            if (this.isSubmitting) {
+              uni.hideLoading();
+              this.isSubmitting = false;
+            }
+          }, 2000);
+        }
+      });
+    },
+    
+    // 重置
+    resetForm() {
+      this.form.earId = ''
+      this.form.buildingName = ''
+      this.form.roomName = ''
+      this.form.penNo = ''
+      this.form.status = 'healthy'
+      this.form.note = ''
+      // 清除扫描结果列表
+      this.dataList = []
+    },
+    
+
+    // 选择
+    onBuildingChange(e) {
+      const buildingName = this.buildingList[e.detail.value];
+      this.form.buildingName = buildingName;
+      // 根据选择的栋舍加载房间列表
+      if (buildingName) {
+        this.fetchRoomList(buildingName);
+      } else {
+        // 清空房间和栏位列表
+        this.roomList = [];
+        this.Fieldnumber = [];
+        this.form.roomName = '';
+        this.form.penNo = '';
+      }
+    },
+    
+    onRoomChange(e) {
+      const roomName = this.roomList[e.detail.value];
+      this.form.roomName = roomName;
+      // 根据选择的房间加载栏位列表
+      if (roomName) {
+        this.fetchFieldList(roomName);
+      } else {
+        // 清空栏位列表
+        this.Fieldnumber = [];
+        this.form.penNo = '';
+      }
+    },
+    
+    onFieldChange(e) {
+      this.form.penNo = this.Fieldnumber[e.detail.value]
+    },
+    
+    // 其他
+    logout() {
+      // 清空数据
+      this.resetForm();
+      
+      // 释放设备资源
+      this.releaseDevice();
+      
+      // 清除用户信息
+      const app = getApp();
+      app.globalData.userInfo = null;
+      uni.removeStorageSync('user_info');
+      
+      // 跳转到登录页面
+      uni.redirectTo({ url: '/pages/login/login' });
+      
+      uni.showToast({ title: '已退出登录', icon: 'none' })
+    },
+    
+    toggleServerConfig() {
+      this.showServerConfig = !this.showServerConfig
+    }
+  }
+}
+</script>
+
+<style>
+.page { display: flex; flex-direction: column; height: 100vh; width: 100%; box-sizing: border-box; padding: 0 10rpx; }
+
+.data-item { display: flex; justify-content: space-between; align-items: center; padding: 10rpx; margin-bottom: 10rpx; background-color: #f5f5f5; border-radius: 5rpx; }
+
+.picker { padding: 5rpx 10rpx; 
+	margin-left: 20px;
+	background-color: #fff; border: 1px solid #ddd; border-radius: 5rpx; min-width: 120rpx; text-align: center; }
+
+.arrow::after { content: '▾'; font-size: 12rpx; margin-left: 5rpx; }
+
+.delete-btn { background-color: #ff4d4f; color: white; font-size: 24rpx; padding: 5rpx 10rpx; min-width: 80rpx; line-height: normal; }
+.nav-bar {
+  display: flex; justify-content: space-between; align-items: center;
+  background: #007aff; color: white; padding: 15rpx 30rpx;
+}
+.nav-title { font-weight: bold; font-size: 32rpx; }
+.nav-btn { background: transparent; border: none; font-size: 32rpx; }
+.scroll-area { flex: 1;  }
+.section-title { display: flex; justify-content: space-between; margin-bottom: 20rpx; }
+.section-title .title { font-weight: bold; font-size: 30rpx; }
+.section-title .date { font-size: 24rpx; color: #666; }
+.rfid-card {
+  background: #f6f6f6; border: 2rpx dashed #ccc; 
+  border-radius: 20rpx; text-align: center; 
+}
+.scan-icon { font-size: 50rpx; color: #007aff; margin-bottom: 10rpx; display: block; }
+.btn-group { display: flex; gap: 20rpx; margin: 20rpx 0; flex-wrap: wrap; justify-content: center; }
+.scan-btn { background: #007aff; color: white; flex: 1; min-width: 200rpx; padding: 20rpx; border-radius: 12rpx; font-size: 28rpx; }
+.manual-btn { background: #ccc; color: black; flex: 1; min-width: 200rpx; padding: 20rpx; border-radius: 12rpx; font-size: 28rpx; }
+.form-item { margin-bottom: 20rpx; }
+.form-item .label { display: block; font-weight: bold; margin-bottom: 10rpx; }
+.input-box { width: 100%; border: 1rpx solid #ccc; border-radius: 10rpx; background: #fff; }
+.status-options { display: flex; gap: 20rpx; margin-top: 10rpx; }
+.status-option {
+  flex: 1; border: 1rpx solid #ccc; border-radius: 10rpx;
+  padding: 20rpx; text-align: center; color: #666;
+}
+.status-option.active { border-color: #007aff; color: #007aff; }
+.sv {
+  height: 300rpx;
+  margin-top: 20rpx;
+}
+</style>

+ 94 - 0
pages/login/css/main.css

@@ -0,0 +1,94 @@
+.content {
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	/* margin-top: 128rpx; */
+}
+
+/* 头部 logo */
+.header {
+	width: 161rpx;
+	height: 161rpx;
+	box-shadow: 0rpx 0rpx 60rpx 0rpx rgba(0, 0, 0, 0.1);
+	border-radius: 50%;
+	background-color: #000000;
+	margin-top: 72rpx;
+	margin-bottom: 72rpx;
+	margin-left: auto;
+	margin-right: auto;
+}
+
+.header image {
+	width: 161rpx;
+	height: 161rpx;
+	border-radius: 50%;
+}
+
+/* 主体 */
+.main {
+	display: flex;
+	flex-direction: column;
+	padding-left: 70rpx;
+	padding-right: 70rpx;
+}
+
+.tips {
+	color: #999999;
+	font-size: 28rpx;
+	margin-top: 64rpx;
+	margin-left: 48rpx;
+}
+
+/* 登录按钮 */
+.wbutton {
+	margin-top: 96rpx;
+}
+
+/* 其他登录方式 */
+.other_login {
+	display: flex;
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	margin-top: 100rpx;
+	text-align: center;
+}
+
+.login_icon {
+	border: none;
+	font-size: 64rpx;
+	margin: 0 64rpx 0 64rpx;
+	color: rgba(0, 0, 0, 0.7)
+}
+
+.wechat_color {
+	color: #83DC42;
+}
+
+.weibo_color {
+	color: #F9221D;
+}
+
+.github_color {
+	color: #24292E;
+}
+
+/* 底部 */
+.footer {
+	display: flex;
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	font-size: 28rpx;
+	margin-top: 64rpx;
+	color: rgba(0, 0, 0, 0.7);
+	text-align: center;
+	height: 40rpx;
+	line-height: 40rpx;
+}
+
+.footer text {
+	font-size: 24rpx;
+	margin-left: 15rpx;
+	margin-right: 15rpx;
+}

File diff suppressed because it is too large
+ 29 - 0
pages/login/login.vue


+ 911 - 0
pages/mine/manage.vue

@@ -0,0 +1,911 @@
+<template>
+	<view class="container">
+		<view class="lists" v-if="login">
+			<!-- 用户信息卡片 -->
+			<view class="cu-card case user-card">
+				<view class="cu-item">
+					
+					<view class="content">
+						<view class="username">用户名: {{user.username}}</view>
+						<view class="user-id" v-if="user.id">ID: {{user.id}}</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 功能列表 -->
+			<view class="cu-list menu sm-border card-menu function-list">
+				<view class="form-section">
+					<picker :range="buildingList" @change="onBuildingChange">
+					  <view class="form-item">
+						<text class="label"><text class="fa fa-home"></text> 栋舍编号</text>
+						<text class="value">{{ form.buildingName || '请选择栋舍' }}</text>
+					  </view>
+					</picker>
+
+					<picker :range="roomList" @change="onRoomChange">
+					  <view class="form-item">
+						<text class="label"><text class="fa fa-door-open"></text> 房间编号</text>
+						<text class="value">{{ form.roomName || '请选择房间' }}</text>
+					  </view>
+					</picker>
+
+					<picker :range="Fieldnumber" @change="onFieldChange">
+					  <view class="form-item">
+						<text class="label"><text class="fa fa-th-large"></text> 栏位编号</text>
+						<text class="value">{{ form.penNo || '请选择栏位' }}</text>
+					  </view>
+					</picker>
+				</view>
+
+				<view class="save-btn-container">
+					<button class="cu-btn block save-btn" @click="saveSettings" style="background-color: #07c160;">
+						<text class="fa fa-save margin-right-xs"></text> 保存设置
+					</button>
+					<button class="cu-btn block save-btn" @click="logout()" style="background-color: red;">
+						<text class="fa fa-save margin-right-xs"></text> 退出登录
+					</button>
+				</view>
+			</view>
+
+		</view>
+		<view v-else>
+			<view class="empty nologin-content">
+				<image src="../../static/nologin.png" mode="widthFix"></image>
+				<view class="text-gray">请先登录账号</view>
+				<button class="cu-btn block bg-black lg" @click="goLogin()">
+					<text class="cuIcon-lock margin-right-xs"></text> 登录账号</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// 移除全局变量声明,改为在方法内使用const声明
+	export default {
+		data() {
+			return {
+				login: false,
+				user: {},
+				avatar: '',
+				managePhone: '',
+
+				// 表单数据
+				form: {
+					buildingName: '',
+					roomName: '',
+					penNo: ''
+				},
+
+				// 动态从接口获取数据
+				buildingList: [],
+				roomList: [],
+				Fieldnumber: []
+			}
+		},
+		onShow() {
+			const _self = this;
+			const app = getApp();
+			if (!_self.login) {
+				const isLogin = !!app.globalData.token;
+				if (isLogin) {
+					_self.login = isLogin;
+					// 获取栋舍列表
+					_self.fetchBuildingList();
+					// 获取最新用户信息
+					_self.fetchUserInfo();
+				}
+			} else {
+				// 获取栋舍列表
+				_self.fetchBuildingList();
+				// 确保已登录时始终有最新的用户信息
+				_self.fetchUserInfo();
+			}
+		},
+		onLoad(e) {
+			const _self = this;
+			const app = getApp();
+
+			const isLogin = !!app.globalData.token;
+			_self.login = isLogin;
+			if (!isLogin) {
+				wx.login({
+					success(res) {
+						if (res.code) {
+							_self.goWeLogin(res.code);
+						}
+					}
+				});
+			} else {
+				// 直接从全局状态获取用户信息
+				_self.user = app.globalData.userInfo || {};
+				_self.avatar = _self.user.avatar || '';
+			}
+
+		},
+		methods: {
+			// 获取栋舍列表
+			fetchBuildingList() {
+				const _self = this;
+				const app = getApp();
+
+				// 从服务器获取栋舍列表
+				uni.request({
+					url: 'http://dev-rfid.7in6.com/index.php/api/index/get_building',
+					method: 'GET',
+					header: {
+						'content-type': 'application/x-www-form-urlencoded',
+						token: app.globalData.token || ''
+					},
+					success: (res) => {
+
+						if (res.data && res.data.code === 0 && res.data.data && res.data.data.length > 0) {
+							// 更新栋舍列表
+							_self.buildingList = res.data.data.map(item => item.name || item.building_name);
+							console.log('更新后的栋舍列表:', _self.buildingList);
+
+							// 如果表单中没有选择栋舍,尝试使用全局数据或本地存储
+							if (!_self.form.buildingName) {
+								_self.form.buildingName = app.globalData.building || uni.getStorageSync('building') || '';
+							}
+
+							// 获取对应栋舍的房间列表
+							_self.fetchRoomList();
+						} else {
+							console.error('get_building响应数据无效或为空:', res.data);
+							// 使用默认栋舍列表
+							_self.buildingList = [];
+						}
+					},
+					fail: (err) => {
+						console.error('获取栋舍列表失败:', err);
+					}
+				});
+			},
+
+			// 新增方法:获取最新用户信息
+			fetchUserInfo() {
+				const _self = this;
+				const app = getApp();
+
+				// 检查用户是否已登录且有用户ID
+				const userInfo = app.globalData.userInfo || uni.getStorageSync('user_info') || {};
+				const userId = userInfo.id || '';
+
+				if (!userId) {
+					console.error('用户ID不存在,无法获取用户信息');
+					// 尝试从本地存储加载
+					_self.loadFromStorage();
+					return;
+				}
+
+				// 显示加载提示
+				uni.showLoading({
+					title: '加载中...'
+				});
+
+				// 从服务器获取最新用户信息
+				uni.request({
+					url: 'http://dev-rfid.7in6.com/index.php/api/index/userlist',
+					method: 'GET',
+					data: {
+						userid: userId // 传递用户ID
+					},
+					header: {
+						'content-type': 'application/x-www-form-urlencoded',
+						token: app.globalData.token || ''
+					},
+					success: (res) => {
+						console.log('userlist接口响应:', res);
+								
+						if (res.data && res.data.code === 0) {
+							// 从数据库获取用户信息
+							// 确保数据是对象格式
+							const dbUserInfo = typeof res.data.data === 'object' && res.data.data !== null ? res.data.data : {};
+
+							// 检查是否包含编号信息
+							if (!dbUserInfo.building || !dbUserInfo.room || !dbUserInfo.pen) {
+								console.warn('用户信息中缺少编号数据:', dbUserInfo);
+							}
+								
+							// 合并用户信息
+							const mergedUserInfo = {
+								...userInfo,
+								...dbUserInfo
+							};
+
+							// 更新全局用户信息
+							app.globalData.userInfo = mergedUserInfo;
+							// 更新编号信息
+							app.globalData.building = dbUserInfo.building || userInfo.building || '';
+							app.globalData.room = dbUserInfo.room || userInfo.room || '';
+							app.globalData.pen = dbUserInfo.pen || userInfo.pen || '';
+
+							console.log('更新后的全局building:', app.globalData.building);
+							console.log('更新后的全局room:', app.globalData.room);
+							console.log('更新后的全局pen:', app.globalData.pen);
+
+							// 更新本地存储
+							uni.setStorageSync('user_info', mergedUserInfo);
+							uni.setStorageSync('building', app.globalData.building);
+							uni.setStorageSync('room', app.globalData.room);
+							uni.setStorageSync('pen', app.globalData.pen);
+
+							// 更新页面数据
+							_self.user = mergedUserInfo;
+							_self.avatar = _self.user.avatar || '';
+
+							// 更新表单数据
+							_self.form.buildingName = app.globalData.building || '';
+							_self.form.roomName = app.globalData.room || '';
+							_self.form.penNo = app.globalData.pen || '';
+
+							console.log('更新后的表单buildingName:', _self.form.buildingName);
+							console.log('更新后的表单roomName:', _self.form.roomName);
+							console.log('更新后的表单penNo:', _self.form.penNo);
+
+							// 更新全局数据中的名称字段
+							app.globalData.buildingName = _self.form.buildingName;
+							app.globalData.roomName = _self.form.roomName;
+							app.globalData.penNo = _self.form.penNo;
+
+							// 更新本地存储的名称字段
+							uni.setStorageSync('buildingName', _self.form.buildingName);
+							uni.setStorageSync('roomName', _self.form.roomName);
+							uni.setStorageSync('penNo', _self.form.penNo);
+
+							// 触发设置更新事件,通知其他组件
+							uni.$emit('settingsUpdated', {
+								building: _self.form.buildingName,
+								room: _self.form.roomName,
+								pen: _self.form.penNo
+							});
+						} else {
+							console.error('userlist响应数据无效:', res.data);
+							// 响应无效时,尝试从全局或本地存储加载
+							_self.loadFromStorage();
+						}
+					},
+					fail: (err) => {
+						console.error('获取用户信息失败:', err);
+						// 失败时,从全局或本地存储加载
+						_self.loadFromStorage();
+					},
+					complete: () => {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 获取房间列表
+			fetchRoomList() {
+				const _self = this;
+				const app = getApp();
+
+				console.log('开始获取房间列表...');
+				console.log('当前选择的栋舍:', _self.form.buildingName);
+
+				// 从服务器获取房间列表
+				uni.request({
+					url: 'http://dev-rfid.7in6.com/index.php/api/index/get_room',
+					method: 'GET',
+					data: {
+						building: _self.form.buildingName // 传递当前选择的栋舍
+					},
+					header: {
+						'content-type': 'application/x-www-form-urlencoded',
+						token: app.globalData.token || ''
+					},
+					success: (res) => {
+						console.log('get_room接口响应:', res);
+
+						if (res.data && res.data.code === 0 && res.data.data && res.data.data.length > 0) {
+							// 更新房间列表
+							_self.roomList = res.data.data.map(item => item.name || item.room_name);
+							console.log('更新后的房间列表:', _self.roomList);
+
+							// 如果表单中没有选择房间,尝试使用全局数据或本地存储
+							if (!_self.form.roomName) {
+								_self.form.roomName = app.globalData.room || uni.getStorageSync('room') || '';
+							}
+
+							// 获取对应房间的栏位列表
+							_self.fetchPenList();
+						} else {
+							console.error('get_room响应数据无效或为空:', res.data);
+							// 使用默认房间列表
+							_self.roomList = [];
+						}
+					},
+					fail: (err) => {
+						console.error('获取房间列表失败:', err);
+					}
+				});
+			},
+
+			// 获取栏位列表
+			fetchPenList() {
+				const _self = this;
+				const app = getApp();
+
+				console.log('开始获取栏位列表...');
+				console.log('当前选择的栋舍:', _self.form.buildingName);
+				console.log('当前选择的房间:', _self.form.roomName);
+
+				// 从服务器获取栏位列表
+				uni.request({
+					url: 'http://dev-rfid.7in6.com/index.php/api/index/get_pen',
+					method: 'GET',
+					data: {
+						building: _self.form.buildingName,
+						room: _self.form.roomName // 传递当前选择的房间
+					},
+					header: {
+						'content-type': 'application/x-www-form-urlencoded',
+						token: app.globalData.token || ''
+					},
+					success: (res) => {
+						console.log('get_pen接口响应:', res);
+
+						if (res.data && res.data.code === 0 && res.data.data && res.data.data.length > 0) {
+							// 更新栏位列表
+							_self.Fieldnumber = res.data.data.map(item => item.name || item.pen_name || item.pen_no);
+							console.log('更新后的栏位列表:', _self.Fieldnumber);
+
+							// 如果表单中没有选择栏位,尝试使用全局数据或本地存储
+							if (!_self.form.penNo) {
+								_self.form.penNo = app.globalData.pen || uni.getStorageSync('pen') || '';
+							}
+						} else {
+							console.error('get_pen响应数据无效或为空:', res.data);
+							// 使用默认栏位列表
+						_self.Fieldnumber = [];
+						}
+					},
+					fail: (err) => {
+						console.error('获取栏位列表失败:', err);
+					}
+				});
+			},
+
+			// 从存储加载数据的辅助方法
+			loadFromStorage() {
+				const _self = this;
+				const app = getApp();
+					
+				console.log('从存储加载数据...');
+					
+				// 失败时,仍然使用本地数据
+				_self.user = app.globalData.userInfo || {};
+				_self.avatar = _self.user.avatar || '';
+
+				// 从全局或本地存储加载已保存的设置
+				// 优先使用全局的building/room/pen,然后是本地存储的,最后是名称字段
+				_self.form.buildingName = app.globalData.building || uni.getStorageSync('building') || app.globalData.buildingName || uni.getStorageSync('buildingName') || '';
+				_self.form.roomName = app.globalData.room || uni.getStorageSync('room') || app.globalData.roomName || uni.getStorageSync('roomName') || '';
+				_self.form.penNo = app.globalData.pen || uni.getStorageSync('pen') || app.globalData.penNo || uni.getStorageSync('penNo') || '';
+
+				console.log('从存储加载的表单buildingName:', _self.form.buildingName);
+				console.log('从存储加载的表单roomName:', _self.form.roomName);
+				console.log('从存储加载的表单penNo:', _self.form.penNo);
+
+				// 更新全局数据中的名称字段
+				app.globalData.buildingName = _self.form.buildingName;
+				app.globalData.roomName = _self.form.roomName;
+				app.globalData.penNo = _self.form.penNo;
+			},
+			// 选择器方法
+			onBuildingChange(e) {
+				this.form.buildingName = this.buildingList[e.detail.value];
+				// 保存到全局或本地存储
+				getApp().globalData.buildingName = this.form.buildingName;
+				uni.setStorageSync('buildingName', this.form.buildingName);
+				// 触发全局事件通知设置更新
+				uni.$emit('settingsUpdated');
+				// 获取对应栋舍的房间列表
+				this.fetchRoomList();
+			},
+
+			onRoomChange(e) {
+				this.form.roomName = this.roomList[e.detail.value];
+				// 保存到全局或本地存储
+				getApp().globalData.roomName = this.form.roomName;
+				uni.setStorageSync('roomName', this.form.roomName);
+				// 触发全局事件通知设置更新
+				uni.$emit('settingsUpdated');
+				// 获取对应房间的栏位列表
+				this.fetchPenList();
+			},
+
+			onFieldChange(e) {
+				this.form.penNo = this.Fieldnumber[e.detail.value];
+				// 保存到全局或本地存储
+				getApp().globalData.penNo = this.form.penNo;
+				uni.setStorageSync('penNo', this.form.penNo);
+				// 触发全局事件通知设置更新
+				uni.$emit('settingsUpdated');
+			},
+
+			// 保存设置到API
+			saveSettings() {
+				if (!this.form.buildingName || !this.form.roomName || !this.form.penNo) {
+					return uni.showToast({
+						title: '请选择完整的栋舍、房间和栏位信息',
+						icon: 'none'
+					});
+				}
+
+				const app = getApp();
+				// 获取用户ID(优先从全局数据,其次从本地存储)
+				const userId = app.globalData.userInfo?.id || uni.getStorageSync('user_info')?.id || '';
+				const username = app.globalData.userInfo?.username || uni.getStorageSync('user_info')?.username || '';
+
+				if (!userId) {
+					console.error('用户ID不存在,无法保存设置');
+					return uni.showToast({
+						title: '请先登录',
+						icon: 'none'
+					});
+				}
+
+				// 显示加载提示
+				uni.showLoading({
+					title: '保存中...',
+					mask: true
+				});
+
+				// 准备提交数据
+				const submitData = {
+					username: username,
+					userid: userId,
+					building: this.form.buildingName,
+					room: this.form.roomName,
+					pen: this.form.penNo
+				};
+
+				console.log('提交的设置数据:', submitData);
+
+				// 发送请求到API
+				uni.request({
+					url: 'http://dev-rfid.7in6.com/index.php/api/index/post_usersetup',
+					method: 'POST',
+					data: submitData,
+					header: {
+						'content-type': 'application/x-www-form-urlencoded',
+						token: app.globalData.token || uni.getStorageSync('token') || ''
+					},
+					timeout: 10000, // 设置10秒超时
+					success: (res) => {
+						console.log('保存设置响应:', res);
+						if (res.data && res.data.code === 0) {
+							uni.showToast({
+								title: '保存成功',
+								icon: 'success'
+							});
+
+							// 更新全局数据
+							app.globalData.building = submitData.building;
+							app.globalData.room = submitData.room;
+							app.globalData.pen = submitData.pen;
+							app.globalData.buildingName = submitData.building;
+							app.globalData.roomName = submitData.room;
+							app.globalData.penNo = submitData.pen;
+
+							// 更新本地存储
+							uni.setStorageSync('building', submitData.building);
+							uni.setStorageSync('room', submitData.room);
+							uni.setStorageSync('pen', submitData.pen);
+							uni.setStorageSync('buildingName', submitData.building);
+							uni.setStorageSync('roomName', submitData.room);
+							uni.setStorageSync('penNo', submitData.pen);
+
+							// 触发设置更新事件
+							uni.$emit('settingsUpdated', {
+								building: submitData.building,
+								room: submitData.room,
+								pen: submitData.pen
+							});
+						} else {
+							console.error('保存设置失败:', res.data);
+							uni.showToast({
+								title: res.data?.msg || '保存失败,请稍后重试',
+								icon: 'none'
+							});
+						}
+					},
+					// fail: (err) => {
+					// 	console.error('保存设置网络失败:', err);
+					// 	uni.showToast({
+					// 		title: '网络异常,请检查网络后重试',
+					// 		icon: 'none'
+					// 	});
+					// },
+					complete: () => {
+						uni.hideLoading();
+					}
+				});
+			},
+
+		// getStaffInfo方法已移除,直接从全局状态获取用户信息
+			goWeLogin(code) {
+				const _self = this;
+				_self.$api.welogin({
+					code
+				}).then((res) => {
+					getApp().globalData.token = res.data.token
+					getApp().globalData.equipmentManage = res.data.equipment_manage
+					uni.setStorageSync('equipment_token', res.data.token)
+					uni.setStorageSync('equipment_openid', res.data.openid)
+					uni.setStorageSync('equipment_manage', res.data.equipment_manage)
+
+					_self.login = true
+					// 登录成功后从全局状态获取用户信息
+					_self.user = getApp().globalData.userInfo || {};
+					_self.avatar = _self.user.avatar || '';
+				}).catch((err) => {
+					console.error('微信登录失败:', err);
+					uni.showToast({
+						title: '登录失败,请稍后重试',
+						icon: 'none'
+					});
+				})
+			},
+			goLogin() {
+			const _self = this;
+			uni.navigateTo({
+				url: '/pages/login/login',
+				events: {
+					// 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
+					isLoginFromLogin: function(data) {
+						if (data && data.isLogin) {
+							_self.login = true
+							// 登录成功后从全局状态获取用户信息
+							_self.user = getApp().globalData.userInfo || {};
+							_self.avatar = _self.user.avatar || '';
+						}
+					}
+				}
+			})
+			},
+			logout() {
+				const _self = this;
+				uni.showModal({
+					title: '提示',
+					content: '是否确认要操作退出登录?',
+					success: function(res) {
+						if (res.confirm) {
+							_self.doLogout()
+						}
+					}
+				});
+			},
+			doLogout() {
+			const _self = this;
+			// 显示加载提示
+			uni.showLoading({
+				title: '退出中...'
+			})
+			
+			// 延迟1.5秒后跳转
+			setTimeout(() => {
+				uni.hideLoading()
+				// 清除本地存储
+				_self.clearStorage()
+			}, 1500)
+		},
+			clearStorage() {
+			const _self = this;
+			try {
+				_self.login = false
+				const app = getApp();
+				// 清空全局数据
+				app.globalData.token = '';
+				app.globalData.equipmentManage = 0;
+				app.globalData.userInfo = {};
+				app.globalData.building = '';
+				app.globalData.room = '';
+				app.globalData.pen = '';
+				app.globalData.buildingName = '';
+				app.globalData.roomName = '';
+				app.globalData.penNo = '';
+				app.globalData.isLoggedIn = false;
+
+				// 清空本地存储
+				uni.setStorageSync('equipment_token', '');
+				uni.setStorageSync('equipment_openid', '');
+				uni.setStorageSync('equipment_manage', '');
+				uni.setStorageSync('user_info', {});
+				uni.setStorageSync('building', '');
+				uni.setStorageSync('room', '');
+				uni.setStorageSync('pen', '');
+				uni.setStorageSync('buildingName', '');
+				uni.setStorageSync('roomName', '');
+				uni.setStorageSync('penNo', '');
+
+				uni.$emit('loginStatusEvent', {
+					isLogin: false
+				})
+
+				uni.showToast({
+					title: '退出登录成功',
+					icon: 'success'
+				})
+
+				// 直接跳转到登录页面,避免延迟导致的重复导航
+				setTimeout(function() {
+					uni.redirectTo({
+						url: '/pages/login/login'
+					})
+				}, 500);
+			} catch (e) {
+				console.error('清除存储失败:', e);
+				uni.showToast({
+					title: '退出失败,请稍后再试',
+					icon: 'none'
+				})
+			}
+		},
+			callManage() {
+			const _self = this;
+			let managePhone = _self.managePhone
+			if (managePhone == '') {
+				uni.showToast({
+					title: '暂无管理员联系方式',
+					icon: 'none'
+				})
+				return;
+			}
+
+			uni.makePhoneCall({
+				phoneNumber: managePhone
+			})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.container {
+	min-height: 100vh;
+	background-color: #f5f5f5;
+	// padding-bottom: 120rpx; /* 增加底部内边距确保按钮可见 */
+	box-sizing: border-box;
+	overflow: auto;
+}
+
+/* 用户信息卡片样式 */
+.user-card {
+	margin: 20rpx;
+	border-radius: 20rpx;
+	box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+	transition: all 0.3s ease;
+	overflow: hidden;
+}
+
+.user-card:hover {
+	box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.12);
+	transform: translateY(-5rpx);
+}
+
+.cu-item {
+	display: flex;
+	align-items: center;
+	padding: 30rpx;
+}
+
+.avatar-container {
+	width: 120rpx;
+	height: 100rpx;
+	border-radius: 50%;
+	overflow: hidden;
+	background-color: #f0f0f0;
+	margin-right: 30rpx;
+}
+
+.avatar {
+	width: 100%;
+	height: 100%;
+}
+
+.avatar-placeholder {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	background-color: #f0f0f0;
+	color: #999;
+	font-size: 60rpx;
+}
+
+.content {
+	flex: 1;
+}
+
+.username {
+	font-size: 36rpx;
+	font-weight: bold;
+	color: #333;
+	margin-bottom: 10rpx;
+}
+
+.user-id {
+	font-size: 28rpx;
+	color: #666;
+}
+
+.cu-list.menu-avatar>.cu-item:after {
+	border: 0;
+}
+
+/* 表单区域样式 */
+.form-section {
+	margin: 0px 20rpx;
+	padding: 25rpx;
+	background-color: #ffffff;
+	border-radius: 16rpx;
+	box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.form-item {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 25rpx 0;
+	border-bottom: 1rpx solid #f0f0f0;
+}
+
+.form-item:last-child {
+	border-bottom: none;
+}
+
+.label {
+	font-size: 28rpx;
+	color: #333;
+}
+
+.value {
+	font-size: 28rpx;
+	color: #666;
+}
+
+/* 保存按钮样式 */
+.save-btn-container {
+	padding: 20rpx;
+}
+
+.save-btn {
+	background-color: #07c160;
+	color: white;
+	border-radius: 44rpx;
+	height: 88rpx;
+	line-height: 88rpx;
+	font-size: 32rpx;
+	margin: 30rpx 20rpx;
+	transition: all 0.3s ease;
+	width: calc(100% - 40rpx);
+}
+
+.save-btn:hover {
+	background-color: #06ad56;
+	transform: scale(1.02);
+}
+
+.save-btn:active {
+	transform: scale(0.98);
+}
+
+/* 功能列表样式 */
+.function-list {
+	margin: 0 20rpx 20rpx; /* 减小边距 */
+	border-radius: 16rpx;
+	box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
+}
+
+.cu-list.card-menu .cu-item {
+	padding: 20rpx; /* 减小内边距适应小屏幕 */
+	transition: all 0.2s ease;
+}
+
+.cu-list.card-menu .cu-item:hover {
+	background-color: #f9f9f9;
+}
+
+/* 退出登录按钮样式 */
+.logout-container {
+	position: fixed;
+	bottom: 120rpx; /* 增加底部距离避开导航栏 */
+	left: 0;
+	width: 100%;
+	padding: 0 20rpx;
+	box-sizing: border-box;
+}
+
+.logout-btn {
+	background-color: #e64340;
+	color: white;
+	border-radius: 35rpx;
+	height: 70rpx; /* 减小按钮高度 */
+	line-height: 70rpx;
+	font-size: 26rpx; /* 减小按钮字体 */
+	transition: all 0.3s ease;
+	width: 100%;
+}
+
+.logout-btn:hover {
+	background-color: #d43835;
+	transform: scale(1.02);
+}
+
+.logout-btn:active {
+	transform: scale(0.98);
+}
+
+/* 未登录状态样式 */
+.empty {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	height: 100vh;
+	text-align: center;
+	background-color: #ffffff;
+	overflow: hidden; /* 防止未登录状态下滚动 */
+
+	image {
+		width: 35%;
+		margin-bottom: 30rpx;
+	}
+
+	.text-gray {
+		font-size: 28rpx;
+		color: #999;
+		margin-bottom: 40rpx;
+	}
+
+	.cu-btn {
+		width: 65%;
+		border-radius: 70rpx;
+		height: 80rpx;
+		line-height: 80rpx;
+		font-size: 28rpx;
+		background-color: #000000;
+		color: white;
+		transition: all 0.3s ease;
+	}
+
+	.cu-btn:hover {
+		background-color: #333333;
+		transform: scale(1.03);
+	}
+
+	.cu-btn:active {
+		transform: scale(0.97);
+	}
+}
+
+.nologin-content {
+	text-align: center;
+	padding: 80rpx 0; /* 减小上下内边距 */
+	height: 100vh;
+	margin-top: 0;
+	background-color: #FFFFFF;
+
+	image {
+		width: 240rpx; /* 减小图片尺寸 */
+		height: 240rpx;
+		margin-bottom: 30rpx; /* 减小图片与文本间距 */
+	}
+
+	.text-gray {
+		font-size: 28rpx; /* 减小文本大小 */
+		margin-bottom: 30rpx; /* 减小文本与按钮间距 */
+	}
+
+	button {
+		width: 70%; /* 增加按钮宽度占比 */
+		margin: 0 auto;
+		font-size: 28rpx; /* 减小按钮字体 */
+		height: 70rpx; /* 减小按钮高度 */
+		line-height: 70rpx;
+	}
+}
+</style>

BIN
static/empty.png


BIN
static/logo.png


BIN
static/menu_mine.png


BIN
static/menu_mine_selected.png


BIN
static/menu_search.png


BIN
static/menu_search_selected.png


BIN
static/nologin.png


BIN
static/scan.png


BIN
static/workbench/archive.png


BIN
static/workbench/inspection.png


BIN
static/workbench/maintenance.png


BIN
static/workbench/repair.png


BIN
static/workbench/repair1.png


BIN
static/workbench/repair_pool.png


BIN
static/workbench/scrapped.png


+ 76 - 0
uni.scss

@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

Some files were not shown because too many files changed in this diff