✨ feat:完成硅谷甄选的学习
This commit is contained in:
commit
e1dcaacdb7
5
.env.development
Normal file
5
.env.development
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
VITE_APP_TITLE = '硅谷甄选运营平台'
|
||||||
|
VITE_APP_BASE_API = '/api'
|
||||||
|
VITE_SERVE="http://sph-api.atguigu.cn"
|
4
.env.production
Normal file
4
.env.production
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'production'
|
||||||
|
VITE_APP_TITLE = '硅谷甄选运营平台'
|
||||||
|
VITE_APP_BASE_API = '/prod-api'
|
4
.env.test
Normal file
4
.env.test
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'test'
|
||||||
|
VITE_APP_TITLE = '硅谷甄选运营平台'
|
||||||
|
VITE_APP_BASE_API = '/test-api'
|
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
62
.eslintrc.cjs
Normal file
62
.eslintrc.cjs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
jest: true
|
||||||
|
},
|
||||||
|
/* 指定如何解析语法 */
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
|
/** 优先级低于 parse 的语法解析配置 */
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
jsxPragma: 'React',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 继承已有的规则 */
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended'
|
||||||
|
],
|
||||||
|
plugins: ['vue', '@typescript-eslint'],
|
||||||
|
/*
|
||||||
|
* "off" 或 0 ==> 关闭规则
|
||||||
|
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
|
||||||
|
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
|
||||||
|
*/
|
||||||
|
rules: {
|
||||||
|
// eslint(https://eslint.bootcss.com/docs/rules/)
|
||||||
|
'no-var': 'error', // 要求使用 let 或 const 而不是 var
|
||||||
|
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
'no-unexpected-multiline': 'error', // 禁止空余的多行
|
||||||
|
'no-useless-escape': 'off', // 禁止不必要的转义字符
|
||||||
|
// 添加允许末尾逗号的规则
|
||||||
|
'comma-dangle': ['error', 'never'], // 对于多行数组、对象字面量和函数参数,要求末尾有逗号
|
||||||
|
// 如果你也希望单行情况下允许尾随逗号,可以设置为:'comma-dangle': ['error', 'always']
|
||||||
|
|
||||||
|
// typeScript (https://typescript-eslint.io/rules)
|
||||||
|
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
|
||||||
|
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
|
||||||
|
'@typescript-eslint/semi': 'off',
|
||||||
|
|
||||||
|
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
|
||||||
|
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
|
||||||
|
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
|
||||||
|
'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
|
||||||
|
'vue/attribute-hyphenation': 'off' // 对模板中的自定义组件强制执行属性命名样式
|
||||||
|
}
|
||||||
|
}
|
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
|
@ -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
|
8
.prettierignore
Normal file
8
.prettierignore
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/dist/*
|
||||||
|
/html/*
|
||||||
|
.local
|
||||||
|
/node_modules/**
|
||||||
|
**/*.svg
|
||||||
|
**/*.sh
|
||||||
|
/public/*
|
||||||
|
vite.config.ts
|
10
.prettierrc.json
Normal file
10
.prettierrc.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"htmlWhitespaceSensitivity": "ignore",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# vite
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm lint
|
||||||
|
```
|
13
index.html
Normal file
13
index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
65
mock/user.ts
Normal file
65
mock/user.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
//用户信息数据
|
||||||
|
function createUserList() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
userId: 1,
|
||||||
|
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||||
|
username: 'admin',
|
||||||
|
password: '111111',
|
||||||
|
desc: '平台管理员',
|
||||||
|
roles: ['平台管理员'],
|
||||||
|
buttons: ['cuser.detail'],
|
||||||
|
routes: ['home'],
|
||||||
|
token: 'Admin Token'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: 2,
|
||||||
|
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||||
|
username: 'system',
|
||||||
|
password: '111111',
|
||||||
|
desc: '系统管理员',
|
||||||
|
roles: ['系统管理员'],
|
||||||
|
buttons: ['cuser.detail', 'cuser.user'],
|
||||||
|
routes: ['home'],
|
||||||
|
token: 'System Token'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// 用户登录接口
|
||||||
|
{
|
||||||
|
url: '/api/user/login', //请求地址
|
||||||
|
method: 'post', //请求方式
|
||||||
|
response: ({ body }) => {
|
||||||
|
//获取请求体携带过来的用户名与密码
|
||||||
|
const { username, password } = body
|
||||||
|
//调用获取用户信息函数,用于判断是否有此用户
|
||||||
|
const checkUser = createUserList().find((item) => item.username === username && item.password === password)
|
||||||
|
//没有用户返回失败信息
|
||||||
|
if (!checkUser) {
|
||||||
|
return { code: 201, data: { message: '账号或者密码不正确' } }
|
||||||
|
}
|
||||||
|
//如果有返回成功信息
|
||||||
|
const { token } = checkUser
|
||||||
|
return { code: 200, data: { token } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取用户信息
|
||||||
|
{
|
||||||
|
url: '/api/user/info',
|
||||||
|
method: 'get',
|
||||||
|
response: (request) => {
|
||||||
|
//获取请求头携带token
|
||||||
|
const token = request.headers.token
|
||||||
|
//查看用户信息是否包含有次token用户
|
||||||
|
const checkUser = createUserList().find((item) => item.token === token)
|
||||||
|
//没有返回失败的信息
|
||||||
|
if (!checkUser) {
|
||||||
|
return { code: 201, data: { message: '获取用户信息失败' } }
|
||||||
|
}
|
||||||
|
//如果有返回成功信息
|
||||||
|
return { code: 200, data: { checkUser } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
45
package.json
Normal file
45
package.json
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "vite",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --open",
|
||||||
|
"build:test": "vue-tsc && vite build --mode test",
|
||||||
|
"build:pro": "vue-tsc && vite build --mode production",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"fix": "eslint src --fix",
|
||||||
|
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
|
||||||
|
"lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
|
||||||
|
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"element-plus": "^2.7.5",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"sass": "^1.77.4",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.8.0",
|
||||||
|
"@tsconfig/node20": "^20.1.4",
|
||||||
|
"@types/node": "^20.12.5",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
|
"@vue/tsconfig": "^0.5.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-plugin-vue": "^9.23.0",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"npm-run-all2": "^6.1.2",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"typescript": "~5.4.0",
|
||||||
|
"vite": "^5.2.8",
|
||||||
|
"vite-plugin-mock": "^3.0.2",
|
||||||
|
"vue-tsc": "^2.0.11"
|
||||||
|
}
|
||||||
|
}
|
2646
pnpm-lock.yaml
Normal file
2646
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
5
src/App.vue
Normal file
5
src/App.vue
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
<style scoped></style>
|
28
src/api/menu/index.ts
Normal file
28
src/api/menu/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
// 引入类型
|
||||||
|
import type { PermisstionResponseData, MenuParams } from './type'
|
||||||
|
|
||||||
|
const API = {
|
||||||
|
//获取全部菜单与按钮的标识数据
|
||||||
|
ALLPERMISSTION_URL: 'http://sph-api.atguigu.cn/admin/acl/permission',
|
||||||
|
//给某一级菜单新增一个子菜单
|
||||||
|
ADDMENU_URL: 'http://sph-api.atguigu.cn/admin/acl/permission/save',
|
||||||
|
//更新某一个已有的菜单
|
||||||
|
UPDATE_URL: 'http://sph-api.atguigu.cn/admin/acl/permission/update',
|
||||||
|
//删除已有的菜单
|
||||||
|
DELETEMENU_URL: 'http://sph-api.atguigu.cn/admin/acl/permission/remove/'
|
||||||
|
}
|
||||||
|
//获取菜单数据
|
||||||
|
export const reqAllPermission = () => {
|
||||||
|
return request.get<any, PermisstionResponseData>(API.ALLPERMISSTION_URL)
|
||||||
|
}
|
||||||
|
//添加与更新菜单的方法
|
||||||
|
export const reqAddOrUpdateMenu = (data: MenuParams) => {
|
||||||
|
if (data.id) {
|
||||||
|
return request.put<any, any>(API.UPDATE_URL, data)
|
||||||
|
} else {
|
||||||
|
return request.post<any, any>(API.ADDMENU_URL, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//删除某一个已有的菜单
|
||||||
|
export const reqRemoveMenu = (id: number) => request.delete<any, any>(API.DELETEMENU_URL + id)
|
35
src/api/menu/type.ts
Normal file
35
src/api/menu/type.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
//数据类型定义
|
||||||
|
export interface ResponseData {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
ok: boolean
|
||||||
|
}
|
||||||
|
//菜单数据与按钮数据的ts类型
|
||||||
|
export interface Permisstion {
|
||||||
|
id?: number
|
||||||
|
createTime: string
|
||||||
|
updateTime: string
|
||||||
|
pid: number
|
||||||
|
name: string
|
||||||
|
code: null
|
||||||
|
toCode: null
|
||||||
|
type: number
|
||||||
|
status: null
|
||||||
|
level: number
|
||||||
|
children?: PermisstionList
|
||||||
|
select: boolean
|
||||||
|
}
|
||||||
|
export type PermisstionList = Permisstion[]
|
||||||
|
//菜单接口返回的数据类型
|
||||||
|
export interface PermisstionResponseData extends ResponseData {
|
||||||
|
data: PermisstionList
|
||||||
|
}
|
||||||
|
|
||||||
|
//添加与修改菜单携带的参数的ts类型
|
||||||
|
export interface MenuParams {
|
||||||
|
id?: number //ID
|
||||||
|
code: string //权限数值
|
||||||
|
level: number //几级菜单
|
||||||
|
name: string //菜单的名字
|
||||||
|
pid: number //菜单的ID
|
||||||
|
}
|
24
src/api/user/index.ts
Normal file
24
src/api/user/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//统一管理咱们项目用户相关的接口
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
import type { loginFormData, loginResponseData, userInfoReponseData } from './type'
|
||||||
|
|
||||||
|
//项目用户相关的请求地址
|
||||||
|
|
||||||
|
enum API {
|
||||||
|
LOGIN_URL = '/user/login',
|
||||||
|
|
||||||
|
USERINFO_URL = '/admin/acl/index/info',
|
||||||
|
|
||||||
|
LOGOUT_URL = '/admin/acl/index/logout'
|
||||||
|
}
|
||||||
|
//登录接口
|
||||||
|
export const reqLogin = (data: loginFormData) => request.post<any, loginResponseData>(API.LOGIN_URL, data)
|
||||||
|
//获取用户信息
|
||||||
|
|
||||||
|
export const reqUserInfo = () => request.get<any, userInfoReponseData>(API.USERINFO_URL)
|
||||||
|
|
||||||
|
//退出登录
|
||||||
|
|
||||||
|
export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL)
|
29
src/api/user/type.ts
Normal file
29
src/api/user/type.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
//定义用户相关数据的ts类型
|
||||||
|
//用户登录接口携带参数的ts类型
|
||||||
|
export interface loginFormData {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
//定义全部接口返回数据都拥有ts类型
|
||||||
|
export interface ResponseData {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
ok: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
//定义登录接口返回数据类型
|
||||||
|
export interface loginResponseData extends ResponseData {
|
||||||
|
data: string
|
||||||
|
}
|
||||||
|
|
||||||
|
//定义获取用户信息返回数据类型
|
||||||
|
export interface userInfoReponseData extends ResponseData {
|
||||||
|
data: {
|
||||||
|
routes: string[]
|
||||||
|
buttons: string[]
|
||||||
|
roles: string[]
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
}
|
||||||
|
}
|
BIN
src/assets/background.jpg
Normal file
BIN
src/assets/background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
|
@ -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 |
41
src/components/HelloWorld.vue
Normal file
41
src/components/HelloWorld.vue
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
msg: string
|
||||||
|
}>()
|
||||||
|
</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>. What's next?
|
||||||
|
</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>
|
88
src/components/TheWelcome.vue
Normal file
88
src/components/TheWelcome.vue
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
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>
|
87
src/components/WelcomeItem.vue
Normal file
87
src/components/WelcomeItem.vue
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<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>
|
7
src/components/icons/IconCommunity.vue
Normal file
7
src/components/icons/IconCommunity.vue
Normal file
|
@ -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>
|
7
src/components/icons/IconDocumentation.vue
Normal file
7
src/components/icons/IconDocumentation.vue
Normal file
|
@ -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>
|
7
src/components/icons/IconEcosystem.vue
Normal file
7
src/components/icons/IconEcosystem.vue
Normal file
|
@ -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>
|
7
src/components/icons/IconSupport.vue
Normal file
7
src/components/icons/IconSupport.vue
Normal file
|
@ -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>
|
19
src/components/icons/IconTooling.vue
Normal file
19
src/components/icons/IconTooling.vue
Normal file
|
@ -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>
|
117
src/layout/index.vue
Normal file
117
src/layout/index.vue
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div class="layout_container">
|
||||||
|
<!-- 左侧菜单 -->
|
||||||
|
<div class="layout_slider" :class="{ fold: LayOutSettingStore.fold ? true : false }">
|
||||||
|
<Logo></Logo>
|
||||||
|
<!-- 展示菜单 -->
|
||||||
|
<!-- 滚动组件 -->
|
||||||
|
<el-scrollbar class="scrollbar">
|
||||||
|
<!-- 菜单组件-->
|
||||||
|
<el-menu
|
||||||
|
:default-active="$route.path"
|
||||||
|
:collapse="LayOutSettingStore.fold ? true : false"
|
||||||
|
background-color="#001529"
|
||||||
|
text-color="white"
|
||||||
|
>
|
||||||
|
<!--根据路由动态生成菜单-->
|
||||||
|
<MenuIndex :menuList="userStore.menuRoutes"></MenuIndex>
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<!-- 内容展示区域 -->
|
||||||
|
<div class="layout_tabbar" :class="{ fold: LayOutSettingStore.fold ? true : false }">
|
||||||
|
<!-- layout组件的顶部导航tabbar -->
|
||||||
|
<Tabbar></Tabbar>
|
||||||
|
</div>
|
||||||
|
<div class="layout_main" :class="{ fold: LayOutSettingStore.fold ? true : false }">
|
||||||
|
<MianIndex></MianIndex>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 获取路由对象
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
//引入左侧菜单logo子组件
|
||||||
|
import Logo from '@/layout/logo/index.vue'
|
||||||
|
//引入菜单组件
|
||||||
|
import MenuIndex from '@/layout/menu/index.vue'
|
||||||
|
//获取用户相关的小仓库
|
||||||
|
import useUserStore from '@/stores/modules/user'
|
||||||
|
import MianIndex from '@/layout/main/index.vue'
|
||||||
|
//引入顶部tabbar组件
|
||||||
|
import Tabbar from '@/layout/tabbar/index.vue'
|
||||||
|
// 动态控制菜单展开折叠
|
||||||
|
import useLayOutSettingStore from '@/stores/modules/setting'
|
||||||
|
//获取layout配置相关的仓库
|
||||||
|
let LayOutSettingStore = useLayOutSettingStore()
|
||||||
|
|
||||||
|
let userStore = useUserStore()
|
||||||
|
//获取路由对象
|
||||||
|
let $route = useRoute()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'Layout'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.layout_container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.layout_slider {
|
||||||
|
color: white;
|
||||||
|
width: $base-menu-width;
|
||||||
|
height: 100vh;
|
||||||
|
background: $base-menu-background;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
.scrollbar {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - $base-menu-logo-height);
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.fold {
|
||||||
|
width: $base-menu-min-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout_tabbar {
|
||||||
|
position: fixed;
|
||||||
|
width: calc(100% - $base-menu-width);
|
||||||
|
height: $base-tabbar-height;
|
||||||
|
top: 0px;
|
||||||
|
left: $base-menu-width;
|
||||||
|
transition: all 0.3s;
|
||||||
|
&.fold {
|
||||||
|
width: calc(100vw - $base-menu-min-width);
|
||||||
|
left: $base-menu-min-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout_main {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - $base-menu-width);
|
||||||
|
height: calc(100vh - $base-tabbar-height);
|
||||||
|
left: $base-menu-width;
|
||||||
|
top: $base-tabbar-height;
|
||||||
|
padding: 20px;
|
||||||
|
overflow: auto;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&.fold {
|
||||||
|
width: calc(100vw - $base-menu-min-width);
|
||||||
|
left: $base-menu-min-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.el-menu-item.is-active) {
|
||||||
|
color: $--active-color;
|
||||||
|
}
|
||||||
|
</style>
|
31
src/layout/logo/index.vue
Normal file
31
src/layout/logo/index.vue
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<div class="logo">
|
||||||
|
<img src="@/assets/logo.svg" alt="" />
|
||||||
|
<p>硅谷甄选</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'Logo'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.logo {
|
||||||
|
width: 100%;
|
||||||
|
height: $base-menu-logo-height;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: $base-logo-title-fontSize;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
56
src/layout/main/index.vue
Normal file
56
src/layout/main/index.vue
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<!-- 路由组件出口的位置 -->
|
||||||
|
<!-- 是Vue Router的核心组件,用于展示当前路由所对应的组件。当路由发生变化时, -->
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<!-- 这是Vue的插槽语法糖,用于接收从<router-view>传递过来的数据。在这个例子中,它接收了一个名为Component的变量,该变量就是当前路由对应的组件。 -->
|
||||||
|
<transition name="fade">
|
||||||
|
<!-- 这是Vue的过渡动画组件,用于在组件切换时添加过渡效果。这里定义了一个名为fade的过渡效果,意味着组件切换时将会有一个淡入淡出的效果。 -->
|
||||||
|
<!-- 渲染layout一级路由组件的子路由 -->
|
||||||
|
<component :is="Component" v-if="flag" />
|
||||||
|
<!-- 这意味着它将渲染当前激活路由对应的组件。v-if="flag"则是一个条件渲染指令,只有当flag为真时,这个组件才会被渲染。 -->
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, nextTick } from 'vue'
|
||||||
|
// 引入菜单折叠和刷新页面
|
||||||
|
import useLayOutSettingStore from '@/stores/modules/setting'
|
||||||
|
let layOutSettingStore = useLayOutSettingStore()
|
||||||
|
|
||||||
|
//控制当前组件是否销毁重建
|
||||||
|
let flag = ref(true)
|
||||||
|
|
||||||
|
//监听仓库内部数据是否发生变化,如果发生变化,说明用户点击过刷新按钮
|
||||||
|
watch(
|
||||||
|
() => layOutSettingStore.refsh,
|
||||||
|
() => {
|
||||||
|
//点击刷新按钮:路由组件销毁
|
||||||
|
flag.value = false
|
||||||
|
nextTick(() => {
|
||||||
|
flag.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'MainIndex'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-active {
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
</style>
|
53
src/layout/menu/index.vue
Normal file
53
src/layout/menu/index.vue
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<template v-for="item in menuList" :key="item.path">
|
||||||
|
<!--没有子路由-->
|
||||||
|
<template v-if="!item.children">
|
||||||
|
<el-menu-item :index="item.path" v-if="!item.meta.hidden" @click="goRoute">
|
||||||
|
<el-icon><Edit /></el-icon>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ item.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<!-- 有子路由但是只有一个子路由 -->
|
||||||
|
<template v-if="item.children && item.children.length == 1">
|
||||||
|
<el-menu-item :index="item.children[0].path" v-if="!item.children[0].meta.hidden" @click="goRoute">
|
||||||
|
<el-icon><Edit /></el-icon>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ item.children[0].meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<!-- 有子路由且个数大于一个1 -->
|
||||||
|
<el-sub-menu :index="item.path" v-if="item.children && item.children.length > 1">
|
||||||
|
<template #title>
|
||||||
|
<el-icon><Edit /></el-icon>
|
||||||
|
<span>{{ item.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
<MenuIndex :menuList="item.children"></MenuIndex>
|
||||||
|
</el-sub-menu>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
//引入图标
|
||||||
|
import { Edit } from '@element-plus/icons-vue'
|
||||||
|
//获取父组件传递过来的全部路由数组
|
||||||
|
defineProps(['menuList'])
|
||||||
|
|
||||||
|
//获取路由器对象
|
||||||
|
let $router = useRouter()
|
||||||
|
//点击菜单的回调
|
||||||
|
const goRoute = (vc: any) => {
|
||||||
|
//路由跳转
|
||||||
|
$router.push(vc.index)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'MenuIndex'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
40
src/layout/tabbar/breadcrumd/index.vue
Normal file
40
src/layout/tabbar/breadcrumd/index.vue
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<!-- 顶部左侧静态 -->
|
||||||
|
<el-icon style="margin-right: 10px" @click="changeIcon">
|
||||||
|
<Moon />
|
||||||
|
</el-icon>
|
||||||
|
<!-- 左侧面包屑 -->
|
||||||
|
<el-breadcrumb separator-icon="ArrowRight">
|
||||||
|
<!-- 面包动态展示路由名字与标题 -->
|
||||||
|
<el-breadcrumb-item v-for="(item, index) in $route.matched" :key="index" v-show="item.meta.title" :to="item.path">
|
||||||
|
<!-- 图标 -->
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
<!-- 面包屑展示匹配路由的标题 -->
|
||||||
|
<span style="margin: 0 5px">{{ item.meta.title }}</span>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
//引入图标
|
||||||
|
import { ArrowRight, Moon } from '@element-plus/icons-vue'
|
||||||
|
import useLayOutSettingStore from '@/stores/modules/setting'
|
||||||
|
//获取layout配置相关的仓库
|
||||||
|
let LayOutSettingStore = useLayOutSettingStore()
|
||||||
|
//获取路由对象
|
||||||
|
const $route = useRoute()
|
||||||
|
const changeIcon = () => {
|
||||||
|
//图标进行切换
|
||||||
|
LayOutSettingStore.fold = !LayOutSettingStore.fold
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'Breadcrumb'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
42
src/layout/tabbar/index.vue
Normal file
42
src/layout/tabbar/index.vue
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<div class="tabbar">
|
||||||
|
<div class="tabbar_left">
|
||||||
|
<Breadcrumb />
|
||||||
|
<!-- 左侧面包屑 -->
|
||||||
|
</div>
|
||||||
|
<div class="tabbar_right">
|
||||||
|
<Setting />
|
||||||
|
<!-- 右侧退出 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Breadcrumb from './breadcrumd/index.vue'
|
||||||
|
import Setting from './setting/index.vue'
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'Tabbar'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tabbar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
// background-image: linear-gradient(to right, rgb(232, 223, 223), rgb(201, 178, 178), rgb(197, 165, 165));
|
||||||
|
|
||||||
|
.tabbar_left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabbar_right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
115
src/layout/tabbar/setting/index.vue
Normal file
115
src/layout/tabbar/setting/index.vue
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<el-button size="small" :icon="Refresh" circle @click="updateRefsh"></el-button>
|
||||||
|
<el-button size="small" :icon="FullScreen" circle @click="fullScreen"></el-button>
|
||||||
|
<el-popover placement="bottom" title="主题设置" :width="300" trigger="click">
|
||||||
|
<!-- 表单元素 -->
|
||||||
|
<el-form>
|
||||||
|
<el-form-item label="主题颜色">
|
||||||
|
<el-color-picker @change="setColor" v-model="color" size="small" show-alpha :predefine="predefineColors" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="暗黑模式">
|
||||||
|
<!-- prompt: 无论图标或文本是否显示在点内,只会呈现文本的第一个字符 -->
|
||||||
|
<el-switch
|
||||||
|
@change="changeDark"
|
||||||
|
v-model="dark"
|
||||||
|
class="mt-2"
|
||||||
|
style="margin-left: 24px"
|
||||||
|
inline-prompt
|
||||||
|
:active-icon="MoonNight"
|
||||||
|
:inactive-icon="Sunny"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #reference>
|
||||||
|
<el-button size="small" :icon="Setting" circle></el-button>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
<img
|
||||||
|
src="https://q4.itc.cn/q_70/images03/20240528/298d4abda5e4469d98fa77e7cde46525.jpeg"
|
||||||
|
style="width: 24px; height: 24px; margin: 0px 10px; border-radius: 50%"
|
||||||
|
/>
|
||||||
|
<!-- 下拉菜单 -->
|
||||||
|
<el-dropdown>
|
||||||
|
<span class="el-dropdown-link">
|
||||||
|
sdy
|
||||||
|
<el-icon class="el-icon--right">
|
||||||
|
<arrow-down />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item>退出登录</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
//引入图标
|
||||||
|
import { Refresh, FullScreen, MoonNight, Sunny, Setting } from '@element-plus/icons-vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import useLayOutSettingStore from '@/stores/modules/setting'
|
||||||
|
//收集开关的数据
|
||||||
|
let dark = ref<boolean>(false)
|
||||||
|
const layoutSettingStore = useLayOutSettingStore()
|
||||||
|
//刷新按钮点击回调
|
||||||
|
const updateRefsh = () => {
|
||||||
|
layoutSettingStore.refsh = !layoutSettingStore.refsh
|
||||||
|
}
|
||||||
|
//全屏按钮点击的回调
|
||||||
|
const fullScreen = () => {
|
||||||
|
//DOM对象的一个属性:可以用来判断当前是不是全屏模式[全屏:true,不是全屏:false]
|
||||||
|
let full = document.fullscreenElement
|
||||||
|
//切换为全屏模式
|
||||||
|
if (!full) {
|
||||||
|
//文档根节点的方法requestFullscreen,实现全屏模式
|
||||||
|
document.documentElement.requestFullscreen()
|
||||||
|
} else {
|
||||||
|
//变为不是全屏模式->退出全屏模式
|
||||||
|
document.exitFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//颜色组件组件的数据
|
||||||
|
const color = ref('rgba(255, 69, 0, 0.68)')
|
||||||
|
const predefineColors = ref([
|
||||||
|
'#ff4500',
|
||||||
|
'#ff8c00',
|
||||||
|
'#ffd700',
|
||||||
|
'#90ee90',
|
||||||
|
'#00ced1',
|
||||||
|
'#1e90ff',
|
||||||
|
'#c71585',
|
||||||
|
'rgba(255, 69, 0, 0.68)',
|
||||||
|
'rgb(255, 120, 0)',
|
||||||
|
'hsv(51, 100, 98)',
|
||||||
|
'hsva(120, 40, 94, 0.5)',
|
||||||
|
'hsl(181, 100%, 37%)',
|
||||||
|
'hsla(209, 100%, 56%, 0.73)',
|
||||||
|
'#c7158577'
|
||||||
|
])
|
||||||
|
// 控制暗黑模式
|
||||||
|
const changeDark = () => {
|
||||||
|
// 获取HTML的根节点
|
||||||
|
let html = document.documentElement
|
||||||
|
// 判断是否是暗黑模式
|
||||||
|
if (dark.value) {
|
||||||
|
// 添加暗黑模式
|
||||||
|
html.classList.add('dark')
|
||||||
|
} else {
|
||||||
|
// 移除暗黑模式
|
||||||
|
html.classList.remove('dark')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获取主体颜色
|
||||||
|
const setColor = () => {
|
||||||
|
const html = document.documentElement
|
||||||
|
html.style.setProperty('--el-color-primary', color.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'Setting'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
22
src/main.ts
Normal file
22
src/main.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// import './assets/main.css'
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
//引入element-plus插件与样式
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
// 引入暗黑模式样式
|
||||||
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
|
//引入模板的全局的样式
|
||||||
|
import '@/style/index.scss'
|
||||||
|
import './permisstion'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
app.use(ElementPlus)
|
||||||
|
|
||||||
|
app.mount('#app')
|
7
src/pages/404/index.vue
Normal file
7
src/pages/404/index.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>404</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
7
src/pages/home/index.vue
Normal file
7
src/pages/home/index.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>首页</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
7
src/pages/home/test.vue
Normal file
7
src/pages/home/test.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>测试</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
109
src/pages/login/index.vue
Normal file
109
src/pages/login/index.vue
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<template>
|
||||||
|
<div class="login_container">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12" :xs="0"></el-col>
|
||||||
|
<el-col :span="12" :xs="24">
|
||||||
|
<!-- 登录的表单 -->
|
||||||
|
<el-form class="login_form" :model="loginForm" ref="loginForms">
|
||||||
|
<h1>Hello</h1>
|
||||||
|
<h2>欢迎来到硅谷甄选</h2>
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<el-input :prefix-icon="User" v-model="loginForm.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input type="password" :prefix-icon="Lock" v-model="loginForm.password" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button :loading="loading" class="login_btn" type="primary" size="default" @click="login">
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { User, Lock } from '@element-plus/icons-vue'
|
||||||
|
import { ElNotification } from 'element-plus'
|
||||||
|
//引入用户相关的小仓库
|
||||||
|
import useUserStore from '@/stores/modules/user'
|
||||||
|
//引入路由跳转
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
const useStore = useUserStore()
|
||||||
|
//获取路由器
|
||||||
|
let router = useRouter()
|
||||||
|
//路由对象
|
||||||
|
let route = useRoute()
|
||||||
|
let loginForm = ref({ username: 'admin', password: 'atguigu123' })
|
||||||
|
//定义变量控制按钮加载效果
|
||||||
|
let loading = ref(false)
|
||||||
|
const login = async () => {
|
||||||
|
await loginForm.value
|
||||||
|
//加载效果:开始加载
|
||||||
|
loading.value = true
|
||||||
|
//点击登录按钮以后干什么?
|
||||||
|
//通知仓库发登录请求
|
||||||
|
//请求成功->首页展示数据的地方
|
||||||
|
//请求失败->弹出登录失败信息
|
||||||
|
try {
|
||||||
|
//保证登录成功
|
||||||
|
await useStore.userLogin(loginForm.value)
|
||||||
|
//编程式导航跳转到展示数据首页
|
||||||
|
//判断登录的时候,路由路径当中是否有query参数,如果有就往query参数挑战,没有跳转到首页
|
||||||
|
let redirect: any = route.query.redirect
|
||||||
|
router.push({ path: redirect || '/' })
|
||||||
|
window.location.reload()
|
||||||
|
//登录成功提示信息
|
||||||
|
ElNotification({
|
||||||
|
type: 'success',
|
||||||
|
message: '欢迎回来'
|
||||||
|
})
|
||||||
|
//登录成功加载效果也消失
|
||||||
|
loading.value = false
|
||||||
|
} catch (error) {
|
||||||
|
//登录失败加载效果消息
|
||||||
|
loading.value = false
|
||||||
|
//登录失败的提示信息
|
||||||
|
ElNotification({
|
||||||
|
type: 'error',
|
||||||
|
message: '登陆失败'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.login_container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: url('@/assets/background.jpg') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
.login_form {
|
||||||
|
position: relative;
|
||||||
|
width: 80%;
|
||||||
|
top: 30vh;
|
||||||
|
background: url('@/assets/images/login_form.png') no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
padding: 40px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
color: white;
|
||||||
|
margin: 20px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login_btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
112
src/pages/permission/menu/index.vue
Normal file
112
src/pages/permission/menu/index.vue
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<template>
|
||||||
|
<!--border:是否带有纵向边框 -->
|
||||||
|
<el-table :data="permission" style="width: 100%; margin-bottom: 20px" row-key="id" border>
|
||||||
|
<el-table-column label="名称" prop="name"></el-table-column>
|
||||||
|
<el-table-column label="权限值" prop="code"></el-table-column>
|
||||||
|
<el-table-column label="修改时间" prop="updateTime"></el-table-column>
|
||||||
|
<el-table-column label="操作" prop="name">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<!-- disabled:禁用效果 -->
|
||||||
|
<el-button type="primary" size="small" @click="addPermission(row)" :disabled="row.level == 4 ? true : false">
|
||||||
|
{{ row.level == 3 ? '添加功能' : '添加菜单' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="updatePermission(row)" :disabled="row.level == 1 ? true : false">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-popconfirm :title="`你确定要删除${row.name}?`" width="260px" @confirm="removeMenu(row.id)">
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="primary" size="small" :disabled="row.level == 1 ? true : false">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 对话框组件 -->
|
||||||
|
<el-dialog v-model="dialogVisible" :title="menuData.id ? '更新菜单' : '添加菜单'">
|
||||||
|
<!-- 表单组件:收集新增与已有的菜单的数据 -->
|
||||||
|
<el-form>
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input placeholder="请你输入菜单名称" v-model="menuData.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="权限">
|
||||||
|
<el-input placeholder="请你输入权限数值" v-model="menuData.code"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="save">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { reqAllPermission, reqAddOrUpdateMenu, reqRemoveMenu } from '@/api/menu/index'
|
||||||
|
import type { PermisstionResponseData, PermisstionList, Permisstion } from '@/api/menu/type'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
// 表单数据
|
||||||
|
const permission = ref<PermisstionList>([])
|
||||||
|
// 对话框显示与隐藏
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
//对话框显示数据
|
||||||
|
const menuData: any = ref({
|
||||||
|
code: '',
|
||||||
|
level: 0,
|
||||||
|
name: '',
|
||||||
|
pid: 0
|
||||||
|
})
|
||||||
|
// 获取菜单数据
|
||||||
|
const permissionList = async () => {
|
||||||
|
const res: PermisstionResponseData = await reqAllPermission()
|
||||||
|
permission.value = res.data
|
||||||
|
console.log(res.data)
|
||||||
|
}
|
||||||
|
// 添加菜单
|
||||||
|
const addPermission = (row: Permisstion) => {
|
||||||
|
Object.assign(menuData.value, {
|
||||||
|
id: 0,
|
||||||
|
code: '',
|
||||||
|
level: 0,
|
||||||
|
name: '',
|
||||||
|
pid: 0
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
//收集新增的菜单的level数值
|
||||||
|
menuData.value.level = row.level + 1
|
||||||
|
//给谁新增子菜单
|
||||||
|
menuData.value.pid = row.id as number
|
||||||
|
}
|
||||||
|
// 编辑菜单
|
||||||
|
const updatePermission = (row: Permisstion) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
// 用于将一个或多个源对象的所有可枚举属性的值复制到目标对象。此方法会返回被修改后的目标对象。
|
||||||
|
Object.assign(menuData.value, row)
|
||||||
|
}
|
||||||
|
// 确认按钮的回调
|
||||||
|
const save = async () => {
|
||||||
|
const result: any = await reqAddOrUpdateMenu(menuData)
|
||||||
|
if (result.code == 200) {
|
||||||
|
//对话框隐藏
|
||||||
|
dialogVisible.value = false
|
||||||
|
//提示信息
|
||||||
|
ElMessage({ type: 'success', message: menuData.value.id ? '更新成功' : '添加成功' })
|
||||||
|
//再次获取全部最新的菜单的数据
|
||||||
|
permissionList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//删除按钮回调
|
||||||
|
const removeMenu = async (id: number) => {
|
||||||
|
let result = await reqRemoveMenu(id)
|
||||||
|
if (result.code == 200) {
|
||||||
|
ElMessage({ type: 'success', message: '删除成功' })
|
||||||
|
permissionList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
permissionList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
7
src/pages/permission/role/index.vue
Normal file
7
src/pages/permission/role/index.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>角色</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
7
src/pages/permission/user/index.vue
Normal file
7
src/pages/permission/user/index.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<div>用户</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
69
src/permisstion.ts
Normal file
69
src/permisstion.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
//路由鉴权:鉴权,项目当中路由能不能被的权限的设置(某一个路由什么条件下可以访问、什么条件下不可以访问)
|
||||||
|
import router from '@/router'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
import nprogress from 'nprogress'
|
||||||
|
//引入进度条样式
|
||||||
|
import 'nprogress/nprogress.css'
|
||||||
|
//获取用户相关的小仓库内部token数据,去判断用户是否登录成功
|
||||||
|
import useUserStore from './stores/modules/user'
|
||||||
|
import pinia from './stores'
|
||||||
|
const userStore = useUserStore(pinia)
|
||||||
|
//全局守卫:项目当中任意路由切换都会触发的钩子
|
||||||
|
//全局前置守卫
|
||||||
|
router.beforeEach(async (to: any, from: any, next: any) => {
|
||||||
|
//to:你将要访问那个路由
|
||||||
|
//from:你从来个路由而来
|
||||||
|
//next:路由的放行函数
|
||||||
|
nprogress.start()
|
||||||
|
//获取token,去判断用户登录、还是未登录
|
||||||
|
const token = userStore.token
|
||||||
|
//获取用户名字
|
||||||
|
const username = userStore.username
|
||||||
|
|
||||||
|
//用户登录判断
|
||||||
|
if (token) {
|
||||||
|
//登录成功,访问login,不能访问,指向首页
|
||||||
|
if (to.path == '/login') {
|
||||||
|
next({ path: '/' })
|
||||||
|
} else {
|
||||||
|
//登录成功访问其余六个路由(登录排除)
|
||||||
|
//有用户信息
|
||||||
|
if (username) {
|
||||||
|
//放行
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
//获取用户信息
|
||||||
|
//放行
|
||||||
|
//万一:刷新的时候是异步路由,有可能获取到用户信息、异步路由还没有加载完毕,出现空白的效果
|
||||||
|
next({ ...to })
|
||||||
|
} catch (error) {
|
||||||
|
//token过期:获取不到用户信息了
|
||||||
|
//用户手动修改本地存储token
|
||||||
|
//退出登录->用户相关的数据清空
|
||||||
|
next({ path: '/login', query: { redirect: to.path } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//用户未登录判断
|
||||||
|
if (to.path == '/login') {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next({ path: '/login', query: { redirect: to.path } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//全局后置守卫
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
router.afterEach((to: any, from: any) => {
|
||||||
|
nprogress.done()
|
||||||
|
})
|
||||||
|
|
||||||
|
//第一个问题:任意路由切换实现进度条业务 ---nprogress
|
||||||
|
//第二个问题:路由鉴权(路由组件访问权限的设置)
|
||||||
|
//全部路由组件:登录|404|任意路由|首页|数据大屏|权限管理(三个子路由)|商品管理(四个子路由)
|
||||||
|
|
||||||
|
//用户未登录:可以访问login,其余六个路由不能访问(指向login)
|
||||||
|
//用户登录成功:不可以访问login[指向首页],其余的路由可以访问
|
17
src/router/index.ts
Normal file
17
src/router/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//通过vue-router插件实现模板路由配置
|
||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import { constantRoute } from './routes'
|
||||||
|
//创建路由器
|
||||||
|
const router = createRouter({
|
||||||
|
//路由模式hash
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes: constantRoute,
|
||||||
|
//滚动行为
|
||||||
|
scrollBehavior() {
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default router
|
115
src/router/routes.ts
Normal file
115
src/router/routes.ts
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import Layout from '@/layout/index.vue'
|
||||||
|
|
||||||
|
export const constantRoute = [
|
||||||
|
{
|
||||||
|
//登录
|
||||||
|
path: '/login',
|
||||||
|
component: () => import('@/pages/login/index.vue'),
|
||||||
|
name: 'login',
|
||||||
|
meta: {
|
||||||
|
title: '登录', //菜单标题
|
||||||
|
hidden: true, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//首页
|
||||||
|
path: '/',
|
||||||
|
component: Layout,
|
||||||
|
name: 'home',
|
||||||
|
redirect: '/home',
|
||||||
|
meta: {
|
||||||
|
title: '首页', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
component: () => import('@/pages/home/index.vue'),
|
||||||
|
name: 'index',
|
||||||
|
meta: {
|
||||||
|
title: '首页', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/test',
|
||||||
|
component: () => import('@/pages/home/test.vue'),
|
||||||
|
name: 'test',
|
||||||
|
meta: {
|
||||||
|
title: '测试', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//菜单
|
||||||
|
path: '/permission',
|
||||||
|
component: Layout,
|
||||||
|
name: 'permission',
|
||||||
|
redirect: '/user',
|
||||||
|
meta: {
|
||||||
|
title: '权限管理', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/user',
|
||||||
|
component: () => import('@/pages/permission/user/index.vue'),
|
||||||
|
name: 'user',
|
||||||
|
meta: {
|
||||||
|
title: '用户管理', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/role',
|
||||||
|
component: () => import('@/pages/permission/role/index.vue'),
|
||||||
|
name: 'role',
|
||||||
|
meta: {
|
||||||
|
title: '角色管理', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/menu',
|
||||||
|
component: () => import('@/pages/permission/menu/index.vue'),
|
||||||
|
name: 'menu',
|
||||||
|
meta: {
|
||||||
|
title: '菜单管理', //菜单标题
|
||||||
|
hidden: false, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
|
||||||
|
icon: 'Promotion' //菜单文字左侧的图标,支持element-plus全部图标
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//404
|
||||||
|
path: '/404',
|
||||||
|
component: () => import('@/pages/404/index.vue'),
|
||||||
|
name: '404',
|
||||||
|
meta: {
|
||||||
|
title: '404',
|
||||||
|
hidden: true,
|
||||||
|
icon: 'DocumentDelete'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//任意路由
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
redirect: '/404',
|
||||||
|
name: 'Any',
|
||||||
|
meta: {
|
||||||
|
title: '任意路由',
|
||||||
|
hidden: true,
|
||||||
|
icon: 'DataLine'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
6
src/stores/index.ts
Normal file
6
src/stores/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
//仓库大仓库
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
//创建大仓库
|
||||||
|
const pinia = createPinia()
|
||||||
|
//对外暴露:入口文件需要安装仓库
|
||||||
|
export default pinia
|
13
src/stores/modules/setting.ts
Normal file
13
src/stores/modules/setting.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//小仓库:layout组件相关配置仓库
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
const useLayOutSettingStore = defineStore('SettingStore', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
fold: false, //用户控制菜单折叠还是收起控制
|
||||||
|
refsh: false //仓库这个属性用于控制刷新效果
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default useLayOutSettingStore
|
42
src/stores/modules/user.ts
Normal file
42
src/stores/modules/user.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//创建用户相关的小仓库
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { reqLogin } from '@/api/user'
|
||||||
|
import type { loginFormData, loginResponseData } from '@/api/user/type'
|
||||||
|
//引入路由(常量路由)
|
||||||
|
import { constantRoute } from '@/router/routes'
|
||||||
|
|
||||||
|
//创建用户小仓库
|
||||||
|
const useUserStore = defineStore('User', {
|
||||||
|
//小仓库存储数据地方
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
token: localStorage.getItem('TOKEN'), //用户唯一标识token
|
||||||
|
menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)
|
||||||
|
username: 'sdy',
|
||||||
|
avatar: 'https://q4.itc.cn/q_70/images03/20240528/298d4abda5e4469d98fa77e7cde46525.jpeg'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//异步|逻辑的地方
|
||||||
|
actions: {
|
||||||
|
// 用户登录的方法
|
||||||
|
async userLogin(data: loginFormData) {
|
||||||
|
//登录请求
|
||||||
|
const result: loginResponseData = await reqLogin(data)
|
||||||
|
//登录请求:成功200->token
|
||||||
|
//登录请求:失败201->登录失败错误的信息
|
||||||
|
if (result.code == 200) {
|
||||||
|
//pinia仓库存储一下token
|
||||||
|
this.token = result.data as string
|
||||||
|
//本地存储持久化存储一份
|
||||||
|
localStorage.setItem('TOKEN', (result.data as any).token)
|
||||||
|
//能保证当前async函数返回一个成功的promise
|
||||||
|
return 'ok'
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error(result.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {}
|
||||||
|
})
|
||||||
|
//对外暴露获取小仓库方法
|
||||||
|
export default useUserStore
|
2
src/style/index.scss
Normal file
2
src/style/index.scss
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// 引入默认样式
|
||||||
|
@import '@/style/reset.scss';
|
186
src/style/reset.scss
Normal file
186
src/style/reset.scss
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
*,
|
||||||
|
*:after,
|
||||||
|
*:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
div,
|
||||||
|
span,
|
||||||
|
applet,
|
||||||
|
object,
|
||||||
|
iframe,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
blockquote,
|
||||||
|
pre,
|
||||||
|
a,
|
||||||
|
abbr,
|
||||||
|
acronym,
|
||||||
|
address,
|
||||||
|
big,
|
||||||
|
cite,
|
||||||
|
code,
|
||||||
|
del,
|
||||||
|
dfn,
|
||||||
|
em,
|
||||||
|
img,
|
||||||
|
ins,
|
||||||
|
kbd,
|
||||||
|
q,
|
||||||
|
s,
|
||||||
|
samp,
|
||||||
|
small,
|
||||||
|
strike,
|
||||||
|
strong,
|
||||||
|
sub,
|
||||||
|
sup,
|
||||||
|
tt,
|
||||||
|
var,
|
||||||
|
b,
|
||||||
|
u,
|
||||||
|
i,
|
||||||
|
center,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
dd,
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
li,
|
||||||
|
fieldset,
|
||||||
|
form,
|
||||||
|
label,
|
||||||
|
legend,
|
||||||
|
table,
|
||||||
|
caption,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
thead,
|
||||||
|
tr,
|
||||||
|
th,
|
||||||
|
td,
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
canvas,
|
||||||
|
details,
|
||||||
|
embed,
|
||||||
|
figure,
|
||||||
|
figcaption,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
output,
|
||||||
|
ruby,
|
||||||
|
section,
|
||||||
|
summary,
|
||||||
|
time,
|
||||||
|
mark,
|
||||||
|
audio,
|
||||||
|
video {
|
||||||
|
font: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
vertical-align: baseline;
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
q {
|
||||||
|
quotes: none;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
button {
|
||||||
|
font-family: inhert;
|
||||||
|
font-size: inherit;
|
||||||
|
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
text-indent: 0.01px;
|
||||||
|
text-overflow: '';
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
19
src/style/variable.scss
Normal file
19
src/style/variable.scss
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//项目提供scss全局变量
|
||||||
|
//定义项目主题颜色
|
||||||
|
|
||||||
|
//左侧的菜单的宽度
|
||||||
|
$base-menu-width: 260px;
|
||||||
|
//左侧菜单的背景颜色
|
||||||
|
$base-menu-background: #001529;
|
||||||
|
$base-menu-min-width: 50px;
|
||||||
|
|
||||||
|
// 顶部导航的高度
|
||||||
|
$base-tabbar-height: 50px;
|
||||||
|
|
||||||
|
//左侧菜单logo高度设置
|
||||||
|
$base-menu-logo-height: 50px;
|
||||||
|
|
||||||
|
//左侧菜单logo右侧文字大小
|
||||||
|
$base-logo-title-fontSize: 20px;
|
||||||
|
//左侧菜单文字颜色
|
||||||
|
$--active-color: red;
|
44
src/utils/request.ts
Normal file
44
src/utils/request.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
//创建axios实例
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||||
|
timeout: 5000
|
||||||
|
})
|
||||||
|
//请求拦截器
|
||||||
|
request.interceptors.request.use((config) => {
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
//响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
//处理网络错误
|
||||||
|
let msg = ''
|
||||||
|
const status = error.response.status
|
||||||
|
switch (status) {
|
||||||
|
case 401:
|
||||||
|
msg = 'token过期'
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
msg = '无权访问'
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
msg = '请求地址错误'
|
||||||
|
break
|
||||||
|
case 500:
|
||||||
|
msg = '服务器出现问题'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
msg = '无网络'
|
||||||
|
}
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: msg
|
||||||
|
})
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
export default request
|
15
tsconfig.app.json
Normal file
15
tsconfig.app.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["element-plus/global"],
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node20/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
29
vite.config.ts
Normal file
29
vite.config.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { viteMockServe } from 'vite-plugin-mock'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteMockServe({
|
||||||
|
mockPath: 'mock',
|
||||||
|
enable: true
|
||||||
|
})
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
additionalData: '@import "@/style/variable.scss";'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user