commit
14c914a30e
@ -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: 允许块级注释紧跟在数组字面量的开始。 |
||||
} |
||||
] |
||||
} |
||||
}; |
@ -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
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; |
||||
} |
After Width: | Height: | Size: 228 KiB |
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> |
||||
You’ve 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> |
||||
|
||||
Vue’s |
||||
<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…
Reference in new issue