Ver código fonte

Merge branch 'master' of http://code.nyzhwlw.com:10202/yf_lj/bigDataApp

allen 1 semana atrás
pai
commit
025c05ec28
11 arquivos alterados com 1687 adições e 399 exclusões
  1. 39 29
      App.vue
  2. 4 0
      main.js
  3. 51 0
      mixins/privacyMixin.js
  4. 36 31
      pages.json
  5. 152 68
      pages/login/agreement.vue
  6. BIN
      pages/login/assets/logo.png
  7. 374 257
      pages/login/login.vue
  8. 665 0
      pages/login/loginOld.vue
  9. 256 0
      pages/privacy/privacy.vue
  10. 34 14
      util/api.js
  11. 76 0
      util/privacyConfig.js

+ 39 - 29
App.vue

@@ -1,35 +1,45 @@
 <script>
-	import { fetchPermissionList } from './util/QueryPermission.js';
+import { fetchPermissionList } from './util/QueryPermission.js';
 
-	export default {
-		onLaunch: function() {
-			// 已登录时提前拉取权限列表,保证“未经过首页”等特殊入口也能拿到权限数据(非阻塞)
-			if (uni.getStorageSync('session_key')) {
-				fetchPermissionList();
-			}
-		},
-		onShow: function() {
-			console.log('App Show')
-		},
-		onHide: function() {
-			console.log('App Hide')
-		}
-	}
+export default {
+  onLaunch: function () {
+    // 已同意隐私协议 → 执行业务初始化(拉取权限列表等)
+    // 未同意 → 等待页面层(如 login.vue)弹出自定义隐私弹窗,同意后再初始化
+    if (uni.getStorageSync('privacy_agreed')) {
+      this.doAfterPrivacy();
+    }
+  },
+  methods: {
+    // 隐私协议同意后才执行的业务初始化(可被页面层调用)
+    doAfterPrivacy() {
+      // 已登录时提前拉取权限列表,保证"未经过首页"等特殊入口也能拿到权限数据(非阻塞)
+      if (uni.getStorageSync('session_key')) {
+        fetchPermissionList();
+      }
+    },
+  },
+  onShow: function () {
+    console.log('App Show');
+  },
+  onHide: function () {
+    console.log('App Hide');
+  },
+};
 </script>
 <style lang="scss">
-	@import "./static/font/iconfont.css";
-	@import "./static/iconfont/iconfont.css";
-	html {
-		box-sizing: border-box;
-		font-size: 28rpx !important;
-	}
+@import './static/font/iconfont.css';
+@import './static/iconfont/iconfont.css';
+html {
+  box-sizing: border-box;
+  font-size: 28rpx !important;
+}
 
-	.status_bar {
-		height: 44px;
-		width: 100%;
-		background-color: #FFFFFF;
-		position: fixed;
-		top: 0;
-		z-index: 99999;
-	}
+.status_bar {
+  height: 44px;
+  width: 100%;
+  background-color: #ffffff;
+  position: fixed;
+  top: 0;
+  z-index: 99999;
+}
 </style>

+ 4 - 0
main.js

@@ -9,6 +9,10 @@ Vue.config.productionTip = false;
 
 App.mpType = 'app';
 
+// 全局隐私协议检查 mixin(所有页面的 onShow 自动拦截)
+import privacyMixin from './mixins/privacyMixin.js';
+Vue.mixin(privacyMixin);
+
 import config from './util/neutral.js';
 Vue.prototype.$isneutral = config.isneutral;
 Vue.prototype.$imageURL = config.imageURL; // 线上图片服务器路径常量

+ 51 - 0
mixins/privacyMixin.js

@@ -0,0 +1,51 @@
+/**
+ * 全局隐私协议检查 mixin
+ * 注入所有页面的 onShow 生命周期
+ * 通过版本号对比判断是否需要重新弹窗,支持协议更新后自动重新同意
+ * 作用于:App 端 + 小程序端(H5 不拦截)
+ */
+import { needPrivacyPopup } from '@/util/privacyConfig.js';
+
+// 防止多次 reLaunch 冲突导致页面卡死
+let isReLaunching = false;
+
+export default {
+  onShow() {
+    // privacy 页面自身不拦截(避免死循环)
+    // 协议详情页也不拦截(用户从弹窗点"查看完整协议"跳过去时不能被 reLaunch 回来,否则白屏)
+    // 登录页也不拦截(登录本身不涉及隐私数据使用,用户应能自由访问;
+    //   若未同意隐私协议,登录成功进入主页时仍会被拦截到 privacy)
+    // 关于我们页也不拦截(纯静态信息展示,不涉及隐私数据使用)
+    // 注意:不能用 this.$route,vue-router 只在 H5 端存在
+    // 统一用 getCurrentPages() 获取当前页面路径(三端都支持)
+    const pages = getCurrentPages();
+    if (pages && pages.length > 0) {
+      const current = pages[pages.length - 1];
+      const route = '/' + (current.route || '');
+
+      if (
+        route.indexOf('/pages/privacy/') !== -1 ||
+        route.indexOf('/pages/login/agreement') !== -1 ||
+        route.indexOf('/pages/login/login') !== -1 ||
+        route.indexOf('/pages/my/about/about') !== -1
+      ) {
+        return;
+      }
+    }
+
+    // App 端 + 小程序端:都拦截隐私协议(版本号不匹配时)
+    // #ifndef H5
+    if (needPrivacyPopup()) {
+      // 防抖锁:避免多个页面 onShow 同时触发 reLaunch 导致页面栈混乱
+      if (isReLaunching) return;
+      isReLaunching = true;
+      uni.reLaunch({
+        url: '/pages/privacy/privacy',
+        complete: () => {
+          isReLaunching = false;
+        },
+      });
+    }
+    // #endif
+  },
+};

+ 36 - 31
pages.json

@@ -12,6 +12,14 @@
       }
     },
     {
+      "path": "pages/privacy/privacy",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/login/agreement",
       "style": {
         "navigationBarTitleText": "用户隐私协议",
@@ -23,17 +31,17 @@
       "path": "pages/index/index",
       "style": {
         "navigationBarTitleText": "首页",
-				"navigationStyle": "custom",
+        "navigationStyle": "custom",
         "navigationBarBackgroundColor": "#00B075"
       }
     },
-		{
-			"path": "pages/index/developing",
-			"style": {
-				"navigationBarTitleText": "智能AI助理",
-				"enablePullDownRefresh": false
-			}
-		},
+    {
+      "path": "pages/index/developing",
+      "style": {
+        "navigationBarTitleText": "智能AI助理",
+        "enablePullDownRefresh": false
+      }
+    },
     {
       "path": "pages/cb/index/index",
       "style": {
@@ -257,7 +265,7 @@
       "style": {
         "navigationBarTitleText": "个人中心",
         "enablePullDownRefresh": false,
-				"navigationStyle": "custom",
+        "navigationStyle": "custom",
         "navigationBarBackgroundColor": "#00B075"
       }
     },
@@ -267,7 +275,7 @@
         "navigationBarTitleText": "设备列表",
         "navigationBarBackgroundColor": "#00B075",
         "enablePullDownRefresh": false,
-				"navigationStyle": "custom"
+        "navigationStyle": "custom"
       }
     },
     {
@@ -275,7 +283,7 @@
       "style": {
         "navigationBarTitleText": "信息修改",
         "enablePullDownRefresh": false,
-				"navigationStyle": "custom"
+        "navigationStyle": "custom"
       }
     },
     {
@@ -1013,22 +1021,22 @@
         "navigationStyle": "custom"
       }
     },
