428 lines
14 KiB
Vue
428 lines
14 KiB
Vue
<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>
|
||
<view class="login-page" v-if="false">
|
||
<!-- 授权登录按钮 -->
|
||
<view class="submit-btn" @tap.stop="openAuthorizationModal">
|
||
授权登录
|
||
</view>
|
||
|
||
<wx-user-info-modal
|
||
v-model="showAuthorizationModal"
|
||
@updated="updatedUserInfoEvent"
|
||
></wx-user-info-modal>
|
||
</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 WxUserInfoModal from '@/uni_modules/tuniaoui-wx-user-info/components/tuniaoui-wx-user-info/tuniaoui-wx-user-info.vue'
|
||
|
||
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,WxUserInfoModal},
|
||
data() {
|
||
return {
|
||
isShowPwd: false,
|
||
codeUrl: "",
|
||
showAuthorizationModal: false,
|
||
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() {
|
||
if (getToken()) {
|
||
this.$tab.reLaunch('/pages/index')
|
||
}
|
||
// ========== 新增:页面加载时自动回填密码 ==========
|
||
this.loadSavedPwd()
|
||
},
|
||
methods: {
|
||
// ========== 新增:uni-data-checkbox 状态切换 ==========
|
||
onRememberChange(e) {
|
||
this.remember = false;
|
||
if (e.detail.value && e.detail.value===0) {
|
||
this.remember = true ;
|
||
}
|
||
},
|
||
// 打开获取用户信息弹框
|
||
openAuthorizationModal() {
|
||
this.showAuthorizationModal = true
|
||
},
|
||
|
||
// 获取到的用户信息
|
||
updatedUserInfoEvent(info) {
|
||
console.log('获取到的用户信息', info)
|
||
},
|
||
// ========== 新增:密码加密存储核心方法 ==========
|
||
// ========== 【小程序修复版】生成加密密钥(无崩溃,兼容微信环境) ==========
|
||
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/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; }
|
||
.login-page {
|
||
width: 300rpx;
|
||
height: 100rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 授权按钮 */
|
||
.submit-btn {
|
||
width: 100%;
|
||
background-color: #05C160;
|
||
color: #FFFFFF;
|
||
margin-top: 60rpx;
|
||
border-radius: 10rpx;
|
||
padding: 25rpx;
|
||
font-size: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 30rpx;
|
||
}
|
||
</style>
|