Explorar el Código

修复已知问题以及优化安卓

jiuling hace 2 meses
padre
commit
3759db593a

+ 1 - 0
.env.development

@@ -2,3 +2,4 @@
 # H5 开发环境使用 proxy 代理
 VITE_BASE_URL=/base
 VITE_UPLOAD_URL=http://nxy.gbdfarm.com
+VITE_TMAP_KEY=2N6BZ-VX5LJ-4GCFA-DGPXT-F4ZNF-7CB5D

+ 10 - 0
.env.example

@@ -0,0 +1,10 @@
+# 环境变量配置示例
+# 复制此文件为 .env 并填入实际值
+
+# API 配置
+VITE_BASE_URL=https://nxy.gbdfarm.com:9000/pro-uniapp
+VITE_UPLOAD_URL=https://nxy.gbdfarm.com
+
+# 腾讯地图 API Key
+# 请在腾讯位置服务控制台申请:https://lbs.qq.com/console/mykey.html
+VITE_TMAP_KEY=IY2BZ-6OLKH-TUQDT-WCKMG-W5Z6O-VUBSD

+ 1 - 0
.gitignore

@@ -12,5 +12,6 @@ node_modules/
 .hbuilderx/launch.json
 
 # 环境变量文件(保留模板文件,忽略本地配置)
+.env
 .env.local
 .env.*.local

+ 1 - 1
androidPrivacy.json

@@ -1,3 +1,3 @@
 {
-    "prompt": "template"
+    "prompt" : "template"
 }

+ 48 - 0
api/services/connect.js

@@ -10,6 +10,42 @@ import {
 import storage from "@/utils/storage.js";
 const request = http.request;
 
+
+/**
+ * 发送手机验证码 (必须是绑定注册过的手机)
+ * @returns {Promise} 发送验证码结果
+ */
+export function sendPhoneVcodeNoUnique(params) {
+  return http.request({
+    url: "uniapp/wechat/sendPhoneVcodeNoUnique",
+    method: Method.POST,
+    data:params
+  });
+}
+/**
+ * 发送手机验证码
+ * @returns {Promise} 发送验证码结果
+ */
+export function sendVcode(params) {
+  return http.request({
+    url: "uniapp/wechat/sendPhoneVcodeUnique",
+    method: Method.POST,
+    data:params
+  });
+}
+
+/**
+ * 
+ * @param {*} params 
+ * @returns 
+ */
+export function phoneRegister(params) {
+  return http.request({
+    url: "uniapp/wechat/register",
+    method: Method.POST,
+    data:params
+  });
+}
 /**
  * 登出
  * @returns {Promise} 登出结果
@@ -70,6 +106,18 @@ export function phoneLogin(params) {
   });
 }
 
+/**
+ * H5用户手机号验证码登录
+ * @param params
+ */
+export function phoneCodeLogin(params) {
+  return http.request({
+    url: "uniapp/wechat/phoneLogin",
+    method: Method.POST,
+    data:params,
+  });
+}
+
 /**
  * 上传用户头像和昵称
  * @param params

+ 14 - 1
api/services/knowledge.js

@@ -91,4 +91,17 @@ export function unfavoriteArticle(id) {
     method: Method.DELETE,
     needToken: true,
   });
-}
+}
+
+/**
+ * 获取文章(根据阅读量)
+ * @param limit 分页数量(默认为4)
+ * @param category 分类(非必填)
+ */
+export function getTopArticles() {
+  return http.request({
+    url: `uniapp/knowledge/top-articles`,
+    method: Method.GET
+    // needToken: true,
+  });
+}

+ 1 - 1
api/services/mall.js

@@ -11,7 +11,7 @@ export function getMallList(params) {
     url: '/base/mall/list',
     method: Method.GET,
     params: params,
-	needToken: true
+	// needToken: true
   });
 }
 

+ 103 - 0
api/services/weather.js

@@ -0,0 +1,103 @@
+import {
+  http,
+  Method
+} from '@/utils/request.js';
+import storage from "@/utils/storage.js";
+import api from "@/config/api.js";
+
+/**
+ * 获取当前地址天气(使用腾讯地图获取当前经纬度坐标)
+ * @param {Object} params 查询参数
+ * @param {number} params.latitude 纬度
+ * @param {number} params.longitude 经度
+ * @param {string} params.type 非必填 查询天气类型,取值:
+ *   now[默认] 实时天气预报
+ *   future 未来天气预报(默认获取当天和未来3天的天气信息)
+ *   hours 未来24小时天气预报(起始时间为当前时间的前一个小时)
+ * @param {string} params.get_md 非必填  未来预报天数,仅在type=future时生效,取值:
+ *   0 [默认]当天加未来3天的天气情况
+ *   1 当天加未来6天的天气情况
+ * @param {string} params.added_fields 非必填  附加字段,取值:
+ *   alarm 预警信息,仅在type=now时生效。
+ *   index 生活指数信息,仅在type=future时生效
+ *   air 空气质量信息
+ * @returns {Promise} 返回天气查询结果
+ *   status: number - 状态码,0为正常,其它为异常
+ *   message: string - 对status的描述
+ *   result: object - 天气查询结果
+ *     realtime: object - 实时天气信息,type=now时返回
+ *     forecast: array - 预报天气信息,type=future时返回
+ */
+export function getWeatherInfo(params = {}) {
+  const { latitude, longitude, type = 'now', added_fields = '' } = params;
+  
+  // 腾讯地图天气API的key
+  const TENCENT_MAP_KEY = '2N6BZ-VX5LJ-4GCFA-DGPXT-F4ZNF-7CB5D';
+  
+  // 构建location参数:纬度,经度
+  const location = `${latitude},${longitude}`;
+  
+  // 判断是否为H5环境,H5环境使用代理避免跨域
+  let baseUrl;
+  // #ifdef H5
+  baseUrl = '/tencent-map-api';
+  // #endif
+  
+  // #ifndef H5
+  baseUrl = 'https://apis.map.qq.com';
+  // #endif
+  
+  // 构建完整的URL
+  let url = `${baseUrl}/ws/weather/v1?key=${TENCENT_MAP_KEY}&location=${location}`;
+  
+  // 添加可选参数
+  if (type) {
+    url += `&type=${type}`;
+  }
+  if (added_fields) {
+    url += `&added_fields=${added_fields}`;
+  }
+  
+  console.log('天气API请求URL:', url);
+  
+  return new Promise((resolve, reject) => {
+    uni.request({
+      url: url,
+      method: 'GET',
+      success: (res) => {
+        console.log('天气API响应:', res);
+        if (res.statusCode === 200 && res.data.status === 0) {
+          resolve(res.data);
+        } else {
+          reject(res.data || { message: '天气查询失败' });
+        }
+      },
+      fail: (err) => {
+        console.error('天气API请求失败:', err);
+        reject(err);
+      }
+    });
+  });
+}
+
+/**
+ * 获取uni-app定位信息
+ * @returns {Promise} 返回定位结果
+ */
+export function getLocation() {
+  return new Promise((resolve, reject) => {
+    uni.getLocation({
+      type: 'gcj02', // 返回国测局坐标,适用于腾讯地图
+      success: (res) => {
+        resolve({
+          latitude: res.latitude,
+          longitude: res.longitude,
+          accuracy: res.accuracy
+        });
+      },
+      fail: (err) => {
+        reject(err);
+      }
+    });
+  });
+}

+ 10 - 1
config/api.js

@@ -19,6 +19,7 @@ const isH5 = false;
 const dev = {
   // H5 开发环境使用代理路径,其他平台使用真实域名
   serve: isH5 ? "/base" : "https://nxy.gbdfarm.com:9000/pro-uniapp",
+  // serve: "https://nxy.gbdfarm.com:9000/pro-uniapp",
   upload: import.meta.env.VITE_UPLOAD_URL || "http://nxy.gbdfarm.com"
 };
 
