From 44dd9c4eb8a2489c63a5eb53fd4254bbb3510e95 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Thu, 18 Jun 2026 09:14:57 +0800
Subject: [PATCH] Merge branch 'feature/v9.0/9.0.4'

---
 src/pages/login/index.vue |  507 +++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 316 insertions(+), 191 deletions(-)

diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue
index c434423..57ad7e0 100644
--- a/src/pages/login/index.vue
+++ b/src/pages/login/index.vue
@@ -1,226 +1,351 @@
 <!-- 登录页 -->
 <template>
-	<view class="login-form-wrap">
-		<image class="logo" :src="logoSvg" />
-		<div class="title">掌控智飞</div>
-		<div class="user-name">
-			<image :src="usernameSvg" />
-			<input v-model="loginForm.username" placeholder="请输入用户名" />
+  <view class="login-form-wrap">
+    <image class="logo" :src="logoSvg" />
+    <div class="title">掌控智飞</div>
+    <div class="user-name">
+      <image :src="usernameSvg" />
+      <input v-model="loginForm.username" placeholder="请输入用户名" />
+    </div>
+    <div class="pass-word">
+      <image :src="passwordSvg" />
+      <input
+        v-model="loginForm.password"
+        type="text"
+        :password="showPassword"
+        placeholder="请输入密码"
+      />
+      <image
+        class="pass-word-icon"
+        :src="passwordTriggerIcon"
+        @click="showPassWord"
+      />
+    </div>
+    <div class="remember-password">
+      <label>
+        <checkbox-group @change="toggleRemember">
+          <checkbox
+            :checked="rememberPassword"
+            color="#1D6FE9"
+            style="transform: scale(0.7)"
+          />
+        </checkbox-group>
+        记住密码
+      </label>
+    </div>
 
-		</div>
-		<div class="pass-word">
-			<image :src="passwordSvg" />
-			<input v-model="loginForm.password" type="password" placeholder="请输入密码" />
-		</div>
+    <button class="login-btn" :style="[inputStyle]" @tap="submit">登录</button>
+    <image class="lowerRightCorner" :src="droneSvg" />
 
-		<button class="login-btn" :style="[inputStyle]" @tap="submit">
-			登录
-		</button>
-
-		<image class="lowerRightCorner" :src="droneSvg" />
-	</view>
+    <!-- #ifdef MP-WEIXIN -->
+    <GetPrivacy
+      ref="privacyComponent"
+      position="center"
+      @allowPrivacy="allowPrivacy"
+    />
+    <!-- #endif -->
+  </view>
 </template>
 
 <script setup>
-	import md5 from "js-md5";
-	import {
-		loginByUsername
-	} from "@/api/user/index.js";
-	import {
-		useUserStore
-	} from "@/store/index.js";
-	import droneSvg from '@/static/images/login/droneSvg.svg'
-	import usernameSvg from '@/static/images/login/username.svg'
-	import passwordSvg from '@/static/images/login/password.svg'
-	import logoSvg from '@/static/images/login/logo.svg'
-	import {
-		HOME_PATH,
-		LOGIN_PATH,
-		removeQueryString
-	} from "@/router";
+import { getAssetsImage } from "@/utils/index.js";
+import md5 from "js-md5";
+import { loginByUsername } from "@/api/user/index.js";
+import { useUserStore } from "@/store/index.js";
+// #ifdef MP-WEIXIN
+import GetPrivacy from "@/components/GetPrivacy.vue";
+// #endif
+import { HOME_PATH, LOGIN_PATH, removeQueryString } from "@/router";
+import { onMounted } from "vue";
 
