Files
device-voice-h5/pages/login/login.vue
sexygoat 243f795e3e
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 47s
feat: 新增授权文件
2026-04-13 11:13:47 +08:00

290 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="login-page">
<view class="content">
<view class="card">
<view class="card-title">用户登录</view>
<view class="form-item">
<view class="label">设备标识</view>
<view class="input-wrap" :class="{ focus: inputFocus }">
<input
v-model="identifier"
class="input"
placeholder="请输入SN/IMEI/虚拟号/ICCID/MSISDN"
placeholder-class="placeholder"
@focus="inputFocus = true"
@blur="inputFocus = false"
@input="showError = false"
/>
<view v-if="identifier" class="clear" @tap="identifier = ''">
<text>×</text>
</view>
</view>
<text v-if="showError" class="error">请输入设备标识</text>
</view>
<view class="form-item">
<button
class="btn-login"
:class="{ disabled: !identifier || loading }"
:disabled="!identifier || loading"
@tap="handleLogin"
>
<view v-if="loading" class="loading">
<view class="dot"/>
<view class="dot"/>
<view class="dot"/>
</view>
<text v-else>登录</text>
</button>
</view>
<view class="agreement">
<text class="agree-text">登录即表示同意</text>
<text class="link">用户协议</text>
<text class="agree-text"></text>
<text class="link">隐私政策</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { authApi } from '@/api/index.js';
import { useUserStore } from '@/store/index.js';
import { APP_ID, USE_WECHAT_AUTH } from '@/utils/env.js';
const userStore = useUserStore();
const identifier = ref('TEST5G');
const loading = ref(false);
const agreed = ref(true);
const inputFocus = ref(false);
const showError = ref(false);
const getCode = () => {
const params = new URLSearchParams(window.location.search);
return params.get('code');
};
const clearCode = () => {
window.history.replaceState({}, '', window.location.pathname);
};
const redirectToWxAuth = (assetToken) => {
sessionStorage.setItem('assetToken', assetToken);
const redirectUri = encodeURIComponent('https://cmp-c.boss160.cn/');
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APP_ID}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=wechat_auth#wechat_redirect`;
window.location.href = url;
};
const handleLogin = () => {
if (!identifier.value) {
showError.value = true;
return;
}
doLogin();
};
const doLogin = async () => {
loading.value = true;
try {
const verifyData = await authApi.verifyAsset(identifier.value);
userStore.setAssetToken(verifyData.asset_token);
userStore.setIdentifier(identifier.value);
if (!USE_WECHAT_AUTH) {
const loginData = await authApi.wechatLogin(verifyData.asset_token, 'mock_code');
userStore.setToken(loginData.token);
uni.setStorageSync('identifier', userStore.state.identifier);
uni.redirectTo({ url: '/pages/index/index' });
return;
}
redirectToWxAuth(verifyData.asset_token);
} catch (e) {
console.error('登录失败', e);
loading.value = false;
}
};
const handleWechatCallback = async () => {
if (!USE_WECHAT_AUTH) return;
const code = getCode();
const assetToken = sessionStorage.getItem('assetToken');
if (!code || !assetToken) return;
loading.value = true;
clearCode();
try {
const loginData = await authApi.wechatLogin(assetToken, code);
userStore.setToken(loginData.token);
uni.setStorageSync('identifier', userStore.state.identifier);
uni.redirectTo({ url: '/pages/index/index' });
} catch (e) {
console.error('微信登录失败', e);
loading.value = false;
}
};
onMounted(() => {
handleWechatCallback();
});
</script>
<style lang="scss" scoped>
.login-page {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #FFFFFF;
padding: 0 40rpx;
}
.content {
width: 100%;
}
.card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 48rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.08);
}
.card-title {
font-size: 40rpx;
font-weight: 600;
color: #1A1A1A;
margin-bottom: 20rpx;
}
.form-item {
margin-bottom: 32rpx;
}
.label {
font-size: 26rpx;
color: #6B7280;
margin-bottom: 16rpx;
}
.input-wrap {
display: flex;
align-items: center;
background: #F9FAFB;
border: 2rpx solid #E5E7EB;
border-radius: 16rpx;
padding: 28rpx 24rpx;
margin-top: 40rpx;
transition: all 0.2s;
}
.input-wrap.focus {
border-color: #2563EB;
background: #FFFFFF;
}
.input {
flex: 1;
font-size: 30rpx;
color: #1A1A1A;
background: transparent;
}
.placeholder {
color: #9CA3AF;
}
.clear {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
color: #9CA3AF;
font-size: 36rpx;
}
.error {
font-size: 24rpx;
color: #EF4444;
margin-top: 12rpx;
}
.btn-login {
width: 100%;
height: 96rpx;
background: #2563EB;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 500;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.btn-login.disabled {
background: #E5E7EB;
color: #9CA3AF;
}
.btn-login:active:not(.disabled) {
background: #1D4ED8;
}
.loading {
display: flex;
align-items: center;
gap: 8rpx;
}
.dot {
width: 8rpx;
height: 8rpx;
background: #9CA3AF;
border-radius: 50%;
animation: load 1.4s infinite ease-in-out both;
}
.dot:nth-child(1) { animation-delay: -0.32s; }
.dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes load {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
.agreement {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
gap: 8rpx;
margin-top: 40rpx;
}
.agree-text {
font-size: 24rpx;
color: #9CA3AF;
white-space: nowrap;
}
.link {
font-size: 24rpx;
color: #2563EB;
white-space: nowrap;
}
</style>