@@ -32,7 +33,15 @@ const prod = {
 let api = process.env.NODE_ENV === "development" ? dev : prod;
 
 // 微信小程序、App、鸿蒙始终使用生产环境配置
-// #ifdef MP-WEIXIN || APP-PLUS || MP-HARMONY
+// #ifdef MP-WEIXIN
+api = prod;
+// #endif
+
+// #ifdef APP-PLUS
+api = prod;
+// #endif
+
+// #ifdef MP-HARMONY
 api = prod;
 // #endif
 

+ 31 - 10
manifest.json

@@ -8,6 +8,18 @@
     "app-plus" : {
         "distribute" : {
             "android" : {
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
+                "minSdkVersion" : 21,
+                "targetSdkVersion" : 30,
+                "networkSecurityConfig" : {
+                    "cleartextTrafficPermitted" : true,
+                    "domain" : [
+                        {
+                            "name" : "nxy.gbdfarm.com",
+                            "includeSubdomains" : true
+                        }
+                    ]
+                },
                 "permissions" : [
                     "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
                     "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
@@ -28,18 +40,15 @@
             },
             "sdkConfigs" : {
                 "geolocation" : {
-                    "amap" : {
-                        "name" : "amap4Lm85iA9",
-                        "__platform__" : [ "ios", "android" ],
-                        "appkey_ios" : "9f2cac7ea18905dd3830cf7360a43a35",
-                        "appkey_android" : "9f2cac7ea18905dd3830cf7360a43a35"
+                    "tencent" : {
+                        "__platform__" : [ "android" ],
+                        "apikey_ios" : "",
+                        "apikey_android" : "2N6BZ-VX5LJ-4GCFA-DGPXT-F4ZNF-7CB5D"
                     }
                 },
                 "maps" : {
-                    "amap" : {
-                        "name" : "amap4Lm85iA9",
-                        "appkey_ios" : "9f2cac7ea18905dd3830cf7360a43a35",
-                        "appkey_android" : "9f2cac7ea18905dd3830cf7360a43a35"
+                    "tencent" : {
+                        "key" : "2N6BZ-VX5LJ-4GCFA-DGPXT-F4ZNF-7CB5D"
                     }
                 },
                 "ad" : {}
@@ -83,6 +92,11 @@
         "modules" : {
             "Geolocation" : {},
             "Maps" : {}
+        },
+        "permissions" : {
+            "location" : {
+                "desc" : "用于获取设备位置信息,实现地图定位功能"
+            }
         }
     },
     "mp-weixin" : {
@@ -150,9 +164,16 @@
             "modules" : {
                 "uni-map" : {
                     "tencent" : {
-                        "key" : ""
+                        "key" : "2N6BZ-VX5LJ-4GCFA-DGPXT-F4ZNF-7CB5D"
                     }
                 }
+            },
+            "icons" : {
+                "foreground" : "E:/boxun/SoftWare/Feishu/download/nongxiaoyu_icon.png",
+                "background" : "E:/boxun/SoftWare/Feishu/download/nongxiaoyu_icon.png"
+            },
+            "splashScreens" : {
+                "startWindowIcon" : "E:/boxun/SoftWare/Feishu/download/nongxiaoyu_icon.png"
             }
         }
     }

+ 54 - 76
package-lock.json

@@ -22,6 +22,7 @@
 				"@dcloudio/uni-cli-shared": "3.0.0-4020920240930001",
 				"@dcloudio/vite-plugin-uni": "3.0.0-4020920240930001",
 				"cross-env": "^10.1.0",
+				"fast-check": "^4.5.3",
 				"sass": "^1.69.5",
 				"vite": "^5.2.8"
 			}
@@ -1524,6 +1525,12 @@
 				"node": ">=6.9.0"
 			}
 		},
+		"node_modules/@dcloudio/types": {
+			"version": "3.4.29",
+			"resolved": "https://registry.npmmirror.com/@dcloudio/types/-/types-3.4.29.tgz",
+			"integrity": "sha512-7uBInqqYLoLmQMqlzW4FsYCEHTUgTkrtZVsFGgQnJT7ZCA12U9y0ovrqAM1ZWkLruHYfOS7xIqO77Who6UBLJg==",
+			"peer": true
+		},
 		"node_modules/@dcloudio/uni-app": {
 			"version": "3.0.0-4020920240930001",
 			"resolved": "https://registry.npmmirror.com/@dcloudio/uni-app/-/uni-app-3.0.0-4020920240930001.tgz",
@@ -2728,7 +2735,7 @@
 			"version": "0.3.11",
 			"resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz",
 			"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
-			"dev": true,
+			"devOptional": true,
 			"dependencies": {
 				"@jridgewell/gen-mapping": "^0.3.5",
 				"@jridgewell/trace-mapping": "^0.3.25"
@@ -2784,7 +2791,6 @@
 			"version": "2.5.4",
 			"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.4.tgz",
 			"integrity": "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==",
-			"dev": true,
 			"hasInstallScript": true,
 			"optional": true,
 			"dependencies": {
@@ -2823,7 +2829,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"android"
@@ -2843,7 +2848,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"darwin"
@@ -2863,7 +2867,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"darwin"
@@ -2883,7 +2886,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"freebsd"
@@ -2903,7 +2905,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -2923,7 +2924,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -2943,7 +2943,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -2963,7 +2962,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -2983,7 +2981,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3003,7 +3000,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3023,7 +3019,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -3043,7 +3038,6 @@
 			"cpu": [
 				"ia32"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -3063,7 +3057,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -3104,7 +3097,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"android"
@@ -3117,7 +3109,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"android"
@@ -3130,7 +3121,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"darwin"
@@ -3143,7 +3133,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"darwin"
@@ -3156,7 +3145,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"freebsd"
@@ -3169,7 +3157,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"freebsd"
@@ -3182,7 +3169,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3195,7 +3181,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3208,7 +3193,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3221,7 +3205,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3234,7 +3217,6 @@
 			"cpu": [
 				"loong64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3247,7 +3229,6 @@
 			"cpu": [
 				"loong64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3260,7 +3241,6 @@
 			"cpu": [
 				"ppc64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3273,7 +3253,6 @@
 			"cpu": [
 				"ppc64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3286,7 +3265,6 @@
 			"cpu": [
 				"riscv64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3299,7 +3277,6 @@
 			"cpu": [
 				"riscv64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3312,7 +3289,6 @@
 			"cpu": [
 				"s390x"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3325,7 +3301,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3338,7 +3313,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -3351,7 +3325,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"openbsd"
@@ -3364,7 +3337,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"openharmony"
@@ -3377,7 +3349,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -3390,7 +3361,6 @@
 			"cpu": [
 				"ia32"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -3403,7 +3373,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -3416,7 +3385,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -4057,7 +4025,7 @@
 			"version": "1.1.2",
 			"resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
 			"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
-			"dev": true
+			"devOptional": true
 		},
 		"node_modules/bytes": {
 			"version": "3.1.2",
@@ -4170,7 +4138,7 @@
 			"version": "2.20.3",
 			"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
 			"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-			"dev": true
+			"devOptional": true
 		},
 		"node_modules/compare-versions": {
 			"version": "3.6.0",
@@ -4342,7 +4310,6 @@
 			"version": "2.1.2",
 			"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
 			"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
-			"dev": true,
 			"optional": true,
 			"engines": {
 				"node": ">=8"
@@ -4594,6 +4561,28 @@
 			"resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz",
 			"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="
 		},
+		"node_modules/fast-check": {
+			"version": "4.5.3",
+			"resolved": "https://registry.npmmirror.com/fast-check/-/fast-check-4.5.3.tgz",
+			"integrity": "sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "individual",
+					"url": "https://github.com/sponsors/dubzzz"
+				},
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/fast-check"
+				}
+			],
+			"dependencies": {
+				"pure-rand": "^7.0.0"
+			},
+			"engines": {
+				"node": ">=12.17.0"
+			}
+		},
 		"node_modules/fast-glob": {
 			"version": "3.3.3",
 			"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -4949,7 +4938,7 @@
 			"version": "5.1.4",
 			"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.4.tgz",
 			"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
-			"dev": true
+			"devOptional": true
 		},
 		"node_modules/inherits": {
 			"version": "2.0.4",
@@ -5453,7 +5442,6 @@
 			"version": "7.1.1",
 			"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
 			"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
-			"dev": true,
 			"optional": true
 		},
 		"node_modules/node-releases": {
@@ -5838,6 +5826,22 @@
 				"node": ">= 0.10"
 			}
 		},
+		"node_modules/pure-rand": {
+			"version": "7.0.1",
+			"resolved": "https://registry.npmmirror.com/pure-rand/-/pure-rand-7.0.1.tgz",
+			"integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "individual",
+					"url": "https://github.com/sponsors/dubzzz"
+				},
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/fast-check"
+				}
+			]
+		},
 		"node_modules/qrcode-reader": {
 			"version": "1.0.4",
 			"resolved": "https://registry.npmmirror.com/qrcode-reader/-/qrcode-reader-1.0.4.tgz",
@@ -6045,7 +6049,6 @@
 			"version": "4.56.0",
 			"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.56.0.tgz",
 			"integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==",
-			"dev": true,
 			"dependencies": {
 				"@types/estree": "1.0.8"
 			},
@@ -6142,7 +6145,7 @@
 			"version": "1.97.3",
 			"resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.3.tgz",
 			"integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==",
-			"dev": true,
+			"devOptional": true,
 			"dependencies": {
 				"chokidar": "^4.0.0",
 				"immutable": "^5.0.2",
@@ -6162,7 +6165,7 @@
 			"version": "4.0.3",
 			"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
 			"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
-			"dev": true,
+			"devOptional": true,
 			"dependencies": {
 				"readdirp": "^4.0.1"
 			},
@@ -6177,7 +6180,7 @@
 			"version": "4.1.2",
 			"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz",
 			"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
-			"dev": true,
+			"devOptional": true,
 			"engines": {
 				"node": ">= 14.18.0"
 			},
@@ -6397,7 +6400,7 @@
 			"version": "0.5.21",
 			"resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz",
 			"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
-			"dev": true,
+			"devOptional": true,
 			"dependencies": {
 				"buffer-from": "^1.0.0",
 				"source-map": "^0.6.0"
@@ -6466,7 +6469,7 @@
 			"version": "5.46.0",
 			"resolved": "https://registry.npmmirror.com/terser/-/terser-5.46.0.tgz",
 			"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
-			"dev": true,
+			"devOptional": true,
 			"dependencies": {
 				"@jridgewell/source-map": "^0.3.3",
 				"acorn": "^8.15.0",
@@ -6773,7 +6776,6 @@
 			"version": "5.4.21",
 			"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
 			"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
-			"dev": true,
 			"dependencies": {
 				"esbuild": "^0.21.3",
 				"postcss": "^8.4.43",
@@ -6835,7 +6837,6 @@
 			"cpu": [
 				"ppc64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"aix"
@@ -6851,7 +6852,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"android"
@@ -6867,7 +6867,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"android"
@@ -6883,7 +6882,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"android"
@@ -6899,7 +6897,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"darwin"
@@ -6915,7 +6912,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"darwin"
@@ -6931,7 +6927,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"freebsd"
@@ -6947,7 +6942,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"freebsd"
@@ -6963,7 +6957,6 @@
 			"cpu": [
 				"arm"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -6979,7 +6972,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -6995,7 +6987,6 @@
 			"cpu": [
 				"ia32"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7011,7 +7002,6 @@
 			"cpu": [
 				"loong64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7027,7 +7017,6 @@
 			"cpu": [
 				"mips64el"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7043,7 +7032,6 @@
 			"cpu": [
 				"ppc64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7059,7 +7047,6 @@
 			"cpu": [
 				"riscv64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7075,7 +7062,6 @@
 			"cpu": [
 				"s390x"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7091,7 +7077,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"linux"
@@ -7107,7 +7092,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"netbsd"
@@ -7123,7 +7107,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"openbsd"
@@ -7139,7 +7122,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"sunos"
@@ -7155,7 +7137,6 @@
 			"cpu": [
 				"arm64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -7171,7 +7152,6 @@
 			"cpu": [
 				"ia32"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -7187,7 +7167,6 @@
 			"cpu": [
 				"x64"
 			],
-			"dev": true,
 			"optional": true,
 			"os": [
 				"win32"
@@ -7200,7 +7179,6 @@
 			"version": "0.21.5",
 			"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
 			"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
-			"dev": true,
 			"hasInstallScript": true,
 			"bin": {
 				"esbuild": "bin/esbuild"

+ 5 - 1
package.json

@@ -19,7 +19,10 @@
 		"test:jessibuca": "node tests/jessibuca-component-functional.test.js",
 		"test:login-store": "node tests/login-page-store-property.test.js",
 		"test:comprehensive": "node tests/comprehensive-property-tests.test.js",
-		"test:cross-platform": "node tests/cross-platform-functional.test.js"
+		"test:cross-platform": "node tests/cross-platform-functional.test.js",
+		"test:map-init": "node tests/map-initialization-unit.test.js",
+		"test:map-center-zoom": "node tests/map-center-zoom-property.test.js",
+		"test:geolocation": "node tests/geolocation-unit.test.js"
 	},
 	"keywords": [
 		"农业",
@@ -42,6 +45,7 @@
 		"@dcloudio/uni-cli-shared": "3.0.0-4020920240930001",
 		"@dcloudio/vite-plugin-uni": "3.0.0-4020920240930001",
 		"cross-env": "^10.1.0",
+		"fast-check": "^4.5.3",
 		"sass": "^1.69.5",
 		"vite": "^5.2.8"
 	}

+ 18 - 16
pages.json

@@ -308,6 +308,20 @@
 				"navigationBarTextStyle": "black",
 				"enablePullDownRefresh": false
 			}
+		},
+		{
+			"path": "pages/login/terms",
+			"style": {
+				"navigationBarTitleText": "用户协议",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/login/privacy",
+			"style": {
+				"navigationBarTitleText": "隐私政策",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"tabBar": {
@@ -315,6 +329,7 @@
 		"selectedColor": "#4CAF50",
 		"backgroundColor": "#ffffff",
 		"borderStyle": "black",
+		"spacing": "3px",
 		"list": [{
 				"pagePath": "pages/dashboard/index",
 				"iconPath": "static/icons/home.png",
@@ -329,9 +344,9 @@
 			},
 			{
 				"pagePath": "pages/knowledge/ai-chat/index",
-				"iconPath": "static/icons/ai.png",
-				"selectedIconPath": "static/icons/ai.png",
-				//"text": "ai农小禹"
+				"iconPath": "static/icons/ai-64.png",
+				"selectedIconPath": "static/icons/ai-64.png",
+				"text": "农小禹"
 			},
 			{
 				"pagePath": "pages/service/mall",
@@ -339,19 +354,6 @@
 				"selectedIconPath": "static/icons/mall-active.png",
 				"text": "农资"
 			},
-			// {
-			// 	"pagePath": "pages/activity/index",
-			// 	"iconPath": "static/icons/activity.png",
-			// 	"selectedIconPath": "static/icons/activity-active.png",
-			// 	"text": "农事"
-			// },
-			// {
-			// 	"pagePath": "pages/device/index",
-			// 	"iconPath": "static/icons/device.png",
-			// 	"selectedIconPath": "static/icons/device-active.png",
-			// 	"text": "设备"
-			// },
-
 			{
 				"pagePath": "pages/user/index",
 				"iconPath": "static/icons/user.png",

+ 459 - 108
pages/dashboard/index.vue

@@ -13,6 +13,9 @@
           <text class="main-text">{{ weatherData.infos.temperature }}° · {{ weatherData.infos.weather }}</text>
           <text class="fixed-subtitle">田间环境概览</text>
         </view>
+        <view class="refresh-btn" :class="{ 'rotating': isLoadingWeather }" @click.stop="handleRefresh">
+          <u-icon name="reload" :color="isLoadingWeather ? '#999' : '#3BB44A'" size="20"></u-icon>
+        </view>
       </view>
 
       <view class="metrics-row">
@@ -29,6 +32,10 @@
           <text class="value">{{ weatherData.infos.air_pressure }} hPa</text>
         </view>
       </view>
+      
+      <view class="update-time" v-if="weatherData.update_time">
+        <text>更新时间:{{ weatherData.update_time }}</text>
+      </view>
     </view>
 
     <!-- AI 主卡 -->
@@ -154,7 +161,7 @@
     </view>
 
     <!-- 天气详情弹窗 -->
-    <u-popup 
+<!--    <u-popup 
       :show="showWeatherPopup" 
       mode="bottom" 
       round="20"
@@ -200,7 +207,7 @@
           去问 AI
         </u-button>
       </view>
-    </u-popup>
+    </u-popup> -->
 
     <!-- 文章详情弹窗 -->
     <u-popup 
@@ -241,20 +248,26 @@
 </template>
 
 <script setup>
-import { ref, reactive } from 'vue'
+import { ref, reactive, onMounted } from 'vue'
+import { getWeatherInfo, getLocation } from '@/api/services/weather.js'
+import { getMallList } from '@/api/services/mall.js'
+import { getTopArticles } from '@/api/services/knowledge.js'
 
-// Mock 数据
+// 天气数据
 const weatherData = reactive({
   infos: {
-    weather: "晴朗",
-    temperature: 28,
-    humidity: 65,
-    wind_power_v2: "3级",
-    air_pressure: 1008
+    weather: "加载中...",
+    temperature: "--",
+    humidity: "--",
+    wind_power_v2: "--",
+    air_pressure: "--"
   },
-  update_time: "10:20" // 暗桩:保留字段供后续对接
+  update_time: "" // 更新时间
 })
 
+// 加载状态
+const isLoadingWeather = ref(false)
+
 const aiQuestions = [
   {
     title: '叶片发黄该从哪查?',
@@ -272,95 +285,11 @@ const aiQuestions = [
 
 const aiQuestion = ref(aiQuestions[0])
 
-const products = ref([
-  {
-    title: '优质玉米种子',
-    desc: '高产抗病品种',
-    price: '68',
-    unit: '袋',
-    image: '/static/images/products/corn-seeds-new.jpg'
-  },
-  {
-    title: '有机肥料',
-    desc: '纯天然环保',
-    price: '128',
-    unit: '袋',
-    image: '/static/images/products/organic-fertilizer-new.jpg'
-  },
-  {
-    title: '杀虫剂',
-    desc: '广谱高效',
-    price: '45',
-    unit: '瓶',
-    image: '/static/images/products/insecticide.jpg'
-  },
-  {
-    title: '农用地膜',
-    desc: '保湿保温',
-    price: '89',
-    unit: '卷',
-    image: '/static/images/products/plastic-film.jpg'
-  }
-])
+// 商品列表数据
+const products = ref([])
 
-const articles = ref([
-  {
-    title: '春季水稻种植技术要点',
-    source: '农技知识',
-    readCount: '1.2k',
-    image: '/static/images/rice-farming/rice-field1.jpg',
-    time: '2024-03-15',
-    content: '春季是水稻种植的关键时期,需要掌握以下技术要点:首先,选择适宜的品种,根据当地气候条件选择抗病性强、产量高的品种。其次,做好秧田准备,确保土壤肥力充足,排水良好。',
-    points: [
-      '选择适宜品种,抗病高产',
-      '秧田准备,土壤肥力充足',
-      '合理密植,通风透光',
-      '水肥管理,分蘖期关键'
-    ]
-  },
-  {
-    title: '玉米病虫害防治指南',
-    source: '植保技术',
-    readCount: '856',
-    image: '/static/images/rice-farming/rice-field2.jpg',
-    time: '2024-03-14',
-    content: '玉米病虫害防治是确保产量的重要环节。常见的病虫害包括玉米螟、蚜虫、叶斑病等。防治应遵循"预防为主,综合防治"的原则。',
-    points: [
-      '定期巡查,早发现早处理',
-      '生物防治与化学防治结合',
-      '合理轮作,减少病虫害发生',
-      '选择抗病品种,提高抗性'
-    ]
-  },
-  {
-    title: '现代农业机械化作业规范',
-    source: '农机管理',
-    readCount: '2.1k',
-    image: '/static/images/rice-farming/rice-field3.jpg',
-    time: '2024-03-13',
-    content: '提升农业机械化作业水平是现代农业发展的必然要求。本规范涵盖了整地、播种、植保及收获等环节的机械化操作标准。',
-    points: [
-      '机具调试,状态良好',
-      '标准化作业,保证质量',
-      '安全生产,预防事故',
-      '效率优化,降低成本'
-    ]
-  },
-  {
-    title: '土壤肥力提升与改良方案',
-    source: '土肥管理',
-    readCount: '1.5k',
-    image: '/static/images/rice-farming/rice-field4.jpg',
-    time: '2024-03-12',
-    content: '健康的土壤是丰收的基础。通过增施有机肥、秸秆还田以及科学轮作,可以有效改善土壤结构,提升肥力水平。',
-    points: [
-      '测土配方,科学施肥',
-      '增加有机质,改良结构',
-      '酸碱度调理,平衡养分',
-      '生态修复,持久肥力'
-    ]
-  }
-])
+// 文章列表数据
+const articles = ref([])
 
 // 弹窗控制
 const showWeatherPopup = ref(false)
@@ -381,22 +310,41 @@ const showWeatherDetail = () => {
 }
 
 const showArticleDetail = (article) => {
-  currentArticle.value = article
-  showArticlePopup.value = true
+  if (!article.id) {
+    uni.showToast({
+      title: '文章信息不完整',
+      icon: 'none',
+      duration: 2000
+    })
+    return;
+  }
+  
+  // 跳转到农技详情页
+  uni.navigateTo({
+    url: `/pages/knowledge/detail?id=${article.id}`
+  })
 }
 
 const showProductDetail = (product) => {
-  uni.showToast({
-    title: '后续接入商品详情',
-    icon: 'none',
-    duration: 2000
+  if (!product.id) {
+    uni.showToast({
+      title: '商品信息不完整',
+      icon: 'none',
+      duration: 2000
+    })
+    return;
+  }
+  
+  // 跳转到农资详情页
+  uni.navigateTo({
+    url: `/pages/service/mall-detail?id=${product.id}`
   })
 }
 
 const goToAI = () => {
-  uni.navigateTo({
-    url: '/pages/knowledge/ai-chat/index'
-  })
+  uni.switchTab({
+  	url: '/pages/knowledge/ai-chat/index'
+  });
 }
 
 
@@ -427,6 +375,360 @@ const askAIAboutArticle = () => {
     })
   }
 }
+
+const handleRefresh = () => {
+  if (isLoadingWeather.value) {
+    uni.showToast({
+      title: '正在刷新中...',
+      icon: 'none',
+      duration: 1500
+    });
+    return;
+  }
+  fetchWeatherData(true);
+}
+
+/**
+ * 获取实时天气数据
+ */
+const fetchWeatherData = async (showToast = false) => {
+  if (isLoadingWeather.value) {
+    return;
+  }
+  
+  // 如果是手动刷新,显示提示
+  if (showToast) {
+    uni.showToast({
+      title: '正在刷新天气...',
+      icon: 'loading',
+      duration: 2000
+    });
+  }
+  
+  isLoadingWeather.value = true;
+  
+  try {
+    // 1. 获取当前位置
+    const location = await getLocation();
+    console.log('获取到位置信息:', location);
+    
+    // 2. 根据位置查询天气
+    const weatherResult = await getWeatherInfo({
+      latitude: location.latitude,
+      longitude: location.longitude,
+      type: 'now', // 查询实时天气
+      added_fields: 'air' // 附加空气质量信息
+    });
+    
+    console.log('天气查询结果:', weatherResult);
+    
+    // 3. 更新天气数据
+    if (weatherResult && weatherResult.result && weatherResult.result.realtime) {
+      const realtime = weatherResult.result.realtime;
+      const infos = realtime[0].infos || {};
+      
+      weatherData.infos = {
+        weather: infos.weather || '未知',
+        temperature: infos.temperature !== undefined ? infos.temperature : '--',
+        humidity: infos.humidity !== undefined ? infos.humidity : '--',
+        wind_power_v2: infos.wind_power_v2 || '--',
+        air_pressure: infos.air_pressure !== undefined ? infos.air_pressure : '--'
+      };
+      
+      // 更新时间
+      weatherData.update_time = realtime.update_time || '';
+      
+      console.log('天气数据更新成功:', weatherData);
+      
+      // 刷新成功提示
+      if (showToast) {
+        uni.showToast({
+          title: '刷新成功',
+          icon: 'success',
+          duration: 1500
+        });
+      }
+    }
+  } catch (error) {
+    console.error('获取天气失败:', error);
+    uni.showToast({
+        title: `${error}`,
+        icon: 'none',
+        duration: 2000
+      })
+    // 错误处理
+    let errorMsg = '获取天气失败';
+    if (error.errMsg) {
+      if (error.errMsg.includes('auth deny')) {
+        errorMsg = '定位权限被拒绝,请在设置中开启定位权限';
+      } else if (error.errMsg.includes('timeout')) {
+        errorMsg = '定位超时,请检查网络连接';
+      }
+    } else if (error.message) {
+      errorMsg = error.message;
+    }
+    
+    uni.showToast({
+      title: errorMsg,
+      icon: 'none',
+      duration: 2000
+    });
+    
+    // 设置默认值
+    weatherData.infos = {
+      weather: "暂无数据",
+      temperature: "--",
+      humidity: "--",
+      wind_power_v2: "--",
+      air_pressure: "--"
+    };
+  } finally {
+    isLoadingWeather.value = false;
+  }
+}
+
+/**
+ * 获取第一张图片地址
+ * @param {string} swiperImages - 图片地址字符串,可能包含多个用逗号分隔的地址
+ * @returns {string} - 返回第一张图片地址
+ */
+const getFirstImage = (swiperImages) => {
+  if (!swiperImages) {
+    return '/static/images/products/agriculture-tools.jpg';
+  }
+  
+  // 如果包含逗号,说明有多张图片,取第一张
+  if (swiperImages.includes(',')) {
+    return swiperImages.split(',')[0].trim();
+  }
+  
+  // 否则直接返回
+  return swiperImages;
+}
+
+/**
+ * 格式化阅读量显示
+ * @param {number} count - 阅读量数字
+ * @returns {string} - 格式化后的阅读量(如:1.2k)
+ */
+const formatReadCount = (count) => {
+  if (!count || count === 0) return '0';
+  if (count >= 1000) {
+    return (count / 1000).toFixed(1) + 'k';
+  }
+  return count.toString();
+}
+
+/**
+ * 获取推荐商品列表
+ */
+const fetchRecommendedProducts = async () => {
+  try {
+    const result = await getMallList({
+      isRecommended: 1, // 查询推荐商品
+      pageNum: 1,
+      pageSize: 4 // 只获取4个推荐商品
+    });
+    
+    console.log('推荐商品查询结果:', result);
+    
+    if (result.data && result.data.rows && result.data.rows.length > 0) {
+      // 映射后端数据到前端展示格式
+      products.value = result.data.rows.map(item => ({
+        id: item.id,
+        title: item.name || item.title || '商品名称',
+        desc: item.description || item.desc || '暂无描述',
+        price: item.price || '0',
+        unit: item.unit || '件',
+        image: getFirstImage(item.swiperImages)
+      }));
+      
+      console.log('推荐商品数据更新成功:', products.value);
+    } else {
+      // 如果没有推荐商品,使用默认数据
+      console.log('暂无推荐商品,使用默认数据');
+      products.value = [
+        {
+          title: '优质玉米种子',
+          desc: '高产抗病品种',
+          price: '68',
+          unit: '袋',
+          image: '/static/images/products/corn-seeds-new.jpg'
+        },
+        {
+          title: '有机肥料',
+          desc: '纯天然环保',
+          price: '128',
+          unit: '袋',
+          image: '/static/images/products/organic-fertilizer-new.jpg'
+        },
+        {
+          title: '杀虫剂',
+          desc: '广谱高效',
+          price: '45',
+          unit: '瓶',
+          image: '/static/images/products/insecticide.jpg'
+        },
+        {
+          title: '农用地膜',
+          desc: '保湿保温',
+          price: '89',
+          unit: '卷',
+          image: '/static/images/products/plastic-film.jpg'
+        }
+      ];
+    }
+  } catch (error) {
+    console.error('获取推荐商品失败:', error);
+    // 失败时使用默认数据
+    products.value = [
+      {
+        title: '优质玉米种子',
+        desc: '高产抗病品种',
+        price: '68',
+        unit: '袋',
+        image: '/static/images/products/corn-seeds-new.jpg'
+      },
+      {
+        title: '有机肥料',
+        desc: '纯天然环保',
+        price: '128',
+        unit: '袋',
+        image: '/static/images/products/organic-fertilizer-new.jpg'
+      },
+      {
+        title: '杀虫剂',
+        desc: '广谱高效',
+        price: '45',
+        unit: '瓶',
+        image: '/static/images/products/insecticide.jpg'
+      },
+      {
+        title: '农用地膜',
+        desc: '保湿保温',
+        price: '89',
+        unit: '卷',
+        image: '/static/images/products/plastic-film.jpg'
+      }
+    ];
+  }
+}
+
+/**
+ * 获取热门文章列表(根据阅读量)
+ */
+const fetchTopArticles = async () => {
+  try {
+    const result = await getTopArticles();
+    
+    console.log('热门文章查询结果:', result);
+    
+    if (result.data && result.data.length > 0) {
+      // 映射后端数据到前端展示格式
+      articles.value = result.data.slice(0, 4).map(item => ({
+        id: item.id,
+        title: item.title || '文章标题',
+        source: item.category || item.source || '农技知识',
+        readCount: formatReadCount(item.viewCount || item.readCount || 0),
+        image: getFirstImage(item.imageUrl) || '/static/images/rice-farming/rice-field1.jpg',
+        time: item.createTime || item.publishTime || '',
+        content: item.content || item.summary || '暂无内容',
+        points: [] // 后端如果有要点字段可以映射
+      }));
+      
+      console.log('热门文章数据更新成功:', articles.value);
+    } else {
+      // 如果没有热门文章,使用默认数据
+      console.log('暂无热门文章,使用默认数据');
+      articles.value = [
+        {
+          title: '春季水稻种植技术要点',
+          source: '农技知识',
+          readCount: '1.2k',
+          image: '/static/images/rice-farming/rice-field1.jpg',
+          time: '2024-03-15',
+          content: '春季是水稻种植的关键时期,需要掌握以下技术要点。',
+          points: []
+        },
+        {
+          title: '玉米病虫害防治指南',
+          source: '植保技术',
+          readCount: '856',
+          image: '/static/images/rice-farming/rice-field2.jpg',
+          time: '2024-03-14',
+          content: '玉米病虫害防治是确保产量的重要环节。',
+          points: []
+        },
+        {
+          title: '现代农业机械化作业规范',
+          source: '农机管理',
+          readCount: '2.1k',
+          image: '/static/images/rice-farming/rice-field3.jpg',
+          time: '2024-03-13',
+          content: '提升农业机械化作业水平是现代农业发展的必然要求。',
+          points: []
+        },
+        {
+          title: '土壤肥力提升与改良方案',
+          source: '土肥管理',
+          readCount: '1.5k',
+          image: '/static/images/rice-farming/rice-field4.jpg',
+          time: '2024-03-12',
+          content: '健康的土壤是丰收的基础。',
+          points: []
+        }
+      ];
+    }
+  } catch (error) {
+    console.error('获取热门文章失败:', error);
+    // 失败时使用默认数据
+    articles.value = [
+      {
+        title: '春季水稻种植技术要点',
+        source: '农技知识',
+        readCount: '1.2k',
+        image: '/static/images/rice-farming/rice-field1.jpg',
+        time: '2024-03-15',
+        content: '春季是水稻种植的关键时期,需要掌握以下技术要点。',
+        points: []
+      },
+      {
+        title: '玉米病虫害防治指南',
+        source: '植保技术',
+        readCount: '856',
+        image: '/static/images/rice-farming/rice-field2.jpg',
+        time: '2024-03-14',
+        content: '玉米病虫害防治是确保产量的重要环节。',
+        points: []
+      },
+      {
+        title: '现代农业机械化作业规范',
+        source: '农机管理',
+        readCount: '2.1k',
+        image: '/static/images/rice-farming/rice-field3.jpg',
+        time: '2024-03-13',
+        content: '提升农业机械化作业水平是现代农业发展的必然要求。',
+        points: []
+      },
+      {
+        title: '土壤肥力提升与改良方案',
+        source: '土肥管理',
+        readCount: '1.5k',
+        image: '/static/images/rice-farming/rice-field4.jpg',
+        time: '2024-03-12',
+        content: '健康的土壤是丰收的基础。',
+        points: []
+      }
+    ];
+  }
+}
+
+// 页面加载时获取天气、推荐商品和热门文章
+onMounted(() => {
+  fetchWeatherData();
+  fetchRecommendedProducts();
+  fetchTopArticles();
+})
 </script>
 
 <style lang="scss" scoped>
@@ -528,7 +830,7 @@ const askAIAboutArticle = () => {
   .status-main {
     display: flex;
     justify-content: space-between;
-    align-items: flex-end;
+    // align-items: flex-end;
     margin-bottom: 20rpx;
 
     .main-info {
@@ -548,6 +850,35 @@ const askAIAboutArticle = () => {
         margin-top: 8rpx;
       }
     }
+    
+    .refresh-btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 60rpx;
+      height: 60rpx;
+      border-radius: 50%;
+      background: rgba(59, 180, 74, 0.08);
+      transition: all 0.3s ease;
+      
+      &:active {
+        background: rgba(59, 180, 74, 0.15);
+        transform: scale(0.95);
+      }
+      
+      &.rotating {
+        animation: rotate 1s linear infinite;
+      }
+    }
+  }
+  
+  @keyframes rotate {
+    from {
+      transform: rotate(0deg);
+    }
+    to {
+      transform: rotate(360deg);
+    }
   }
 
   .metrics-row {
@@ -575,6 +906,16 @@ const askAIAboutArticle = () => {
       }
     }
   }
+  
+  .update-time {
+    margin-top: 12rpx;
+    text-align: right;
+    
+    text {
+      font-size: 20rpx;
+      color: #999;
+    }
+  }
 }
 
 // AI 主卡
@@ -948,4 +1289,14 @@ const askAIAboutArticle = () => {
     }
   }
 }
+
+// 旋转动画
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
 </style>

+ 329 - 52
pages/knowledge/ai-chat/index.vue

@@ -8,7 +8,7 @@
 					<!-- AI消息 -->
 					<template v-if="message.sender === 'ai'">
 						<view class="avatar-container">
-							<image class="avatar" src="/static/icons/ai.png" mode="aspectFill"></image>
+							<image class="avatar" src="/static/icons/ai-1.png" mode="aspectFill"></image>
 						</view>
 						<view class="message-content">
 							<view class="message-bubble ai-bubble"
@@ -25,6 +25,11 @@
 									:class="{'highlight': containsKeywords(message.content)}">
 									{{ message.content }}
 								</text>
+								
+								<!-- AI免责声明(非欢迎消息且非输入中显示) -->
+								<text  class="ai-disclaimer">
+									内容均由AI生成,仅供参考
+								</text>
 							</view>
 							
 							<!-- 操作按钮区域(仅最后一条AI消息显示) -->
@@ -55,7 +60,14 @@
 			</view>
 		</scroll-view>
 		<!-- 底部输入区 -->
-		<view class="input-container" :style="{ paddingBottom: `${isIOS ? safeAreaBottom : 20}rpx` }">
+		<view class="input-container" :style="{ 
+			// #ifdef H5
+			paddingBottom: `${safeAreaBottom + 100}rpx`
+			// #endif
+			// #ifdef APP-HARMONY
+			paddingBottom: `${safeAreaBottom + 20}rpx`
+			// #endif
+		}">
 			<!-- 问题建议区 -->
 			<scroll-view v-if="chatMessages.length <= 3 && !inputMessage && !isProcessing" class="suggested-questions" scroll-x>
 				<view v-for="(question, index) in suggestedQuestions" :key="index" class="question-chip"
@@ -85,12 +97,14 @@
 		</view>
 		
 		<!-- renderjs 模块容器(用于 H5/App 端 SSE 流式连接) -->
+		<!--  #ifdef H5 -->
 		<view 
 			:change:prop="renderModule.onDataChange" 
 			:prop="renderjsData"
 			:onStreamData="onStreamData"
 			class="renderjs-container"
 		></view>
+		<!-- #endif -->
 	</view>
 </template>
 <script setup>
@@ -131,7 +145,7 @@
 		'有机肥和化肥怎么搭配使用?'
 	])
 	const statusBarHeight = ref(20)
-	const safeAreaBottom = ref(34)
+	const safeAreaBottom = ref(0)
 	const isIOS = ref(false)
 	
 	// renderjs 通信数据
@@ -165,9 +179,8 @@
 	
 	// renderjs 回调:接收流式数据
 	const onStreamData = (data) => {
-		console.log('=== Vue收到流式数据 ===', data);
+		console.log('=== Vue收到流式数据 ===');
 		console.log('数据类型:', data.type);
-		console.log('当前输入消息索引:', currentTypingMessage.value);
 		
 		if (data.type === 'thinking') {
 			console.log('进入 Thinking 模式');
@@ -176,7 +189,9 @@
 			thinkingBuffer.value = data.content;
 			processThinkingContent();
 		} else if (data.type === 'message') {
-			console.log('收到消息内容:', data.content);
+			const contentLength = data.content ? data.content.length : 0;
+			console.log('收到消息内容,长度:', contentLength);
+			
 			// 判断是否在 Thinking 模式中
 			if (isInThinkingMode.value) {
 				console.log('仍在 Thinking 模式,累积内容');
@@ -190,8 +205,7 @@
 			}
 		} else if (data.type === 'end') {
 			// 流式结束
-			console.log("流式结束,消息id:",data.id);
-			
+			console.log("流式结束,消息id:", data.id);
 			finishStreaming(data.id);
 		} else if (data.type === 'error') {
 			// 错误处理
@@ -247,33 +261,58 @@
 		console.log('startTypingEffect 被调用');
 		console.log('isTypingEffect:', isTypingEffect.value);
 		console.log('currentTypingMessage:', currentTypingMessage.value);
+		console.log('messageQueue 长度:', messageQueue.value.length);
+		
+		if (isTypingEffect.value) {
+			console.log('打字机效果已在运行,退出');
+			return;
+		}
 		
-		if (isTypingEffect.value || currentTypingMessage.value === null) {
-			console.log('打字机效果已在运行或没有当前消息,退出');
+		if (currentTypingMessage.value === null) {
+			console.log('没有当前消息,退出');
+			return;
+		}
+		
+		if (messageQueue.value.length === 0) {
+			console.log('消息队列为空,退出');
 			return;
 		}
 		
 		isTypingEffect.value = true;
-		chatMessages.value[currentTypingMessage.value].isTyping = false;
+		
+		// 关闭 typing 指示器
+		if (chatMessages.value[currentTypingMessage.value]) {
+			chatMessages.value[currentTypingMessage.value].isTyping = false;
+			console.log('已关闭 typing 指示器');
+		}
+		
 		console.log('开始打字机效果,消息索引:', currentTypingMessage.value);
 		
 		const processQueue = () => {
 			if (messageQueue.value.length > 0 && currentTypingMessage.value !== null) {
 				// 每次取出一个字符
 				const char = messageQueue.value.shift();
-				const currentContent = chatMessages.value[currentTypingMessage.value].content || '';
-				chatMessages.value[currentTypingMessage.value].content = currentContent + char;
+				const currentMsg = chatMessages.value[currentTypingMessage.value];
 				
-				// 每20个字符滚动一次,优化性能
-				if (currentContent.length % 20 === 0) {
-					scrollToBottom();
+				if (currentMsg) {
+					const currentContent = currentMsg.content || '';
+					currentMsg.content = currentContent + char;
+					
+					// 每20个字符滚动一次,优化性能
+					if (currentContent.length % 20 === 0) {
+						scrollToBottom();
+					}
 				}
 				
 				// 继续处理队列
 				typingTimer.value = setTimeout(processQueue, 10);
-			} else if (messageQueue.value.length === 0) {
-				// 队列为空,等待新数据
+			} else if (messageQueue.value.length === 0 && isProcessing.value) {
+				// 队列为空但还在处理中,等待新数据
 				typingTimer.value = setTimeout(processQueue, 50);
+			} else {
+				// 队列为空且不在处理中,停止打字机效果
+				console.log('队列处理完成,停止打字机效果');
+				isTypingEffect.value = false;
 			}
 		};
 		
@@ -398,6 +437,7 @@
 			user: 'user_' + sessionId.value
 		};
 		
+		// #ifdef H5 
 		// 更新 renderjs 数据,触发 SSE 连接
 		renderjsData.value = {
 			action: 'start',
@@ -406,17 +446,217 @@
 			token: storage.getAccessToken(),
 			timestamp: Date.now()
 		};
+		// #endif
+		
+		// #ifdef APP-HARMONY || APP-PLUS
+		// 鸿蒙等不支持 renderjs 的平台,使用原生方式
+		startSSERequestNative(url, requestData);
+		// #endif
+	}
+	
+	// 鸿蒙端 SSE 请求任务引用
+	let sseRequestTask = null;
+	
+	// 原生方式处理 SSE 请求(用于鸿蒙等平台)
+	const startSSERequestNative = (url, data) => {
+		console.log('[鸿蒙端] 开始 SSE 请求:', url);
+		
+		// 创建请求任务
+		sseRequestTask = uni.request({
+			url: url,
+			method: 'POST',
+			header: {
+				'Content-Type': 'application/json',
+				'Accept': 'text/event-stream',
+				'Authorization': `Bearer ${storage.getAccessToken()}`
+			},
+			data: data,
+			enableChunked: true, // 启用分块传输
+			responseType: 'text', // 接收文本数据
+			success: (res) => {
+				console.log('[鸿蒙端] 请求成功,状态码:', res.statusCode);
+				console.log('[鸿蒙端] 响应数据类型:', typeof res.data);
+				
+				if (res.statusCode === 200 && res.data) {
+					console.log('[鸿蒙端] 响应数据长度:', res.data.length);
+					// 处理 SSE 格式的响应数据
+					parseSSEResponse(res.data);
+				} else {
+					console.error('[鸿蒙端] 请求失败或无数据');
+					handleStreamError('请求失败,状态码: ' + res.statusCode);
+				}
+			},
+			fail: (err) => {
+				console.error('[鸿蒙端] 请求失败:', err);
+				handleStreamError(err.errMsg || '网络异常');
+			}
+		});
+		
+		// 监听数据接收(如果支持分块传输)
+		if (sseRequestTask && typeof sseRequestTask.onChunkReceived === 'function') {
+			console.log('[鸿蒙端] 支持分块接收,启用监听');
+			sseRequestTask.onChunkReceived((chunkRes) => {
+				console.log('[鸿蒙端] 收到数据块');
+				if (chunkRes && chunkRes.data) {
+					parseSSEResponseChunk(chunkRes.data);
+				}
+			});
+		} else {
+			console.log('[鸿蒙端] 不支持分块接收,将在 success 回调中处理完整响应');
+		}
+	}
+	
+	// 解析 SSE 格式的响应数据(处理分块数据)
+	let sseBuffer = ''; // 用于累积不完整的行
+	
+	const parseSSEResponseChunk = (chunkText) => {
+		if (!chunkText || typeof chunkText !== 'string') {
+			console.log('[鸿蒙端] 无效的数据块');
+			return;
+		}
+		
+		console.log('[鸿蒙端] 处理数据块,长度:', chunkText.length);
+		
+		// 累积到缓冲区
+		sseBuffer += chunkText;
+		
+		// 按行分割
+		const lines = sseBuffer.split('\n');
+		
+		// 保留最后一行(可能不完整)
+		sseBuffer = lines.pop() || '';
+		
+		// 处理完整的行
+		processSSELines(lines);
+	}
+	
+	// 解析 SSE 格式的响应数据(处理完整响应)
+	const parseSSEResponse = (responseText) => {
+		if (!responseText || typeof responseText !== 'string') {
+			console.log('[鸿蒙端] 无效的响应数据');
+			finishStreaming();
+			return;
+		}
+		
+		console.log('[鸿蒙端] 开始解析完整 SSE 数据,长度:', responseText.length);
+		
+		// 按行分割
+		const lines = responseText.split('\n');
+		
+		// 处理所有行
+		processSSELines(lines);
+		
+		// 清空缓冲区
+		sseBuffer = '';
+	}
+	
+	// 处理 SSE 行数据
+	const processSSELines = (lines) => {
+		let messageId = null;
+		
+		for (let i = 0; i < lines.length; i++) {
+			const line = lines[i].trim();
+			
+			if (!line) continue; // 跳过空行
+			
+			console.log(`[鸿蒙端] 处理第 ${i} 行:`, line.substring(0, 100));
+			
+			// 解析 event: 行
+			if (line.startsWith('event:')) {
+				// event: message
+				continue;
+			}
+			
+			// 解析 data: 行
+			if (line.startsWith('data:')  || line !== '') {
+				let data = line;
+				if (line.startsWith('data:')) {
+					data = data.substring(5).trim();
+				}
+				 			
+				if (!data || data.includes("ping")) continue;
+				
+				console.log('[鸿蒙端] 解析后的数据,长度:', data.length);
+				
+				// 过滤掉 MESSAGE_END 等元数据事件(JSON 格式)
+				if (data.startsWith('{') && data.includes('"eventType"')) {
+					try {
+						const jsonData = JSON.parse(data);
+						console.log('[鸿蒙端] JSON 事件:', jsonData.eventType || jsonData.event);
+						
+						// 如果是 MESSAGE_END 事件,通知结束
+						if (jsonData.eventType === 'MESSAGE_END' || jsonData.event === 'message_end') {
+							console.log('[鸿蒙端] 收到 MESSAGE_END 事件,流式结束');
+							messageId = jsonData.id;
+							// 不要在这里 continue,继续处理后续行
+							continue;
+						}
+						
+						// 其他元数据事件也忽略
+						if (jsonData.eventType || jsonData.event) {
+							console.log('[鸿蒙端] 跳过元数据事件');
+							continue;
+						}
+					} catch (e) {
+						console.log('[鸿蒙端] JSON 解析失败,作为普通文本处理');
+						// 不是 JSON,继续处理
+					}
+				}
+				
+				// 检查是否是 Thinking 内容
+				if (data.includes('<details') && data.includes('<summary>')) {
+					console.log('[鸿蒙端] 发送 thinking 类型数据');
+					onStreamData({ type: 'thinking', content: data });
+				} else if (data) {
+					// 普通消息内容 - 逐行发送,不累积
+					console.log('[鸿蒙端] 发送 message 类型数据,长度:', data.length);
+					onStreamData({ type: 'message', content: data });
+				}
+			}
+		}
+		
+		// 如果找到了 messageId,结束流式输出
+		if (messageId) {
+			console.log('[鸿蒙端] 数据处理完成,结束流式输出');
+			setTimeout(() => {
+				onStreamData({ type: 'end', id: messageId });
+			}, 300);
+		} else {
+			// 如果没有 messageId,延迟结束(兼容处理)
+			console.log('[鸿蒙端] 无 messageId,延迟结束');
+			setTimeout(() => {
+				if (isProcessing.value) {
+					onStreamData({ type: 'end', id: null });
+				}
+			}, 1000);
+		}
+	}
+	
+	// 停止鸿蒙端的 SSE 请求
+	const stopSSERequestNative = () => {
+		if (sseRequestTask) {
+			console.log('[鸿蒙端] 中止请求');
+			sseRequestTask.abort();
+			sseRequestTask = null;
+		}
 	}
 	
 	// 停止流式输出
 	const stopStreaming = () => {
 		console.log('用户中止流式输出');
 		
+		// #ifdef H5
 		// 通知 renderjs 停止
 		renderjsData.value = {
 			action: 'stop',
 			timestamp: Date.now()
 		};
+		// #endif
+		
+		// #ifdef APP-HARMONY
+		// 停止鸿蒙端的请求
+		stopSSERequestNative();
+		// #endif
 		
 		// 立即停止打字机效果并完成
 		finishStreaming();
@@ -633,7 +873,11 @@
 		const systemInfo = uni.getSystemInfoSync();
 		statusBarHeight.value = systemInfo.statusBarHeight || 20;
 		isIOS.value = systemInfo.platform === 'ios';
-		safeAreaBottom.value = systemInfo.safeAreaInsets ? (systemInfo.safeAreaInsets.bottom || 0) : 0;
+		
+		// 计算底部安全区域(包含 tabbar 高度)
+		const safeAreaInsetsBottom = systemInfo.safeAreaInsets ? (systemInfo.safeAreaInsets.bottom || 0) : 0;
+		// 转换为 rpx,并确保至少有基础间距
+		safeAreaBottom.value = Math.max(safeAreaInsetsBottom * 2, 0);
 
 		// 初始化消息
 		initMessages();
@@ -643,11 +887,15 @@
 			scrollToBottom();
 		});
 		
-		// 监听来自 renderjs 的自定义事件
-		window.addEventListener('renderjs-stream-data', (event) => {
-			console.log('通过事件接收到数据:', event.detail);
-			onStreamData(event.detail);
-		});
+		// 监听来自 renderjs 的自定义事件(仅在支持的平台)
+		// #ifdef H5 || APP-PLUS
+		if (typeof window !== 'undefined') {
+			window.addEventListener('renderjs-stream-data', (event) => {
+				console.log('通过事件接收到数据:', event.detail);
+				onStreamData(event.detail);
+			});
+		}
+		// #endif
 	})
 	
 	// 组件销毁时清理资源
@@ -666,11 +914,17 @@
 		// 清理消息队列
 		messageQueue.value = [];
 		
-		// 移除事件监听器
-		window.removeEventListener('renderjs-stream-data', onStreamData);
+		// 移除事件监听器(仅在支持的平台)
+		// #ifdef H5 || APP-PLUS
+		if (typeof window !== 'undefined') {
+			window.removeEventListener('renderjs-stream-data', onStreamData);
+		}
+		// #endif
 	})
 </script>
 
+
+<!--  #ifdef H5 || APP-PLUS -->
 <script module="renderModule" lang="renderjs">
 	// renderjs 模块状态
 	let eventSource = null;
@@ -718,9 +972,11 @@
 				
 				if (done) {
 					console.log('SSE 流结束');
-					window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
-						detail: { type: 'end', id: messageIdS }
-					}));
+					if (typeof window !== 'undefined') {
+						window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
+							detail: { type: 'end', id: messageIdS }
+						}));
+					}
 					break;
 				}
 				
@@ -738,12 +994,14 @@
 			
 		} catch (error) {
 			console.error('SSE 连接错误:', error);
-			window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
-				detail: { 
-					type: 'error', 
-					error: error.message || '连接失败' 
-				}
-			}));
+			if (typeof window !== 'undefined') {
+				window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
+					detail: { 
+						type: 'error', 
+						error: error.message || '连接失败' 
+					}
+				}));
+			}
 		} finally {
 			stopSSE();
 		}	
@@ -780,9 +1038,11 @@
 					if (jsonData.eventType === 'MESSAGE_END' || jsonData.event === 'message_end') {
 						console.log('[renderjs] 收到 MESSAGE_END 事件,流式结束');
 						messageIdS = jsonData.id;
-						window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
-							detail: { type: 'end', id: messageIdS }
-						}));
+						if (typeof window !== 'undefined') {
+							window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
+								detail: { type: 'end', id: messageIdS }
+							}));
+						}
 						return;
 					}
 					// 其他元数据事件也忽略
