agri-app/pages/login.vue

387 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="normal-login-container">
<view class="logo-content align-center justify-center flex">
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
</image>
<text class="title">智能农业移动端登录</text>
</view>
<view class="login-form-content">
<view class="input-item flex align-center">
<view class="iconfont icon-user icon"></view>
<input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
</view>
<view class="input-item flex align-center" style="position: relative;">
<view class="iconfont icon-password icon"></view>
<input v-if="!isShowPwd"
v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
<input v-else v-model="loginForm.password" type="text" class="input" placeholder="请输入密码" maxlength="20" />
<!-- 替换为 uni-icons 密码图标 -->
<uni-icons
class="pwd-icon"
:type="isShowPwd ? 'eye-filled' : 'eye'"
size="18"
color="#666"
@click="isShowPwd = !isShowPwd"
></uni-icons>
</view>
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
<view class="iconfont icon-code icon"></view>
<input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
<view class="login-code">
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
</view>
</view>
<!-- ========== 新增uni-data-checkbox 记住密码(可正常勾选) ========== -->
<view style="margin: 10px 0 0 10px; text-align: left;">
<uni-data-checkbox
multiple
v-model="value"
:localdata="data"
size="14"
color="#007aff"
@change="onRememberChange"
></uni-data-checkbox>
</view>
<!-- ========== 新增结束 ========== -->
<view class="action-btn">
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
</view>
<view class="reg text-center" v-if="register">
<text class="text-grey1">没有账号?</text>
<text @click="handleUserRegister" class="text-blue">立即注册</text>
</view>
<view class="xieyi text-center">
<text class="text-grey1">登录即代表同意</text>
<text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
</view>
</view>
<view class="icp ">
<view class="icp-one">
<view>
主办单位北京小策技术有限公司
</view>
<view>
版权所有北京小策技术有限公司
</view>
<view>
ICP备案/许可证号京ICP备2026002112号-2X
</view>
</view>
</view>
</view>
</template>
<script>
import { getCodeImg } from '@/api/login'
import { getToken } from '@/utils/auth'
import UniIcons from "../uni_modules/uni-icons/components/uni-icons/uni-icons.vue";
// ========== 新增引入uni-data-checkbox ==========
import UniDataCheckbox from "@/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue";
import CryptoJS from 'crypto-js'
export default {
// ========== 新增:注册组件 ==========
components: {UniIcons, UniDataCheckbox},
data() {
return {
isShowPwd: false,
codeUrl: "",
captchaEnabled: true,
// 用户注册开关
register: true,
data:[{value: 0, text: '记住密码'}],
globalConfig: getApp().globalData.config,
loginForm: {
username: "",
password: "",
code: "",
uuid: ""
},
value:[0],
// ========== 新增:记住密码相关数据 ==========
remember: true, // 记住密码选中状态
STORAGE_KEY: 'agri_login_pwd' // 存储密码的key
}
},
created() {
this.getCode()
},
onLoad() {
//#ifdef H5
if (getToken()) {
this.$tab.reLaunch('/pages/control/index')
}
//#endif
// ========== 新增:页面加载时自动回填密码 ==========
this.loadSavedPwd()
},
methods: {
// ========== 新增uni-data-checkbox 状态切换 ==========
onRememberChange(e) {
this.remember = false;
if (e.detail.value && e.detail.value===0) {
this.remember = true ;
}
},
// ========== 新增:密码加密存储核心方法 ==========
// ========== 【小程序修复版】生成加密密钥(无崩溃,兼容微信环境) ==========
getEncryptKey() {
try {
const sys = uni.getSystemInfoSync();
// 固定写死一部分避免调用wx.getAccountInfoSync导致的兼容问题
const appId = "agri-mini-program-fixed";
const deviceId = sys.deviceId || "default-device";
// 只做MD5不调用原生crypto安全绕过报错
return CryptoJS.MD5(deviceId + appId + "agri-pwd-key").toString();
} catch (e) {
return "agri-safe-key-2026-fixed";
}
},
// ========== 【小程序修复版】加密密码(彻底解决 crypto 随机数报错) ==========
encryptPwd(pwd) {
try {
const key = this.getEncryptKey();
// 核心修复强制指定编码关闭CryptoJS安全随机数适配小程序
const srcs = CryptoJS.enc.Utf8.parse(pwd);
const keys = CryptoJS.enc.Utf8.parse(key.slice(0, 16)); // 16位密钥
const encrypted = CryptoJS.AES.encrypt(srcs, keys, {
mode: CryptoJS.mode.ECB, // 用ECB模式小程序无兼容问题
padding: CryptoJS.pad.Pkcs7
});
// 返回base64且替换特殊字符避免请求异常
return encrypted.toString().replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '~');
} catch (e) {
console.error("加密失败", e);
return "";
}
},
// ========== 【小程序修复版】解密密码(对应解密) ==========
decryptPwd(encryptedStr) {
try {
if (!encryptedStr) return "";
// 先还原特殊字符
const decodeStr = encryptedStr.replace(/-/g, '+').replace(/_/g, '/').replace(/~/g, '=');
const key = this.getEncryptKey();
const keys = CryptoJS.enc.Utf8.parse(key.slice(0, 16));
const decrypt = CryptoJS.AES.decrypt(decodeStr, keys, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return decrypt.toString(CryptoJS.enc.Utf8) || "";
} catch (e) {
console.error("解密失败", e);
return "";
}
},
// 加载本地存储的密码并回填
loadSavedPwd() {
try {
const savedData = uni.getStorageSync(this.STORAGE_KEY)
if (!savedData) return
const data = JSON.parse(savedData)
if (data.username && data.encryptedPwd) {
this.loginForm.username = data.username
this.loginForm.password = this.decryptPwd(data.encryptedPwd)
this.remember = true // 回填密码时自动选中
}
} catch (e) {
console.error('加载记住密码失败:', e)
}
},
// 保存密码到本地(加密)
savePwd() {
// 修复:加密失败时不存储,避免异常
const pwd = this.encryptPwd(this.loginForm.password);
if (!this.remember || !pwd) {
uni.removeStorageSync(this.STORAGE_KEY);
return;
}
const saveData = {
username: this.loginForm.username,
encryptedPwd: pwd,
saveTime: Date.now()
};
uni.setStorageSync(this.STORAGE_KEY, JSON.stringify(saveData));
},
// ========== 原有方法(完全未修改) ==========
// 用户注册
handleUserRegister() {
this.$tab.redirectTo(`/pages/register`)
},
// 隐私协议
handlePrivacy() {
let site = this.globalConfig.appInfo.agreements[0]
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
},
// 用户协议
handleUserAgrement() {
let site = this.globalConfig.appInfo.agreements[1]
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
},
// 获取图形验证码
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (this.captchaEnabled) {
this.codeUrl = 'data:image/gif;base64,' + res.img
this.loginForm.uuid = res.uuid
}
})
},
// 登录方法
async handleLogin() {
if (this.loginForm.username === "") {
this.$modal.msgError("请输入账号")
} else if (this.loginForm.password === "") {
this.$modal.msgError("请输入密码")
} else if (this.loginForm.code === "" && this.captchaEnabled) {
this.$modal.msgError("请输入验证码")
} else {
this.$modal.loading("登录中,请耐心等待...")
this.pwdLogin()
}
},
// 密码登录
async pwdLogin() {
this.$store.dispatch('Login', this.loginForm).then(() => {
this.$modal.closeLoading()
// ========== 新增:登录成功后保存密码 ==========
this.savePwd()
this.loginSuccess()
}).catch((err) => {
this.$modal.closeLoading(); // 新增:关闭加载框
this.$modal.msgError('登录失败:' + (err.msg || '账号密码错误')); // 新增:提示错误
if (this.captchaEnabled) {
this.getCode();
}
console.error('登录失败详情:', err); // 新增:打印错误,方便排查
})
},
// 登录成功后,处理函数
loginSuccess(result) {
// 设置用户信息
this.$store.dispatch('GetInfo').then(res => {
// ========== 原有MQTT初始化逻辑最小改动 ==========
// 1. 获取全局App实例
const app = getApp()
// 2. 获取登录后的token从auth工具获取
const token = getToken()
// 3. 订阅列表:暂时写死(后续可改为从后端接口获取)
// 示例:按用户名/用户ID生成专属订阅列表
// 864865085016294 十方北棚
// 864536071808560 七方北棚
// 864865085008135 八方北棚
// 4. 调用App.vue的loginSuccess方法初始化MQTT
app.loginSuccess(token)
// ========== 原有逻辑保留 ==========
this.$tab.reLaunch('/pages/control/index')
})
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #ffffff;
}
.pwd-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
.normal-login-container {
width: 100%;
.logo-content {
width: 100%;
font-size: 21px;
text-align: center;
padding-top: 15%;
image {
border-radius: 4px;
}
.title {
margin-left: 10px;
}
}
.login-form-content {
text-align: center;
margin: 20px auto;
margin-top: 15%;
width: 80%;
.input-item {
margin: 20px auto;
background-color: #f5f6f7;
height: 45px;
border-radius: 20px;
.icon {
font-size: 38rpx;
margin-left: 10px;
color: #999;
}
.input,
.input:focus,
.input:active,
.input:hover {
font-size: 14px;
line-height: 20px;
background-color: #f5f6f7; /* 输入框背景色(和页面背景区分) */
text-align: left;
padding-left: 15px;
border: none;
outline: none !important;
box-shadow: none;
}
}
.login-btn {
margin-top: 40px;
height: 45px;
}
.reg {
margin-top: 15px;
}
.xieyi {
color: #333;
margin-top: 20px;
}
.login-code {
height: 38px;
float: right;
.login-code-img {
height: 38px;
position: absolute;
margin-left: 10px;
width: 200rpx;
}
}
}
}
.icp { position: fixed; bottom: 0; width: 100%; background: #fff; padding: 12px 0; box-shadow: 0 -2px 4px rgba(0,0,0,0.1); }
.icp-one { max-width: 800px; margin: 0 auto; text-align: center; font-size: 12px; color: #666; }
</style>