-		{
-			"path": "pages/server/index",
-			"style": {
-				"navigationBarTitleText": "更多服务",
-				"navigationStyle": "custom",
-				"enablePullDownRefresh": false
-			}
-		},
-		{
-			"path": "pages/banner/index",
-			"style": {
-				"navigationBarTitleText": "水肥",
-				"navigationStyle": "custom",
-				"enablePullDownRefresh": false
-			}
-		},
+    {
+      "path": "pages/server/index",
+      "style": {
+        "navigationBarTitleText": "更多服务",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/banner/index",
+      "style": {
+        "navigationBarTitleText": "水肥",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
     {
       "path": "pages/cb/zhamenFirst/zhamenzs",
       "style": {
@@ -1331,7 +1339,7 @@
         "navigationStyle": "custom"
       }
     },
-    
+
     {
       "path": "pages/deviceDetails/weatherStation1/index",
       "style": {
@@ -1368,7 +1376,6 @@
       }
     },
 
-    
     {
       "path": "pages/deviceDetails/weatherStation2/index",
       "style": {
@@ -1405,8 +1412,6 @@
       }
     },
 
-
-    
     {
       "path": "pages/deviceDetails/SoilMoisturelist/index",
       "style": {

Diferenças do arquivo suprimidas por serem muito extensas
+ 152 - 68
pages/login/agreement.vue


BIN
pages/login/assets/logo.png


+ 374 - 257
pages/login/login.vue

@@ -1,98 +1,119 @@
 <template>
-  <view class="bg-img">
-    <!-- <view class="status_bar"></view> -->
-    <view class="apptitle" @longpress="logoTime">
-      {{ $isneutral ? '云飞智控' : '智控' }}
+  <view class="login-container">
+    <!-- 顶部渐变背景区域 -->
+    <view class="header-bg"></view>
+
+    <!-- Logo 区域(长按显示设置按钮) -->
+    <view class="logo-section" @longpress="logoTime">
+      <view class="logo-circle">
+        <image
+          src="./assets/logo.png"
+          class="logo-img"
+          mode="aspectFit"
+        ></image>
+      </view>
+      <text class="app-title">{{
+        $isneutral ? '智慧农业大数据平台' : '云飞智控'
+      }}</text>
     </view>
-    <view class="set" @click="set" v-if="setTF">
-      <u-icon name="setting-fill" size="50" color="#fff"></u-icon>
+
+    <!-- 设置按钮(长按 Logo 显示) -->
+    <view class="set-btn" @click="set" v-if="setTF">
+      <u-icon name="setting-fill" size="44" color="#24b35d"></u-icon>
     </view>
-    <view class="formbox">
+
+    <!-- 登录表单卡片 -->
+    <view class="form-card">
       <form @submit="formSubmit">
-        <view class="uni-form-item uni-column">
-          <view class="username">
-            <u-icon
-              name="account"
-              size="36"
-              style="margin-right: 30rpx; color: #fff"
-            ></u-icon>
-            <u-input
-              class="uni-input"
-              name="username"
-              v-model="formdata.username"
-              placeholder-class="icon iconfont icon-bianji1"
-              placeholder="请输入用户名"
-              placeholderStyle="color:#ffffff;"
-              color="#FFFFFF"
-              @blur="blur"
-            />
-          </view>
-          <view class="passwold">
-            <u-icon
-              name="lock"
-              size="36"
-              style="margin-right: 30rpx; color: #fff"
-            ></u-icon>
-            <u-input
-              v-model="formdata.passwold"
-              type="password"
-              :password-icon="true"
-              :clearable="false"
-              placeholder="请输入密码"
-              @confirm="formSubmit"
-              @input="passwoldddata"
-              placeholderStyle="color:#fff;"
-              suffixIconStyle="color:#fff;"
-              color="#fff"
-              class="uni-input"
-            />
-          </view>
-          <view class="aboutpass">
-            <u-checkbox-group>
-              <u-checkbox
-                v-model="checked"
-                :label-disabled="false"
-                size="28"
-                @change="rempass"
-                >记住密码</u-checkbox
-              >
-            </u-checkbox-group>
-          </view>
-          <view class="uni-btn-v">
-            <button form-type="submit" @click="denglu">登 录</button>
-          </view>
-          <!--用户隐私协议-->
-          <view class="agreement">
-            <u-checkbox-group>
-              <u-checkbox
-                v-model="agree"
-                shape="circle"
-                size="28"
-                active-color="#2979ff"
-                :label-disabled="false"
-                >我已阅读并同意
-              </u-checkbox>
-            </u-checkbox-group>
-            <text class="agree-link" @click="goAgreement"
-              >《用户隐私协议》</text
+        <!-- 账号输入 -->
+        <view class="form-item">
+          <u-icon name="account" size="36" color="#999"></u-icon>
+          <u-input
+            v-model="formdata.username"
+            class="form-input"
+            placeholder="请输入账号/手机号"
+            placeholderStyle="color:#BBBBBB;"
+            color="#333"
+            :border="false"
+            @blur="blur"
+          />
+        </view>
+
+        <!-- 密码输入 -->
+        <view class="form-item">
+          <u-icon name="lock" size="36" color="#999"></u-icon>
+          <u-input
+            v-model="formdata.passwold"
+            type="password"
+            :password-icon="true"
+            :clearable="false"
+            placeholder="请输入密码"
+            placeholderStyle="color:#BBBBBB;"
+            suffixIconStyle="color:#999;"
+            color="#333"
+            :border="false"
+            class="form-input"
+            @confirm="formSubmit"
+            @input="passwoldddata"
+          />
+        </view>
+
+        <!-- 记住密码 -->
+        <view class="form-extra">
+          <u-checkbox-group>
+            <u-checkbox
+              v-model="checked"
+              shape="square"
+              size="28"
+              active-color="#24b35d"
+              :label-disabled="false"
+              @change="rempass"
+              >记住密码</u-checkbox
             >
-          </view>
+          </u-checkbox-group>
+        </view>
+
+        <!-- 登录按钮 -->
+        <view class="login-btn-wrap">
+          <button form-type="submit" class="login-btn" @click="denglu">
+            登 录
+          </button>
+        </view>
+
+        <!-- 用户隐私协议 -->
+        <view class="agreement">
+          <u-checkbox-group>
+            <u-checkbox
+              v-model="agree"
+              shape="circle"
+              size="26"
+              active-color="#24b35d"
+              :label-disabled="false"
+            >
+              我已阅读并同意
+            </u-checkbox>
+          </u-checkbox-group>
+          <text class="agree-link" @click="goAgreement">《用户隐私协议》</text>
         </view>
       </form>
     </view>
-    <!-- <view class="bg">
-			<image :src="$imageURL+ '/bigdata_app'+'/image/login/850c9307f4ef2d7dc6db1049711ab55.jpg'" mode=""></image>
-		</view> -->
+
+    <!-- 底部版本号 -->
+    <view class="version-footer">
+      <text class="version-text">V{{ appVersion || '1.0.0' }}</text>
+    </view>
+
+    <!-- 设置服务器地址弹窗 -->
     <view class="setbg" v-if="setbgtf">
       <view class="mengban" @click.stop="setbgtf = !setbgtf"></view>
       <view class="set_http">
         <view class="set_http_top">
           <u-icon name="close" size="40" @click="setbgtf = !setbgtf"></u-icon>
-          <p>设置服务器地址</p>
+          <text class="set_title">设置服务器地址</text>
           <u-icon name="checkbox-mark" size="40" @click="sethttp"></u-icon>
         </view>
         <view class="set_http_bot">
-          <p>服务器访问地址</p>
+          <text class="set_label">服务器访问地址</text>
           <view class="set_http_bot_input">
             <input
               type="text"
@@ -107,7 +128,7 @@
           <scroll-view scroll-y="true" class="scroll-Y" v-if="arrowtf">
             <view
               :id="'demo' + index"
-              class="scroll-view-item uni-bg-red"
+              class="scroll-view-item"
               v-for="(item, index) in httparr"
               :key="index"
               @click="value = item"
@@ -117,6 +138,8 @@
         </view>
       </view>
     </view>
+
+    <!-- 升级弹窗 -->
     <u-modal
       title="升级中请勿随意操作"
       :show-confirm-button="false"
@@ -126,7 +149,7 @@
       <view class="upgradeBox">
         <u-line-progress
           v-show="isShow"
-          active-color="#19be6b"
+          active-color="#24b35d"
           :striped="true"
           :percent="percentNum"
           :striped-active="true"
@@ -137,6 +160,8 @@
 </template>
 
 <script>
+import { needPrivacyPopup } from '@/util/privacyConfig.js';
+
 export default {
   data() {
     return {
@@ -150,17 +175,29 @@ export default {
       value: 'https://web.hnyfwlw.com',
       httparr: ['https://web.hnyfwlw.com'],
       arrowtf: false,
-      showA: false, //
+      showA: false,
       contentA: '',
-      isShow: false, //进度条
-      percentNum: 0, //在线下载进度
+      isShow: false,
+      percentNum: 0,
       passvalue: false,
       turnover: true,
       agree: false,
+      appVersion: '',
     };
   },
-  onLoad() {},
   onShow() {
+    // 已同意当前版本的隐私协议,自动勾选表单协议复选框
+    if (!needPrivacyPopup()) {
+      this.agree = true;
+    }
+    // 获取当前 app 版本号
+    // #ifdef APP-PLUS
+    this.appVersion = plus.runtime.version;
+    // #endif
+    // #ifndef APP-PLUS
+    this.appVersion = '1.0.0';
+    // #endif
+    // 恢复记住的密码
     uni.getStorage({
       key: 'user_pass',
       success: (res) => {
@@ -172,12 +209,14 @@ export default {
         }
       },
     });
+    // 恢复记住的用户名
     uni.getStorage({
       key: 'user_name',
       success: (res) => {
         this.formdata.username = res.data;
       },
     });
+    // 恢复服务器地址
     uni.getStorage({
       key: 'http',
       success: (res) => {
@@ -195,6 +234,7 @@ export default {
     this.getEquipList();
   },
   methods: {
+    // 版本检测
     async getEquipList() {
       const res = await this.$myRequest({
         url: '/api/api_gateway?method=home.homes.app_version_record',
@@ -225,7 +265,6 @@ export default {
                   this.isShow = true;
                   this.upgrade();
                 } else if (res.cancel) {
-                  // plus.runtime.quit();
                   console.log('用户点击取消');
                   uni.showModal({
                     title: '是否每次进入提示更新?',
@@ -278,24 +317,24 @@ export default {
         }
       }
     },
+    // 应用升级下载
     upgrade() {
       console.log(this.appName);
-      // var url = this.value + "/app_file/" + this.appName
       var appName = '';
       if (this.$isneutral) {
-        appName = 'big_data'; //云飞
+        appName = 'big_data'; // 云飞
       } else {
-        appName = 'big_data2'; //中性
+        appName = 'big_data2'; // 中性
       }
       var url = 'https://hnyfwlw.com/app/' + appName + '.apk';
       const downloadTask = uni.downloadFile({
-        url: url, //仅为示例,并非真实的资源
+        url: url,
         success: (res) => {
           console.log(res);
           if (res.statusCode === 200) {
             console.log('下载成功');
             console.log(
-              '安装包下载成功,即将安装:' + JSON.stringify(res, null, 4)
+              '安装包下载成功,即将安装:' + JSON.stringify(res, null, 4),
             );
             plus.runtime.openFile(res.tempFilePath);
             this.showA = false;
@@ -316,7 +355,25 @@ export default {
         }
       });
     },
+    // 登录表单提交
     async formSubmit() {
+      // 1. 必填校验:用户名
+      if (!this.formdata.username || !this.formdata.username.trim()) {
+        uni.showToast({
+          title: '请输入账号/手机号',
+          icon: 'none',
+        });
+        return;
+      }
+      // 2. 必填校验:密码
+      if (!this.formdata.passwold) {
+        uni.showToast({
+          title: '请输入密码',
+          icon: 'none',
+        });
+        return;
+      }
+      // 3. 隐私协议校验
       if (!this.agree) {
         uni.showToast({
           title: '请先阅读并同意用户隐私协议',
@@ -324,34 +381,56 @@ export default {
         });
         return;
       }
-      const res = await this.$myRequest({
-        url: '/api/api_gateway?method=user.login.login_user',
-        data: {
-          username: this.formdata.username,
-          password: this.formdata.passwold,
-        },
-      });
+      // 4. 发起登录请求(仅成功时才继续后续流程)
+      try {
+        const res = await this.$myRequest({
+          url: '/api/api_gateway?method=user.login.login_user',
+          data: {
+            username: this.formdata.username,
+            password: this.formdata.passwold,
+          },
+        });
 
-      let session_key = res.session_key;
-      uni.setStorage({
-        key: 'session_key',
-        data: session_key,
-        success: () => {
-          // 登录前清除可能残留的权限缓存,再用新账号拉取
-          this.$resetPermissionList()
-          this.$fetchPermissionList()
-          uni.switchTab({
-            url: '../index/index',
+        // 兼容:登录接口可能直接返回 session_key,也可能在 data 里
+        let session_key = res.session_key || (res.data && res.data.session_key);
+        if (!session_key) {
+          uni.showToast({
+            title: res.msg || res.message || '登录失败,请检查账号密码',
+            icon: 'none',
           });
-        },
-      });
+          return;
+        }
+
+        // 5. 登录成功,存储 session_key 并跳转
+        uni.setStorage({
+          key: 'session_key',
+          data: session_key,
+          success: () => {
+            // 登录前清除可能残留的权限缓存,再用新账号拉取
+            this.$resetPermissionList();
+            this.$fetchPermissionList();
+            uni.switchTab({
+              url: '../index/index',
+            });
+          },
+        });
+      } catch (err) {
+        // 网络错误或接口抛错,提示后不再往下走
+        console.log('登录请求失败:', err);
+        uni.showToast({
+          title: (err && err.message) || '网络异常,请稍后重试',
+          icon: 'none',
+        });
+      }
     },
+    // 密码输入过滤中文
     passwoldddata() {
       this.formdata.passwold = this.formdata.passwold.replace(
         /[\u4E00-\u9FA5]/g,
-        ''
+        '',
       );
     },
+    // 记住密码
     rempass(val) {
       this.passvalue = val.value;
       if (val.value) {
@@ -364,6 +443,7 @@ export default {
         });
       }
     },
+    // 用户名失焦保存
     blur(val) {
       uni.setStorage({
         key: 'user_name',
@@ -373,18 +453,20 @@ export default {
         },
       });
     },
+    // 长按标题触发设置按钮显示
     logoTime() {
       this.setTF = true;
     },
+    // 打开设置弹窗
     set() {
       this.setbgtf = true;
     },
+    // 保存服务器地址设置
     sethttp() {
       uni.setStorage({
         key: 'http',
         data: this.value,
         success: () => {
-          // console.log(this.value);
           this.setbgtf = false;
           uni.showToast({
             title: '修改成功',
@@ -397,9 +479,11 @@ export default {
         },
       });
     },
+    // 服务器地址下拉展开/收起
     arrow() {
       this.arrowtf = !this.arrowtf;
     },
+    // 登录时处理记住密码
     denglu() {
       if (this.passvalue) {
         uni.setStorage({
@@ -418,6 +502,7 @@ export default {
         });
       }
     },
+    // 跳转用户隐私协议
     goAgreement() {
       uni.navigateTo({
         url: './agreement',
@@ -427,146 +512,182 @@ export default {
 };
 </script>
 
-<style lang="scss">
-.bg-img {
+<style lang="scss" scoped>
+/* 页面整体容器 */
+.login-container {
   height: 100vh;
-  background-image: url(../../static/images/login/bg.png);
-  background-size: 100% 100%;
-  padding-top: 500rpx;
+  width: 100%;
+  background: linear-gradient(180deg, #d1f2e8 6.77%, #fff 28.02%);
+  position: relative;
+  overflow: hidden;
   box-sizing: border-box;
 }
-.apptitle {
-  font-size: 52rpx;
-  color: #fff;
-  width: 80%;
-  margin: 0 auto 40rpx;
-}
-.logo {
+
+/* 顶部淡绿色渐变背景层 */
+.header-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
   width: 100%;
-  height: 340rpx;
-  text-align: center;
+  height: 420rpx;
+  z-index: 0;
+}
+
+/* Logo 区域 */
+.logo-section {
+  position: relative;
+  z-index: 1;
   display: flex;
+  flex-direction: column;
   align-items: center;
-  padding-top: 240rpx;
+  padding-top: 160rpx;
+}
 
-  image {
-    width: 280rpx;
-    margin: 0 auto;
-    height: 120rpx;
-  }
+/* 绿色圆形 Logo */
+.logo-circle {
+  width: 160rpx;
+  height: 160rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 32rpx;
+}
+
+/* Logo 内的真实图片 */
+.logo-img {
+  width: 80%;
+  height: 80%;
+}
+
+/* 应用标题 */
+.app-title {
+  font-size: 40rpx;
+  color: #333333;
+  font-weight: 500;
+  letter-spacing: 2rpx;
 }
 
-.set {
+/* 设置按钮 */
+.set-btn {
   position: absolute;
-  right: 50rpx;
-  top: 150rpx;
+  right: 40rpx;
+  top: 80rpx;
+  z-index: 10;
 }
 
-.bg {
-  width: 100%;
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  z-index: -1;
+/* 登录表单(无卡片,直接平铺) */
+.form-card {
+  position: relative;
+  z-index: 1;
+  width: 640rpx;
+  margin: 72rpx auto 0;
+  padding: 0 16rpx;
+}
 
-  image {
-    width: 100%;
+/* 表单输入项 */
+.form-item {
+  display: flex;
+  align-items: center;
+  border-bottom: 1rpx solid #e5e5e5;
+  padding: 28rpx 0;
+
+  .form-input {
+    flex: 1;
+    margin-left: 20rpx;
+    font-size: 28rpx;
+  }
+
+  ::v-deep .uni-input-input {
+    color: #333333 !important;
+    font-size: 28rpx !important;
+  }
+
+  ::v-deep .uicon-eye,
+  ::v-deep .uicon-eye-off {
+    color: #bbbbbb !important;
   }
 }
 
-::v-deep .u-input__right-icon {
-  line-height: 35px !important;
+/* 表单附加项:记住密码 */
+.form-extra {
+  margin-top: 24rpx;
+  display: flex;
+  align-items: center;
+
+  ::v-deep .u-checkbox__label {
+    font-size: 26rpx;
+    color: #666666;
+    margin-left: 8rpx;
+  }
+
+  ::v-deep .u-checkbox__icon-wrap {
+    border-color: #cccccc;
+  }
 }
-.uni-form-item {
-  width: 100%;
 
-  .username {
-    width: 80%;
-    margin: 0 auto;
-    display: flex;
-    margin-bottom: 40rpx;
-    padding-bottom: 10rpx;
-    border-bottom: 2rpx solid rgba(249, 249, 249, 0.4);
+/* 登录按钮 */
+.login-btn-wrap {
+  margin-top: 48rpx;
 
-    .u-icon__icon {
-      margin-top: 17rpx;
-    }
+  .login-btn {
+    width: 100%;
+    height: 96rpx;
+    line-height: 96rpx;
+    background: linear-gradient(135deg, #24b35d 0%, #1a9e4e 100%);
+    border-radius: 48rpx;
+    color: #ffffff;
+    font-size: 32rpx;
+    font-weight: 500;
+    border: none;
+    box-shadow: 0 8rpx 24rpx rgba(36, 179, 93, 0.3);
 
-    .uni-input {
-      width: 100%;
-      color: #fff;
-    }
-    ::v-deep .uni-input-input {
-      color: #fff;
+    &::after {
+      border: none;
     }
   }
+}
 
-  .passwold {
-    width: 80%;
-    margin: 0 auto;
-    display: flex;
-    margin-bottom: 40rpx;
-    padding-bottom: 10rpx;
-    border-bottom: 2rpx solid rgba(249, 249, 249, 0.4);
+/* 用户隐私协议 */
+.agreement {
+  margin-top: 28rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 
-    .u-icon__icon {
-      margin-top: 17rpx;
-    }
-    ::v-deep .uni-input-input {
-      color: #fff;
-    }
-    .uni-input {
-      width: 100%;
-      color: #fff;
-    }
-    ::v-deep .uicon-eye {
-      color: #fff !important;
-    }
+  ::v-deep .u-checkbox__label {
+    font-size: 22rpx;
+    color: #999999;
+    margin-left: 4rpx;
   }
 
-  .aboutpass {
-    width: 80%;
-    margin: 0 auto;
-    display: flex;
-    justify-content: flex-end;
+  ::v-deep .u-checkbox__icon-wrap {
+    border-color: #cccccc;
+  }
 
-    p {
-      color: #fff;
-      font-size: 28rpx;
-    }
-    ::v-deep .uicon-checkbox-mark {
-      border-color: #ff0000;
-      // color: #f00 !important;
-    }
-    ::v-deep .u-checkbox__label {
-      font-size: 28rpx;
-      color: #fff;
-      margin-right: 0;
-    }
+  .agree-link {
+    font-size: 22rpx;
+    color: #24b35d;
+    margin-left: 4rpx;
   }
+}
 
-  .uni-btn-v {
-    width: 80%;
-    margin: 112rpx auto 0;
-    position: relative;
-    z-index: 100;
+/* 底部版本号 */
+.version-footer {
+  position: absolute;
+  bottom: 60rpx;
+  left: 0;
+  width: 100%;
+  text-align: center;
+  z-index: 1;
 
-    button {
-      width: 100%;
-      height: 90rpx;
-      line-height: 90rpx;
-      color: #ffffff;
-      font-size: 36rpx;
-      background-image: linear-gradient(
-        to bottom,
-        rgba(249, 249, 249, 0.6),
-        rgba(249, 249, 249, 0.1)
-      );
-      color: #5dc18b;
-    }
+  .version-text {
+    font-size: 24rpx;
+    color: #bbbbbb;
+    letter-spacing: 4rpx;
   }
 }
 
+/* 服务器设置弹窗 */
 .setbg {
   width: 100%;
   height: 100vh;
@@ -591,75 +712,71 @@ export default {
     .set_http_top {
       display: flex;
       justify-content: space-around;
-      background-color: #5dc18b;
-      height: 60px;
-      line-height: 60px;
+      align-items: center;
+      background-color: #24b35d;
+      height: 100rpx;
+      line-height: 100rpx;
       color: #ffffff;
-      border-top-right-radius: 20px;
-      border-top-left-radius: 20px;
-      font-size: 32rpx;
+      border-top-right-radius: 20rpx;
+      border-top-left-radius: 20rpx;
+
+      .set_title {
+        font-size: 30rpx;
+      }
     }
 
     .set_http_bot {
-      height: 150px;
       background-color: #ffffff;
-      border-bottom-right-radius: 20px;
-      border-bottom-left-radius: 20px;
-      padding: 30px;
+      border-bottom-right-radius: 20rpx;
+      border-bottom-left-radius: 20rpx;
+      padding: 30rpx;
+
+      .set_label {
+        font-size: 26rpx;
+        color: #666666;
+      }
 
       .set_http_bot_input {
         margin-top: 20rpx;
-        border: 2rpx solid #bdb6a6;
-        border-radius: 20rpx;
-        padding: 10rpx 40rpx 0 20rpx;
-        font-size: 32rpx;
-        height: 30px;
+        border: 2rpx solid #eeeeee;
+        border-radius: 12rpx;
+        padding: 16rpx 24rpx;
+        font-size: 28rpx;
         display: flex;
         justify-content: space-between;
+        align-items: center;
 
         input {
           width: 90%;
+          font-size: 28rpx;
         }
       }
     }
 
     .scroll-Y {
-      border: 2rpx solid #d0d0d0;
-      border-radius: 20rpx;
+      border: 2rpx solid #eeeeee;
+      border-radius: 12rpx;
+      margin-top: 16rpx;
 
       .scroll-view-item {
-        padding-left: 20rpx;
+        padding: 0 20rpx;
         height: 70rpx;
-        font-size: 28rpx;
+        font-size: 26rpx;
         line-height: 70rpx;
-        border-bottom: 2rpx solid #d0d0d0;
+        border-bottom: 2rpx solid #f5f5f5;
+        color: #333333;
       }
     }
   }
 }
 
+/* 升级进度框 */
 .upgradeBox {
   padding: 15rpx;
 }
-.agreement {
-  width: 80%;
-  margin: 40rpx auto 0;
+
+::v-deep .u-flex {
   display: flex;
   align-items: center;
-  justify-content: center;
-
-  /deep/.u-checkbox__label {
-    font-size: 24rpx;
-    color: #fff;
-    margin-right: 0;
-  }
-  /deep/.u-checkbox__icon-wrap {
-    border-color: rgba(255, 255, 255, 0.8);
-  }
-  .agree-link {
-    font-size: 24rpx;
-    color: #fff;
-    text-decoration: underline;
-  }
 }
 </style>

+ 665 - 0
pages/login/loginOld.vue

@@ -0,0 +1,665 @@
+<template>
+  <view class="bg-img">
+    <!-- <view class="status_bar"></view> -->
+    <view class="apptitle" @longpress="logoTime">
+      {{ $isneutral ? '云飞智控' : '智控' }}
+    </view>
+    <view class="set" @click="set" v-if="setTF">
+      <u-icon name="setting-fill" size="50" color="#fff"></u-icon>
+    </view>
+    <view class="formbox">
+      <form @submit="formSubmit">
+        <view class="uni-form-item uni-column">
+          <view class="username">
+            <u-icon
+              name="account"
+              size="36"
+              style="margin-right: 30rpx; color: #fff"
+            ></u-icon>
+            <u-input
+              class="uni-input"
+              name="username"
+              v-model="formdata.username"
+              placeholder-class="icon iconfont icon-bianji1"
+              placeholder="请输入用户名"
+              placeholderStyle="color:#ffffff;"
+              color="#FFFFFF"
+              @blur="blur"
+            />
+          </view>
+          <view class="passwold">
+            <u-icon
+              name="lock"
+              size="36"
+              style="margin-right: 30rpx; color: #fff"
+            ></u-icon>
+            <u-input
+              v-model="formdata.passwold"
+              type="password"
+              :password-icon="true"
+              :clearable="false"
+              placeholder="请输入密码"
+              @confirm="formSubmit"
+              @input="passwoldddata"
+              placeholderStyle="color:#fff;"
+              suffixIconStyle="color:#fff;"
+              color="#fff"
+              class="uni-input"
+            />
+          </view>
+          <view class="aboutpass">
+            <u-checkbox-group>
+              <u-checkbox
+                v-model="checked"
+                :label-disabled="false"
+                size="28"
+                @change="rempass"
+                >记住密码</u-checkbox
+              >
+            </u-checkbox-group>
+          </view>
+          <view class="uni-btn-v">
+            <button form-type="submit" @click="denglu">登 录</button>
+          </view>
+          <!--用户隐私协议-->
+          <view class="agreement">
+            <u-checkbox-group>
+              <u-checkbox
+                v-model="agree"
+                shape="circle"
+                size="28"
+                active-color="#2979ff"
+                :label-disabled="false"
+                >我已阅读并同意
+              </u-checkbox>
+            </u-checkbox-group>
+            <text class="agree-link" @click="goAgreement"
+              >《用户隐私协议》</text
+            >
+          </view>
+        </view>
+      </form>
+    </view>
+    <!-- <view class="bg">
+			<image :src="$imageURL+ '/bigdata_app'+'/image/login/850c9307f4ef2d7dc6db1049711ab55.jpg'" mode=""></image>
+		</view> -->
+    <view class="setbg" v-if="setbgtf">
+      <view class="mengban" @click.stop="setbgtf = !setbgtf"></view>
+      <view class="set_http">
+        <view class="set_http_top">
+          <u-icon name="close" size="40" @click="setbgtf = !setbgtf"></u-icon>
+          <p>设置服务器地址</p>
+          <u-icon name="checkbox-mark" size="40" @click="sethttp"></u-icon>
+        </view>
+        <view class="set_http_bot">
+          <p>服务器访问地址</p>
+          <view class="set_http_bot_input">
+            <input
+              type="text"
+              v-model="value"
+              placeholder="请在此处输入服务器地址(http://...)"
+            />
+            <u-icon
+              :name="arrowtf ? 'arrow-up' : 'arrow-down'"
+              @click="arrow"
+            ></u-icon>
+          </view>
+          <scroll-view scroll-y="true" class="scroll-Y" v-if="arrowtf">
+            <view
+              :id="'demo' + index"
+              class="scroll-view-item uni-bg-red"
+              v-for="(item, index) in httparr"
+              :key="index"
+              @click="value = item"
+              >{{ item }}</view
+            >
+          </scroll-view>
+        </view>
+      </view>
+    </view>
+    <u-modal
+      title="升级中请勿随意操作"
+      :show-confirm-button="false"
+      v-model="showA"
+      :content="contentA"
+    >
+      <view class="upgradeBox">
+        <u-line-progress
+          v-show="isShow"
+          active-color="#19be6b"
+          :striped="true"
+          :percent="percentNum"
+          :striped-active="true"
+        ></u-line-progress>
+      </view>
+    </u-modal>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      checked: false,
+      formdata: {
+        username: '',
+        passwold: '',
+      },
+      setbgtf: false,
+      setTF: false,
+      value: 'https://web.hnyfwlw.com',
+      httparr: ['https://web.hnyfwlw.com'],
+      arrowtf: false,
+      showA: false, //
+      contentA: '',
+      isShow: false, //进度条
+      percentNum: 0, //在线下载进度
+      passvalue: false,
+      turnover: true,
+      agree: false,
+    };
+  },
+  onLoad() {},
+  onShow() {
+    uni.getStorage({
+      key: 'user_pass',
+      success: (res) => {
+        if (res.data) {
+          this.formdata.passwold = res.data;
+          this.checked = true;
+        } else {
+          this.checked = false;
+        }
+      },
+    });
+    uni.getStorage({
+      key: 'user_name',
+      success: (res) => {
+        this.formdata.username = res.data;
+      },
+    });
+    uni.getStorage({
+      key: 'http',
+      success: (res) => {
+        this.value = res.data;
+      },
+    });
+    console.log(this.value);
+    uni.getStorage({
+      key: 'turnover',
+      success: (res) => {
+        console.log(res.data);
+        this.turnover = res.data;
+      },
+    });
+    this.getEquipList();
+  },
+  methods: {
+    async getEquipList() {
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=home.homes.app_version_record',
+        data: {
+          ret: 'first',
+        },
+      });
+      console.log(res);
+      this.appName = res[0].app_name;
+      this.versions = Number(res[0].app_num.match(/\d+/g).join(''));
+      var id = Number(plus.runtime.version.match(/\d+/g).join(''));
+      console.log(this.versions, plus.runtime.version);
+      if (this.percentNum > 0) {
+        console.log('更新中');
+      } else {
+        console.log(this.turnover);
+        if (this.turnover) {
+          if (this.versions > id) {
+            uni.showModal({
+              title: '检测到有新版本,是否更新?',
+              content: '建议更新,不更新可能会出现部分数据无法获取!',
+              confirmText: '更新',
+              cancelText: '不更新',
+              success: (res) => {
+                if (res.confirm) {
+                  console.log('用户点击确定');
+                  this.showA = true;
+                  this.isShow = true;
+                  this.upgrade();
+                } else if (res.cancel) {
+                  // plus.runtime.quit();
+                  console.log('用户点击取消');
+                  uni.showModal({
+                    title: '是否每次进入提示更新?',
+                    content: '不再提示后可在<我的>-<关于我们>-<版本更新>中更新',
+                    confirmText: '提示',
+                    cancelText: '不再提示',
+                    success: (res) => {
+                      if (res.confirm) {
+                        console.log('用户点击确定');
+                        uni.setStorage({
+                          key: 'turnover',
+                          data: true,
+                        });
+                      } else if (res.cancel) {
+                        uni.setStorage({
+                          key: 'turnover',
+                          data: false,
+                        });
+                      }
+                    },
+                  });
+                }
+              },
+            });
+          } else {
+            uni.getStorage({
+              key: 'session_key',
+              success: (res) => {
+                console.log(res);
+                if (res.data != '') {
+                  uni.switchTab({
+                    url: '../index/index',
+                  });
+                }
+              },
+            });
+          }
+        } else {
+          uni.getStorage({
+            key: 'session_key',
+            success: (res) => {
+              console.log(res);
+              if (res.data != '') {
+                uni.switchTab({
+                  url: '../index/index',
+                });
+              }
+            },
+          });
+        }
+      }
+    },
+    upgrade() {
+      console.log(this.appName);
+      // var url = this.value + "/app_file/" + this.appName
+      var appName = '';
+      if (this.$isneutral) {
+        appName = 'big_data'; //云飞
+      } else {
+        appName = 'big_data2'; //中性
+      }
+      var url = 'https://hnyfwlw.com/app/' + appName + '.apk';
+      const downloadTask = uni.downloadFile({
+        url: url, //仅为示例,并非真实的资源
+        success: (res) => {
+          console.log(res);
+          if (res.statusCode === 200) {
+            console.log('下载成功');
+            console.log(
+              '安装包下载成功,即将安装:' + JSON.stringify(res, null, 4)
+            );
+            plus.runtime.openFile(res.tempFilePath);
+            this.showA = false;
+            this.isShow = false;
+          }
+        },
+        fail: (err) => {
+          console.log(err);
+        },
+        complete: (com) => {
+          console.log(com);
+        },
+      });
+      downloadTask.onProgressUpdate((res) => {
+        this.percentNum = res.progress;
+        if (res.progress == 100) {
+          console.log('下载完成了');
+        }
+      });
+    },
+    async formSubmit() {
+      if (!this.agree) {
+        uni.showToast({
+          title: '请先阅读并同意用户隐私协议',
+          icon: 'none',
+        });
+        return;
+      }
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=user.login.login_user',
+        data: {
+          username: this.formdata.username,
+          password: this.formdata.passwold,
+        },
+      });
+
+      let session_key = res.session_key;
+      uni.setStorage({
+        key: 'session_key',
+        data: session_key,
+        success: () => {
+          // 登录前清除可能残留的权限缓存,再用新账号拉取
+          this.$resetPermissionList()
+          this.$fetchPermissionList()
+          uni.switchTab({
+            url: '../index/index',
+          });
+        },
+      });
+    },
+    passwoldddata() {
+      this.formdata.passwold = this.formdata.passwold.replace(
+        /[\u4E00-\u9FA5]/g,
+        ''
+      );
+    },
+    rempass(val) {
+      this.passvalue = val.value;
+      if (val.value) {
+        uni.setStorage({
+          key: 'user_pass',
+          data: this.formdata.passwold,
+          success: function () {
+            console.log('success');
+          },
+        });
+      }
+    },
+    blur(val) {
+      uni.setStorage({
+        key: 'user_name',
+        data: val,
+        success: function () {
+          console.log('success');
+        },
+      });
+    },
+    logoTime() {
+      this.setTF = true;
+    },
+    set() {
+      this.setbgtf = true;
+    },
+    sethttp() {
+      uni.setStorage({
+        key: 'http',
+        data: this.value,
+        success: () => {
+          // console.log(this.value);
+          this.setbgtf = false;
+          uni.showToast({
+            title: '修改成功',
+            icon: 'none',
+          });
+          this.getEquipList();
+          uni.removeStorage({
+            key: 'session_key',
+          });
+        },
+      });
+    },
+    arrow() {
+      this.arrowtf = !this.arrowtf;
+    },
+    denglu() {
+      if (this.passvalue) {
+        uni.setStorage({
+          key: 'user_pass',
+          data: this.formdata.passwold,
+          success: function () {
+            console.log('success');
+          },
+        });
+      } else {
+        uni.removeStorage({
+          key: 'user_pass',
+          success: function () {
+            console.log('success');
+          },
+        });
+      }
+    },
+    goAgreement() {
+      uni.navigateTo({
+        url: './agreement',
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+.bg-img {
+  height: 100vh;
+  background-image: url(../../static/images/login/bg.png);
+  background-size: 100% 100%;
+  padding-top: 500rpx;
+  box-sizing: border-box;
+}
+.apptitle {
+  font-size: 52rpx;
+  color: #fff;
+  width: 80%;
+  margin: 0 auto 40rpx;
+}
+.logo {
+  width: 100%;
+  height: 340rpx;
+  text-align: center;
+  display: flex;
+  align-items: center;
+  padding-top: 240rpx;
+
+  image {
+    width: 280rpx;
+    margin: 0 auto;
+    height: 120rpx;
+  }
+}
+
+.set {
+  position: absolute;
+  right: 50rpx;
+  top: 150rpx;
+}
+
+.bg {
+  width: 100%;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  z-index: -1;
+
+  image {
+    width: 100%;
+  }
+}
+
+::v-deep .u-input__right-icon {
+  line-height: 35px !important;
+}
+.uni-form-item {
+  width: 100%;
+
+  .username {
+    width: 80%;
+    margin: 0 auto;
+    display: flex;
+    margin-bottom: 40rpx;
+    padding-bottom: 10rpx;
+    border-bottom: 2rpx solid rgba(249, 249, 249, 0.4);
+
+    .u-icon__icon {
+      margin-top: 17rpx;
+    }
+
+    .uni-input {
+      width: 100%;
+      color: #fff;
+    }
+    ::v-deep .uni-input-input {
+      color: #fff;
+    }
+  }
+
+  .passwold {
+    width: 80%;
+    margin: 0 auto;
+    display: flex;
+    margin-bottom: 40rpx;
+    padding-bottom: 10rpx;
+    border-bottom: 2rpx solid rgba(249, 249, 249, 0.4);
+
+    .u-icon__icon {
+      margin-top: 17rpx;
+    }
+    ::v-deep .uni-input-input {
+      color: #fff;
+    }
+    .uni-input {
+      width: 100%;
+      color: #fff;
+    }
+    ::v-deep .uicon-eye {
+      color: #fff !important;
+    }
+  }
+
+  .aboutpass {
+    width: 80%;
+    margin: 0 auto;
+    display: flex;
+    justify-content: flex-end;
+
+    p {
+      color: #fff;
+      font-size: 28rpx;
+    }
+    ::v-deep .uicon-checkbox-mark {
+      border-color: #ff0000;
+      // color: #f00 !important;
+    }
+    ::v-deep .u-checkbox__label {
+      font-size: 28rpx;
+      color: #fff;
+      margin-right: 0;
+    }
+  }
+
+  .uni-btn-v {
+    width: 80%;
+    margin: 112rpx auto 0;
+    position: relative;
+    z-index: 100;
+
+    button {
+      width: 100%;
+      height: 90rpx;
+      line-height: 90rpx;
+      color: #ffffff;
+      font-size: 36rpx;
+      background-image: linear-gradient(
+        to bottom,
+        rgba(249, 249, 249, 0.6),
+        rgba(249, 249, 249, 0.1)
+      );
+      color: #5dc18b;
+    }
+  }
+}
+
+.setbg {
+  width: 100%;
+  height: 100vh;
+  position: absolute;
+  top: 0;
+  z-index: 99999;
+
+  .mengban {
+    width: 100%;
+    height: 100vh;
+    position: absolute;
+    top: 0;
+    background-color: rgba($color: #000000, $alpha: 0.5);
+  }
+
+  .set_http {
+    position: absolute;
+    width: 90%;
+    left: 5%;
+    top: 30%;
+
+    .set_http_top {
+      display: flex;
+      justify-content: space-around;
+      background-color: #5dc18b;
+      height: 60px;
+      line-height: 60px;
+      color: #ffffff;
+      border-top-right-radius: 20px;
+      border-top-left-radius: 20px;
+      font-size: 32rpx;
+    }
+
+    .set_http_bot {
+      height: 150px;
+      background-color: #ffffff;
+      border-bottom-right-radius: 20px;
+      border-bottom-left-radius: 20px;
+      padding: 30px;
+
+      .set_http_bot_input {
+        margin-top: 20rpx;
+        border: 2rpx solid #bdb6a6;
+        border-radius: 20rpx;
+        padding: 10rpx 40rpx 0 20rpx;
+        font-size: 32rpx;
+        height: 30px;
+        display: flex;
+        justify-content: space-between;
+
+        input {
+          width: 90%;
+        }
+      }
+    }
+
+    .scroll-Y {
+      border: 2rpx solid #d0d0d0;
+      border-radius: 20rpx;
+
+      .scroll-view-item {
+        padding-left: 20rpx;
+        height: 70rpx;
+        font-size: 28rpx;
+        line-height: 70rpx;
+        border-bottom: 2rpx solid #d0d0d0;
+      }
+    }
+  }
+}
+
+.upgradeBox {
+  padding: 15rpx;
+}
+.agreement {
+  width: 80%;
+  margin: 40rpx auto 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  /deep/.u-checkbox__label {
+    font-size: 24rpx;
+    color: #fff;
+    margin-right: 0;
+  }
+  /deep/.u-checkbox__icon-wrap {
+    border-color: rgba(255, 255, 255, 0.8);
+  }
+  .agree-link {
+    font-size: 24rpx;
+    color: #fff;
+    text-decoration: underline;
+  }
+}
+</style>

+ 256 - 0
pages/privacy/privacy.vue

@@ -0,0 +1,256 @@
+<template>
+  <view class="privacy-container">
+    <!-- 遮罩层 -->
+    <view class="privacy-mask"></view>
+    <!-- 弹窗卡片 -->
+    <view class="privacy-content">
+      <view class="privacy-title">{{ titleText }}</view>
+      <view class="privacy-body">
+        <view class="privacy-intro">
+          {{ introText }}
+          <text class="privacy-highlight">《用户服务与隐私协议》</text>
+          。我们将遵循"最小必要"原则,仅收集实现服务所必需的信息。
+        </view>
+        <view class="privacy-list">
+          <view class="privacy-item">
+            <text class="privacy-dot">•</text>
+            <text>微信昵称、头像 —— 用于展示您的身份</text>
+          </view>
+          <view class="privacy-item">
+            <text class="privacy-dot">•</text>
+            <text>手机号 —— 用于售后联系与账号安全</text>
+          </view>
+          <view class="privacy-item">
+            <text class="privacy-dot">•</text>
+            <text>位置信息 —— 用于推荐附近服务(需授权)</text>
+          </view>
+          <view class="privacy-item">
+            <text class="privacy-dot">•</text>
+            <text>设备信息 —— 用于保障服务稳定运行</text>
+          </view>
+        </view>
+        <view class="privacy-note">
+          我们不会主动向第三方共享您的个人信息,除非获得您的明确同意或法律法规要求。
+        </view>
+        <text class="privacy-link" @click="goAgreement"
+          >查看完整协议详情 ></text
+        >
+      </view>
+      <view class="privacy-footer">
+        <view class="privacy-btn privacy-btn-cancel" @click="onDisagree">
+          不同意
+        </view>
+        <view class="privacy-btn privacy-btn-confirm" @click="onAgree">
+          同意并继续
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import {
+  markPrivacyAgreed,
+  PRIVACY_STORAGE_KEY,
+} from '@/util/privacyConfig.js';
+
+export default {
+  name: 'PrivacyPage',
+  data() {
+    return {
+      // 是否为协议更新(老用户之前同意过旧版本)
+      isUpdate: false,
+    };
+  },
+  computed: {
+    // 标题:首次显示"用户服务与隐私协议",更新时显示"隐私协议已更新"
+    titleText() {
+      return this.isUpdate ? '隐私协议已更新' : '用户服务与隐私协议';
+    },
+    // 介绍文案
+    introText() {
+      return this.isUpdate
+        ? '我们更新了隐私协议,请您认真阅读'
+        : '欢迎使用本应用!在您首次使用前,请认真阅读';
+    },
+  },
+  created() {
+    // 判断是首次还是协议更新:
+    // 有老数据 privacy_agreed: true(旧方案) 或 有旧版本号(新方案但版本不同)→ 视为更新
+    const oldFlag = uni.getStorageSync('privacy_agreed');
+    const oldVersion = uni.getStorageSync(PRIVACY_STORAGE_KEY);
+    this.isUpdate = !!(oldFlag || (oldVersion && oldVersion.length > 0));
+  },
+  methods: {
+    // 同意隐私协议
+    onAgree() {
+      // 存版本号(替换老数据)
+      markPrivacyAgreed();
+      // 清理老的 boolean 标志(如果存在)
+      uni.removeStorageSync('privacy_agreed');
+
+      uni.showToast({
+        title: '已同意隐私协议',
+        icon: 'none',
+        duration: 1200,
+      });
+      setTimeout(() => {
+        // #ifdef APP-PLUS
+        // 通知 App.vue 执行全局初始化
+        var app = getApp();
+        if (app && app.doAfterPrivacy) {
+          app.doAfterPrivacy();
+        }
+        // #endif
+        // 跳回首页
+        uni.reLaunch({
+          url: '/pages/index/index',
+        });
+      }, 800);
+    },
+    // 不同意
+    onDisagree() {
+      uni.showModal({
+        title: '提示',
+        content: '您需要同意隐私协议才能使用本应用',
+        confirmText: '再次查看',
+        cancelText: '退出应用',
+        success: (res) => {
+          if (!res.confirm) {
+            // 退出应用:App 端和小程序端分别处理
+            // #ifdef APP-PLUS
+            plus.runtime.quit();
+            // #endif
+            // #ifdef MP
+            uni.showToast({
+              title: '请同意隐私协议后重试',
+              icon: 'none',
+            });
+            // #endif
+          }
+          // "再次查看"则什么也不做,弹窗仍在
+        },
+      });
+    },
+    // 查看完整协议
+    goAgreement() {
+      uni.navigateTo({
+        url: '/pages/login/agreement',
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.privacy-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 9999;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.privacy-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.55);
+}
+
+.privacy-content {
+  position: relative;
+  width: 80%;
+  max-width: 620rpx;
+  background-color: #ffffff;
+  border-radius: 24rpx;
+  overflow: hidden;
+}
+
+.privacy-title {
+  text-align: center;
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333333;
+  padding: 40rpx 40rpx 0;
+}
+
+.privacy-body {
+  padding: 28rpx 40rpx 0;
+}
+
+.privacy-intro {
+  font-size: 26rpx;
+  color: #555555;
+  line-height: 1.7;
+
+  .privacy-highlight {
+    color: #24b35d;
+  }
+}
+
+.privacy-list {
+  margin-top: 20rpx;
+
+  .privacy-item {
+    font-size: 24rpx;
+    color: #666666;
+    line-height: 2;
+
+    .privacy-dot {
+      color: #24b35d;
+      margin-right: 6rpx;
+    }
+  }
+}
+
+.privacy-note {
+  margin-top: 16rpx;
+  font-size: 24rpx;
+  color: #999999;
+  line-height: 1.6;
+}
+
+.privacy-link {
+  display: block;
+  margin-top: 28rpx;
+  font-size: 26rpx;
+  color: #24b35d;
+  text-align: center;
+  padding: 10rpx 0;
+}
+
+.privacy-footer {
+  display: flex;
+  border-top: 1rpx solid #f0f0f0;
+  margin-top: 32rpx;
+
+  .privacy-btn {
+    flex: 1;
+    height: 96rpx;
+    line-height: 96rpx;
+    text-align: center;
+    font-size: 28rpx;
+
+    &.privacy-btn-cancel {
+      color: #999999;
+      border-right: 1rpx solid #f0f0f0;
+    }
+
+    &.privacy-btn-confirm {
+      color: #24b35d;
+      font-weight: 500;
+    }
+
+    &:active {
+      opacity: 0.7;
+    }
+  }
+}
+</style>

+ 34 - 14
util/api.js

@@ -1,5 +1,35 @@
-
 import config from './neutral.js';
+
+// token 失效全局拦截:清除登录态并跳转登录页(并发请求去重)
+let isRedirecting = false;
+const handleTokenExpired = () => {
+  uni.removeStorageSync('session_key');
+  uni.removeStorageSync('isLink'); // 清理旧版本遗留的跳转标记
+  // 并发请求同时返回 403 时只处理一次
+  if (isRedirecting) {
+    return;
+  }
+  isRedirecting = true;
+  // 已在登录页则不重复跳转
+  const pages = getCurrentPages();
+  const currentRoute = pages.length ? pages[pages.length - 1].route : '';
+  if (currentRoute === 'pages/login/login') {
+    isRedirecting = false;
+    return;
+  }
+  uni.showToast({
+    title: '登录已过期,请重新登录!',
+    icon: 'none',
+  });
+  // reLaunch 清空页面栈,避免返回键回到已失效页面
+  uni.reLaunch({
+    url: '/pages/login/login',
+    complete: () => {
+      isRedirecting = false;
+    },
+  });
+};
+
 export const myRequest = (options) => {
   let BASE_URL = uni.getStorageSync('http');
   console.log(BASE_URL, 'my request', process.env.NODE_ENV);
@@ -55,20 +85,10 @@ export const myRequest = (options) => {
       },
       data: data,
       success: (res) => {
+        // 全局拦截:token 失效(errorCode 403),清除登录态并跳转登录页
         if (res.data.errorCode == 403) {
-          uni.removeStorageSync('session_key');
-          uni.showToast({
-            title: '登录已过期,请重新登录!',
-            icon: 'none',
-          });
-          if (uni.getStorageSync('isLink')) {
-            return false;
-          } else {
-            uni.setStorageSync('isLink', true);
-            return uni.navigateTo({
-              url: '/pages/login/login',
-            });
-          }
+          handleTokenExpired();
+          return reject(res.data.message || '登录已过期');
         }
         if (res.data.message) {
           if (

+ 76 - 0
util/privacyConfig.js

@@ -0,0 +1,76 @@
+/**
+ * 隐私协议版本号 - 自动跟随 App/小程序版本
+ *
+ * 【正常流程】
+ *   每次发版时,开发者只需要改 manifest.json 中的 versionName / versionCode,
+ *   或者发版工具自动改版本号,隐私协议就会自动重新弹窗,无需手动调整此处。
+ *
+ * 【特殊兜底】
+ *   如果协议改了但不想发新版 App,把下方 FORCE_REFRESH_FLAG 改成新值即可
+ *   (比如改成日期 '2026-10-01'),所有用户下次启动会重新弹窗。
+ *   协议没改时保持空字符串 '' 即可。
+ *
+ * 各端获取版本号方式:
+ *   App 端    → plus.runtime.versionCode   (manifest.json 中 versionCode)
+ *   小程序端  → uni.getAccountInfoSync().miniProgram.version
+ */
+
+// ====== 兜底强制刷新标志 ======
+// 协议变了但 App/小程序版本号没变时,改这里触发重新弹窗
+// 保持 '' 不额外触发;改成任意新值(如 '2026-10-01')即可强制所有用户重新同意
+const FORCE_REFRESH_FLAG = '';
+
+// 本地存储 key
+export const PRIVACY_STORAGE_KEY = 'privacy_version';
+
+// 动态获取当前运行端的版本号(含兜底标志)
+function getCurrentVersion() {
+  let base = '';
+
+  // #ifdef APP-PLUS
+  // App 端:用 versionCode(数字,每次发版必变)
+  base = 'app_' + plus.runtime.versionCode;
+  // #endif
+
+  // #ifdef MP
+  // 小程序端:用微信小程序平台的版本号
+  try {
+    const info = uni.getAccountInfoSync();
+    base =
+      'mp_' +
+      (info.miniProgram.version || info.miniProgram.envVersion || 'dev');
+  } catch (e) {
+    base = 'mp_unknown';
+  }
+  // #endif
+
+  // #ifdef H5
+  // H5 暂不强制拦截
+  return 'h5_static';
+  // #endif
+
+  // 拼上兜底标志:有值时即使版本号没变也触发重新弹窗
+  return FORCE_REFRESH_FLAG ? base + '__' + FORCE_REFRESH_FLAG : base;
+}
+
+// 判断是否需要重新弹窗
+// 用户同意的版本号 !== 当前运行端版本号 → 需要弹窗
+export function needPrivacyPopup() {
+  // #ifdef H5
+  // H5 不强制拦截
+  return false;
+  // #endif
+
+  const current = getCurrentVersion();
+  const saved = uni.getStorageSync(PRIVACY_STORAGE_KEY);
+
+  // 没有记录(含老数据 privacy_agreed: true)或版本号不匹配 → 需要弹窗
+  if (!saved) return true;
+  return saved !== current;
+}
+
+// 标记已同意当前版本的隐私协议
+export function markPrivacyAgreed() {
+  const current = getCurrentVersion();
+  uni.setStorageSync(PRIVACY_STORAGE_KEY, current);
+}