Files
gt-agent-company/src/pages/common/login/index.vue
sexygoat 91d7d4c21f
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 1m27s
fix: 优化界面以及更新接口
2026-04-15 11:54:43 +08:00

492 lines
10 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="page">
<!-- 背景装饰 -->
<view class="bg-decor">
<view class="shape shape-1" />
<view class="shape shape-2" />
<view class="shape shape-3" />
</view>
<!-- 登录卡片 -->
<view class="login-card">
<!-- 顶部品牌区 -->
<view class="brand-header">
<view class="welcome-info">
<view class="welcome-title">欢迎回来</view>
<view class="welcome-desc">请输入您的账号密码进行登录</view>
</view>
</view>
<!-- 表单区域 -->
<view class="form-area">
<!-- 账号输入框 -->
<view class="input-wrap" :class="{ focused: accountFocused }">
<input
v-model="account"
class="input"
type="text"
placeholder="请输入账号"
@focus="accountFocused = true"
@blur="accountFocused = false"
>
</view>
<!-- 密码输入框 -->
<view class="input-wrap" :class="{ focused: passwordFocused }">
<input
v-model="password"
class="input"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码"
@focus="passwordFocused = true"
@blur="passwordFocused = false"
>
<image
v-if="password"
class="pwd-toggle"
:src="showPassword ? '/static/icons/preview-open.png' : '/static/icons/preview-close-one.png'"
mode="aspectFit"
@tap="togglePassword"
/>
</view>
<!-- 登录按钮 -->
<button
class="btn"
:disabled="loading"
@tap="submit"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { HOME_PATH, isTabBarPath, LOGIN_PATH, removeQueryString } from '@/router';
import { setToken } from '@/utils/auth';
import { login } from '@/api/auth';
const account = ref<string>('');
const password = ref<string>('');
const accountFocused = ref<boolean>(false);
const passwordFocused = ref<boolean>(false);
const showPassword = ref<boolean>(false);
const loading = ref<boolean>(false);
let redirect = HOME_PATH;
const isFormValid = computed(() => {
return account.value.trim().length > 0 && password.value.trim().length > 0;
});
function togglePassword() {
showPassword.value = !showPassword.value;
}
async function submit() {
if (!isFormValid.value) {
if (!account.value.trim()) {
uni.$u.toast('请输入账号');
return;
}
if (!password.value.trim()) {
uni.$u.toast('请输入密码');
return;
}
}
if (loading.value) return;
loading.value = true;
try {
// 调用登录接口(禁用拦截器的自动 toast由页面层统一处理
const res = await login({
username: account.value.trim(),
password: password.value.trim(),
});
// 保存 access_token
setToken(res.access_token);
// 保存 refresh_token 到本地存储
uni.setStorageSync('refresh_token', res.refresh_token);
// 保存用户信息到本地存储
uni.setStorageSync('user_info', res.user);
// 调试日志:查看用户信息
console.log('登录成功,用户信息:', res.user);
// 验证用户类型 (只允许 3=代理 和 4=企业 登录)
if (res.user.user_type !== 3 && res.user.user_type !== 4) {
const typeNames: Record<number, string> = {
1: '超级管理员',
2: '平台用户',
};
const typeName = typeNames[res.user.user_type] || '未知';
uni.showToast({
title: `${typeName}账号无法登录此系统`,
icon: 'none',
duration: 2500,
});
// 清除已保存的 token
setToken('');
uni.removeStorageSync('refresh_token');
uni.removeStorageSync('user_info');
return;
}
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 1500,
});
setTimeout(() => {
// 代理商和企业用户都跳转到首页(首页会根据用户类型显示不同功能)
uni.$u.route({
type: 'redirectTo',
url: '/pages/agent-system/home/index',
});
}, 1500);
} catch (error: any) {
console.error('登录失败:', error);
// 拦截器已经显示了后端返回的错误信息,这里不需要再次提示
// 如果需要额外处理,可以访问 error.data.msg 获取错误信息
} finally {
loading.value = false;
}
}
onLoad((options: any) => {
if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) {
redirect = decodeURIComponent(options.redirect);
}
});
</script>
<style lang="scss" scoped>
.page {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%);
overflow: hidden;
animation: bgFade 0.8s ease-out;
}
@keyframes bgFade {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* 背景装饰 - 小米风格 */
.bg-decor {
position: absolute;
inset: 0;
pointer-events: none;
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background:
linear-gradient(90deg, transparent 0%, rgba(22, 119, 255, 0.02) 50%, transparent 100%);
animation: scanline 8s linear infinite;
}
.shape {
position: absolute;
}
.shape-1 {
width: 800rpx;
height: 800rpx;
top: -300rpx;
right: -200rpx;
background: radial-gradient(circle, rgba(22, 119, 255, 0.08), transparent 60%);
border-radius: 50%;
animation: pulse1 10s infinite ease-in-out;
}
.shape-2 {
width: 600rpx;
height: 600rpx;
bottom: -200rpx;
left: -150rpx;
background: radial-gradient(circle, rgba(22, 119, 255, 0.06), transparent 60%);
border-radius: 50%;
animation: pulse2 12s infinite ease-in-out;
}
.shape-3 {
width: 400rpx;
height: 400rpx;
top: 50%;
right: -100rpx;
background: radial-gradient(circle, rgba(22, 119, 255, 0.04), transparent 60%);
border-radius: 50%;
animation: pulse3 14s infinite ease-in-out;
}
}
@keyframes scanline {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
@keyframes pulse1 {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
@keyframes pulse2 {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.15);
opacity: 0.7;
}
}
@keyframes pulse3 {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.6;
}
}
/* 登录卡片 - 优化风格 */
.login-card {
position: relative;
width: 100%;
max-width: 680rpx;
padding: 60rpx 48rpx;
background: #ffffff;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08), 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
z-index: 1;
animation: cardFadeIn 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
@keyframes cardFadeIn {
0% {
opacity: 0;
transform: translateY(20rpx);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 顶部欢迎区 */
.brand-header {
margin-bottom: 64rpx;
animation: slideInDown 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.1s both;
.welcome-info {
display: flex;
flex-direction: column;
gap: 12rpx;
.welcome-title {
font-size: 48rpx;
font-weight: 600;
background: linear-gradient(135deg, #333 0%, #666 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: 1rpx;
line-height: 1.2;
}
.welcome-desc {
font-size: 26rpx;
font-weight: 400;
color: #999;
letter-spacing: 0.5rpx;
line-height: 1.5;
}
}
}
@keyframes slideInDown {
0% {
opacity: 0;
transform: translateY(-20rpx);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 表单区域 - 小米风格 */
.form-area {
animation: fadeInUp 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.2s both;
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(15rpx);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 输入框 - 柔和配色 */
.input-wrap {
position: relative;
display: flex;
align-items: center;
margin-bottom: 32rpx;
padding: 0 24rpx;
height: 100rpx;
background: #ffffff;
border: 2rpx solid #e8e8e8;
border-radius: 25rpx;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:last-of-type {
margin-bottom: 48rpx;
}
&.focused {
background: #ffffff;
border-color: var(--brand-primary);
box-shadow: 0 0 0 4rpx rgba(22, 119, 255, 0.08);
transform: translateY(-2rpx);
}
.input {
flex: 1;
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
background: transparent;
border: none;
outline: none;
padding: 0;
margin: 0;
line-height: normal;
&::placeholder {
color: #b0b0b0;
}
}
/* 移除 input 组件的默认边框 */
:deep(input) {
border: none !important;
outline: none !important;
}
.pwd-toggle {
width: 44rpx;
height: 44rpx;
margin-left: 16rpx;
flex-shrink: 0;
cursor: pointer;
opacity: 0.5;
transition: all 0.2s;
&:active {
opacity: 1;
transform: scale(1.1);
}
}
}
/* 登录按钮 - 统一橙色 */
.btn {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100rpx;
font-size: 32rpx;
font-weight: 500;
color: #fff !important;
background: #ff7c24 !important;
border: none;
border-radius: 25rpx;
letter-spacing: 2rpx;
transition: all 0.3s ease;
cursor: pointer;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(255, 124, 36, 0.3);
padding: 0;
margin: 0;
line-height: 1;
&[disabled] {
opacity: 0.6;
cursor: not-allowed;
}
&::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transform: translateX(-100%);
animation: shimmer 2.5s infinite;
}
&:active:not([disabled]) {
transform: scale(0.98);
box-shadow: 0 2rpx 10rpx rgba(255, 124, 36, 0.35);
}
&::after {
content: none;
border: none;
}
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}
</style>