@@ -797,21 +1057,25 @@
 			if (data.includes('<details') && data.includes('<summary>')) {
 				console.log('[renderjs] 发送 thinking 类型数据');
 				// 使用自定义事件发送数据
-				window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
-					detail: {
-						type: 'thinking',
-						content: data
-					}
-				}));
+				if (typeof window !== 'undefined') {
+					window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
+						detail: {
+							type: 'thinking',
+							content: data
+						}
+					}));
+				}
 			} else {
 				console.log('[renderjs] 发送 message 类型数据,长度:', data.length);
 				// 普通消息内容
-				window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
-					detail: {
-						type: 'message',
-						content: data
-					}
-				}));
+				if (typeof window !== 'undefined') {
+					window.dispatchEvent(new CustomEvent('renderjs-stream-data', {
+						detail: {
+							type: 'message',
+							content: data
+						}
+					}));
+				}
 			}
 		}
 	}
@@ -851,6 +1115,7 @@
 		}
 	};
 </script>
+<!-- #endif -->
 
 <style>
 	/* 容器样式 */
@@ -991,7 +1256,7 @@
 	/* 输入区域 */
 	.input-container {
 		position: fixed;
-		bottom: 90rpx;
+		bottom: 0;
 		left: 0;
 		right: 0;
 		background-color: #fff;
@@ -999,7 +1264,8 @@
 		box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
 		display: flex;
 		flex-direction: column;
-		z-index: 10;
+		/* z-index: 999; */
+		/* 确保在 tabbar 之上 */
 	}
 
 	.input-wrapper {
@@ -1167,6 +1433,17 @@
 		color: #2E7D32;
 		font-weight: 500;
 	}
