2. 双Token
main
expressgy 3 months ago
commit 14c914a30e
  1. 5
      .env
  2. 78
      .eslintrc.cjs
  3. 30
      .gitignore
  4. 15
      .prettierrc.json
  5. 7
      .vscode/extensions.json
  6. 35
      README.md
  7. 30
      index.html
  8. 8
      jsconfig.json
  9. 35
      package.json
  10. 3282
      pnpm-lock.yaml
  11. BIN
      public/favicon.ico
  12. 9
      src/App.vue
  13. 44
      src/api/AuthUser/Sign.js
  14. 30
      src/api/AuthUser/index.js
  15. 127
      src/api/http.js
  16. 16
      src/api/index.js
  17. 86
      src/assets/base.css
  18. BIN
      src/assets/images/hutao.webp
  19. 1
      src/assets/logo.svg
  20. 51
      src/assets/main.css
  21. 194
      src/assets/pacman.css
  22. 43
      src/components/HelloWorld.vue
  23. 22
      src/components/Pacman/index.vue
  24. 78
      src/components/TheWelcome.vue
  25. 86
      src/components/WelcomeItem.vue
  26. 7
      src/components/icons/IconCommunity.vue
  27. 7
      src/components/icons/IconDocumentation.vue
  28. 7
      src/components/icons/IconEcosystem.vue
  29. 7
      src/components/icons/IconSupport.vue
  30. 19
      src/components/icons/IconTooling.vue
  31. 11
      src/components/icons/key.vue
  32. 11
      src/components/icons/user.vue
  33. 25
      src/main.js
  34. 35
      src/router/authorization.js
  35. 36
      src/router/index.js
  36. 12
      src/stores/counter.js
  37. 89
      src/stores/system.js
  38. 16
      src/views/Home/index.vue
  39. 289
      src/views/SignIn/index.vue
  40. 35
      vite.config.js

@ -0,0 +1,5 @@
VITE_HOME_REDIRECT = '/home' # 默认路由
VITE_TITLE = '星撰玉衡' # 项目名称
VITE_BASE_URL = '/api' # 请求默认前缀
VITE_HTTP_TIMEOUT = 30000
VITE_HTTP_PROXY = 'http://127.0.0.1:3000'

@ -0,0 +1,78 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules:{
'indent': [
'error',
4,
{
SwitchCase: 1
}
],
'max-len': ['error', {
'code': 160, // 指定最大代码长度
'ignoreStrings': true, // 忽略字符串中的字符长度
'ignoreUrls': true, // 忽略URL的长度
'ignoreComments': false, // 注释中的字符会计入长度限制
'ignoreTemplateLiterals': true, // 忽略模板字符串的长度
'tabWidth': 4 // 设置制表符的宽度
}],
'quotes': ['error', 'single'], // 强制使用一致的引号风格
'no-unused-expressions': 'error', // 禁止使用未使用的表达式
'no-console': 'error', // 禁止使用console
'semi': ['error', 'always'], // 启用 semi 规则,要求语句后总是使用分号,并将其设置为错误
// "new-cap": "error", //要求使用 new 关键字创建的实例的构造函数名必须大写。
'no-const-assign': 'error', //禁止给 const 声明的变量赋值
'no-duplicate-case': 'error', // 禁止 switch 语句中出现重复的 case
'no-extra-parens': 'error', // 限制不必要的括号
'no-fallthrough': 'error', //:禁止 switch 语句中的穿透行为
'no-multi-spaces': 'error', //禁止使用多个空格。
'no-trailing-spaces': 'error', //:禁止行尾有空格。
'no-undef': 'error', //:禁止使用未声明的变量。
'no-undef-init': 'error', //禁止初始化变量时使用 undefined。
'no-empty': 2, //块语句中的内容不能为空
'no-extra-semi': 2, //禁止多余的冒号
'no-func-assign': 2, //禁止重复的函数声明
'no-inline-comments': 2, //禁止行内备注
'space-before-function-paren': [
'error',
{
// "always" - 要求在函数名和参数列表之间总是有空白。
// "never" - 禁止在函数名和参数列表之间有空格。
anonymous: 'always', // "anonymous" - 指定匿名函数表达式前的空格要求("always" 或 "never")。
named: 'never', // "named" - 指定具名函数表达式前的空格要求("always" 或 "never")。
asyncArrow: 'always' // "asyncArrow" - 指定异步箭头函数前的空格要求("always" 或 "never")
}
],
'no-multiple-empty-lines': [1, { max: 2 }], //空行最多不能超过2行
'no-nested-ternary': 0, //禁止使用嵌套的三目运算
'no-redeclare': 2, //禁止重复声明变量
'no-shadow': 2, //外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
// 'no-unused-vars': [2, { vars: 'all', args: 'after-used' }], //不能有声明后未被使用的变量或参数
'no-use-before-define': 2, //未定义前不能使用
'no-mixed-spaces-and-tabs': 'error', // 禁止在代码中混用空格和制表符
'lines-around-comment': [
'error',
{
beforeBlockComment: true, // beforeBlockComment: 在块级注释(以 /* 开头)之前需要有空行。
afterBlockComment: false, // afterBlockComment: 在块级注释之后需要有空行。
beforeLineComment: true, // beforeLineComment: 在行注释(以 // 开头)之前需要有空行。
afterLineComment: false, // afterLineComment: 在行注释之后需要有空行。
allowBlockStart: true, // allowBlockStart: 允许块级注释紧跟在函数或块的开始。
allowClassStart: true, // allowClassStart: 允许块级注释紧跟在类定义的开始。
allowObjectStart: true, // allowObjectStart: 允许块级注释紧跟在对象字面量的开始。
allowArrayStart: true // allowArrayStart: 允许块级注释紧跟在数组字面量的开始。
}
]
}
};

