Files
oa_app/pages/login/login.vue
T
2026-09-23 16:52:48 +08:00

154 lines
5.3 KiB
Vue

<template>
<view class="page login">
<fy-header title="员工认证" />
<view class="body">
<view class="brand-box">
<view class="logo"></view>
<text class="name">风影随行通讯</text>
<text class="sub">员工凭据认证 · 企业内网安全通道</text>
</view>
<view class="field">
<text class="lab">手机号 / 企业工号</text>
<view class="box">
<text class="pre">+86</text>
<input v-model="phone" placeholder="请输入手机号或工号" />
</view>
</view>
<view class="field">
<text class="lab">账户密码</text>
<view class="box">
<input v-model="password" :password="!showPwd" placeholder="请输入密码" />
<text class="eye" @click="showPwd = !showPwd">{{ showPwd ? '' : '' }}</text>
</view>
</view>
<view v-if="smsRequired" class="field">
<text class="lab">双因素安全验证码</text>
<view class="box">
<input v-model="sms" placeholder="6位动态码" maxlength="8" />
<text class="sms" :class="{ off: wait > 0 }" @click="sendSms">{{ wait > 0 ? wait + 's 后重新获取' : '获取验证码' }}</text>
</view>
</view>
<view class="agree" @click="agreed = !agreed">
<view class="ck" :class="{ on: agreed }" />
<text class="ag">我已阅读并同意企业信息安全保密协议员工使用守则</text>
</view>
<view class="btn-primary" :class="{ dim: busy }" @click="submit">{{ busy ? '校验中' : '安全登录' }}</view>
<text v-if="error" class="err">{{ error }}</text>
</view>
</view>
</template>
<script>
import FyHeader from '../../components/fy-header.vue'
import { store, savedPhone } from '../../utils/store.js'
import { sendLoginCode } from '../../utils/api.js'
import { toast } from '../../utils/format.js'
export default {
components: { FyHeader },
data() {
return {
phone: '',
password: '',
sms: '',
showPwd: false,
agreed: true,
wait: 0,
timer: 0,
busy: false,
error: '',
smsRequired: false,
off: null,
lastPhase: ''
}
},
onShow() {
if (this.off) this.off()
this.phone = this.phone || savedPhone()
this.smsRequired = store.smsRequired
if (store.fixedSms && !this.sms) this.sms = store.fixedSms
this.sync()
this.off = store.on(() => this.sync())
},
onHide() {
if (this.off) this.off()
this.off = null
},
onUnload() {
if (this.timer) clearInterval(this.timer)
if (this.off) this.off()
this.off = null
},
methods: {
sync() {
this.busy = store.busy
this.error = store.error
this.smsRequired = store.smsRequired
if (store.phase === this.lastPhase) return
this.lastPhase = store.phase
if (store.phase === 'forcePassword') {
uni.redirectTo({ url: '/pages/password/password' })
} else if (store.phase === 'app') {
uni.switchTab({ url: '/pages/tabs/messages' })
}
},
async sendSms() {
if (this.wait > 0) return
if (!this.phone.trim()) return toast('请先填写手机号或工号')
try {
await sendLoginCode(this.phone.trim())
toast('验证码已发送', 'success')
this.wait = 60
this.timer = setInterval(() => {
this.wait -= 1
if (this.wait <= 0) clearInterval(this.timer)
}, 1000)
} catch (e) {
toast(e.message || '发送失败')
}
},
async submit() {
if (!this.agreed) return toast('请先阅读并勾选企业安全协议')
if (!this.phone.trim() || !this.password) return toast('请填写账号和密码')
await store.login({ phone: this.phone, password: this.password, sms: this.sms })
this.sync()
}
}
}
</script>
<style scoped>
.login { padding-bottom: 48rpx; }
.body { padding: 48rpx 40rpx 80rpx; }
.brand-box { display: flex; flex-direction: column; align-items: center; margin-bottom: 48rpx; }
.logo {
width: 128rpx; height: 128rpx; border-radius: 32rpx;
background: linear-gradient(135deg, #004ac6, #2563eb);
color: #fff; font-size: 52rpx; font-weight: 700;
display: flex; align-items: center; justify-content: center;
margin-bottom: 20rpx;
}
.name { font-size: 40rpx; font-weight: 700; }
.sub { font-size: 22rpx; color: #737686; margin-top: 8rpx; }
.field { margin-bottom: 24rpx; }
.lab { font-size: 24rpx; color: #434655; margin-bottom: 8rpx; display: block; }
.box {
height: 96rpx; background: #fff; border-radius: 16rpx;
border: 1rpx solid rgba(0,0,0,0.04); padding: 0 24rpx;
display: flex; align-items: center;
}
.box input { flex: 1; font-size: 28rpx; }
.pre { font-size: 24rpx; color: #737686; margin-right: 16rpx; padding-right: 16rpx; border-right: 1rpx solid #eaedff; }
.eye, .sms { font-size: 22rpx; color: #2563eb; padding-left: 12rpx; }
.sms.off { color: #737686; }
.agree { display: flex; align-items: flex-start; gap: 12rpx; margin: 16rpx 0 32rpx; }
.ck { width: 28rpx; height: 28rpx; border: 2rpx solid #c3c6d7; border-radius: 6rpx; margin-top: 4rpx; }
.ck.on { background: #2563eb; border-color: #2563eb; }
.ag { flex: 1; font-size: 22rpx; color: #737686; line-height: 1.5; }
.err { display: block; color: #ba1a1a; font-size: 24rpx; margin-top: 16rpx; text-align: center; }
.dim { opacity: 0.65; }
</style>