登录页修改
Some checks failed
Build And Deploy v3-admin-vite / build-and-deploy (push) Has been cancelled

This commit is contained in:
吕杰刚 2025-05-07 16:58:44 +08:00
parent fec3098825
commit e12f4cc9e9
7 changed files with 361 additions and 62 deletions

2
.env
View File

@ -1,7 +1,7 @@
# 所有环境的环境变量(命名必须以 VITE_ 开头)
## 项目标题
VITE_APP_TITLE = V3 Admin Vite
VITE_APP_TITLE = 数据运营管理平台
## 路由模式 hash 或 html5
VITE_ROUTER_HISTORY = hash

View File

@ -3,7 +3,9 @@
## 后端接口地址(如果解决跨域问题采用反向代理就只需写相对路径)
## VITE_BASE_URL = /api/v1
VITE_BASE_URL = http://localhost:8000
# VITE_BASE_URL = http://124.71.209.231:8000
VITE_BASE_URL = null
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
VITE_PUBLIC_PATH = /

View File

@ -1,6 +1,6 @@
<div align="center">
<img alt="logo" width="120" height="120" src="./src/common/assets/images/layouts/logo.png">
<h1>V3 Admin Vite</h1>
<h1>V3 Admin Vit1e</h1>
</div>
[![github release](https://img.shields.io/github/v/release/un-pany/v3-admin-vite?style=flat)](https://github.com/un-pany/v3-admin-vite/releases)

View File

@ -13,8 +13,8 @@ initTheme()
//
initGreyAndColorWeakness()
//
initStarNotification()
initStoreNotification()
// initStarNotification()
// initStoreNotification()
</script>
<template>

View File

@ -2,7 +2,24 @@ export interface LoginRequestData {
/** admin 或 editor */
username: string
/** 密码 */
password: string
password: string,
//手机号
phone: string,
//手机验证码
mobileCode: string
}
export interface forgetRequestData {
/** admin 或 editor */
username: string
/** 密码 */
password: string,
//确认密码
confirmPassword: string,
//手机号
phone: string,
//手机验证码
mobileCode: string
}
export type CaptchaResponseData = ApiResponseData<string>

View File

@ -1,67 +1,181 @@
<script lang="ts" setup>
import type { FormInstance, FormRules } from "element-plus"
import type { LoginRequestData } from "./apis/type"
import { useSettingsStore } from "@/pinia/stores/settings"
import { useUserStore } from "@/pinia/stores/user"
import ThemeSwitch from "@@/components/ThemeSwitch/index.vue"
import { Lock, User } from "@element-plus/icons-vue"
import { loginApi } from "./apis"
import Owl from "./components/Owl.vue"
import { useFocus } from "./composables/useFocus"
import type { FormInstance, FormRules } from "element-plus";
import type { LoginRequestData, forgetRequestData } from "./apis/type";
import { useSettingsStore } from "@/pinia/stores/settings";
import { useUserStore } from "@/pinia/stores/user";
import ThemeSwitch from "@@/components/ThemeSwitch/index.vue";
import { Lock, User, Iphone, Promotion, Check } from "@element-plus/icons-vue";
import { loginApi } from "./apis";
import Owl from "./components/Owl.vue";
import { useFocus } from "./composables/useFocus";
const router = useRouter()
const router = useRouter();
const userStore = useUserStore()
const userStore = useUserStore();
const settingsStore = useSettingsStore()
const settingsStore = useSettingsStore();
const { isFocus, handleBlur, handleFocus } = useFocus()
const { isFocus, handleBlur, handleFocus } = useFocus();
/** 登录表单元素的引用 */
const loginFormRef = ref<FormInstance | null>(null)
const loginFormRef = ref<FormInstance | null>(null);
/** 登录按钮 Loading */
const loading = ref(false)
const loading = ref(false);
//
let loginMethod = ref(true);
//
let countdown = ref(60);
//
let isDisabled = ref(false);
//
let isForgetPwd = ref(false);
/** 登录表单数据 */
const loginFormData: LoginRequestData = reactive({
username: "admin",
password: "12345678"
})
username: "",
password: "",
phone: "",
mobileCode: "",
});
/** 忘记密码表单数据 */
const forgetFormData: forgetRequestData = reactive({
// username: "",
/** 密码 */
password: "",
//
confirmPassword: "",
//
phone: "",
//
mobileCode: "",
});
/** 登录表单校验规则 */
const loginFormRules: FormRules = {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" }
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" },
],
mobileCode: [
{ required: true, message: "请输入手机验证码", trigger: "blur" },
],
phone: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ min: 4, max: 4, message: "请检查验证码是否正确!", trigger: "blur" },
],
};
//
const forgetFormRules: FormRules = {
phone: [
{ required: true, message: "请输入手机号 ", trigger: "blur" },
{
validator: (rule, value, callback) => {
console.log(value.toString());
if (!value) {
return callback();
}
if (
value.toString().length != 11 ||
!/^1[3-9]\d{9}$/.test(value.toString())
) {
callback(new Error("请检查手机号是否正确!"));
} else {
callback();
}
},
trigger: "blur",
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" }
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (!value) {
return callback();
}
if (
!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/.test(
value
)
) {
callback(new Error("密码应该包含数字、大小写字母和特殊符号!"));
} else {
callback();
}
},
trigger: "blur",
},
],
code: [
{ required: true, message: "请输入验证码", trigger: "blur" }
]
}
mobileCode: [{ required: true, message: "请输入验证码", trigger: "blur" }],
confirmPassword: [
{ required: true, message: "请输入确认密码", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (!value) {
return callback();
}
if (value !== forgetFormData.password) {
callback(new Error("密码和确认密码请保持一致!"));
} else {
callback();
}
},
trigger: "blur",
},
],
};
/** 登录 */
function handleLogin() {
loginFormRef.value?.validate((valid) => {
if (!valid) {
ElMessage.error("表单校验不通过")
return
ElMessage.error("表单校验不通过");
return;
}
loading.value = true
loginApi(loginFormData).then(({ data }) => {
console.log(data)
userStore.setToken(data.token)
router.push("/")
}).catch(() => {
loginFormData.password = ""
}).finally(() => {
loading.value = false
})
})
loading.value = true;
loginApi(loginFormData)
.then(({ data }) => {
console.log(data);
userStore.setToken(data.token);
router.push("/");
})
.catch(() => {
loginFormData.password = "";
})
.finally(() => {
loading.value = false;
});
});
}
// true false
function mobileCode(type: boolean) {
loginMethod.value = type;
}
//
function getMobileCode(num: string) {
isDisabled.value = true;
}
//
function updateCountdown() {
if (countdown.value === 0) {
isDisabled.value = false;
} else {
countdown.value = countdown.value - 1;
isDisabled.value = true;
}
}
//
function forgetPwd() {
isForgetPwd.value = true;
}
watch(isDisabled, (newVal, oldVal) => {
if (newVal) {
setInterval(updateCountdown, 1000);
}
});
</script>
<template>
@ -71,25 +185,28 @@ function handleLogin() {
<div class="login-card">
<div class="title">
<div class="title-left">
<img src="@@/assets/images/layouts/img.png">
</div>
<div class="title-text">
数据运营管理平台
<img src="@@/assets/images/layouts/img.png" />
</div>
<div class="title-text">数据运营管理平台</div>
</div>
<div class="content">
<el-form ref="loginFormRef" :model="loginFormData" :rules="loginFormRules" @keyup.enter="handleLogin">
<el-form-item prop="username">
<div class="content" v-if="!isForgetPwd">
<el-form
ref="loginFormRef"
:model="loginFormData"
:rules="loginFormRules"
@keyup.enter="handleLogin"
>
<el-form-item prop="username" v-show="loginMethod">
<el-input
v-model.trim="loginFormData.username"
placeholder="用户名或手机号"
placeholder="用户名"
type="text"
tabindex="1"
:prefix-icon="User"
size="large"
/>
</el-form-item>
<el-form-item prop="password">
<el-form-item prop="password" v-show="loginMethod">
<el-input
v-model.trim="loginFormData.password"
placeholder="密码"
@ -102,6 +219,36 @@ function handleLogin() {
@focus="handleFocus"
/>
</el-form-item>
<el-form-item prop="phone" v-show="!loginMethod">
<el-input
v-model.trim="loginFormData.phone"
placeholder="手机号"
tabindex="3"
:prefix-icon="Iphone"
size="large"
maxlength="11"
/>
</el-form-item>
<el-form-item prop="mobileCode" v-show="!loginMethod">
<el-input
v-model.trim="loginFormData.mobileCode"
placeholder="手机验证码"
tabindex="4"
:prefix-icon="Promotion"
size="large"
@blur="handleBlur"
@focus="handleFocus"
maxlength="4"
/>
<button
id="sendCode"
@click="getMobileCode(loginFormData.mobileCode)"
:disabled="isDisabled"
>
<span v-if="!isDisabled">获取验证码</span>
<span v-if="isDisabled">{{ countdown + " 秒后重试" }}</span>
</button>
</el-form-item>
<!-- <el-form-item prop="code">
<el-input
v-model.trim="loginFormData.code"
@ -130,10 +277,108 @@ function handleLogin() {
</template>
</el-input>
</el-form-item> -->
<el-button :loading="loading" type="primary" size="large" @click.prevent="handleLogin">
<el-button
:loading="loading"
type="primary"
size="large"
@click.prevent="handleLogin"
>
</el-button>
</el-form>
<div class="login-btn">
<el-link type="primary" @click="mobileCode(false)" v-if="loginMethod"
>手机验证码登录</el-link
>
<el-link type="primary" @click="mobileCode(true)" v-if="!loginMethod"
>用户名密码登录</el-link
>
<el-link type="primary" @click="forgetPwd()">忘记密码 </el-link>
</div>
</div>
<div class="forget-content" v-if="isForgetPwd">
<el-form
ref="loginFormRef"
:model="forgetFormData"
:rules="forgetFormRules"
>
<!-- <el-form-item prop="username" v-show="loginMethod">
<el-input
v-model.trim="forgetFormData.username"
placeholder="用户名"
type="text"
tabindex="1"
:prefix-icon="User"
size="large"
/>
</el-form-item> -->
<el-form-item prop="phone">
<el-input
v-model.trim="forgetFormData.phone"
placeholder="手机号"
tabindex="3"
:prefix-icon="Iphone"
size="large"
maxlength="11"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="forgetFormData.mobileCode"
placeholder="手机验证码"
tabindex="4"
:prefix-icon="Promotion"
size="large"
@blur="handleBlur"
@focus="handleFocus"
maxlength="4"
/>
<button
id="sendCode"
@click="getMobileCode(forgetFormData.mobileCode)"
:disabled="isDisabled"
>
<span v-if="!isDisabled">获取验证码</span>
<span v-if="isDisabled">{{ countdown + " 秒后重试" }}</span>
</button>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="forgetFormData.password"
placeholder="密码"
type="password"
tabindex="2"
:prefix-icon="Lock"
size="large"
show-password
@blur="handleBlur"
@focus="handleFocus"
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model.trim="forgetFormData.confirmPassword"
placeholder="确认密码"
type="password"
tabindex="2"
:prefix-icon="Check"
size="large"
show-password
@blur="handleBlur"
@focus="handleFocus"
/>
</el-form-item>
<el-button
class="forgetLogin"
:loading="loading"
type="primary"
size="large"
@click.prevent="handleLogin"
>
</el-button>
</el-form>
</div>
</div>
</div>
@ -187,7 +432,26 @@ function handleLogin() {
white-space: nowrap;
}
}
.forget-content {
padding: 20px 50px 50px 50px;
.forgetLogin {
width: 100%;
}
:deep(.el-form-item__content) {
display: flex;
flex-wrap: nowrap;
#sendCode {
width: 50%;
height: 100%;
cursor: pointer;
background-color: #fff;
width: 50%;
margin-left: 15px;
border: 1px solid #dbdde2;
border-radius: 5px;
}
}
}
.content {
padding: 20px 50px 50px 50px;
:deep(.el-input-group__append) {
@ -202,10 +466,29 @@ function handleLogin() {
text-align: center;
}
}
.login-btn {
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.el-button {
width: 100%;
margin-top: 10px;
}
:deep(.el-form-item__content) {
display: flex;
flex-wrap: nowrap;
#sendCode {
width: 50%;
height: 100%;
cursor: pointer;
background-color: #fff;
width: 50%;
margin-left: 15px;
border: 1px solid #dbdde2;
border-radius: 5px;
}
}
}
}
}

View File

@ -18,7 +18,6 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
@ -27,21 +26,19 @@ declare module 'vue' {
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormbtn: typeof import('element-plus/es')['ElFormbtn']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']