mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-06 00:18:51 +08:00
refactor: login page (#259)
This commit is contained in:
parent
3d304457f5
commit
bedbf8077b
@ -38,7 +38,7 @@
|
|||||||
"@wangeditor/editor": "^5.0.1",
|
"@wangeditor/editor": "^5.0.1",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.27.1",
|
||||||
"china-area-data": "^5.0.1",
|
"china-area-data": "^5.0.1",
|
||||||
"cropperjs": "^1.5.12",
|
"cropperjs": "^1.5.12",
|
||||||
"css-color-function": "^1.3.3",
|
"css-color-function": "^1.3.3",
|
||||||
@ -130,7 +130,7 @@
|
|||||||
"stylelint-config-standard": "^24.0.0",
|
"stylelint-config-standard": "^24.0.0",
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.6.3",
|
||||||
"vite": "^2.9.5",
|
"vite": "^2.9.6",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-mock": "^2.9.6",
|
||||||
"vite-plugin-remove-console": "^0.0.7",
|
"vite-plugin-remove-console": "^0.0.7",
|
||||||
"vite-plugin-windicss": "^1.8.4",
|
"vite-plugin-windicss": "^1.8.4",
|
||||||
|
1567
pnpm-lock.yaml
generated
1567
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,6 @@ import Close from "@iconify-icons/ep/close";
|
|||||||
import CloseBold from "@iconify-icons/ep/close-bold";
|
import CloseBold from "@iconify-icons/ep/close-bold";
|
||||||
import Bell from "@iconify-icons/ep/bell";
|
import Bell from "@iconify-icons/ep/bell";
|
||||||
import Guide from "@iconify-icons/ep/guide";
|
import Guide from "@iconify-icons/ep/guide";
|
||||||
import User from "@iconify-icons/ep/user";
|
|
||||||
import Iphone from "@iconify-icons/ep/iphone";
|
import Iphone from "@iconify-icons/ep/iphone";
|
||||||
import Location from "@iconify-icons/ep/location";
|
import Location from "@iconify-icons/ep/location";
|
||||||
import Tickets from "@iconify-icons/ep/tickets";
|
import Tickets from "@iconify-icons/ep/tickets";
|
||||||
@ -48,7 +47,6 @@ addIcon("close", Close);
|
|||||||
addIcon("close-bold", CloseBold);
|
addIcon("close-bold", CloseBold);
|
||||||
addIcon("bell", Bell);
|
addIcon("bell", Bell);
|
||||||
addIcon("guide", Guide);
|
addIcon("guide", Guide);
|
||||||
addIcon("user", User);
|
|
||||||
addIcon("iphone", Iphone);
|
addIcon("iphone", Iphone);
|
||||||
addIcon("location", Location);
|
addIcon("location", Location);
|
||||||
addIcon("tickets", Tickets);
|
addIcon("tickets", Tickets);
|
||||||
@ -87,6 +85,8 @@ import Dept from "@iconify-icons/ri/git-branch-line";
|
|||||||
import Password from "@iconify-icons/ri/lock-password-line";
|
import Password from "@iconify-icons/ri/lock-password-line";
|
||||||
import Ppt from "@iconify-icons/ri/file-ppt-2-line";
|
import Ppt from "@iconify-icons/ri/file-ppt-2-line";
|
||||||
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
|
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
|
||||||
|
import User from "@iconify-icons/ri/user-3-fill";
|
||||||
|
import Lock from "@iconify-icons/ri/lock-fill";
|
||||||
addIcon("arrow-right-s-line", ArrowRightSLine);
|
addIcon("arrow-right-s-line", ArrowRightSLine);
|
||||||
addIcon("arrow-left-s-line", ArrowLeftSLine);
|
addIcon("arrow-left-s-line", ArrowLeftSLine);
|
||||||
addIcon("logout-circle-r-line", LogoutCircleRLine);
|
addIcon("logout-circle-r-line", LogoutCircleRLine);
|
||||||
@ -110,6 +110,8 @@ addIcon("dept", Dept);
|
|||||||
addIcon("password", Password);
|
addIcon("password", Password);
|
||||||
addIcon("ppt", Ppt);
|
addIcon("ppt", Ppt);
|
||||||
addIcon("terminal-window-line", TerminalWindowLine);
|
addIcon("terminal-window-line", TerminalWindowLine);
|
||||||
|
addIcon("user", User);
|
||||||
|
addIcon("lock", Lock);
|
||||||
|
|
||||||
// Font Awesome 4
|
// Font Awesome 4
|
||||||
import FaUser from "@iconify-icons/fa/user";
|
import FaUser from "@iconify-icons/fa/user";
|
||||||
|
@ -9,11 +9,6 @@ export default defineComponent({
|
|||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
|
||||||
// default element plus icon
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: "ep:"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
@ -21,7 +16,7 @@ export default defineComponent({
|
|||||||
return h(
|
return h(
|
||||||
IconifyIcon,
|
IconifyIcon,
|
||||||
{
|
{
|
||||||
icon: `${this.type}${this.icon}`,
|
icon: `${this.icon}`,
|
||||||
...attrs
|
...attrs
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -130,7 +130,7 @@ watch(
|
|||||||
class="w-40px h-32px cursor-pointer flex justify-center items-center"
|
class="w-40px h-32px cursor-pointer flex justify-center items-center"
|
||||||
@click="visible = !visible"
|
@click="visible = !visible"
|
||||||
>
|
>
|
||||||
<IconifyIconOnline :icon="icon" :type="currentActiveType" />
|
<IconifyIconOnline :icon="currentActiveType + icon" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ watch(
|
|||||||
:style="iconItemStyle(item)"
|
:style="iconItemStyle(item)"
|
||||||
@click="onChangeIcon(item)"
|
@click="onChangeIcon(item)"
|
||||||
>
|
>
|
||||||
<IconifyIconOnline :icon="item" :type="currentActiveType" />
|
<IconifyIconOnline :icon="currentActiveType + item" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
12
src/components/ReImageVerify/index.ts
Normal file
12
src/components/ReImageVerify/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { App } from "vue";
|
||||||
|
import reImageVerify from "./src/index.vue";
|
||||||
|
|
||||||
|
export const ReImageVerify = Object.assign(reImageVerify, {
|
||||||
|
install(app: App) {
|
||||||
|
app.component(reImageVerify.name, reImageVerify);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
ReImageVerify
|
||||||
|
};
|
85
src/components/ReImageVerify/src/hooks.ts
Normal file
85
src/components/ReImageVerify/src/hooks.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图形验证码
|
||||||
|
* @param width - 图形宽度
|
||||||
|
* @param height - 图形高度
|
||||||
|
*/
|
||||||
|
export const useImageVerify = (width = 120, height = 40) => {
|
||||||
|
const domRef = ref<HTMLCanvasElement>();
|
||||||
|
const imgCode = ref("");
|
||||||
|
|
||||||
|
function setImgCode(code: string) {
|
||||||
|
imgCode.value = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImgCode() {
|
||||||
|
if (!domRef.value) return;
|
||||||
|
imgCode.value = draw(domRef.value, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getImgCode();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
domRef,
|
||||||
|
imgCode,
|
||||||
|
setImgCode,
|
||||||
|
getImgCode
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function randomNum(min: number, max: number) {
|
||||||
|
const num = Math.floor(Math.random() * (max - min) + min);
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomColor(min: number, max: number) {
|
||||||
|
const r = randomNum(min, max);
|
||||||
|
const g = randomNum(min, max);
|
||||||
|
const b = randomNum(min, max);
|
||||||
|
return `rgb(${r},${g},${b})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw(dom: HTMLCanvasElement, width: number, height: number) {
|
||||||
|
let imgCode = "";
|
||||||
|
|
||||||
|
const NUMBER_STRING = "0123456789";
|
||||||
|
|
||||||
|
const ctx = dom.getContext("2d");
|
||||||
|
if (!ctx) return imgCode;
|
||||||
|
|
||||||
|
ctx.fillStyle = randomColor(180, 230);
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
for (let i = 0; i < 4; i += 1) {
|
||||||
|
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)];
|
||||||
|
imgCode += text;
|
||||||
|
const fontSize = randomNum(18, 41);
|
||||||
|
const deg = randomNum(-30, 30);
|
||||||
|
ctx.font = `${fontSize}px Simhei`;
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
ctx.fillStyle = randomColor(80, 150);
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(30 * i + 15, 15);
|
||||||
|
ctx.rotate((deg * Math.PI) / 180);
|
||||||
|
ctx.fillText(text, -15 + 5, -15);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 5; i += 1) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(randomNum(0, width), randomNum(0, height));
|
||||||
|
ctx.lineTo(randomNum(0, width), randomNum(0, height));
|
||||||
|
ctx.strokeStyle = randomColor(180, 230);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 41; i += 1) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = randomColor(150, 200);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
return imgCode;
|
||||||
|
}
|
48
src/components/ReImageVerify/src/index.vue
Normal file
48
src/components/ReImageVerify/src/index.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "ReImageVerify"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watch } from "vue";
|
||||||
|
import { useImageVerify } from "./hooks";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: "update:code", code: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
code: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.code,
|
||||||
|
newValue => {
|
||||||
|
setImgCode(newValue);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
watch(imgCode, newValue => {
|
||||||
|
emit("update:code", newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({ getImgCode });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<canvas
|
||||||
|
ref="domRef"
|
||||||
|
width="120"
|
||||||
|
height="40"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="getImgCode"
|
||||||
|
/>
|
||||||
|
</template>
|
@ -5,7 +5,7 @@ const remainingRouter = [
|
|||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
name: "login",
|
name: "login",
|
||||||
component: () => import("/@/views/login.vue"),
|
component: () => import("/@/views/login/index.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t("menus.hslogin"),
|
title: $t("menus.hslogin"),
|
||||||
showLink: false,
|
showLink: false,
|
||||||
|
@ -38,4 +38,6 @@ export type setType = {
|
|||||||
export type userType = {
|
export type userType = {
|
||||||
token: string;
|
token: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
verifyCode?: string;
|
||||||
|
currentPage?: number;
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,11 @@ export const useUserStore = defineStore({
|
|||||||
id: "pure-user",
|
id: "pure-user",
|
||||||
state: (): userType => ({
|
state: (): userType => ({
|
||||||
token,
|
token,
|
||||||
name
|
name,
|
||||||
|
// 前端生成的验证码(按实际需求替换)
|
||||||
|
verifyCode: "",
|
||||||
|
// 登陆显示组件判断 0:登陆 1:手机登陆 2:二维码登陆 3:注册 4:忘记密码,默认0:登陆
|
||||||
|
currentPage: 0
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
SET_TOKEN(token) {
|
SET_TOKEN(token) {
|
||||||
@ -31,6 +35,12 @@ export const useUserStore = defineStore({
|
|||||||
SET_NAME(name) {
|
SET_NAME(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
},
|
},
|
||||||
|
SET_VERIFYCODE(verifyCode) {
|
||||||
|
this.verifyCode = verifyCode;
|
||||||
|
},
|
||||||
|
SET_CURRENTPAGE(value) {
|
||||||
|
this.currentPage = value;
|
||||||
|
},
|
||||||
// 登入
|
// 登入
|
||||||
async loginByUsername(data) {
|
async loginByUsername(data) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
@ -47,141 +47,6 @@
|
|||||||
font: bold 200% Consolas, Monaco, monospace;
|
font: bold 200% Consolas, Monaco, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 7% 93%;
|
|
||||||
margin: 25px 0;
|
|
||||||
padding: 5px 0;
|
|
||||||
border-bottom: 2px solid #d9d9d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group:nth-child(1) {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group::before,
|
|
||||||
.input-group::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
width: 0;
|
|
||||||
height: 2px;
|
|
||||||
background-color: #c5d3f7;
|
|
||||||
transition: 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group::after {
|
|
||||||
right: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group::before {
|
|
||||||
left: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
color: #d9d9d9;
|
|
||||||
transition: 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group > div {
|
|
||||||
position: relative;
|
|
||||||
height: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group > div > h5 {
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: #d9d9d9;
|
|
||||||
font-size: 18px;
|
|
||||||
transition: 0.3s;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group.focus .icon svg {
|
|
||||||
color: #5392f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group.focus div h5 {
|
|
||||||
top: -5px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group.focus::after,
|
|
||||||
.input-group.focus::before {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: none;
|
|
||||||
padding: 0.5rem 0.7rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #555;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
text-align: right;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #999;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #5392f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 25px;
|
|
||||||
margin: 1rem 0;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
background-image: linear-gradient(to right, #567dbe, #5392f0, #567dbe);
|
|
||||||
cursor: pointer;
|
|
||||||
color: #fff;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
background-size: 200%;
|
|
||||||
transition: 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
background-position: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copyright {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
bottom: 2px;
|
|
||||||
color: #5392f0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 18px;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1080px) {
|
@media screen and (max-width: 1080px) {
|
||||||
.login-container {
|
.login-container {
|
||||||
grid-gap: 9rem;
|
grid-gap: 9rem;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable */
|
|
||||||
const toString = Object.prototype.toString;
|
const toString = Object.prototype.toString;
|
||||||
|
|
||||||
export function is(val: unknown, type: string) {
|
export function is(val: unknown, type: string) {
|
||||||
@ -94,9 +93,26 @@ export const isServer = typeof window === "undefined";
|
|||||||
|
|
||||||
export const isClient = !isServer;
|
export const isClient = !isServer;
|
||||||
|
|
||||||
export function isUrl<T>(path: T): boolean {
|
/** url链接正则 */
|
||||||
|
export function isUrl<T>(value: T): boolean {
|
||||||
const reg =
|
const reg =
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
|
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return reg.test(path);
|
return reg.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 手机号码正则 */
|
||||||
|
export function isPhone<T>(value: T): boolean {
|
||||||
|
const reg =
|
||||||
|
/^[1](([3][0-9])|([4][0,1,4-9])|([5][0-3,5-9])|([6][2,5,6,7])|([7][0-8])|([8][0-9])|([9][0-3,5-9]))[0-9]{8}$/;
|
||||||
|
// @ts-expect-error
|
||||||
|
return reg.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 邮箱正则 */
|
||||||
|
export function isEmail<T>(value: T): boolean {
|
||||||
|
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||||
|
// @ts-expect-error
|
||||||
|
return reg.test(value);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export default {
|
|||||||
import { getCardList } from "/@/api/list";
|
import { getCardList } from "/@/api/list";
|
||||||
import ReCard from "/@/components/ReCard";
|
import ReCard from "/@/components/ReCard";
|
||||||
import { ref, onMounted, nextTick } from "vue";
|
import { ref, onMounted, nextTick } from "vue";
|
||||||
import DialogForm from "./components/DialogForm.vue";
|
import dialogForm from "./components/DialogForm.vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
@ -172,6 +172,6 @@ const handleManageProduct = product => {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<DialogForm v-model:visible="formDialogVisible" :data="formData" />
|
<dialogForm v-model:visible="formDialogVisible" :data="formData" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { initRouter } from "/@/router/utils";
|
|
||||||
import { storageSession } from "/@/utils/storage";
|
|
||||||
import { addClass, removeClass } from "/@/utils/operate";
|
|
||||||
import bg from "/@/assets/login/bg.png";
|
|
||||||
import avatar from "/@/assets/login/avatar.svg?component";
|
|
||||||
import illustration0 from "/@/assets/login/illustration0.svg?component";
|
|
||||||
import illustration1 from "/@/assets/login/illustration1.svg?component";
|
|
||||||
import illustration2 from "/@/assets/login/illustration2.svg?component";
|
|
||||||
import illustration3 from "/@/assets/login/illustration3.svg?component";
|
|
||||||
import illustration4 from "/@/assets/login/illustration4.svg?component";
|
|
||||||
import illustration5 from "/@/assets/login/illustration5.svg?component";
|
|
||||||
import illustration6 from "/@/assets/login/illustration6.svg?component";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// eslint-disable-next-line vue/return-in-computed-property
|
|
||||||
const currentWeek = computed(() => {
|
|
||||||
switch (String(new Date().getDay())) {
|
|
||||||
case "0":
|
|
||||||
return illustration0;
|
|
||||||
case "1":
|
|
||||||
return illustration1;
|
|
||||||
case "2":
|
|
||||||
return illustration2;
|
|
||||||
case "3":
|
|
||||||
return illustration3;
|
|
||||||
case "4":
|
|
||||||
return illustration4;
|
|
||||||
case "5":
|
|
||||||
return illustration5;
|
|
||||||
case "6":
|
|
||||||
return illustration6;
|
|
||||||
default:
|
|
||||||
return illustration4;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let user = ref("admin");
|
|
||||||
let pwd = ref("123456");
|
|
||||||
|
|
||||||
const onLogin = (): void => {
|
|
||||||
storageSession.setItem("info", {
|
|
||||||
username: "admin",
|
|
||||||
accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
|
|
||||||
});
|
|
||||||
initRouter("admin").then(() => {});
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
function onUserFocus() {
|
|
||||||
addClass(document.querySelector(".user"), "focus");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUserBlur() {
|
|
||||||
if (user.value.length === 0)
|
|
||||||
removeClass(document.querySelector(".user"), "focus");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPwdFocus() {
|
|
||||||
addClass(document.querySelector(".pwd"), "focus");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPwdBlur() {
|
|
||||||
if (pwd.value.length === 0)
|
|
||||||
removeClass(document.querySelector(".pwd"), "focus");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<img :src="bg" class="wave" />
|
|
||||||
<div class="login-container">
|
|
||||||
<div class="img">
|
|
||||||
<component :is="currentWeek" />
|
|
||||||
</div>
|
|
||||||
<div class="login-box">
|
|
||||||
<div class="login-form">
|
|
||||||
<avatar class="avatar" />
|
|
||||||
<h2
|
|
||||||
v-motion
|
|
||||||
:initial="{
|
|
||||||
opacity: 0,
|
|
||||||
y: 100
|
|
||||||
}"
|
|
||||||
:enter="{
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
delay: 100
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
Pure Admin
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="input-group user focus"
|
|
||||||
v-motion
|
|
||||||
:initial="{
|
|
||||||
opacity: 0,
|
|
||||||
y: 100
|
|
||||||
}"
|
|
||||||
:enter="{
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
delay: 200
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="icon">
|
|
||||||
<IconifyIconOffline icon="fa-user" width="14" height="14" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h5>用户名</h5>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input"
|
|
||||||
v-model="user"
|
|
||||||
@focus="onUserFocus"
|
|
||||||
@blur="onUserBlur"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="input-group pwd focus"
|
|
||||||
v-motion
|
|
||||||
:initial="{
|
|
||||||
opacity: 0,
|
|
||||||
y: 100
|
|
||||||
}"
|
|
||||||
:enter="{
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
delay: 300
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="icon">
|
|
||||||
<IconifyIconOffline icon="fa-lock" width="14" height="14" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h5>密码</h5>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
class="input"
|
|
||||||
v-model="pwd"
|
|
||||||
@focus="onPwdFocus"
|
|
||||||
@blur="onPwdBlur"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="btn"
|
|
||||||
v-motion
|
|
||||||
:initial="{
|
|
||||||
opacity: 0,
|
|
||||||
y: 10
|
|
||||||
}"
|
|
||||||
:enter="{
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
delay: 400
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
@click="onLogin"
|
|
||||||
>
|
|
||||||
登录
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@import url("/@/style/login.css");
|
|
||||||
</style>
|
|
96
src/views/login/components/phone.vue
Normal file
96
src/views/login/components/phone.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import Motion from "../utils/motion";
|
||||||
|
import { phoneRules } from "../utils/rule";
|
||||||
|
import { message } from "@pureadmin/components";
|
||||||
|
import type { FormInstance } from "element-plus";
|
||||||
|
import { useVerifyCode } from "../utils/verifyCode";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
|
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const ruleForm = reactive({
|
||||||
|
phone: "",
|
||||||
|
verifyCode: ""
|
||||||
|
});
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
const { isDisabled, text } = useVerifyCode();
|
||||||
|
|
||||||
|
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
// 模拟登陆请求,需根据实际开发进行修改
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success("登陆成功");
|
||||||
|
loading.value = false;
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function onBack() {
|
||||||
|
useVerifyCode().end();
|
||||||
|
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-form ref="ruleFormRef" :model="ruleForm" :rules="phoneRules" size="large">
|
||||||
|
<Motion>
|
||||||
|
<el-form-item prop="phone">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.phone"
|
||||||
|
placeholder="手机号码"
|
||||||
|
:prefix-icon="useRenderIcon('iphone')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="100">
|
||||||
|
<el-form-item prop="verifyCode">
|
||||||
|
<div class="w-full flex justify-between">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.verifyCode"
|
||||||
|
placeholder="短信验证码"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
:disabled="isDisabled"
|
||||||
|
class="ml-2"
|
||||||
|
@click="useVerifyCode().start(ruleFormRef, 'phone')"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="150">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
class="w-full"
|
||||||
|
size="default"
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="onLogin(ruleFormRef)"
|
||||||
|
>
|
||||||
|
登陆
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="200">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button class="w-full" size="default" @click="onBack">
|
||||||
|
返回
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
22
src/views/login/components/qrCode.vue
Normal file
22
src/views/login/components/qrCode.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Motion from "../utils/motion";
|
||||||
|
import ReQrcode from "/@/components/ReQrcode";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Motion class="-mt-2 -mb-2"> <ReQrcode text="模拟测试" /> </Motion>
|
||||||
|
<Motion :delay="100">
|
||||||
|
<el-divider>
|
||||||
|
<p class="text-gray-500 text-xs">扫码后点击"确认",即可完成登录</p>
|
||||||
|
</el-divider>
|
||||||
|
</Motion>
|
||||||
|
<Motion :delay="150">
|
||||||
|
<el-button
|
||||||
|
class="w-full mt-4"
|
||||||
|
@click="useUserStoreHook().SET_CURRENTPAGE(0)"
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</el-button>
|
||||||
|
</Motion>
|
||||||
|
</template>
|
169
src/views/login/components/regist.vue
Normal file
169
src/views/login/components/regist.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import Motion from "../utils/motion";
|
||||||
|
import { updateRules } from "../utils/rule";
|
||||||
|
import { message } from "@pureadmin/components";
|
||||||
|
import type { FormInstance } from "element-plus";
|
||||||
|
import { useVerifyCode } from "../utils/verifyCode";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
|
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
|
const checked = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const ruleForm = reactive({
|
||||||
|
username: "",
|
||||||
|
phone: "",
|
||||||
|
verifyCode: "",
|
||||||
|
password: "",
|
||||||
|
repeatPassword: ""
|
||||||
|
});
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
const { isDisabled, text } = useVerifyCode();
|
||||||
|
const repeatPasswordRule = [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入确认密码"));
|
||||||
|
} else if (ruleForm.password !== value) {
|
||||||
|
callback(new Error("两次密码不一致!"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
if (checked.value) {
|
||||||
|
// 模拟请求,需根据实际开发进行修改
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success("注册成功");
|
||||||
|
loading.value = false;
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
message.warning("请勾选隐私政策");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function onBack() {
|
||||||
|
useVerifyCode().end();
|
||||||
|
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-form
|
||||||
|
ref="ruleFormRef"
|
||||||
|
:model="ruleForm"
|
||||||
|
:rules="updateRules"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<Motion>
|
||||||
|
<el-form-item
|
||||||
|
:rules="[{ required: true, message: '请输入账号', trigger: 'blur' }]"
|
||||||
|
prop="username"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.username"
|
||||||
|
placeholder="账号"
|
||||||
|
:prefix-icon="useRenderIcon('user')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="100">
|
||||||
|
<el-form-item prop="phone">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.phone"
|
||||||
|
placeholder="手机号码"
|
||||||
|
:prefix-icon="useRenderIcon('iphone')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="150">
|
||||||
|
<el-form-item prop="verifyCode">
|
||||||
|
<div class="w-full flex justify-between">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.verifyCode"
|
||||||
|
placeholder="短信验证码"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
:disabled="isDisabled"
|
||||||
|
class="ml-2"
|
||||||
|
@click="useVerifyCode().start(ruleFormRef, 'phone')"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="200">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
v-model="ruleForm.password"
|
||||||
|
placeholder="密码"
|
||||||
|
:prefix-icon="useRenderIcon('lock')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="250">
|
||||||
|
<el-form-item :rules="repeatPasswordRule" prop="repeatPassword">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
v-model="ruleForm.repeatPassword"
|
||||||
|
placeholder="确认密码"
|
||||||
|
:prefix-icon="useRenderIcon('lock')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="300">
|
||||||
|
<el-form-item>
|
||||||
|
<el-checkbox v-model="checked"> 我已仔细阅读并接受 </el-checkbox>
|
||||||
|
<el-button type="text"> 《隐私政策》 </el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="350">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
class="w-full"
|
||||||
|
size="default"
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="onUpdate(ruleFormRef)"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="400">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button class="w-full" size="default" @click="onBack">
|
||||||
|
返回
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
141
src/views/login/components/update.vue
Normal file
141
src/views/login/components/update.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import Motion from "../utils/motion";
|
||||||
|
import { updateRules } from "../utils/rule";
|
||||||
|
import { message } from "@pureadmin/components";
|
||||||
|
import type { FormInstance } from "element-plus";
|
||||||
|
import { useVerifyCode } from "../utils/verifyCode";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
|
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const ruleForm = reactive({
|
||||||
|
phone: "",
|
||||||
|
verifyCode: "",
|
||||||
|
password: "",
|
||||||
|
repeatPassword: ""
|
||||||
|
});
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
const { isDisabled, text } = useVerifyCode();
|
||||||
|
const repeatPasswordRule = [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入确认密码"));
|
||||||
|
} else if (ruleForm.password !== value) {
|
||||||
|
callback(new Error("两次密码不一致!"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
// 模拟请求,需根据实际开发进行修改
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success("修改密码成功");
|
||||||
|
loading.value = false;
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function onBack() {
|
||||||
|
useVerifyCode().end();
|
||||||
|
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-form
|
||||||
|
ref="ruleFormRef"
|
||||||
|
:model="ruleForm"
|
||||||
|
:rules="updateRules"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<Motion>
|
||||||
|
<el-form-item prop="phone">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.phone"
|
||||||
|
placeholder="手机号码"
|
||||||
|
:prefix-icon="useRenderIcon('iphone')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="100">
|
||||||
|
<el-form-item prop="verifyCode">
|
||||||
|
<div class="w-full flex justify-between">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
v-model="ruleForm.verifyCode"
|
||||||
|
placeholder="短信验证码"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
:disabled="isDisabled"
|
||||||
|
class="ml-2"
|
||||||
|
@click="useVerifyCode().start(ruleFormRef, 'phone')"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="150">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
v-model="ruleForm.password"
|
||||||
|
placeholder="密码"
|
||||||
|
:prefix-icon="useRenderIcon('lock')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="200">
|
||||||
|
<el-form-item :rules="repeatPasswordRule" prop="repeatPassword">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
v-model="ruleForm.repeatPassword"
|
||||||
|
placeholder="确认密码"
|
||||||
|
:prefix-icon="useRenderIcon('lock')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="250">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
class="w-full"
|
||||||
|
size="default"
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="onUpdate(ruleFormRef)"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="300">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button class="w-full" size="default" @click="onBack">
|
||||||
|
返回
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
208
src/views/login/index.vue
Normal file
208
src/views/login/index.vue
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Motion from "./utils/motion";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { loginRules } from "./utils/rule";
|
||||||
|
import phone from "./components/phone.vue";
|
||||||
|
import qrCode from "./components/qrCode.vue";
|
||||||
|
import regist from "./components/regist.vue";
|
||||||
|
import update from "./components/update.vue";
|
||||||
|
import { initRouter } from "/@/router/utils";
|
||||||
|
import { message } from "@pureadmin/components";
|
||||||
|
import type { FormInstance } from "element-plus";
|
||||||
|
import { storageSession } from "/@/utils/storage";
|
||||||
|
import { ref, reactive, watch, computed } from "vue";
|
||||||
|
import { operates, thirdParty } from "./utils/enums";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
|
import { bg, avatar, currentWeek } from "./utils/static";
|
||||||
|
import { ReImageVerify } from "/@/components/ReImageVerify";
|
||||||
|
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
|
const imgCode = ref("");
|
||||||
|
const router = useRouter();
|
||||||
|
const loading = ref(false);
|
||||||
|
const checked = ref(false);
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
const currentPage = computed(() => {
|
||||||
|
return useUserStoreHook().currentPage;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ruleForm = reactive({
|
||||||
|
username: "admin",
|
||||||
|
password: "admin123",
|
||||||
|
verifyCode: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
// 模拟请求,需根据实际开发进行修改
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false;
|
||||||
|
storageSession.setItem("info", {
|
||||||
|
username: "admin",
|
||||||
|
accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
|
||||||
|
});
|
||||||
|
initRouter("admin").then(() => {});
|
||||||
|
message.success("登陆成功");
|
||||||
|
router.push("/");
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function onHandle(value) {
|
||||||
|
useUserStoreHook().SET_CURRENTPAGE(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(imgCode, value => {
|
||||||
|
useUserStoreHook().SET_VERIFYCODE(value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img :src="bg" class="wave" />
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="img">
|
||||||
|
<component :is="currentWeek" />
|
||||||
|
</div>
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-form">
|
||||||
|
<avatar class="avatar" />
|
||||||
|
<Motion>
|
||||||
|
<h2>Pure Admin</h2>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<el-form
|
||||||
|
v-if="currentPage === 0"
|
||||||
|
ref="ruleFormRef"
|
||||||
|
:model="ruleForm"
|
||||||
|
:rules="loginRules"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<Motion :delay="100">
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
:input-style="{ 'user-select': 'none' }"
|
||||||
|
v-model="ruleForm.username"
|
||||||
|
placeholder="账号"
|
||||||
|
:prefix-icon="useRenderIcon('user')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="150">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
:input-style="{ 'user-select': 'none' }"
|
||||||
|
show-password
|
||||||
|
v-model="ruleForm.password"
|
||||||
|
placeholder="密码"
|
||||||
|
:prefix-icon="useRenderIcon('lock')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="200">
|
||||||
|
<el-form-item prop="verifyCode">
|
||||||
|
<el-input
|
||||||
|
clearable
|
||||||
|
:input-style="{ 'user-select': 'none' }"
|
||||||
|
v-model="ruleForm.verifyCode"
|
||||||
|
placeholder="验证码"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<ReImageVerify v-model:code="imgCode" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="250">
|
||||||
|
<el-form-item>
|
||||||
|
<div class="w-full h-20px flex justify-between items-center">
|
||||||
|
<el-checkbox v-model="checked">记住密码</el-checkbox>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
@click="useUserStoreHook().SET_CURRENTPAGE(4)"
|
||||||
|
>
|
||||||
|
忘记密码?
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
class="w-full mt-4"
|
||||||
|
size="default"
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="onLogin(ruleFormRef)"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="300">
|
||||||
|
<el-form-item>
|
||||||
|
<div class="w-full h-20px flex justify-between items-center">
|
||||||
|
<el-button
|
||||||
|
v-for="(item, index) in operates"
|
||||||
|
:key="index"
|
||||||
|
class="w-full mt-4"
|
||||||
|
size="default"
|
||||||
|
@click="onHandle(index + 1)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<Motion v-if="currentPage === 0" :delay="350">
|
||||||
|
<el-form-item>
|
||||||
|
<el-divider>
|
||||||
|
<p class="text-gray-500 text-xs">第三方登录</p>
|
||||||
|
</el-divider>
|
||||||
|
<div class="w-full flex justify-evenly">
|
||||||
|
<span
|
||||||
|
v-for="(item, index) in thirdParty"
|
||||||
|
:key="index"
|
||||||
|
:title="`${item.title}登陆`"
|
||||||
|
>
|
||||||
|
<IconifyIconOnline
|
||||||
|
:icon="`ri:${item.icon}-fill`"
|
||||||
|
width="20"
|
||||||
|
class="cursor-pointer text-gray-500 hover:text-blue-400"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
<!-- 手机号登陆 -->
|
||||||
|
<phone v-if="currentPage === 1" />
|
||||||
|
<!-- 二维码登陆 -->
|
||||||
|
<qrCode v-if="currentPage === 2" />
|
||||||
|
<!-- 注册 -->
|
||||||
|
<regist v-if="currentPage === 3" />
|
||||||
|
<!-- 忘记密码 -->
|
||||||
|
<update v-if="currentPage === 4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import url("/@/style/login.css");
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-input-group__append, .el-input-group__prepend) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
32
src/views/login/utils/enums.ts
Normal file
32
src/views/login/utils/enums.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const operates = [
|
||||||
|
{
|
||||||
|
title: "手机登录"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "二维码登录"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "注册"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const thirdParty = [
|
||||||
|
{
|
||||||
|
title: "微信",
|
||||||
|
icon: "wechat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "支付宝",
|
||||||
|
icon: "alipay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "QQ",
|
||||||
|
icon: "qq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "微博",
|
||||||
|
icon: "weibo"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export { operates, thirdParty };
|
40
src/views/login/utils/motion.ts
Normal file
40
src/views/login/utils/motion.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { h, defineComponent, withDirectives, resolveDirective } from "vue";
|
||||||
|
|
||||||
|
// 封装@vueuse/motion动画库中的自定义指令v-motion
|
||||||
|
export default defineComponent({
|
||||||
|
name: "Motion",
|
||||||
|
props: {
|
||||||
|
delay: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { delay } = this;
|
||||||
|
const motion = resolveDirective("motion");
|
||||||
|
return withDirectives(
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
default: () => [this.$slots.default()]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[
|
||||||
|
[
|
||||||
|
motion,
|
||||||
|
{
|
||||||
|
initial: { opacity: 0, y: 100 },
|
||||||
|
enter: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
128
src/views/login/utils/rule.ts
Normal file
128
src/views/login/utils/rule.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { reactive } from "vue";
|
||||||
|
import { isPhone } from "/@/utils/is";
|
||||||
|
import type { FormRules } from "element-plus";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
|
|
||||||
|
/** 6位数字验证码正则 */
|
||||||
|
export const REGEXP_SIX = /^\d{6}$/;
|
||||||
|
|
||||||
|
/** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */
|
||||||
|
export const REGEXP_PWD =
|
||||||
|
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
|
||||||
|
|
||||||
|
/** 登陆校验 */
|
||||||
|
const loginRules = reactive(<FormRules>{
|
||||||
|
username: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入密码"));
|
||||||
|
} else if (!REGEXP_PWD.test(value)) {
|
||||||
|
callback(
|
||||||
|
new Error("密码格式应为8-18位数字、字母、符号的任意两种组合")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
verifyCode: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入验证码"));
|
||||||
|
} else if (useUserStoreHook().verifyCode !== value) {
|
||||||
|
callback(new Error("请输入正确的验证码"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 手机登陆校验 */
|
||||||
|
const phoneRules = reactive(<FormRules>{
|
||||||
|
phone: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入手机号码"));
|
||||||
|
} else if (!isPhone(value)) {
|
||||||
|
callback(new Error("请输入正确的手机号码格式"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
verifyCode: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入验证码"));
|
||||||
|
} else if (!REGEXP_SIX.test(value)) {
|
||||||
|
callback(new Error("请输入6位数字验证码"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 忘记密码校验 */
|
||||||
|
const updateRules = reactive(<FormRules>{
|
||||||
|
phone: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入手机号码"));
|
||||||
|
} else if (!isPhone(value)) {
|
||||||
|
callback(new Error("请输入正确的手机号码格式"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
verifyCode: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入验证码"));
|
||||||
|
} else if (!REGEXP_SIX.test(value)) {
|
||||||
|
callback(new Error("请输入6位数字验证码"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入密码"));
|
||||||
|
} else if (!REGEXP_PWD.test(value)) {
|
||||||
|
callback(
|
||||||
|
new Error("密码格式应为8-18位数字、字母、符号的任意两种组合")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export { loginRules, phoneRules, updateRules };
|
34
src/views/login/utils/static.ts
Normal file
34
src/views/login/utils/static.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { computed } from "vue";
|
||||||
|
import bg from "/@/assets/login/bg.png";
|
||||||
|
import avatar from "/@/assets/login/avatar.svg?component";
|
||||||
|
import illustration0 from "/@/assets/login/illustration0.svg?component";
|
||||||
|
import illustration1 from "/@/assets/login/illustration1.svg?component";
|
||||||
|
import illustration2 from "/@/assets/login/illustration2.svg?component";
|
||||||
|
import illustration3 from "/@/assets/login/illustration3.svg?component";
|
||||||
|
import illustration4 from "/@/assets/login/illustration4.svg?component";
|
||||||
|
import illustration5 from "/@/assets/login/illustration5.svg?component";
|
||||||
|
import illustration6 from "/@/assets/login/illustration6.svg?component";
|
||||||
|
|
||||||
|
/* Show a different background every day */
|
||||||
|
const currentWeek = computed(() => {
|
||||||
|
switch (String(new Date().getDay())) {
|
||||||
|
case "0":
|
||||||
|
return illustration0;
|
||||||
|
case "1":
|
||||||
|
return illustration1;
|
||||||
|
case "2":
|
||||||
|
return illustration2;
|
||||||
|
case "3":
|
||||||
|
return illustration3;
|
||||||
|
case "4":
|
||||||
|
return illustration4;
|
||||||
|
case "5":
|
||||||
|
return illustration5;
|
||||||
|
case "6":
|
||||||
|
return illustration6;
|
||||||
|
default:
|
||||||
|
return illustration4;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export { bg, avatar, currentWeek };
|
50
src/views/login/utils/verifyCode.ts
Normal file
50
src/views/login/utils/verifyCode.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import type { FormInstance, FormItemProp } from "element-plus";
|
||||||
|
import { cloneDeep } from "lodash-unified";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const isDisabled = ref(false);
|
||||||
|
const TEXT = "获取验证码";
|
||||||
|
const timer = ref(null);
|
||||||
|
const text = ref(TEXT);
|
||||||
|
|
||||||
|
export const useVerifyCode = () => {
|
||||||
|
const start = async (
|
||||||
|
formEl: FormInstance | undefined,
|
||||||
|
props: FormItemProp,
|
||||||
|
time = 60
|
||||||
|
) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
const initTime = cloneDeep(time);
|
||||||
|
await formEl.validateField(props, isValid => {
|
||||||
|
if (isValid) {
|
||||||
|
clearInterval(timer.value);
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
if (time > 0) {
|
||||||
|
text.value = `${time}秒后重新获取`;
|
||||||
|
isDisabled.value = true;
|
||||||
|
time -= 1;
|
||||||
|
} else {
|
||||||
|
text.value = TEXT;
|
||||||
|
isDisabled.value = false;
|
||||||
|
clearInterval(timer.value);
|
||||||
|
time = initTime;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const end = () => {
|
||||||
|
text.value = TEXT;
|
||||||
|
isDisabled.value = false;
|
||||||
|
clearInterval(timer.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDisabled,
|
||||||
|
timer,
|
||||||
|
text,
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
};
|
||||||
|
};
|
@ -39,23 +39,6 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias
|
alias
|
||||||
},
|
},
|
||||||
css: {
|
|
||||||
// https://github.com/vitejs/vite/issues/5833
|
|
||||||
postcss: {
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
postcssPlugin: "internal:charset-removal",
|
|
||||||
AtRule: {
|
|
||||||
charset: atRule => {
|
|
||||||
if (atRule.name === "charset") {
|
|
||||||
atRule.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 服务端渲染
|
// 服务端渲染
|
||||||
server: {
|
server: {
|
||||||
// 是否开启 https
|
// 是否开启 https
|
||||||
|
Loading…
x
Reference in New Issue
Block a user