This commit is contained in:
sexygoat
2026-03-31 18:41:52 +08:00
commit 3c62cc1cd1
527 changed files with 96725 additions and 0 deletions

View File

@@ -0,0 +1,481 @@
<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);
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 1500,
});
setTimeout(() => {
// 根据用户类型跳转到不同页面
// user_type: 1=超级管理员 2=平台 3=代理 4=企业
if (res.user.user_type === 3 || res.user.user_type === 4) {
// 代理商和企业用户都跳转到首页(首页会根据用户类型显示不同功能)
uni.$u.route({
type: 'redirectTo',
url: '/pages/agent-system/home/index',
});
} else {
// 其他用户(超级管理员、平台)跳转到默认页面
uni.$u.route({
type: 'redirectTo',
url: redirect,
});
}
}, 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(255, 103, 0, 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(255, 103, 0, 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(255, 103, 0, 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(255, 103, 0, 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: #d4af37;
box-shadow: 0 0 0 4rpx rgba(212, 175, 55, 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>