-	const userStore = useUserStore();
-	const loginForm = ref({
-		username: "",
-		password: "",
-	});
-	const inputStyle = computed(() => {
-		const style = {};
-		if (loginForm.value.username && loginForm.value.password) {
-			style.color = "#fff";
-			style.backgroundColor = '#1D6FE9';
-		}
-		return style;
-	});
-	let redirect = HOME_PATH;
+const droneSvg = getAssetsImage("/images/login/droneSvg.png");
+const usernameSvg = getAssetsImage("/images/login/username.png");
+const passwordSvg = getAssetsImage("/images/login/password.png");
+const logoSvg = getAssetsImage("/images/login/logo.png");
+const passwordShow = getAssetsImage("/images/password-show.png");
+const passwordHide = getAssetsImage("/images/password-hide.png");
 
-	async function submit() {
-		let userInfo = {
-			tenantId: "000000",
-			deptId: "",
-			roleId: "",
-			username: loginForm.value.username,
-			password: loginForm.value.password,
-			type: "account",
-			code: "",
-			key: "",
-		};
-		try {
-			const res = await loginByUsername(
-				userInfo.tenantId,
-				userInfo.deptId,
-				userInfo.roleId,
-				userInfo.username,
-				md5(userInfo.password),
-				userInfo.type,
-				userInfo.key,
-				userInfo.code
-			);
-			userStore.setUserInfo(res.data);
-			uni.reLaunch({
-				url: "/pages/user/index",
-			});
-		} catch (error) {
-			const errorMsg =error.data?.error_description !=="Bad credentials" ? error.data?.error_description:  "登录失败,请重试";
-			uni.showToast({
-				title: errorMsg,
-				icon: "none",
-				duration: 2000
-			});
+const userStore = useUserStore();
+const loginForm = ref({
+  username: "",
+  password: "",
+});
+const rememberPassword = ref(false);
+const inputStyle = computed(() => {
+  const style = {};
+  if (loginForm.value.username && loginForm.value.password) {
+    style.color = "#fff";
+    style.backgroundColor = "#1D6FE9";
+  }
+  return style;
+});
+let redirect = HOME_PATH;
+function toggleRemember(e) {
+  rememberPassword.value = e.detail.value.length > 0;
+}
 
-		}
-	}
+async function submit() {
+  if (!loginForm.value.username.trim()) {
+    uni.showToast({
+      title: "请输入用户名",
+      icon: "none",
+      duration: 2000,
+    });
+    return;
+  }
 
-	onLoad((options) => {
-		if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) {
-			redirect = decodeURIComponent(options.redirect);
-		}
-	});
+  if (!loginForm.value.password.trim()) {
+    uni.showToast({
+      title: "请输入密码",
+      icon: "none",
+      duration: 2000,
+    });
+    return;
+  }
+  let userInfo = {
+    tenantId: "000000",
+    deptId: "",
+    roleId: "",
+    username: loginForm.value.username,
+    password: loginForm.value.password,
+    type: "account",
+    code: "",
+    key: "",
+  };
+
+  if (rememberPassword.value) {
+    uni.setStorageSync("rememberedUser", {
+      username: loginForm.value.username,
+      password: loginForm.value.password,
+    });
+  } else {
+    uni.removeStorageSync("rememberedUser");
+  }
+
+  try {
+    const res = await loginByUsername(
+      userInfo.tenantId,
+      userInfo.deptId,
+      userInfo.roleId,
+      userInfo.username,
+      md5(userInfo.password),
+      userInfo.type,
+      userInfo.key,
+      userInfo.code
+    );
+    userStore.setUserInfo(res.data);
+    uni.reLaunch({
+      url: "/pages/map/index",
+    });
+  } catch (error) {
+    const errorMsg =
+      error.data?.error_description !== "Bad credentials"
+        ? error.data?.error_description
+        : "登录失败,请重试";
+    uni.showToast({
+      title: errorMsg,
+      icon: "none",
+      duration: 2000,
+    });
+  }
+}
+
+const showPassword = ref(true);
+
+const showPassWord = () => {
+  showPassword.value = !showPassword.value;
+};
+
+const passwordTriggerIcon = computed(() => {
+  return showPassword.value ? passwordHide : passwordShow;
+});
+
+// 从本地存储加载记住的密码
+onMounted(() => {
+  const savedUserInfo = uni.getStorageSync("rememberedUser");
+  console.log("记住密码", savedUserInfo);
+  if (savedUserInfo) {
+    loginForm.value.username = savedUserInfo.username;
+    loginForm.value.password = savedUserInfo.password;
+    rememberPassword.value = true;
+  }
+});
+onLoad((options) => {
+  if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) {
+    redirect = decodeURIComponent(options.redirect);
+  }
+});
+
+// #ifdef MP-WEIXIN
+const allowPrivacy = () => {
+  // 同意隐私协议触发事件,有些接口需要同意授权后才能执行,比如获取手机号授权接口,可以在同意隐私协议后,再执行授权获取手机号接口,如果不需要可以不添加该方法
+  console.log("同意隐私授权");
+};
+const privacyComponent = ref(null);
+// onShow(() =>{
+//   wx.getPrivacySetting({
+//     success: res => {
+//       console.log(res)
+//       if (!res.needAuthorization) {
+//         privacyComponent?.value?.closePrivacy()
+//         // 查询授权,针对有tab切换的页面,可以在onshow中查询隐私授权状态,判断在tab切换后是否需要关闭授权弹框
+//         console.log('已经同意隐私授权,不需要再次授权')
+//       }
+//     },
+//     fail: () => {},
+//     complete: () => {}
+//   })
+// })
+// #endif
 </script>
 
 <style lang="scss" scoped>
