✨ 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