+	
+	/* AI免责声明 */
+	.ai-disclaimer {
+		display: block;
+		font-size: 20rpx;
+		color: #999;
+		margin-top: 12rpx;
+		padding-top: 8rpx;
+		border-top: 1rpx solid rgba(0, 0, 0, 0.05);
+		line-height: 1.4;
+	}
 
 	/* 欢迎消息特殊样式 */
 	.welcome {

+ 30 - 6
pages/knowledge/detail.vue

@@ -1,16 +1,22 @@
-<template>
+#<template>
   <view class="container">
     <!-- H5环境自定义导航栏 -->
     <view class="h5-custom-navbar" v-if="isH5">
-		
-<!--      <view class="h5-navbar-left" @click="goBack">
+      <view class="h5-navbar-left" @click="goBack">
         <view class="h5-back-icon">
           <view class="h5-arrow-left"></view>
         </view>
-      </view> -->
+      </view>
       <text class="h5-navbar-title">农业知识</text>
       <view class="h5-navbar-right"></view>
     </view>
+    
+    <!-- 非H5环境返回按钮 -->
+<!--    <view class="float-back-button" v-if="!isH5" @click="goBack">
+      <view class="float-back-icon">
+        <view class="h5-arrow-left"></view>
+      </view>
+    </view> -->
     <!-- 加载中状态 -->
     <view class="loading-container" v-if="loading">
       <view class="spinner"></view>
@@ -516,6 +522,7 @@ onNavigationBarButtonTap((e) => {
   min-height: 100vh;
   position: relative;
   padding-bottom: 120rpx;
+  padding-top: 90rpx; /* 为H5导航栏留出空间 */
 }
 
 /* 加载状态 */
@@ -1088,8 +1095,25 @@ onNavigationBarButtonTap((e) => {
 }
 
 /* 删除浮动返回按钮样式 */
-.float-back-button, .float-back-icon {
-  display: none;
+.float-back-button {
+  position: fixed;
+  top: 30rpx;
+  left: 30rpx;
+  width: 80rpx;
+  height: 80rpx;
+  background-color: rgba(255, 255, 255, 0.9);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 100;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+}
+
+.float-back-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
 /* H5导航栏样式 */

+ 274 - 12
pages/login/index.vue

@@ -19,15 +19,36 @@
 				<view class="input-group">
 					<input type="text" v-model="phoneNumber" placeholder="请输入手机号" class="input-field" maxlength="11" />
 				</view>
-				<view class="input-group">
+				
+				<!-- 密码登录 -->
+				<view v-if="loginType === 'password'" class="input-group">
 					<input type="password" v-model="password" placeholder="请输入密码" class="input-field" />
 				</view>
-				<view class="form-actions">
-					<text class="forgot-password" @click="navigateToForgetPassword">忘记密码?</text>
+				
+				<!-- 验证码登录 -->
+				<view v-if="loginType === 'code'" class="input-group verification-group">
+					<input type="text" v-model="verificationCode" placeholder="请输入验证码" class="input-field code-input" maxlength="6" />
+					<button 
+						class="send-code-btn" 
+						:disabled="countdown > 0" 
+						@click="sendVerificationCode"
+					>
+						{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
+					</button>
 				</view>
-				<button type="primary" class="login-btn" @click="handlePhoneLogin">登录</button>
-				<view class="register-link">
-					还没有账号?<text class="link" @click="navigateToRegister">立即注册</text>
+				
+				<!-- <view class="form-actions">
+					<text v-if="loginType === 'password'" class="forgot-password" @click="navigateToForgetPassword">忘记密码?</text>
+				</view> -->
+				<button  class="login-btn" @click="handleLogin">登录</button>
+				
+				<!-- 底部操作区 -->
+				<view class="bottom-actions">
+					<text class="action-link" @click="switchLoginType(loginType === 'password' ? 'code' : 'password')">
+						{{ loginType === 'password' ? '验证码登录' : '密码登录' }}
+					</text>
+					<text class="action-link" @click="navigateToRegister">注册账号</text>
+					<text v-if="loginType === 'password'" class="action-link" @click="navigateToForgetPassword">忘记密码</text>
 				</view>
 			</view>
 			<!-- #endif -->
@@ -55,11 +76,45 @@
 <script setup>
 	import { ref, onMounted } from 'vue'
 	import {
-		mpAutoLogin, phoneLogin
+		mpAutoLogin, phoneLogin, phoneCodeLogin, sendVcode, sendPhoneVcodeNoUnique 
 	} from "@/api/services/connect.js";
+	import { fetchUserFieldList } from "@/api/services/field.js";
 	import storage from "@/utils/storage.js";
 	import Foundation from "@/utils/Foundation.js";
 
+	// 获取并设置默认地块信息
+	const fetchAndSetDefaultField = async () => {
+		try {
+			const res = await fetchUserFieldList(1, 10);
+			console.log("获取地块列表", res);
+			
+			if (res.data && res.data.code === 200 && res.data.data && res.data.data.list && res.data.data.list.length > 0) {
+				const firstField = res.data.data.list[0];
+				
+				// 保存默认地块到本地存储
+				storage.setPlots(JSON.stringify({
+					id: firstField.id,
+					code: firstField.code,
+					name: firstField.name,
+					growCrops: firstField.crop,
+					managerName: firstField.manager,
+					size: firstField.area,
+					farmId: firstField.farmId,
+					timestamp: Date.now()
+				}));
+				
+				console.log("已设置默认地块:", firstField.name);
+				return true;
+			} else {
+				console.log("暂无可用地块");
+				return false;
+			}
+		} catch (err) {
+			console.error("获取地块列表失败", err);
+			return false;
+		}
+	};
+
 	// Reactive data
 	const agreed = ref(false)
 	const code = ref("")
@@ -68,9 +123,13 @@
 	const gender = ref("")
 	const phoneNumber = ref("")
 	const password = ref("")
+	const verificationCode = ref("")
+	const loginType = ref("password") // 'password' 或 'code'
+	const countdown = ref(0)
 	const loading = ref(false)
 	const logingFlag = ref(false)
 	const isLogin = ref(0)
+	let timer = null
 
 	// Lifecycle hooks - uni-app specific lifecycle
 	onMounted(() => {
@@ -107,14 +166,14 @@
 
 	const navigateToTerms = () => {
 		uni.navigateTo({
-			url: '/pages/privacy/terms'
+			url: '/pages/login/terms'
 		});
 	}
 
 	const navigateToPrivacy = () => {
 		uni.navigateTo({
-			url: '/pages/privacy/index'
-		});
+			url: '/pages/login/privacy'
+		})
 	}
 
 	const navigateToForgetPassword = () => {
@@ -129,6 +188,155 @@
 		});
 	}
 
+	// 切换登录方式
+	const switchLoginType = (type) => {
+		loginType.value = type;
+		// 清空输入
+		password.value = "";
+		verificationCode.value = "";
+	}
+
+	// 发送验证码
+	const sendVerificationCode = () => {
+		if (!phoneNumber.value) {
+			uni.showToast({
+				title: "请输入手机号",
+				icon: 'none'
+			});
+			return;
+		}
+
+		// 手机号验证
+		const result = Foundation.validatePhoneNumber(phoneNumber.value);
+		if (!result.valid) {
+			uni.showToast({
+				title: result.message,
+				icon: 'none'
+			});
+			return;
+		}
+
+		// 发送验证码
+		uni.showLoading({ title: '发送中...' }); 
+		sendPhoneVcodeNoUnique({ terminal: phoneNumber.value,scene:'LOGIN' })
+			.then(res => {
+				console.log("发送验证码", res);
+				if (res.data.code === 200) {
+					uni.showToast({
+						title: "验证码已发送",
+						icon: 'success'
+					});
+					// 开始倒计时
+					countdown.value = 60;
+					timer = setInterval(() => {
+						countdown.value--;
+						if (countdown.value <= 0) {
+							clearInterval(timer);
+						}
+					}, 1000);
+				} else {
+					uni.showToast({
+						title: res.data.msg || "发送失败",
+						icon: 'none'
+					});
+				}
+			})
+			.catch(err => {
+				console.error(err);
+				uni.showToast({
+					title: "网络异常,请稍后重试",
+					icon: 'none'
+				});
+			})
+			.finally(() => {
+				uni.hideLoading();
+			});
+	}
+
+	// 统一登录处理
+	const handleLogin = () => {
+		if (loginType.value === 'password') {
+			handlePhoneLogin();
+		} else {
+			handleCodeLogin();
+		}
+	}
+
+	// 验证码登录
+	const handleCodeLogin = () => {
+		if (!agreed.value) {
+			uni.showToast({
+				title: "请同意用户协议和隐私政策",
+				icon: 'none'
+			});
+			return;
+		}
+
+		if (!phoneNumber.value || !verificationCode.value) {
+			uni.showToast({
+				title: "请输入手机号和验证码",
+				icon: 'none'
+			});
+			return;
+		}
+
+		// 手机号验证
+		const result = Foundation.validatePhoneNumber(phoneNumber.value);
+		if (!result.valid) {
+			uni.showToast({
+				title: result.message,
+				icon: 'none'
+			});
+			return;
+		}
+
+		loading.value = true;
+		uni.showLoading({ title: '登录中...' });
+		
+		const data = {
+			phonenumber: phoneNumber.value,
+			vcode: verificationCode.value
+		};
+
+		phoneCodeLogin(data).then(async res => {
+			console.log("验证码登录", res);
+			if (res.data.code === 200) {
+				storage.setAccessToken(res.data.data.access_token);
+				storage.setUserInfo(res.data.data.userInfo);
+				storage.setHasLogin(true);
+
+				await fetchAndSetDefaultField();	
+
+				uni.showToast({
+					title: "登录成功!",
+					icon: "success",
+				});
+
+				setTimeout(() => {
+					uni.navigateBack({
+						delta: 1,
+					});
+				}, 1500);
+			} else {
+				uni.showToast({
+					title: res.data.msg || "登录失败,请检查验证码",
+					icon: 'none'
+				});
+			}
+		})
+		.catch(err => {
+			uni.showToast({
+				title: "网络异常,请稍后重试",
+				icon: 'none'
+			});
+			console.error(err);
+		})
+		.finally(() => {
+			uni.hideLoading();
+			loading.value = false;
+		});
+	}
+
 	// H5平台手机号密码登录方法
 	const handlePhoneLogin = () => {
 		if (!agreed.value) {
@@ -166,7 +374,7 @@
 			password: password.value,
 			phoneNumber: phoneNumber.value,
 		}
-		phoneLogin(data).then(res => {
+		phoneLogin(data).then(async res => {
 			console.log("res登录", res);
 			if (res.data.code === 200) {
 				// 登录成功
@@ -174,6 +382,9 @@
 				storage.setUserInfo(res.data.data.userInfo);
 				storage.setHasLogin(true);
 
+				// 获取并设置默认地块信息
+				await fetchAndSetDefaultField();
+
 				uni.showToast({
 					title: "登录成功!",
 					icon: "success",
@@ -243,7 +454,7 @@
 						avatarUrl: avatarUrlVal,
 						nickName: nickNameVal,
 						gender: genderVal,
-					}).then((apiRes) => {
+					}).then(async (apiRes) => {
 						console.log("apiRes", apiRes);
 						storage.setAccessToken(apiRes.data.data.token);
 						// storage.setRefreshToken(apiRes.data.result.refreshToken);
@@ -255,6 +466,10 @@
 						//存储用户信息
 						storage.setUserInfo(apiRes.data.data.userInfo);
 						storage.setHasLogin(true);
+						
+						// 获取并设置默认地块信息
+						await fetchAndSetDefaultField();
+						
 						// 用户手动授权头像昵称
 						if (apiRes.data.data.isNewUser) {
 							// 新用户跳转上传头像昵称界面
@@ -361,6 +576,40 @@
 		width: 100%;
 	}
 
+	.verification-group {
+		display: flex;
+		align-items: center;
+		gap: 20rpx;
+	}
+
+	.code-input {
+		flex: 1;
+	}
+
+	.send-code-btn {
+		width: 200rpx;
+		height: 90rpx;
+		background-color: #4CAF50;
+		color: #fff;
+		border-radius: 45rpx;
+		font-size: 24rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		border: none;
+		padding: 0;
+		line-height: 90rpx;
+	}
+
+	.send-code-btn[disabled] {
+		background-color: #ccc;
+		color: #999;
+	}
+
+	.send-code-btn::after {
+		border: none;
+	}
+
 	.input-field {
 		width: 100%;
 		height: 90rpx;
@@ -397,6 +646,19 @@
 		margin-bottom: 30rpx;
 	}
 
+	.bottom-actions {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin-top: 20rpx;
+	}
+
+	.action-link {
+		font-size: 26rpx;
+		color: #666;
+		margin: 0 20rpx;
+	}
+
 	.register-link {
 		font-size: 24rpx;
 		color: #666;

+ 293 - 0
pages/login/privacy.vue

@@ -0,0 +1,293 @@
+<template>
+  <view class="privacy-container">
+    <view class="page-header">
+      <text class="back-icon" @click="goBack">←</text>
+      <text class="page-title">隐私政策</text>
+      <text class="placeholder"></text>
+    </view>
+    
+    <scroll-view class="content" scroll-y>
+      <view class="privacy-content">
+        <view class="icon-section">
+          <text class="icon-text">🛡️</text>
+        </view>
+        
+        <view class="title">关于农小禹与隐私的声明</view>
+        <view class="date">
+          <text class="font-secondary">更新日期:</text>
+          <text class="font-secondary">2026.2.5</text>
+        </view>
+
+        <view class="paragraph">
+          <text class="bold">农小禹</text> 是由 <text class="bold">江苏格拔地智能科技有限公司</text> (以下简称"我们")为您提供的,用于<text class="bold">面向农户的农业生产管理应用,用于记录农事操作、设备运行及相关数据,并提供农技知识查询、农资信息浏览和农产品交易信息等服务。</text>的应用。本隐私声明由我们为处理您的个人信息而制定。
+        </view>
+
+        <view class="paragraph">
+          我们非常重视您的个人信息和隐私保护,将会按照法律要求和业界成熟的安全标准,为您的个人信息提供相应的安全保护措施。
+        </view>
+
+        <view class="section">
+          <view class="section-title">1. 我们如何收集和使用您的个人信息</view>
+          <view class="paragraph">
+            我们仅在有合法性基础的情形下才会使用您的个人信息。根据适用的法律,我们可能会基于您的同意、为履行/订立您与我们的合同所必需、履行法定义务所必需等合法性基础,使用您的个人信息。
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">1.1 基本功能服务和全量功能服务</view>
+            <view class="paragraph">
+              我们为您提供了基本功能服务和全量功能服务,您可以前往个人中心选择是否注册农小禹账号进行选择,不同设备或版本其路径可能存在差异,具体以您当前使用的设备为准。在基本功能服务下不收集个人信息,仅提供农技知识、农资商城浏览、农品交易信息查看等基本功能,不提供农事管理、设备管理、地块管理等其他附加功能,这可能会影响您的使用体验。
+            </view>
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">1.2 注册、设备管理、农品交易等功能</view>
+            <view class="paragraph">
+              我们为您提供下述注册、设备管理、农品交易等功能在您使用相关业务的过程中,我们会处理所必需的信息,以便履行我们的合同义务若您不提供相关信息,会影响到您使用本应用的相关功能/服务。
+            </view>
+            <view class="paragraph">
+              为了<text class="bold">实现应用功能</text>,在获取您的同意后我们需要收集您的<text class="bold">GPS 位置、</text><text>其他大致位置信息、</text><text>账号信息、</text><text>电话号码</text>。
+            </view>
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">1.3 基于履行法定义务</view>
+            <view class="paragraph">
+              基于履行法定义务或其他法律法规规定的情形,我们可能会处理您的以下个人信息:
+            </view>
+            <view class="paragraph">
+              为了<text>实现应用功能</text>,在获取您的同意后我们需要收集您的<text>账号信息、</text><text>电话号码</text>。
+            </view>
+            <view class="paragraph">
+              用于账号注册后,可以使用农小禹的全部功能
+            </view>
+            <view class="paragraph">
+              为了<text class="bold">实现应用功能</text>,在获取您的同意后我们需要收集您的<text class="bold">GPS 位置、</text><text>其他大致位置信息</text>。
+            </view>
+            <view class="paragraph">
+              用于割草机的状态监控和正常使用。
+            </view>
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">2. 设备权限调用</view>
+          <view class="paragraph">
+            <text class="bold">位置(获取设备位置信息、获取设备模糊位置信息)</text>
+          </view>
+          <view class="paragraph">
+            用于设备管理中割草机设备的状态监控和正常使用
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">3. 管理您的个人信息</view>
+          <view class="paragraph">
+            关于您的个人信息,您可以通过以下方式,行使查阅、复制、更正、删除等法定权利。
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">3.1 信息访问</view>
+            <view class="paragraph">
+              您可以前往个人中心,访问您的账号信息。
+            </view>
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">3.2 信息更正</view>
+            <view class="paragraph">
+              当您需要更新您的个人信息,或发现我们处理您的个人信息有误时,您有权作出更正或更新。
+            </view>
+            <view class="paragraph">
+              您可以前往个人中心—联系客服,访问并修改您的账号信息。
+            </view>
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">3.3 信息删除</view>
+            <view class="paragraph">
+              您可以前往个人中心—联系客服,以删除您的账号信息。
+            </view>
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">3.4 撤销同意</view>
+            <view class="paragraph">
+              如果您需撤销账号信息处理您的信息,您可以前往个人中心—联系客服来撤销您的同意。
+            </view>
+          </view>
+
+          <view class="subsection">
+            <view class="subsection-title">3.5 注销服务</view>
+            <view class="paragraph">
+              您可以前往个人中心—联系客服注销此前注册的账户。在注销账户之后,我们将停止为您提供产品或服务,并依据您的要求,删除您的个人信息,法律法规另有规定的除外。
+            </view>
+          </view>
+
+          <view class="paragraph">
+            如您对您的数据主体权利有进一步要求或存在任何疑问、意见或建议,可通过本声明中"如何联系我们"章节中所述方式与我们取得联系,并行使您的相关权利。
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">4. 信息存储地点及期限</view>
+          <view class="paragraph">
+            4.1 我们承诺,除法律法规另有规定外,我们对您的信息的保存期限应当为实现处理目的所必要的最短时间。
+          </view>
+          <view class="paragraph">
+            4.2 上述信息将会传输并保存至中国境内的服务器。
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">5. 如何联系我们</view>
+          <view class="paragraph">
+            您可通过以下方式联系我们,并行使您的相关权利,我们会尽快回复。
+          </view>
+          <view class="paragraph">
+            <text class="bold">电话:</text>
+            <text>13379508760</text>
+          </view>
+          <view class="paragraph">
+            如果您对我们的回复不满意, 特别是当个人信息处理行为损害了您的合法权益时您还可以通过向有管辖权的人民法院提起诉讼、向行业自律协会或政府相关管理机构投诉等外部途径进行解决。 您也可以向我们了解可能适用的相关投诉途径的信息。
+          </view>
+        </view>
+
+        <view class="footer">
+          <text class="font-secondary">生效日期:</text>
+          <text class="font-secondary">2026.2.5</text>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+const goBack = () => {
+  uni.navigateBack()
+}
+</script>
+
+<style scoped>
+.privacy-container {
+  min-height: 100vh;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 40rpx;
+  border-bottom: 1rpx solid #E0E0E0;
+}
+
+.back-icon {
+  font-size: 50rpx;
+  color: #333;
+  width: 60rpx;
+}
+
+.page-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.placeholder {
+  width: 60rpx;
+}
+
+.content {
+  flex: 1;
+  height: calc(100vh - 120rpx);
+}
+
+.privacy-content {
+  padding: 40rpx;
+}
+
+.icon-section {
+  text-align: center;
+  margin-bottom: 20rpx;
+}
+
+.icon-text {
+  font-size: 80rpx;
+}
+
+.title {
+  font-size: 36rpx;
+  font-weight: bold;
+  text-align: center;
+  margin-bottom: 20rpx;
+  color: #333;
+}
+
+.date {
+  text-align: center;
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 40rpx;
+  display: flex;
+  flex-direction: column;
+  line-height: 1.8;
+}
+
+.font-secondary {
+  color: #666;
+}
+
+.section {
+  margin-bottom: 40rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+  margin-top: 40rpx;
+}
+
+.subsection {
+  margin-top: 30rpx;
+}
+
+.subsection-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+
+.paragraph {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.8;
+  text-align: justify;
+  margin-bottom: 20rpx;
+}
+
+.bold {
+  font-weight: bold;
+  color: #333;
+}
+
+.footer {
+  text-align: right;
+  margin-top: 60rpx;
+  margin-bottom: 40rpx;
+  font-size: 24rpx;
+}
+
+/* #ifdef H5 */
+@media screen and (min-width: 768px) {
+  .privacy-content {
+    max-width: 800rpx;
+    margin: 0 auto;
+  }
+}
+/* #endif */
+</style>

+ 72 - 10
pages/login/register.vue

@@ -20,7 +20,7 @@
         </view>
       </view>
       
-<!--      <view class="form-group">
+     <view class="form-group">
         <view class="input-group">
           <text class="input-icon">验</text>
           <input 
@@ -38,7 +38,7 @@
             {{ codeBtnText }}
           </text>
         </view>
-      </view> -->
+      </view>
       
       <view class="form-group">
         <view class="input-group">
@@ -115,13 +115,14 @@
 <script setup>
 import { ref, reactive } from 'vue'
 import storage from "@/utils/storage.js"
-import { register } from '@/api/services/connect.js'
+import { register,sendVcode, phoneRegister } from '@/api/services/connect.js'
 import Foundation from "@/utils/Foundation.js"
 
 // 响应式数据
 const formData = reactive({
   phone: '',
   code: '',
+  scene: "REGIST",
   password: '',
   confirmPassword: '',
   nickname: ''
@@ -129,7 +130,7 @@ const formData = reactive({
 
 const showPassword = ref(false)
 const showConfirmPassword = ref(false)
-const agreed = ref(true)
+const agreed = ref(false)
 const codeBtnText = ref('获取验证码')
 const codeBtnDisabled = ref(false)
 const countdown = ref(60)
@@ -158,13 +159,13 @@ const navigateToLogin = () => {
 
 const navigateToTerms = () => {
   uni.navigateTo({
-    url: '/pages/privacy/terms'
+    url: '/pages/login/terms'
   })
 }
 
 const navigateToPrivacy = () => {
   uni.navigateTo({
-    url: '/pages/privacy/index'
+    url: '/pages/login/privacy'
   })
 }
 
@@ -185,6 +186,58 @@ const startCountdown = () => {
   }, 1000)
 }
 
+const getVerificationCode = () => {
+  if (!formData.phone) {
+    uni.showToast({
+      title: '请输入手机号',
+      icon: 'none'
+    })
+    return
+  }
+  
+  const phone = formData.phone
+  const result = Foundation.validatePhoneNumber(phone)
+
+  if (!result.valid) {
+    uni.showToast({
+      title: result.message,
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 调用获取验证码接口
+  uni.showLoading({ title: '发送验证码...' })
+  
+
+  sendVcode({
+    terminal: formData.phone,
+    scene: formData.scene
+  }).then(res => {
+    console.log("验证码:",res.data.code);
+    if (res.data.code == 500 || res.data.code == 1) {
+      uni.showToast({
+        title: res.data.msg || '发送验证码失败,请稍后重试',
+        icon: 'none'
+      })
+      return
+    }
+    uni.hideLoading()
+    uni.showToast({
+      title: '验证码已发送',
+      icon: 'success'
+    })
+    startCountdown()
+  }).catch(err => {
+    uni.hideLoading()
+    uni.showToast({
+      title: '发送验证码失败,请稍后重试',
+      icon: 'none'
+    })
+    console.error(err)
+  })
+}
+
 const validateForm = () => {
   if (!formData.phone) {
     uni.showToast({
@@ -249,16 +302,25 @@ const handleRegister = () => {
   uni.showLoading({ title: '注册中...' })
   
   const data = {
-    phoneNumber: formData.phone,
+    phonenumber: formData.phone,
     password: formData.password,
-    username: formData.nickname || `用户${formData.phone.substr(-4)}`
+    userName: formData.nickname || `用户${formData.phone.substr(-4)}`,
+    vcode: formData.code,
   }
   
   // 调用注册接口
-  register(data)
+  phoneRegister(data)
     .then(res => {
       console.log("res注册", res)
-      if (res.data.code === 200) {
+      if (res.data.code == 1 || res.data.code == 2) {
+        uni.showToast({
+          title: res.data.msg || '注册失败,请稍后重试',
+          icon: 'none'
+        })
+        return
+      }
+      
+      if (res.statusCode === 200) {
         uni.showToast({
           title: '注册成功',
           icon: 'success'

+ 222 - 0
pages/login/terms.vue

@@ -0,0 +1,222 @@
+<template>
+  <view class="terms-container">
+    <view class="page-header">
+      <text class="back-icon" @click="goBack">←</text>
+      <text class="page-title">用户协议</text>
+      <text class="placeholder"></text>
+    </view>
+    
+    <scroll-view class="content" scroll-y>
+      <view class="terms-content">
+        <view class="title">农小禹用户协议</view>
+        <view class="date">
+          <text>更新日期:2026 年 2 月 6 日</text>
+          <text>生效日期:2026 年 2 月 6 日</text>
+        </view>
+
+        <view class="paragraph">
+          欢迎您使用农小禹应用(以下简称"本应用")。
+          农小禹由江苏格拔地智能科技有限公司(以下简称"我们")提供。
+          请您在使用本应用前,认真阅读并充分理解本协议内容。
+        </view>
+
+        <view class="paragraph">
+          一旦您开始使用农小禹,即表示您已同意并接受本协议的全部内容。
+        </view>
+
+        <view class="section">
+          <view class="section-title">一、协议适用范围</view>
+          <view class="paragraph">本协议适用于您通过农小禹应用使用的全部功能与服务,包括但不限于:</view>
+          <view class="list">
+            <view class="list-item">• 农事记录与管理</view>
+            <view class="list-item">• 设备与数据记录</view>
+            <view class="list-item">• 农技知识与知识库内容浏览</view>
+            <view class="list-item">• 农资信息、农品交易信息展示</view>
+            <view class="list-item">• 相关数据分析与服务支持功能</view>
+          </view>
+          <view class="paragraph">如您不同意本协议的任何内容,请立即停止使用本应用。</view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">二、账号说明</view>
+          <view class="list">
+            <view class="list-item">1. 本应用账号支持用户通过应用内注册流程自行创建,或由管理后台为用户创建并分配。用户应使用合法获得的账号和对应密码登录并使用本应用。</view>
+            <view class="list-item">2. 用户应妥善保管账号及密码信息,不得转让、出租、出借或以其他方式提供给他人使用。</view>
+            <view class="list-item">3. 因用户个人原因导致账号信息泄露、账号被他人使用或产生相关损失的,由用户自行承担相应责任。</view>
+            <view class="list-item">4. 如用户发现账号存在异常使用情况,应及时联系平台管理人员进行处理。</view>
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">三、用户使用规范</view>
+          <view class="paragraph">在使用农小禹过程中,您应遵守以下规定:</view>
+          <view class="list">
+            <view class="list-item">1. 不得利用本应用从事违法、违规或侵害他人合法权益的行为;</view>
+            <view class="list-item">2. 不得恶意篡改、伪造、虚假填写农事、设备或相关数据;</view>
+            <view class="list-item">3. 不得通过技术手段干扰、破坏本应用的正常运行;</view>
+            <view class="list-item">4. 不得擅自复制、传播本应用中的内容用于商业用途。</view>
+          </view>
+          <view class="paragraph">如您违反上述规定,我们有权视情况采取限制功能、暂停或终止账号使用等措施。</view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">四、数据与内容说明</view>
+          <view class="list">
+            <view class="list-item">1. 您在使用本应用过程中录入的农事、设备等数据,主要用于功能展示、统计分析和服务优化。</view>
+            <view class="list-item">2. 本应用中的分析结果、建议内容仅作为辅助参考,不构成任何形式的生产、经营或收益承诺。</view>
+            <view class="list-item">3. 农技知识、知识库内容来源于公开资料或合作渠道,仅供学习参考。</view>
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">五、服务的变更与中断</view>
+          <view class="list">
+            <view class="list-item">1. 我们有权根据实际情况,对应用功能进行调整、升级或优化。</view>
+            <view class="list-item">2. 因系统维护、升级或不可抗力因素导致服务中断的,我们将尽力提前告知,但不承担由此产生的直接或间接损失。</view>
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">六、知识产权声明</view>
+          <view class="list">
+            <view class="list-item">1. 农小禹应用及其相关内容(包括但不限于界面设计、功能结构、文本内容等)的知识产权,均归我们或相关权利人所有。</view>
+            <view class="list-item">2. 未经许可,任何单位或个人不得擅自使用、复制或传播。</view>
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">七、协议的修改</view>
+          <view class="paragraph">
+            我们可能根据业务调整或法律法规要求,对本协议内容进行更新。
+            协议更新后,将在应用内进行提示或公示,更新内容自公布之日起生效。
+          </view>
+        </view>
+
+        <view class="section">
+          <view class="section-title">八、其他</view>
+          <view class="list">
+            <view class="list-item">1. 本协议的订立、执行及解释均适用中华人民共和国法律。</view>
+            <view class="list-item">2. 如本协议部分条款无效,不影响其他条款的效力。</view>
+            <view class="list-item">3. 如您对本协议有任何疑问,可通过应用内反馈渠道与我们联系。</view>
+          </view>
+        </view>
+
+        <view class="paragraph footer">
+          再次感谢您使用农小禹。
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+const goBack = () => {
+  uni.navigateBack()
+}
+</script>
+
+<style scoped>
+.terms-container {
+  min-height: 100vh;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 40rpx;
+  border-bottom: 1rpx solid #E0E0E0;
+}
+
+.back-icon {
+  font-size: 50rpx;
+  color: #333;
+  width: 60rpx;
+}
+
+.page-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.placeholder {
+  width: 60rpx;
+}
+
+.content {
+  flex: 1;
+  height: calc(100vh - 120rpx);
+}
+
+.terms-content {
+  padding: 40rpx;
+}
+
+.title {
+  font-size: 36rpx;
+  font-weight: bold;
+  text-align: center;
+  margin-bottom: 20rpx;
+  color: #333;
+}
+
+.date {
+  text-align: center;
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 40rpx;
+  display: flex;
+  flex-direction: column;
+  line-height: 1.8;
+}
+
+.section {
+  margin-bottom: 40rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.paragraph {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.8;
+  text-align: justify;
+  margin-bottom: 20rpx;
+}
+
+.list {
+  padding-left: 20rpx;
+}
+
+.list-item {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.8;
+  margin-bottom: 16rpx;
+  text-align: justify;
+}
+
+.footer {
+  text-align: center;
+  margin-top: 40rpx;
+  margin-bottom: 40rpx;
+}
+
+/* #ifdef H5 */
+@media screen and (min-width: 768px) {
+  .terms-content {
+    max-width: 800rpx;
+    margin: 0 auto;
+  }
+}
+/* #endif */
+</style>

+ 7 - 6
pages/service/mall-detail.vue

@@ -79,16 +79,17 @@
 <script setup>
 import { ref, onMounted } from 'vue'
 import { getMallById } from '@/api/services/mall.js'
+import { onLoad } from '@dcloudio/uni-app'
 
 const goodsId = ref('')
 const goodsDetail = ref({})
 const goodsImages = ref([])
 
 // 页面加载
-onMounted(() => {
-  const pages = getCurrentPages()
-  const currentPage = pages[pages.length - 1]
-  const options = currentPage.options
+onLoad((options) => {
+  // const pages = getCurrentPages()
+  // const currentPage = pages[pages.length - 1]
+  // const options = currentPage.options
   
   if (options.id) {
     goodsId.value = options.id
@@ -161,13 +162,13 @@ const previewImage = (image, index) => {
 const handleConsult = () => {
   uni.showModal({
     title: '咨询服务',
-    content: '您可以通过以下方式联系我们:\n1. 拨打客服热线:400-888-8888\n2. 在线客服(工作时间:9:00-18:00)',
+    content: '您可以通过以下方式联系我们:\n1. 拨打客服热线:13379508760\n2. 在线客服(工作时间:9:00-18:00)',
     confirmText: '拨打电话',
     cancelText: '取消',
     success: (res) => {
       if (res.confirm) {
         uni.makePhoneCall({
-          phoneNumber: '400-888-8888'
+          phoneNumber: '13379508760'
         })
       }
     }

+ 1 - 0
pages/service/mall.vue

@@ -178,6 +178,7 @@ const loadMore = () => {
 const switchCategory = (categoryId) => {
   activeCategory.value = categoryId
   goodsList.value = []
+  pageNum.value = 1
   loadMallData(searchKeyword.value.trim())
   nextTick(() => {
     scrollToActiveCategory(categoryId)

+ 15 - 5
pages/service/sales-detail.vue

@@ -173,6 +173,7 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue'
+import { onShow, onLoad } from '@dcloudio/uni-app'
 import LocationPicker from "@/components/common/LocationPicker.vue"
 import { getProductInfoById, editProductInfo } from '@/api/services/productInfo.js'
 import { useDict } from '@/utils/composables/useDict'
@@ -229,11 +230,19 @@ const shouldShowActionButtons = computed(() => {
 })
 
 // 页面显示时刷新数据
-const onShow = () => {
+onShow(() => {
   if (productInfo.value.id) {
     loadProductDetail(productInfo.value.id)
   }
-}
+})
+onLoad((options)=>{
+	// 获取页面参数
+	if (options.id) {
+	    source.value = options.source || '';
+	    productStatus.value = options.status || '';
+	    loadProductDetail(options.id, options.type);
+	}
+})
 
 // 页面加载
 onMounted(() => {
@@ -259,10 +268,11 @@ const handleAction = (action) => {
 }
 
 const getDictLabel = (dictKey, value) => {
-  if (!dictData.value || !dictData.value[dictKey]) {
+  if (!dictData[dictKey]) {
     return ''
   }
-  const list = dictData.value[dictKey] || []
+  
+  const list = dictData[dictKey] || []
   const item = list.find(u => u.dictValue == value)
   return item ? item.dictLabel : ''
 }
@@ -470,7 +480,7 @@ const handleCancelProduct = () => {
 
 // 获取真实电话号码
 const getRealPhoneNumber = () => {
-  return '18812341234'
+  return '13379508760'
 }
 
 // 导出 onShow 供 uni-app 使用

+ 3 - 3
pages/user/index.vue

@@ -113,13 +113,13 @@ const serviceList = ref([
 		path: '/pages/service/sales'
 	},
 	{
-		name: '农事',
-		iconText: '',
+		name: '农事记录',
+		iconText: '',
 		iconSvg: '/static/icons/activity-active.png',
 		path: '/pages/activity/index'
 	},
 	{
-		name: '设备',
+		name: '设备管理',
 		iconText: '备',
 		iconSvg: '/static/icons/device-active.png',
 		path: '/pages/device/index'

BIN
static/icons/ai-64.png


+ 2 - 2
utils/request.js

@@ -163,11 +163,11 @@ http.interceptors.response.use(
 		}
 
 		// 未登录也跳转
-		if ((!token && !storage.getAccessToken() && response.statusCode === 401) || response.data.code === 401) {
+		if ((!token && !storage.getAccessToken() && response.statusCode === 401) || response.data.code === 401 || response.data.code === 403) {
 			console.log('无token,跳转登录');
 			cleanStorage();
 			uni.showToast({
-				title: '请先登录',
+				title: `请先登录${storage.getAccessToken()}`,
 				icon: 'none',
 				duration: 2000,
 				complete: () => {

+ 9 - 1
vite.config.js

@@ -31,11 +31,19 @@ server: {
     proxy: {
       // H5 开发环境代理配置
       '/base': {
-        target: 'http://localhost:8080',
+        // target: 'http://localhost:8080',
+		target:'https://nxy.gbdfarm.com:9000/pro-uniapp',
         changeOrigin: true,
         secure: false,
         rewrite: (path) => path.replace(/^\/base/, ''),
       },
+      // 腾讯地图API代理配置(解决CORS跨域问题)
+      '/tencent-map-api': {
+        target: 'https://apis.map.qq.com',
+        changeOrigin: true,
+        secure: false,
+        rewrite: (path) => path.replace(/^\/tencent-map-api/, ''),
+      },
     },
   },
   build: {