571 lines
13 KiB
Vue
571 lines
13 KiB
Vue
<template>
|
||
<view class="login-page">
|
||
<!-- 顶部渐变区域 -->
|
||
<view class="login-header">
|
||
<image src="/static/banner_c.png" mode="widthFix" class="login-banner"></image>
|
||
<view class="login-welcome">
|
||
<text class="login-title">欢迎回来</text>
|
||
<text class="login-subtitle">登录您的账号以继续</text>
|
||
</view>
|
||
</view>
|
||
<!-- 登录卡片 -->
|
||
<view class="login-card">
|
||
<view class="login-tabs">
|
||
<view class="login-tab" :class="{active: current===0}" @click="onClickItem({currentIndex:0})">密码登录</view>
|
||
<view class="login-tab" :class="{active: current===1}" @click="onClickItem({currentIndex:1})">验证码登录</view>
|
||
<view class="login-tab-indicator" :style="{left: current===0?'0%':'50%'}"></view>
|
||
</view>
|
||
<view class="login-form-wrap">
|
||
<view v-if="current==0">
|
||
<uni-forms ref="form1" :modelValue="formData" label-position="left" :rules="rules1">
|
||
<uni-forms-item required label="用户名" name="username">
|
||
<uni-easyinput type="text" v-model="formData.username" placeholder="请输入用户名" />
|
||
</uni-forms-item>
|
||
<uni-forms-item required label="密码" name="password">
|
||
<uni-easyinput type="password" v-model="formData.password" placeholder="请输入密码" />
|
||
</uni-forms-item>
|
||
</uni-forms>
|
||
</view>
|
||
<view v-if="current==1">
|
||
<uni-forms ref="form2" :modelValue="formData2" label-position="left" :rules="rules2">
|
||
<uni-forms-item label="手机号" name="phone" required>
|
||
<uni-easyinput type="number" v-model="formData2.phone" placeholder="请输入手机号" />
|
||
</uni-forms-item>
|
||
<uni-forms-item label="验证码" name="code" required>
|
||
<view class="code-row">
|
||
<view class="code-input">
|
||
<uni-easyinput type="number" v-model="formData2.code" placeholder="请输入验证码" />
|
||
</view>
|
||
<view class="code-btn" @click="getCode">
|
||
<text class="code-btn-text">{{content}}</text>
|
||
</view>
|
||
</view>
|
||
</uni-forms-item>
|
||
</uni-forms>
|
||
</view>
|
||
</view>
|
||
<button class="login-btn" @click="login(current)">登 录</button>
|
||
</view>
|
||
<!-- 下载进度弹窗 -->
|
||
<view v-show="dShow" class="download-mask">
|
||
<view class="download-dialog">
|
||
<view class="download-icon">⬇</view>
|
||
<text class="download-text">下载中 请勿退出</text>
|
||
<view class="download-progress-bar">
|
||
<view class="download-progress-fill" :style="{width: percentVal+'%'}"></view>
|
||
</view>
|
||
<text class="download-percent">{{percentVal}}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import config from '/utils/config';
|
||
import { auth } from '@/utils/auth.js'
|
||
export default {
|
||
data() {
|
||
return {
|
||
current: 0,
|
||
items: ['密码登录', '验证码登录'],
|
||
content: '获取验证码',
|
||
formData: {},
|
||
formData2: {},
|
||
timer: null,
|
||
time: 30,
|
||
rules1: {
|
||
username: {
|
||
rules: [{
|
||
required: true,
|
||
errorMessage: '请填写',
|
||
}]
|
||
},
|
||
password: {
|
||
rules: [{
|
||
required: true,
|
||
errorMessage: '请填写',
|
||
}]
|
||
}
|
||
},
|
||
rules2: {
|
||
phone: {
|
||
rules: [{
|
||
required: true,
|
||
errorMessage: '请填写',
|
||
}]
|
||
},
|
||
code: {
|
||
rules: [{
|
||
required: true,
|
||
errorMessage: '请填写',
|
||
}]
|
||
}
|
||
},
|
||
wxmp_openid: null,
|
||
dShow: false,
|
||
percentVal: 0
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
var that = this
|
||
var autoLoading = options.autoLoading;
|
||
that.checkVersion();
|
||
if (autoLoading != 'no') {
|
||
// #ifdef APP-PLUS || H5
|
||
that.appLogin()
|
||
// #endif
|
||
// #ifdef MP-WEIXIN
|
||
that.wxmpLogin()
|
||
// #endif
|
||
}
|
||
},
|
||
onUnload() {
|
||
if (this.timer) {
|
||
clearInterval(this.timer);
|
||
this.timer = null;
|
||
}
|
||
},
|
||
onShow() {
|
||
// #ifdef MP-WEIXIN
|
||
uni.hideHomeButton()
|
||
// #endif
|
||
},
|
||
methods: {
|
||
checkVersion() {
|
||
let that = this;
|
||
uni.getSystemInfo({
|
||
success(res) {
|
||
uni.setStorageSync("vuex_version", res.appVersion)
|
||
// #ifdef APP-PLUS
|
||
if (res.platform == 'android') {
|
||
that.$api.apkCheck().then(res1 => {
|
||
if (res.appWgtVersion != res1.version) {
|
||
uni.showModal({
|
||
title: "版本更新",
|
||
content: '有新的版本发布, 请下载新版本',
|
||
confirmText: '立即更新',
|
||
cancelText: '稍后进行',
|
||
success: function(res2) {
|
||
if (res2.confirm) {
|
||
let fullurl = res1.file
|
||
if (res1.file.indexOf("http") == -1) {
|
||
fullurl = config.baseUrl + res1.file
|
||
}
|
||
that.dShow =
|
||
true // show变量控制一个下载进度弹框(这个UI样式自己写即可)
|
||
uni.hideTabBar()
|
||
// 创建一个下载任务,并根据后端返回的apk静态资源地址filePath进行下载
|
||
var dtask = plus.downloader.createDownload(
|
||
fullurl, {},
|
||
function(d, status) {
|
||
// 下载完成
|
||
if (status == 200) {
|
||
that.dShow =
|
||
false // 下载完成再把下载进度弹框关闭即可
|
||
uni.showTabBar();
|
||
plus.runtime.install(plus.io
|
||
.convertLocalFileSystemURL(
|
||
d
|
||
.filename), {}, {},
|
||
function(error) {
|
||
uni.showToast({
|
||
title: '安装失败',
|
||
duration: 1500
|
||
});
|
||
})
|
||
} else {
|
||
uni.showToast({
|
||
title: '更新失败',
|
||
duration: 1500
|
||
});
|
||
}
|
||
});
|
||
|
||
function onStateChanged(download, status) {
|
||
|
||
if (status == 200) {
|
||
const totalSize = download.totalSize
|
||
let nowSize = download.downloadedSize
|
||
that.percentVal = nowSize / 17700000 *
|
||
100 > 99 ? 99 : (nowSize /
|
||
17700000 * 100).toFixed(0)
|
||
}
|
||
|
||
}
|
||
dtask.addEventListener("statechanged",
|
||
onStateChanged, false);
|
||
dtask.start();
|
||
} else {
|
||
|
||
}
|
||
}
|
||
});
|
||
}
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
})
|
||
},
|
||
wxmpLogin() {
|
||
var that = this;
|
||
uni.showLoading({
|
||
title: '自动登录中...',
|
||
mask: true
|
||
})
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: (loginRes) => {
|
||
that.$api.wxmpLogin({
|
||
code: loginRes.code
|
||
}).then(res => {
|
||
uni.setStorageSync('access', res.access)
|
||
that.$api.getUserInfo().then(res => {
|
||
uni.setStorageSync('userInfo', res)
|
||
auth.setPermissions(Object.keys(res.perms))
|
||
uni.reLaunch({
|
||
url: '/pages/index/index'
|
||
})
|
||
|
||
})
|
||
}).catch(e => {
|
||
this.wxmp_openid = e.data.wxmp_openid
|
||
})
|
||
},
|
||
complete: function() {
|
||
uni.hideLoading();
|
||
},
|
||
})
|
||
},
|
||
appLogin() {
|
||
var that = this;
|
||
// App自动登录
|
||
uni.showLoading({
|
||
title: '自动登录中...',
|
||
mask: true
|
||
})
|
||
uni.getStorage({
|
||
key: 'mySecret',
|
||
success: function(res) {
|
||
let secret = res.data
|
||
if (secret) {
|
||
that.$api.loginSecret(JSON.parse(secret)).then(res => {
|
||
uni.setStorageSync('access', res.access)
|
||
that.$api.getUserInfo().then(res => {
|
||
uni.setStorageSync('userInfo', res)
|
||
auth.setPermissions(Object.keys(res.perms))
|
||
uni.reLaunch({
|
||
url: '/pages/index/index'
|
||
})
|
||
})
|
||
})
|
||
}
|
||
},
|
||
complete: function() {
|
||
uni.hideLoading();
|
||
},
|
||
|
||
});
|
||
},
|
||
onClickItem(e) {
|
||
if (this.current !== e.currentIndex) {
|
||
this.current = e.currentIndex
|
||
}
|
||
},
|
||
countDown() {
|
||
var that = this;
|
||
if (that.time >= 0) {
|
||
that.content = that.time.toString() + '秒';
|
||
--that.time
|
||
} else {
|
||
clearInterval(that.timer)
|
||
that.timer = null
|
||
that.content = '获取验证码'
|
||
}
|
||
},
|
||
getCode() {
|
||
var that = this
|
||
if (that.content == '获取验证码') {
|
||
if (that.$check.mobile(that.formData2.phone)) {
|
||
that.$api.getCode({
|
||
phone: that.formData2.phone
|
||
}).then(res => {
|
||
uni.showToast({
|
||
icon: "none",
|
||
title: '验证码已发送'
|
||
})
|
||
setInterval(that.countDown, 1000)
|
||
})
|
||
} else {
|
||
uni.showToast({
|
||
icon: "none",
|
||
title: '手机号格式有误'
|
||
})
|
||
}
|
||
}
|
||
},
|
||
login() {
|
||
var that = this;
|
||
if (that.current == 1) {
|
||
that.$refs.form2.validate().then(res0 => {
|
||
that.$api.codeLogin(res0).then(res => {
|
||
uni.setStorageSync('access', res.access)
|
||
that.$api.getUserInfo().then(res => {
|
||
uni.setStorageSync('userInfo', res)
|
||
auth.setPermissions(Object.keys(res.perms))
|
||
that.bindXX()
|
||
uni.reLaunch({
|
||
url: '/pages/index/index'
|
||
})
|
||
})
|
||
})
|
||
}).catch(err => {
|
||
console.log('表单错误信息:', err);
|
||
})
|
||
} else if (that.current == 0) {
|
||
that.$refs.form1.validate().then(res0 => {
|
||
that.$api.login(res0).then(res => {
|
||
uni.setStorageSync('access', res.access)
|
||
that.bindXX()
|
||
that.$api.getUserInfo().then(res => {
|
||
uni.setStorageSync('userInfo', res)
|
||
auth.setPermissions(Object.keys(res.perms))
|
||
uni.reLaunch({
|
||
url: '/pages/index/index'
|
||
})
|
||
})
|
||
})
|
||
}).catch(err => {
|
||
console.log('表单错误信息:', err);
|
||
})
|
||
}
|
||
},
|
||
ranStr(e) {
|
||
//形参e,需要产生随机字符串的长度
|
||
//如果没有传参,默认生成32位长度随机字符串
|
||
e = e || 32;
|
||
//模拟随机字符串库
|
||
var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789",
|
||
a = t.length, //字符串t的长度,随机数生成最大值
|
||
n = "";
|
||
for (let i = 0; i < e; i++) {
|
||
//随机生成长度为e的随机字符串拼接
|
||
n += t.charAt(Math.floor(Math.random() * a));
|
||
}
|
||
//返回随机组合字符串
|
||
return n
|
||
},
|
||
bindXX() {
|
||
let that = this;
|
||
// #ifdef APP-PLUS || H5
|
||
let userInfo = uni.getStorageSync("userInfo")
|
||
let secret = that.ranStr(12)
|
||
let mySecret = {
|
||
'username': userInfo.username,
|
||
'secret': secret
|
||
}
|
||
this.$api.bindSecret({
|
||
secret: secret
|
||
}).then(res => {
|
||
uni.setStorageSync('mySecret', JSON.stringify(mySecret))
|
||
}).catch(e => {})
|
||
// #endif
|
||
// #ifdef MP-WEIXIN
|
||
if (this.wxmp_openid != null) {
|
||
this.$api.bindWxmp({
|
||
"openid": this.wxmp_openid
|
||
}).then(res => {
|
||
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.login-page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #14694A 0%, #1F8C5E 35%, #F5FAF7 40%);
|
||
padding: 0;
|
||
}
|
||
|
||
.login-header {
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.login-banner {
|
||
width: 100%;
|
||
display: block;
|
||
opacity: 0.08;
|
||
}
|
||
|
||
.login-welcome {
|
||
position: absolute;
|
||
bottom: 60rpx;
|
||
left: 48rpx;
|
||
}
|
||
|
||
.login-title {
|
||
display: block;
|
||
font-size: 48rpx;
|
||
font-weight: 800;
|
||
color: #FFFFFF;
|
||
letter-spacing: 6rpx;
|
||
}
|
||
|
||
.login-subtitle {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.login-card {
|
||
margin: -40rpx 32rpx 0;
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
padding: 44rpx 32rpx;
|
||
box-shadow: 0 12rpx 40rpx rgba(25, 27, 35, 0.06);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
.login-tabs {
|
||
display: flex;
|
||
position: relative;
|
||
margin-bottom: 40rpx;
|
||
border-bottom: 2rpx solid #C4C6D0;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.login-tab {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 30rpx;
|
||
color: #74777F;
|
||
padding-bottom: 20rpx;
|
||
transition: color 0.3s;
|
||
&.active {
|
||
color: #2BA471;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.login-tab-indicator {
|
||
position: absolute;
|
||
bottom: 0;
|
||
width: 50%;
|
||
height: 5rpx;
|
||
background: linear-gradient(90deg, #2BA471, #2BA471);
|
||
border-radius: 4rpx;
|
||
transition: left 0.3s ease;
|
||
}
|
||
|
||
.login-form-wrap {
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.code-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.code-input {
|
||
flex: 1;
|
||
}
|
||
|
||
.code-btn {
|
||
min-width: 180rpx;
|
||
height: 72rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #2BA471, #1F8C5E);
|
||
border-radius: 12rpx;
|
||
padding: 0 20rpx;
|
||
}
|
||
|
||
.code-btn-text {
|
||
color: #FFFFFF;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.login-btn {
|
||
width: 100% !important;
|
||
height: 92rpx;
|
||
line-height: 92rpx;
|
||
background: linear-gradient(135deg, #2BA471, #1F8C5E) !important;
|
||
color: #FFFFFF !important;
|
||
font-size: 33rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 8rpx;
|
||
border: none !important;
|
||
border-radius: 16rpx !important;
|
||
box-shadow: 0 8rpx 24rpx rgba(43, 164, 113, 0.2);
|
||
margin-top: 16rpx;
|
||
&:active {
|
||
opacity: 0.85;
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
|
||
.download-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.download-dialog {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 48rpx 40rpx;
|
||
width: 560rpx;
|
||
text-align: center;
|
||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.download-icon {
|
||
font-size: 64rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.download-text {
|
||
display: block;
|
||
font-size: 30rpx;
|
||
color: #191B23;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.download-progress-bar {
|
||
height: 12rpx;
|
||
background: #C4C6D0;
|
||
border-radius: 6rpx;
|
||
overflow: hidden;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.download-progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #2BA471, #1F8C5E);
|
||
border-radius: 6rpx;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
.download-percent {
|
||
font-size: 28rpx;
|
||
color: #2BA471;
|
||
font-weight: 600;
|
||
}
|
||
</style> |