-	.login-form-wrap {
-		position: relative;
-		text-align: center;
-		height: 100%;
-		width: 100%;
+.login-form-wrap {
+  position: relative;
+  text-align: center;
+  height: 100%;
+  width: 100%;
 
-		.logo {
-			width: 127px;
-			height: 51px;
-			margin: 0 auto;
-			margin-top: 104px;
-			margin-bottom: 16px;
-		}
+  .logo {
+    width: 127px;
+    height: 51px;
+    margin: 0 auto;
+    margin-top: 104px;
+    margin-bottom: 16px;
+  }
 
-		.lowerRightCorner {
-			position: absolute;
-			right: 0;
-			bottom: 0;
-			width: 425.61rpx;
-			height: 316.46rpx;
-		}
+  .lowerRightCorner {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: 425.61rpx;
+    height: 316.46rpx;
+  }
 
-		.title {
-			font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
-			font-weight: 400;
-			font-size: 84rpx;
-			line-height: 82rpx;
-			letter-spacing: 1px;
-			text-shadow: 0px 8px 8px rgba(0, 0, 0, 0.18);
-			text-align: center;
-			font-style: normal;
-			text-transform: none;
-			color: #1452D3;
-			text-align: center;
-			margin-bottom: 168rpx;
-		}
+  .title {
+    font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
+    font-weight: 400;
+    font-size: 84rpx;
+    line-height: 82rpx;
+    letter-spacing: 1px;
+    text-shadow: 0px 8px 8px rgba(0, 0, 0, 0.18);
+    text-align: center;
+    font-style: normal;
+    text-transform: none;
+    color: #1452d3;
+    text-align: center;
+    margin-bottom: 168rpx;
+  }
 
-		.user-name,
-		.pass-word {
-			display: flex;
-			//justify-content: center;
-			align-items: center;
-			margin-left: 62rpx;
-			margin-right: 58rpx;
-			height: 118rpx;
-			border-bottom: 2rpx solid #E3E3E3;
+  .user-name,
+  .pass-word {
+    display: flex;
+    //justify-content: center;
+    align-items: center;
+    margin-left: 62rpx;
+    margin-right: 58rpx;
+    height: 118rpx;
+    border-bottom: 2rpx solid #e3e3e3;
 
-			image {
-				width: 40rpx;
-				height: 40rpx;
-				margin-right: 12rpx;
-			}
+    image {
+      width: 40rpx;
+      height: 40rpx;
+      margin-right: 12rpx;
+    }
 
-			:deep(uni-input) {
-				height: 40rpx;
-				line-height: 40rpx;
-				width: 80%;
-				margin-top: 20rpx;
-			}
+    .pass-word-icon {
+      margin-left: 12rpx;
+      margin-right: 0;
+    }
 
-			:deep(.uni-input-placeholder) {
-				font-size: 32rpx;
-				color: #E3E3E3;
-				font-weight: 500;
-			}
+    :deep(uni-input) {
+      height: 40rpx;
+      line-height: 40rpx;
+      width: 0;
+      flex: 1;
+      margin-top: 20rpx;
+      padding: 0 !important;
+    }
 
-			//:deep(.uni-input-input) {
-			//  top: 10px !important;
-			//}
-		}
+    :deep(input) {
+      height: 40rpx;
+      line-height: 40rpx;
+      width: 0;
+      flex: 1;
+      margin-top: 20rpx;
+      padding: 0 !important;
+      margin: 0 !important;
+    }
 
-		.pass-word {}
+    :deep(.uni-input-placeholder) {
+      font-size: 32rpx;
+      color: #e3e3e3;
+      font-weight: 500;
+    }
+  }
 
-		input {
-			@apply pb-6rpx mb-10rpx text-left;
-		}
+  .remember-password {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    margin-left: 62rpx;
+    margin-right: 58rpx;
+    margin-top: 20rpx;
 
-		.tips {
-			@apply mt-8rpx mb-60rpx;
+    color: #666;
+    font-size: 28rpx;
 
-			color: $u-info;
-		}
+    label {
+      display: flex;
+      align-items: center;
+    }
+  }
 
-		.login-btn {
-			z-index: 1;
-			@apply flex items-center justify-center py-12rpx px-0 text-30rpx border-none;
-			background: #5a93e6;
-			color: white;
-			width: 590rpx;
-			height: 76rpx;
-			border-radius: 40rpx 40rpx 40rpx 40rpx;
-			margin-top: 100rpx;
+  input {
+    @apply pb-6rpx mb-10rpx text-left;
+  }
 
-			&::after {
-				@apply border-none;
-			}
-		}
+  .tips {
+    @apply mt-8rpx mb-60rpx;
+    color: $u-info;
+  }
 
-		.alternative {
-			@apply flex justify-between mt-30rpx;
+  .login-btn {
+    z-index: 1;
+    @apply flex items-center justify-center py-12rpx px-0 text-30rpx border-none;
+    background: #1d6fe9;
+    color: white;
+    width: 590rpx;
+    height: 76rpx;
+    border-radius: 40rpx 40rpx 40rpx 40rpx;
+    margin-top: 100rpx;
 
-			color: $u-tips-color;
-		}
-	}
+    &::after {
+      @apply border-none;
+    }
+  }
 
-	.login-type-wrap {
-		@apply flex justify-between pt-350rpx px-150rpx pb-150rpx;
+  .alternative {
+    @apply flex justify-between mt-30rpx;
+    color: $u-tips-color;
+  }
+}
 
-		.item {
-			@apply flex items-center flex-col text-28rpx;
+.login-type-wrap {
+  @apply flex justify-between pt-350rpx px-150rpx pb-150rpx;
 
-			color: $u-content-color;
-		}
-	}
+  .item {
+    @apply flex items-center flex-col text-28rpx;
+    color: $u-content-color;
+  }
+}
 
-	.hint {
-		@apply px-40rpx py-20rpx text-24rpx;
+.hint {
+  @apply px-40rpx py-20rpx text-24rpx;
+  color: $u-tips-color;
 
-		color: $u-tips-color;
-
-		.link {
-			color: $u-warning;
-		}
-	}
-</style>
\ No newline at end of file
+  .link {
+    color: $u-warning;
+  }
+}
+</style>

--
Gitblit v1.9.3