30
.gitignore vendored

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"printWidth": 160,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "consistent",
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"endOfLine": "auto"
}

@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

@ -0,0 +1,35 @@
# hoto-auth-vue3
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<div id="homeLoading" class="pacmanBox">
<div class="pacmanLoader">
<div class="pacman">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<span class="ear"></span>
</div>
</div>
</div>
</body>
<script>
window.onload = () => {
document.body.querySelector('#homeLoading').remove()
}
</script>
</html>

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

@ -0,0 +1,35 @@
{
"name": "hoto-auth-vue3",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"naive-ui": "^2.38.2",
"pinia": "^2.1.7",
"pino": "^9.2.0",
"sass": "^1.77.6",
"vfonts": "^0.0.3",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@vue/eslint-config-prettier": "^9.0.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"prettier": "^3.2.5",
"vite": "^5.3.1",
"vite-plugin-vue-devtools": "^7.3.1"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,9 @@
<script setup>
import { RouterView } from 'vue-router';
</script>
<template>
<RouterView />
</template>
<style scoped></style>

@ -0,0 +1,44 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: Sign.js -
// | @创建时间: 2024-06-30 18:36
// | @更新时间: 2024-06-30 18:36
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import HTTP from '@/api/http.js';
export const Sign = {
/**
* Name: signIn
* Desc: 登录接口,获取Token
* Time: 2024-06-30 18:42:42 -
* */
signIn: async (username, password) => {
return HTTP({
method: 'post',
url: '/authUser/sign/in',
data: {
username,
password,
},
});
},
/**
* Name: refreshToken
* Desc: 刷新Token
* Time: 2024-06-30 18:42:42 -
* */
refreshToken: async () => {
return HTTP({
method: 'post',
url: '/authUser/sign/refresh',
});
},
};

@ -0,0 +1,30 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: index.js -
// | @创建时间: 2024-06-30 20:30
// | @更新时间: 2024-06-30 20:30
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import HTTP from '@/api/http.js';
export const DefaultSign = {
/**
* Name: signIn
* Desc: 登录接口
* Time: 2024-06-30 18:42:42 -
* */
getUser: async (params) => {
return HTTP({
method: 'get',
url: '/authUser',
params,
});
},
};

@ -0,0 +1,127 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: http.js -
// | @创建时间: 2024-06-30 18:29
// | @更新时间: 2024-06-30 18:29
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import axios from 'axios';
import { createDiscreteApi } from 'naive-ui';
import { useSystemStore } from '@/stores/system.js';
import { Sign } from '@/api/AuthUser/Sign.js';
const naiveui = createDiscreteApi(['message']);
const Message = naiveui.message;
const systemStore = useSystemStore();
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
timeout: import.meta.env.VITE_HTTP_TIMEOUT,
});
// ! token过期
let isExpired = false;
// ! 等待列表
const waitList = [];
// ! 等待构造器
function makeWait() {
return new Promise((resolve) => {
waitList.push(resolve);
});
}
// 请求拦截器
service.interceptors.request.use(
async (config) => {
if (systemStore.apiWhiteList.includes(config.url)) {
// ! 白名单请求,不需要添加Token
} else if (config.url === '/authUser/sign/refresh') {
// ! 刷新Token请求,传入RefreshToken
config.headers.Authorization = `Bearer ${systemStore.refreshToken}`;
} else {
if (isExpired) {
// ! 等待刷新token后放行
await makeWait();
}
// ! 一般请求,添加Token
config.headers.Authorization = `Bearer ${systemStore.token}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
},
);
// 响应拦截器
service.interceptors.response.use(
async (response) => {
const { data } = response;
if (data && (data.statusCode === 200 || data.statusCode === 201)) {
// ! 正常响应,返回数据
return data.data;
} else if (data.statusCode === 403) {
// ! WARN ============================================
window.pino.error('经常报Token过期');
// ! 如果已经过期,返回的响应就正常返回
if (!isExpired) {
isExpired = true;
// ! token过期请求
Sign.refreshToken()
.then((resd) => {
// 将状态置为正常
isExpired = false;
// 写入新token
systemStore.setNewToken(resd.token);
// todo 放行之前等待的
while (waitList.length > 0) {
const resolve = waitList.shift();
resolve();
}
})
.catch((e) => {
systemStore.goBackSignin();
return e;
});
}
// ! token刷新后 将请求重新发起
await makeWait();
return await service(response.config);
} else {
if (Array.isArray(data.message)) {
data.message.forEach((msg) => {
Message.error(msg);
});
} else {
Message.error(data.message);
}
return Promise.reject(data);
}
},
async (error) => {
console.log('B');
// const { /*response, code,*/ message } = error;
Message.error(error.message || 'Error');
return Promise.reject(error);
},
);
const HTTP = service;
export default HTTP;

@ -0,0 +1,16 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: index.js -
// | @创建时间: 2024-06-30 18:43
// | @更新时间: 2024-06-30 18:43
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { Sign } from '@/api/AuthUser/Sign.js';
import { DefaultSign } from '@/api/AuthUser/index.js';
export { Sign, DefaultSign };

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

@ -0,0 +1,51 @@
@import './base.css';
@import 'pacman.css';
html,
body {
position: relative;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-weight: normal;
}
body{
padding: 8px;
box-sizing: border-box;
}
#app {
position: relative;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
/*body {*/
/* display: flex;*/
/* place-items: center;*/
/*}*/
/*#app {*/
/* display: grid;*/
/* grid-template-columns: 1fr 1fr;*/
/* padding: 0 2rem;*/
/*}*/
}

@ -0,0 +1,194 @@
.pacmanBox{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
border: 2px solid #cdcdcd;
}
.ear{
position: absolute;
display: block;
width: 6px;
height: 6px;
background: #111;
border-radius: 100%;
top: 7px;
left: -6px;
z-index: 20;
box-shadow: 1px 1px 2px 2px #fff;
}
.pacmanLoader {
transition: opacity .25s linear;
opacity: 1;
box-sizing: border-box;
display: flex;
flex: 0 1 auto;
flex-direction: column;
flex-grow: 1;
flex-shrink: 0;
flex-basis: 25%;
width: 200px;
//background: #ed5565;
height: 100px;
align-items: center;
justify-content: center;
}
.pacman {
position: relative;
}
@-webkit-keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
}
@keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
}
@-webkit-keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
}
@keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
}
@-webkit-keyframes pacman-balls {
75% {
opacity: 0.7;
}
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px);
}
}
@keyframes pacman-balls {
75% {
opacity: 0.8;
}
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px);
}
}
.pacman > div:nth-child(2) {
-webkit-animation: pacman-balls 1s -0.99s infinite linear;
animation: pacman-balls 1s -0.99s infinite linear;
}
.pacman > div:nth-child(3) {
-webkit-animation: pacman-balls 1s -0.66s infinite linear;
animation: pacman-balls 1s -0.66s infinite linear;
}
.pacman > div:nth-child(4) {
-webkit-animation: pacman-balls 1s -0.33s infinite linear;
animation: pacman-balls 1s -0.33s infinite linear;
}
.pacman > div:nth-child(5) {
-webkit-animation: pacman-balls 1s 0s infinite linear;
animation: pacman-balls 1s 0s infinite linear;
}
.pacman > div:first-of-type {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #ffdf00;
border-left: 25px solid #ffdf00;
border-bottom: 25px solid #ffdf00;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_up 0.5s 0s infinite;
animation: rotate_pacman_half_up 0.5s 0s infinite;
position: relative;
left: -30px;
z-index: 10;
}
.pacman > div:nth-child(2) {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #ffdf00;
border-left: 25px solid #ffdf00;
border-bottom: 25px solid #ffdf00;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_down 0.5s 0s infinite;
animation: rotate_pacman_half_down 0.5s 0s infinite;
margin-top: -50px;
position: relative;
left: -30px;
z-index: 10;
}
.pacman > div:nth-child(3),
.pacman > div:nth-child(4),
.pacman > div:nth-child(5),
.pacman > div:nth-child(6) {
background-color: #999;
border-radius: 100%;
margin: 2px;
width: 10px;
height: 10px;
position: absolute;
-webkit-transform: translate(0, -6.25px);
transform: translate(0, -6.25px);
top: 25px;
left: 70px;
}

@ -0,0 +1,43 @@
<script setup>
defineProps({
msg: {
type: String,
required: true,
},
});
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> + <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

@ -0,0 +1,22 @@
<script setup name="Pacman">
defineProps(['loading']);
</script>
<template>
<div class="pacmanBox">
<div v-if="loading" class="pacmanLoader">
<div class="pacman">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<span class="ear"></span>
</div>
</div>
</div>
</template>
<style scoped>
</style>

@ -0,0 +1,78 @@
<script setup>
import WelcomeItem from './WelcomeItem.vue';
import DocumentationIcon from './icons/IconDocumentation.vue';
import ToolingIcon from './icons/IconTooling.vue';
import EcosystemIcon from './icons/IconEcosystem.vue';
import CommunityIcon from './icons/IconCommunity.vue';
import SupportIcon from './icons/IconSupport.vue';
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" rel="noopener">Cypress Component Testing</a>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official Discord server, or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener">StackOverflow</a>. You should also subscribe to
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow the official
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

@ -0,0 +1,86 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

@ -0,0 +1,11 @@
<script setup name="key">
</script>
<template>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#icon-33460a027770af78)"><circle cx="15" cy="33" r="8" fill="none" stroke="#333" stroke-width="4"/><path d="M29 16L35.5 22" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 26L37 7" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M35 11L42 17.5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></g><defs><clipPath id="icon-33460a027770af78"><rect width="48" height="48" fill="#333"/></clipPath></defs></svg>
</template>
<style scoped>
</style>

@ -0,0 +1,11 @@
<script setup name="user">
</script>
<template>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="12" r="8" fill="none" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M42 44C42 34.0589 33.9411 26 24 26C14.0589 26 6 34.0589 6 44" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</template>
<style scoped>
</style>

@ -0,0 +1,25 @@
import './assets/main.css';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import Pino from 'pino';
window.pino = new Pino();
import App from './App.vue';
import createRoutering from './router';
import authRouter from '@/router/authorization.js';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
const router = createRoutering();
app.use(router);
app.use(authRouter, {
router
});
app.mount('#app');

@ -0,0 +1,35 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: authorization.js -
// | @创建时间: 2024-06-30 15:31
// | @更新时间: 2024-06-30 15:31
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { useSystemStore } from '@/stores/system.js';
export default function authRouter(app, option) {
const { router } = option;
const systemStore = useSystemStore();
router.beforeEach(async (to, from) => {
// ? 判断是否在白名单
if (systemStore.routeWhiteList.includes(to.path)) {
return true;
}
// 判断是否存在Token
if (systemStore.refreshToken === undefined) {
return '/signin';
}
// ? 其他均在权限范围内
});
router.afterEach(async (to, from) => {
console.log('AFETR AUTH ROUTER');
});
}

@ -0,0 +1,36 @@
import { createRouter, createWebHistory } from 'vue-router';
import { useSystemStore } from '@/stores/system.js';
export default function createRoutering() {
const systemStore = useSystemStore();
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '',
redirect: systemStore.defaultRoute,
},
{
path: '/home',
name: 'home',
component: () => import('@/views/Home/index.vue'),
},
{
path: '/signIn',
name: 'SignIn',
component: () => import('@/views/SignIn/index.vue'),
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('@/views/Home/index.vue'),
},
],
});
return router;
}

@ -0,0 +1,12 @@
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubleCount, increment };
});

@ -0,0 +1,89 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: system.js -
// | @创建时间: 2024-06-30 15:29
// | @更新时间: 2024-06-30 15:29
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { reactive, ref } from 'vue';
import { defineStore } from 'pinia';
import { useRouter } from 'vue-router';
import { createDiscreteApi } from 'naive-ui';
const naiveui = createDiscreteApi(['message']);
const router = useRouter();
export const useSystemStore = defineStore('system', () => {
// token
const token = ref(window.sessionStorage.getItem('token') || undefined);
// refreshToken
const refreshToken = ref(window.localStorage.getItem('refreshToken') || window.sessionStorage.getItem('refreshToken') || undefined);
// 路由白名单
const routeWhiteList = ['/', '/signin'];
// 接口白名单
const apiWhiteList = ['/authUser/sign/in'];
// 默认跳转路由
const defaultRoute = import.meta.env.VITE_HOME_REDIRECT;
// 标题
const title = import.meta.env.VITE_TITLE;
// 登陆成功保存Token
const setToken = (opt) => {
if (opt.remember) {
// 记住账户
window.localStorage.setItem('refreshToken', opt.refreshToken);
} else {
window.sessionStorage.setItem('refreshToken', opt.refreshToken);
}
window.sessionStorage.setItem('token', opt.token);
token.value = opt.token;
refreshToken.value = opt.refreshToken;
};
// 清除Token
const clearToken = () => {
window.localStorage.removeItem('refreshToken');
window.sessionStorage.removeItem('refreshToken');
window.sessionStorage.removeItem('token');
token.value = undefined;
refreshToken.value = undefined;
};
// 回到登录页
const goBackSignin = async () => {
naiveui.message.warning('即将返回登录页!');
setTimeout(() => {
clearToken();
router.push('/signin');
}, 2000);
};
// 设置新Token
const setNewToken = (newToken) => {
window.sessionStorage.setItem('token', newToken);
token.value = newToken;
};
return {
token,
refreshToken,
routeWhiteList,
apiWhiteList,
defaultRoute,
title,
setToken,
goBackSignin,
clearToken,
setNewToken,
};
});

@ -0,0 +1,16 @@
<script setup name="Home">
import Pacman from '@/components/Pacman/index.vue';
import { DefaultSign } from '@/api/index.js';
import { onMounted } from 'vue';
onMounted(async () => {
const resd = await DefaultSign.getUser();
console.log(resd);
});
</script>
<template>
<Pacman loading="1"></Pacman>
</template>
<style scoped></style>

@ -0,0 +1,289 @@
<script setup name="SignIn">
import { ref, toValue } from 'vue';
import hutao from '@/assets/images/hutao.webp';
import { useSystemStore } from '@/stores/system.js';
import key from '@/components/icons/key.vue';
import user from '@/components/icons/user.vue';
import { NSwitch } from 'naive-ui';
import { Sign } from '@/api/index.js';
import { useRouter } from 'vue-router';
import Pacman from '@/components/Pacman/index.vue'
const systemStore = useSystemStore();
const router = useRouter();
// !
const username = ref('');
// !
const password = ref('');
// !
const remember = ref(false);
// !
const userTarget = ref(false);
// !
const passTarget = ref(false);
// !
const isLoading = ref(false)
// !
const restrictUsername = (event) => {
//
let value = event.target.value;
// 使replace
username.value = value.replace(/[^A-Za-z0-9]/g, '');
};
// !
const restrictPassword = (event) => {
//
let value = event.target.value;
// 使replace
password.value = value.replace(/[^A-Za-z0-9!@#$%^&*()_+-=<>,.:;'"{}]/g, '');
};
// !
const handleSignIn = async () => {
if (username.value.length < 4 || username.value.length > 128) {
return userTarget.value = true;
}
if (password.value.length < 6 || password.value.length > 128) {
return passTarget.value = true;
}
isLoading.value = true;
const resd = await Sign.signIn(username.value, password.value);
systemStore.setToken({
...resd,
remember: toValue(remember),
});
// router.push(systemStore.defaultRoute);
};
// !
const handleFocus = (name) => {
if (name == 'user') {
userTarget.value = false;
} else {
passTarget.value = false;
}
};
</script>
<template>
<div class="SignIn">
<div class="maskLayer">
<div class="container">
<header>{{ systemStore.title }}</header>
<main>
<div class="left">
<div><img :src="hutao" alt="" /></div>
</div>
<div class="right">
<Pacman :loading="isLoading"/>
<header>
<div>登录</div>
</header>
<div class="inputBox">
<div class="inputLine">
<user class="icon" />
<input
:class="userTarget ? 'redInput' : ''"
type="text"
v-model="username"
@input="restrictUsername"
autocomplete="off"
@focus="handleFocus('user')"
/>
</div>
<div class="inputLine">
<key class="icon" />
<input
:class="passTarget ? 'redInput' : ''"
type="password"
v-model="password"
@input="restrictPassword"
autocomplete="off"
@keyup.enter="handleSignIn"
@focus="handleFocus('pass')"
/>
</div>
<div></div>
</div>
<footer>
<div class="remember">
保持登录
<NSwitch size="small" v-model:value="remember" />
</div>
<div class="signin" @click="handleSignIn">进入</div>
</footer>
</div>
</main>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.SignIn {
position: relative;
height: 100%;
width: 100%;
background-image: url("@/assets/images/hutao.webp");
background-position: center;
background-size: cover;
background-repeat: no-repeat;
border-radius: 5px;
overflow: hidden;
& > .maskLayer {
user-select: none;
position: relative;
height: 100%;
width: 100%;
backdrop-filter: blur(50px);
background: #33333322;
display: flex;
align-items: center;
justify-content: center;
& > div.container {
position: relative;
& > header {
position: relative;
top: -100px;
line-height: 2em;
font-size: 46px;
text-align: center;
font-family: "仿宋", "华文仿宋", serif;
font-weight: 600;
color: #fff;
letter-spacing: 0.7em;
text-shadow: 3px 3px 3px #333;
overflow: hidden;
}
& > main {
position: relative;
//top: 100px;
width: 700px;
height: 340px;
background: #fefefecc;
border-radius: 10px;
display: flex;
box-sizing: border-box;
box-shadow: 3px 3px 10px 2px #33333311;
padding: 10px;
& > div.left {
position: relative;
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
&:before {
content: " ";
position: absolute;
height: 70%;
width: 4px;
background: #fff;
right: -2px;
border-radius: 4px;
}
& > div {
position: relative;
width: 180px;
height: 180px;
border-radius: 180px;
box-shadow: 2px 2px 2px 2px #33333311;
border: 1px #33333311 solid;
overflow: hidden;
z-index: 1;
& > img {
position: absolute;
left: -260px;
top: -20px;
width: 600px;
}
}
}
& > div.right {
position: relative;
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
& > header {
position: relative;
line-height: 2em;
font-size: 22px;
letter-spacing: 1em;
text-align: center;
padding: 40px 0 0 0;
}
& > div.inputBox {
position: relative;
flex: 1;
padding: 0 20px;
& > div.inputLine {
position: relative;
padding: 1px;
border-radius: 5px;
margin: 30px 0;
.icon {
position: absolute;
width: 18px;
margin: 4px 0 0 6px;
}
input {
border: none;
background: #fff;
outline: none;
font-size: 16px;
//font-weight: bold;
padding: 6px;
padding-left: 30px;
display: inline-block;
letter-spacing: 1px;
width: 100%;
border-radius: 5px;
}
& > input.redInput {
background: #cc361c99;
}
}
}
& > footer {
position: relative;
padding: 0 20px;
padding-bottom: 40px;
line-height: 30px;
display: flex;
& > div.remember {
position: relative;
flex: 1;
}
& > div.signin {
position: relative;
width: 120px;
border-radius: 5px;
background: linear-gradient(72deg, #3a3130 -39% -39%, #cc361c 100% 100%, #fff 22% 22%, #fcf7f6 39% 39%, #a24030 89% 89%);
cursor: pointer;
color: #fefefe;
transition: all 300ms ease-in-out;
text-align: center;
&:hover {
}
}
}
}
}
}
}
}
</style>

@ -0,0 +1,35 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import vueDevTools from 'vite-plugin-vue-devtools';
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
// eslint-disable-next-line no-undef
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
vue(),
vueJsx(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
proxy: {
[env.VITE_BASE_URL]: {
target: env.VITE_HTTP_PROXY,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(env.VITE_BASE_URL) , ''),
}
}
}
};
});
Loading…
Cancel
Save