style: 代码统一风格格式化

Former-commit-id: 5d0a75e411
This commit is contained in:
郝先瑞 2022-05-08 13:06:12 +08:00
parent 77a71db326
commit eab11687aa
137 changed files with 10635 additions and 10635 deletions

View File

@ -1,30 +1,30 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly'
},
parser: 'vue-eslint-parser',
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended'
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: ['vue', '@typescript-eslint'],
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-empty-function': 'off', // 关闭空方法检查
'@typescript-eslint/no-explicit-any': 'off', // 关闭any类型的警告
'vue/no-v-model-argument': 'off'
}
env: {
browser: true,
es2021: true,
node: true
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly'
},
parser: 'vue-eslint-parser',
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended'
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: ['vue', '@typescript-eslint'],
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-empty-function': 'off', // 关闭空方法检查
'@typescript-eslint/no-explicit-any': 'off', // 关闭any类型的警告
'vue/no-v-model-argument': 'off'
}
};

View File

@ -2,35 +2,35 @@
* 代码格式化配置
*/
module.exports = {
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: true,
// 在语句末尾打印分号
semi: true,
// 使用单引号而不是双引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: 'none',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
arrowParens: 'avoid',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 换行设置 always\never\preserve
proseWrap: 'never',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf'
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: false,
// 在语句末尾打印分号
semi: true,
// 使用单引号而不是双引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: 'none',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
arrowParens: 'avoid',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 换行设置 always\never\preserve
proseWrap: 'never',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf'
};

View File

@ -1,26 +1,26 @@
module.exports = {
// 继承的规则
extends: ['@commitlint/config-conventional'],
// 定义规则类型
rules: {
// type 类型定义,表示 git 提交的 type 必须在以下类型范围内
'type-enum': [
2,
'always',
[
'feat', // 新功能 feature
'fix', // 修复 bug
'docs', // 文档注释
'style', // 代码格式(不影响代码运行的变动)
'refactor', // 重构(既不增加新功能也不是修复bug)
'perf', // 性能优化
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', // 回退
'build' // 打包
]
],
// subject 大小写不做校验
'subject-case': [0]
}
// 继承的规则
extends: ['@commitlint/config-conventional'],
// 定义规则类型
rules: {
// type 类型定义,表示 git 提交的 type 必须在以下类型范围内
'type-enum': [
2,
'always',
[
'feat', // 新功能 feature
'fix', // 修复 bug
'docs', // 文档注释
'style', // 代码格式(不影响代码运行的变动)
'refactor', // 重构(既不增加新功能也不是修复bug)
'perf', // 性能优化
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', // 回退
'build' // 打包
]
],
// subject 大小写不做校验
'subject-case': [0]
}
};

View File

@ -1,13 +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>vue3-element-admin</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue3-element-admin</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,55 +1,55 @@
{
"name": "vue3-element-admin",
"version": "1.0.0",
"scripts": {
"dev": "vite serve --mode development",
"build:prod": "vue-tsc --noEmit && vite build --mode production",
"serve": "vite preview",
"lint": "eslint src/**/*.{ts,js,vue} --fix",
"prepare": "husky install",
"prettier": "prettier --write ."
},
"dependencies": {
"@element-plus/icons-vue": "^1.0.0",
"@wangeditor/editor": "^5.0.0",
"@wangeditor/editor-for-vue": "^5.1.10",
"axios": "^0.24.0",
"better-scroll": "^2.4.2",
"echarts": "^5.2.2",
"element-plus": "^2.1.8",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.12",
"screenfull": "^6.0.0",
"sortablejs": "^1.14.0",
"vue": "^3.2.25",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@types/node": "^16.11.7",
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"@vitejs/plugin-vue": "^1.9.3",
"eslint": "^8.14.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.6.0",
"fast-glob": "^3.2.11",
"husky": "^7.0.4",
"prettier": "^2.6.2",
"sass": "^1.43.4",
"typescript": "^4.5.4",
"vite": "^2.9.7",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^0.34.7"
},
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
"author": "有来开源组织",
"license": "MIT"
"name": "vue3-element-admin",
"version": "1.0.0",
"scripts": {
"dev": "vite serve --mode development",
"build:prod": "vue-tsc --noEmit && vite build --mode production",
"serve": "vite preview",
"lint": "eslint src/**/*.{ts,js,vue} --fix",
"prepare": "husky install",
"prettier": "prettier --write ."
},
"dependencies": {
"@element-plus/icons-vue": "^1.0.0",
"@wangeditor/editor": "^5.0.0",
"@wangeditor/editor-for-vue": "^5.1.10",
"axios": "^0.24.0",
"better-scroll": "^2.4.2",
"echarts": "^5.2.2",
"element-plus": "^2.1.8",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.12",
"screenfull": "^6.0.0",
"sortablejs": "^1.14.0",
"vue": "^3.2.25",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@types/node": "^16.11.7",
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"@vitejs/plugin-vue": "^1.9.3",
"eslint": "^8.14.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.6.0",
"husky": "^7.0.4",
"prettier": "^2.6.2",
"sass": "^1.43.4",
"typescript": "^4.5.4",
"vite": "^2.9.7",
"fast-glob": "^3.2.11",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^0.34.7"
},
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
"author": "有来开源组织",
"license": "MIT"
}

View File

@ -1,7 +1,7 @@
<template>
<el-config-provider :locale="locale" :size="size">
<router-view />
</el-config-provider>
<el-config-provider :locale="locale" :size="size">
<router-view />
</el-config-provider>
</template>
<script setup lang="ts">
@ -22,13 +22,13 @@ const size: any = computed(() => app.size);
const locale = ref();
watch(
language,
value => {
locale.value = value == 'en' ? en : zhCn;
},
{
//
immediate: true
}
language,
value => {
locale.value = value == 'en' ? en : zhCn;
},
{
//
immediate: true
}
);
</script>

View File

@ -6,11 +6,11 @@ import request from '@/utils/request';
* @returns
*/
export function payOrder(data: SeataFormData) {
return request({
url: '/youlai-lab/api/v1/seata/order/_pay',
method: 'post',
data: data
});
return request({
url: '/youlai-lab/api/v1/seata/order/_pay',
method: 'post',
data: data
});
}
/**
@ -18,10 +18,10 @@ export function payOrder(data: SeataFormData) {
* @returns
*/
export function getSeataData() {
return request({
url: '/youlai-lab/api/v1/seata/data',
method: 'get'
});
return request({
url: '/youlai-lab/api/v1/seata/data',
method: 'get'
});
}
/**
@ -29,8 +29,8 @@ export function getSeataData() {
* @returns
*/
export function resetSeataData() {
return request({
url: '/youlai-lab/api/v1/seata/data/_reset',
method: 'put'
});
return request({
url: '/youlai-lab/api/v1/seata/data/_reset',
method: 'put'
});
}

View File

@ -7,32 +7,32 @@ import { AxiosPromise } from 'axios';
* @param data
*/
export function login(data: LoginFormData): AxiosPromise<LoginResponseData> {
return request({
url: '/youlai-auth/oauth/token',
method: 'post',
params: data,
headers: {
Authorization: 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2' // 客户端信息Base64明文mall-admin-web:123456
}
});
return request({
url: '/youlai-auth/oauth/token',
method: 'post',
params: data,
headers: {
Authorization: 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2' // 客户端信息Base64明文mall-admin-web:123456
}
});
}
/**
*
*/
export function logout() {
return request({
url: '/youlai-auth/oauth/logout',
method: 'delete'
});
return request({
url: '/youlai-auth/oauth/logout',
method: 'delete'
});
}
/**
*
*/
export function getCaptcha(): AxiosPromise<Captcha> {
return request({
url: '/captcha?t=' + new Date().getTime().toString(),
method: 'get'
});
return request({
url: '/captcha?t=' + new Date().getTime().toString(),
method: 'get'
});
}

View File

@ -8,13 +8,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listOrderPages(
queryParams: OrderQueryParam
queryParams: OrderQueryParam
): AxiosPromise<OrderPageResult> {
return request({
url: '/mall-oms/api/v1/orders',
method: 'get',
params: queryParams
});
return request({
url: '/mall-oms/api/v1/orders',
method: 'get',
params: queryParams
});
}
/**
@ -23,8 +23,8 @@ export function listOrderPages(
* @param orderId
*/
export function getOrderDetail(orderId: number) {
return request({
url: '/mall-oms/api/v1/orders/' + orderId,
method: 'get'
});
return request({
url: '/mall-oms/api/v1/orders/' + orderId,
method: 'get'
});
}

View File

@ -6,11 +6,11 @@ import request from '@/utils/request';
* @param params
*/
export function listAttributes(params: object) {
return request({
url: '/mall-pms/api/v1/attributes',
method: 'get',
params: params
});
return request({
url: '/mall-pms/api/v1/attributes',
method: 'get',
params: params
});
}
/**
@ -19,9 +19,9 @@ export function listAttributes(params: object) {
* @param data
*/
export function saveAttributeBatch(data: object) {
return request({
url: '/mall-pms/api/v1/attributes/batch',
method: 'post',
data: data
});
return request({
url: '/mall-pms/api/v1/attributes/batch',
method: 'post',
data: data
});
}

View File

@ -1,8 +1,8 @@
import {
BrandFormData,
BrandItem,
BrandPageResult,
BrandQueryParam
BrandFormData,
BrandItem,
BrandPageResult,
BrandQueryParam
} from '@/types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@ -13,13 +13,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listBrandPages(
queryParams: BrandQueryParam
queryParams: BrandQueryParam
): AxiosPromise<BrandPageResult> {
return request({
url: '/mall-pms/api/v1/brands/page',
method: 'get',
params: queryParams
});
return request({
url: '/mall-pms/api/v1/brands/page',
method: 'get',
params: queryParams
});
}
/**
@ -28,13 +28,13 @@ export function listBrandPages(
* @param queryParams
*/
export function listBrands(
queryParams?: BrandQueryParam
queryParams?: BrandQueryParam
): AxiosPromise<BrandItem[]> {
return request({
url: '/mall-pms/api/v1/brands',
method: 'get',
params: queryParams
});
return request({
url: '/mall-pms/api/v1/brands',
method: 'get',
params: queryParams
});
}
/**
@ -43,10 +43,10 @@ export function listBrands(
* @param id
*/
export function getBrandFormDetail(id: number): AxiosPromise<BrandFormData> {
return request({
url: '/mall-pms/api/v1/brands/' + id,
method: 'get'
});
return request({
url: '/mall-pms/api/v1/brands/' + id,
method: 'get'
});
}
/**
@ -55,11 +55,11 @@ export function getBrandFormDetail(id: number): AxiosPromise<BrandFormData> {
* @param data
*/
export function addBrand(data: BrandFormData) {
return request({
url: '/mall-pms/api/v1/brands',
method: 'post',
data: data
});
return request({
url: '/mall-pms/api/v1/brands',
method: 'post',
data: data
});
}
/**
@ -69,11 +69,11 @@ export function addBrand(data: BrandFormData) {
* @param data
*/
export function updateBrand(id: number, data: BrandFormData) {
return request({
url: '/mall-pms/api/v1/brands/' + id,
method: 'put',
data: data
});
return request({
url: '/mall-pms/api/v1/brands/' + id,
method: 'put',
data: data
});
}
/**
@ -82,8 +82,8 @@ export function updateBrand(id: number, data: BrandFormData) {
* @param ids
*/
export function deleteBrands(ids: string) {
return request({
url: '/mall-pms/api/v1/brands/' + ids,
method: 'delete'
});
return request({
url: '/mall-pms/api/v1/brands/' + ids,
method: 'delete'
});
}

View File

@ -6,11 +6,11 @@ import request from '@/utils/request';
* @param queryParams
*/
export function listCategories(queryParams: object) {
return request({
url: '/mall-pms/api/v1/categories',
method: 'get',
params: queryParams
});
return request({
url: '/mall-pms/api/v1/categories',
method: 'get',
params: queryParams
});
}
/**
@ -19,11 +19,11 @@ export function listCategories(queryParams: object) {
* @param queryParams
*/
export function listCascadeCategories(queryParams?: object) {
return request({
url: '/mall-pms/api/v1/categories/cascade',
method: 'get',
params: queryParams
});
return request({
url: '/mall-pms/api/v1/categories/cascade',
method: 'get',
params: queryParams
});
}
/**
@ -32,10 +32,10 @@ export function listCascadeCategories(queryParams?: object) {
* @param id
*/
export function getCategoryDetail(id: number) {
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'get'
});
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'get'
});
}
/**
@ -44,11 +44,11 @@ export function getCategoryDetail(id: number) {
* @param data
*/
export function addCategory(data: object) {
return request({
url: '/mall-pms/api/v1/categories',
method: 'post',
data: data
});
return request({
url: '/mall-pms/api/v1/categories',
method: 'post',
data: data
});
}
/**
@ -58,11 +58,11 @@ export function addCategory(data: object) {
* @param data
*/
export function updateCategory(id: number, data: object) {
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'put',
data: data
});
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'put',
data: data
});
}
/**
@ -71,10 +71,10 @@ export function updateCategory(id: number, data: object) {
* @param ids
*/
export function deleteCategories(ids: string) {
return request({
url: '/mall-pms/api/v1/categories/' + ids,
method: 'delete'
});
return request({
url: '/mall-pms/api/v1/categories/' + ids,
method: 'delete'
});
}
/**
@ -84,9 +84,9 @@ export function deleteCategories(ids: string) {
* @param data
*/
export function updateCategoryPart(id: number, data: object) {
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'patch',
data: data
});
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'patch',
data: data
});
}

View File

@ -8,13 +8,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listGoodsPages(
queryParams: GoodsQueryParam
queryParams: GoodsQueryParam
): AxiosPromise<GoodsPageResult> {
return request({
url: '/mall-pms/api/v1/goods/page',
method: 'get',
params: queryParams
});
return request({
url: '/mall-pms/api/v1/goods/page',
method: 'get',
params: queryParams
});
}
/**
@ -23,10 +23,10 @@ export function listGoodsPages(
* @param id
*/
export function getGoodsDetail(id: string): AxiosPromise<GoodsDetail> {
return request({
url: '/mall-pms/api/v1/goods/' + id,
method: 'get'
});
return request({
url: '/mall-pms/api/v1/goods/' + id,
method: 'get'
});
}
/**
@ -35,11 +35,11 @@ export function getGoodsDetail(id: string): AxiosPromise<GoodsDetail> {
* @param data
*/
export function addGoods(data: object) {
return request({
url: '/mall-pms/api/v1/goods',
method: 'post',
data: data
});
return request({
url: '/mall-pms/api/v1/goods',
method: 'post',
data: data
});
}
/**
@ -49,11 +49,11 @@ export function addGoods(data: object) {
* @param data
*/
export function updateGoods(id: number, data: object) {
return request({
url: '/mall-pms/api/v1/goods/' + id,
method: 'put',
data: data
});
return request({
url: '/mall-pms/api/v1/goods/' + id,
method: 'put',
data: data
});
}
/**
@ -62,8 +62,8 @@ export function updateGoods(id: number, data: object) {
* @param ids
*/
export function deleteGoods(ids: string) {
return request({
url: '/mall-pms/api/v1/goods/' + ids,
method: 'delete'
});
return request({
url: '/mall-pms/api/v1/goods/' + ids,
method: 'delete'
});
}

View File

@ -8,13 +8,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listAdvertPages(
queryParams: AdvertQueryParam
queryParams: AdvertQueryParam
): AxiosPromise<AdvertPageResult> {
return request({
url: '/mall-sms/api/v1/adverts',
method: 'get',
params: queryParams
});
return request({
url: '/mall-sms/api/v1/adverts',
method: 'get',
params: queryParams
});
}
/**
@ -23,10 +23,10 @@ export function listAdvertPages(
* @param id
*/
export function getAdvertFormDetail(id: number): AxiosPromise<AdvertFormData> {
return request({
url: '/mall-sms/api/v1/adverts/' + id,
method: 'get'
});
return request({
url: '/mall-sms/api/v1/adverts/' + id,
method: 'get'
});
}
/**
@ -35,11 +35,11 @@ export function getAdvertFormDetail(id: number): AxiosPromise<AdvertFormData> {
* @param data
*/
export function addAdvert(data: AdvertFormData) {
return request({
url: '/mall-sms/api/v1/adverts',
method: 'post',
data: data
});
return request({
url: '/mall-sms/api/v1/adverts',
method: 'post',
data: data
});
}
/**
@ -49,11 +49,11 @@ export function addAdvert(data: AdvertFormData) {
* @param data
*/
export function updateAdvert(id: number, data: AdvertFormData) {
return request({
url: '/mall-sms/api/v1/adverts/' + id,
method: 'put',
data: data
});
return request({
url: '/mall-sms/api/v1/adverts/' + id,
method: 'put',
data: data
});
}
/**
@ -62,8 +62,8 @@ export function updateAdvert(id: number, data: AdvertFormData) {
* @param ids
*/
export function deleteAdverts(ids: string) {
return request({
url: '/mall-sms/api/v1/adverts/' + ids,
method: 'delete'
});
return request({
url: '/mall-sms/api/v1/adverts/' + ids,
method: 'delete'
});
}

View File

@ -1,55 +1,55 @@
import {
ClientFormData,
ClientPageResult,
ClientQueryParam
ClientFormData,
ClientPageResult,
ClientQueryParam
} from '@/types/api/system/client';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export function listClientPages(
queryParams: ClientQueryParam
queryParams: ClientQueryParam
): AxiosPromise<ClientPageResult> {
return request({
url: '/youlai-admin/api/v1/oauth-clients',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/oauth-clients',
method: 'get',
params: queryParams
});
}
export function getClientFormDetial(id: number): AxiosPromise<ClientFormData> {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'get'
});
}
export function addClient(data: ClientFormData) {
return request({
url: '/youlai-admin/api/v1/oauth-clients',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v1/oauth-clients',
method: 'post',
data: data
});
}
export function updateClient(id: string, data: ClientFormData) {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'put',
data: data
});
}
export function deleteClients(ids: string) {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + ids,
method: 'delete'
});
}
export function updateClientPart(id: number, data: object) {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'patch',
data: data
});
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'patch',
data: data
});
}

View File

@ -8,23 +8,23 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listTableDepartments(
queryParams?: DeptQueryParam
queryParams?: DeptQueryParam
): AxiosPromise<DeptItem[]> {
return request({
url: '/youlai-admin/api/v1/depts/table',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/depts/table',
method: 'get',
params: queryParams
});
}
/**
*
*/
export function listSelectDepartments(): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v1/depts/select',
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/depts/select',
method: 'get'
});
}
/**
@ -33,10 +33,10 @@ export function listSelectDepartments(): AxiosPromise<Option[]> {
* @param id
*/
export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
return request({
url: '/youlai-admin/api/v1/depts/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/depts/' + id,
method: 'get'
});
}
/**
@ -45,11 +45,11 @@ export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
* @param data
*/
export function addDept(data: DeptFormData) {
return request({
url: '/youlai-admin/api/v1/depts',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v1/depts',
method: 'post',
data: data
});
}
/**
@ -59,11 +59,11 @@ export function addDept(data: DeptFormData) {
* @param data
*/
export function updateDept(id: string, data: DeptFormData) {
return request({
url: '/youlai-admin/api/v1/depts/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v1/depts/' + id,
method: 'put',
data: data
});
}
/**
@ -72,8 +72,8 @@ export function updateDept(id: string, data: DeptFormData) {
* @param ids
*/
export function deleteDept(ids: string) {
return request({
url: '/youlai-admin/api/v1/depts/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v1/depts/' + ids,
method: 'delete'
});
}

View File

@ -1,11 +1,11 @@
import {
DictFormData,
DictItemFormData,
DictItemPageResult,
DictItemQueryParam,
DictPageResult,
DictQueryParam,
Option
DictFormData,
DictItemFormData,
DictItemPageResult,
DictItemQueryParam,
DictPageResult,
DictQueryParam,
Option
} from '@/types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@ -16,13 +16,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listDictPages(
queryParams: DictQueryParam
queryParams: DictQueryParam
): AxiosPromise<DictPageResult> {
return request({
url: '/youlai-admin/api/v2/dict/page',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v2/dict/page',
method: 'get',
params: queryParams
});
}
/**
@ -31,10 +31,10 @@ export function listDictPages(
* @param id
*/
export function getDictFormDetail(id: number): AxiosPromise<DictFormData> {
return request({
url: '/youlai-admin/api/v2/dict/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v2/dict/' + id,
method: 'get'
});
}
/**
@ -43,11 +43,11 @@ export function getDictFormDetail(id: number): AxiosPromise<DictFormData> {
* @param data
*/
export function addDict(data: DictFormData) {
return request({
url: '/youlai-admin/api/v2/dict',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v2/dict',
method: 'post',
data: data
});
}
/**
@ -57,11 +57,11 @@ export function addDict(data: DictFormData) {
* @param data
*/
export function updateDict(id: number, data: DictFormData) {
return request({
url: '/youlai-admin/api/v2/dict/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v2/dict/' + id,
method: 'put',
data: data
});
}
/**
@ -69,10 +69,10 @@ export function updateDict(id: number, data: DictFormData) {
* @param ids ID(,)
*/
export function deleteDict(ids: string) {
return request({
url: '/youlai-admin/api/v2/dict/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v2/dict/' + ids,
method: 'delete'
});
}
/**
@ -81,13 +81,13 @@ export function deleteDict(ids: string) {
* @param queryParams
*/
export function listDictItemPages(
queryParams: DictItemQueryParam
queryParams: DictItemQueryParam
): AxiosPromise<DictItemPageResult> {
return request({
url: '/youlai-admin/api/v2/dict/items/page',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v2/dict/items/page',
method: 'get',
params: queryParams
});
}
/**
@ -96,11 +96,11 @@ export function listDictItemPages(
* @param dictCode
*/
export function listDictsByCode(dictCode: string): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v2/dict/items',
method: 'get',
params: { dictCode: dictCode }
});
return request({
url: '/youlai-admin/api/v2/dict/items',
method: 'get',
params: { dictCode: dictCode }
});
}
/**
@ -109,10 +109,10 @@ export function listDictsByCode(dictCode: string): AxiosPromise<Option[]> {
* @param id
*/
export function getDictItemDetail(id: number): AxiosPromise<DictItemFormData> {
return request({
url: '/youlai-admin/api/v2/dict/items/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v2/dict/items/' + id,
method: 'get'
});
}
/**
@ -121,11 +121,11 @@ export function getDictItemDetail(id: number): AxiosPromise<DictItemFormData> {
* @param data
*/
export function addDictItem(data: any) {
return request({
url: '/youlai-admin/api/v2/dict/items',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v2/dict/items',
method: 'post',
data: data
});
}
/**
@ -135,11 +135,11 @@ export function addDictItem(data: any) {
* @param data
*/
export function updateDictItem(id: number, data: any) {
return request({
url: '/youlai-admin/api/v2/dict/items/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v2/dict/items/' + id,
method: 'put',
data: data
});
}
/**
@ -147,8 +147,8 @@ export function updateDictItem(id: number, data: any) {
* @param ids ID(,)
*/
export function deleteDictItem(ids: string) {
return request({
url: '/youlai-admin/api/v2/dict/items/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v2/dict/items/' + ids,
method: 'delete'
});
}

View File

@ -6,16 +6,16 @@ import request from '@/utils/request';
* @param file
*/
export function uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file);
return request({
url: '/youlai-admin/api/v1/files',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
const formData = new FormData();
formData.append('file', file);
return request({
url: '/youlai-admin/api/v1/files',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
/**
@ -24,9 +24,9 @@ export function uploadFile(file: File) {
* @param path
*/
export function deleteFile(path?: string) {
return request({
url: '/youlai-admin/api/v1/files',
method: 'delete',
params: { path: path }
});
return request({
url: '/youlai-admin/api/v1/files',
method: 'delete',
params: { path: path }
});
}

View File

@ -6,10 +6,10 @@ import { AxiosPromise } from 'axios';
*
*/
export function listRoutes() {
return request({
url: '/youlai-admin/api/v1/menus/route',
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/menus/route',
method: 'get'
});
}
/**
@ -18,23 +18,23 @@ export function listRoutes() {
* @param queryParams
*/
export function listTableMenus(
queryParams: MenuQueryParam
queryParams: MenuQueryParam
): AxiosPromise<MenuItem[]> {
return request({
url: '/youlai-admin/api/v1/menus/table',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/menus/table',
method: 'get',
params: queryParams
});
}
/**
*
*/
export function listSelectMenus(): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v1/menus/select',
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/menus/select',
method: 'get'
});
}
/**
@ -42,10 +42,10 @@ export function listSelectMenus(): AxiosPromise<Option[]> {
* @param id
*/
export function getMenuDetail(id: number): AxiosPromise<MenuFormData> {
return request({
url: '/youlai-admin/api/v1/menus/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/menus/' + id,
method: 'get'
});
}
/**
@ -54,11 +54,11 @@ export function getMenuDetail(id: number): AxiosPromise<MenuFormData> {
* @param data
*/
export function addMenu(data: MenuFormData) {
return request({
url: '/youlai-admin/api/v1/menus',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v1/menus',
method: 'post',
data: data
});
}
/**
@ -68,11 +68,11 @@ export function addMenu(data: MenuFormData) {
* @param data
*/
export function updateMenu(id: string, data: MenuFormData) {
return request({
url: '/youlai-admin/api/v1/menus/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v1/menus/' + id,
method: 'put',
data: data
});
}
/**
@ -81,8 +81,8 @@ export function updateMenu(id: string, data: MenuFormData) {
* @param ids ID(,)
*/
export function deleteMenus(ids: string) {
return request({
url: '/youlai-admin/api/v1/menus/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v1/menus/' + ids,
method: 'delete'
});
}

View File

@ -1,8 +1,8 @@
import {
PermFormData,
PermItem,
PermPageResult,
PermQueryParam
PermFormData,
PermItem,
PermPageResult,
PermQueryParam
} from '@/types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@ -13,13 +13,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listPermPages(
queryParams: PermQueryParam
queryParams: PermQueryParam
): AxiosPromise<PermPageResult> {
return request({
url: '/youlai-admin/api/v1/permissions/page',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/permissions/page',
method: 'get',
params: queryParams
});
}
/**
@ -28,13 +28,13 @@ export function listPermPages(
* @param queryParams
*/
export function listPerms(
queryParams: PermQueryParam
queryParams: PermQueryParam
): AxiosPromise<PermItem[]> {
return request({
url: '/youlai-admin/api/v1/permissions',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/permissions',
method: 'get',
params: queryParams
});
}
/**
@ -43,10 +43,10 @@ export function listPerms(
* @param id
*/
export function getPermFormDetail(id: number): AxiosPromise<PermFormData> {
return request({
url: '/youlai-admin/api/v1/permissions/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/permissions/' + id,
method: 'get'
});
}
/**
@ -55,11 +55,11 @@ export function getPermFormDetail(id: number): AxiosPromise<PermFormData> {
* @param data
*/
export function addPerm(data: PermFormData) {
return request({
url: '/youlai-admin/api/v1/permissions',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v1/permissions',
method: 'post',
data: data
});
}
/**
@ -69,11 +69,11 @@ export function addPerm(data: PermFormData) {
* @param data
*/
export function updatePerm(id: number, data: PermFormData) {
return request({
url: '/youlai-admin/api/v1/permissions/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v1/permissions/' + id,
method: 'put',
data: data
});
}
/**
@ -82,8 +82,8 @@ export function updatePerm(id: number, data: PermFormData) {
* @param ids
*/
export function deletePerms(ids: string) {
return request({
url: '/youlai-admin/api/v1/permissions/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v1/permissions/' + ids,
method: 'delete'
});
}

View File

@ -1,8 +1,8 @@
import {
RoleFormData,
RoleItem,
RolePageResult,
RoleQueryParam
RoleFormData,
RoleItem,
RolePageResult,
RoleQueryParam
} from '@/types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@ -13,13 +13,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listRolePages(
queryParams?: RoleQueryParam
queryParams?: RoleQueryParam
): AxiosPromise<RolePageResult> {
return request({
url: '/youlai-admin/api/v1/roles/page',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/roles/page',
method: 'get',
params: queryParams
});
}
/**
@ -28,13 +28,13 @@ export function listRolePages(
* @param queryParams
*/
export function listRoles(
queryParams?: RoleQueryParam
queryParams?: RoleQueryParam
): AxiosPromise<RoleItem[]> {
return request({
url: '/youlai-admin/api/v1/roles',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/roles',
method: 'get',
params: queryParams
});
}
/**
@ -43,10 +43,10 @@ export function listRoles(
* @param id
*/
export function getRoleFormDetail(id: number): AxiosPromise<RoleFormData> {
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'get'
});
}
/**
@ -55,11 +55,11 @@ export function getRoleFormDetail(id: number): AxiosPromise<RoleFormData> {
* @param data
*/
export function addRole(data: RoleFormData) {
return request({
url: '/youlai-admin/api/v1/roles',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v1/roles',
method: 'post',
data: data
});
}
/**
@ -69,11 +69,11 @@ export function addRole(data: RoleFormData) {
* @param data
*/
export function updateRole(id: number, data: RoleFormData) {
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'put',
data: data
});
}
/**
@ -82,10 +82,10 @@ export function updateRole(id: number, data: RoleFormData) {
* @param ids
*/
export function deleteRoles(ids: string) {
return request({
url: '/youlai-admin/api/v1/roles/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v1/roles/' + ids,
method: 'delete'
});
}
/**
@ -94,10 +94,10 @@ export function deleteRoles(ids: string) {
* @param roleId
*/
export function listRoleMenuIds(roleId: number): AxiosPromise<number[]> {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/menu_ids',
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/menu_ids',
method: 'get'
});
}
/**
@ -107,11 +107,11 @@ export function listRoleMenuIds(roleId: number): AxiosPromise<number[]> {
* @param menuIds
*/
export function updateRoleMenu(roleId: number, menuIds: Array<number>) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/menus',
method: 'put',
data: { menuIds: menuIds }
});
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/menus',
method: 'put',
data: { menuIds: menuIds }
});
}
/**
@ -120,11 +120,11 @@ export function updateRoleMenu(roleId: number, menuIds: Array<number>) {
* @param roleId
*/
export function listRolePerms(roleId: number, menuId: number) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
method: 'get',
params: { menuId: menuId }
});
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
method: 'get',
params: { menuId: menuId }
});
}
/**
@ -135,13 +135,13 @@ export function listRolePerms(roleId: number, menuId: number) {
* @param permIds
*/
export function saveRolePerms(
roleId: number,
menuId: number,
permIds: Array<number>
roleId: number,
menuId: number,
permIds: Array<number>
) {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
method: 'put',
data: { menuId: menuId, permIds: permIds }
});
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
method: 'put',
data: { menuId: menuId, permIds: permIds }
});
}

View File

@ -1,20 +1,20 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {
UserFormData,
UserInfo,
UserPageResult,
UserQueryParam
UserFormData,
UserInfo,
UserPageResult,
UserQueryParam
} from '@/types';
/**
*
*/
export function getUserInfo(): AxiosPromise<UserInfo> {
return request({
url: '/youlai-admin/api/v1/users/me',
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/users/me',
method: 'get'
});
}
/**
@ -23,13 +23,13 @@ export function getUserInfo(): AxiosPromise<UserInfo> {
* @param queryParams
*/
export function listUsersPage(
queryParams: UserQueryParam
queryParams: UserQueryParam
): AxiosPromise<UserPageResult> {
return request({
url: '/youlai-admin/api/v1/users/page',
method: 'get',
params: queryParams
});
return request({
url: '/youlai-admin/api/v1/users/page',
method: 'get',
params: queryParams
});
}
/**
@ -38,10 +38,10 @@ export function listUsersPage(
* @param userId
*/
export function getUserDetail(userId: number): AxiosPromise<UserFormData> {
return request({
url: '/youlai-admin/api/v1/users/' + userId,
method: 'get'
});
return request({
url: '/youlai-admin/api/v1/users/' + userId,
method: 'get'
});
}
/**
@ -50,11 +50,11 @@ export function getUserDetail(userId: number): AxiosPromise<UserFormData> {
* @param data
*/
export function addUser(data: any) {
return request({
url: '/youlai-admin/api/v1/users',
method: 'post',
data: data
});
return request({
url: '/youlai-admin/api/v1/users',
method: 'post',
data: data
});
}
/**
@ -64,11 +64,11 @@ export function addUser(data: any) {
* @param data
*/
export function updateUser(id: number, data: UserFormData) {
return request({
url: '/youlai-admin/api/v1/users/' + id,
method: 'put',
data: data
});
return request({
url: '/youlai-admin/api/v1/users/' + id,
method: 'put',
data: data
});
}
/**
@ -78,11 +78,11 @@ export function updateUser(id: number, data: UserFormData) {
* @param data
*/
export function updateUserPart(id: number, data: any) {
return request({
url: '/youlai-admin/api/v1/users/' + id,
method: 'patch',
data: data
});
return request({
url: '/youlai-admin/api/v1/users/' + id,
method: 'patch',
data: data
});
}
/**
@ -91,10 +91,10 @@ export function updateUserPart(id: number, data: any) {
* @param ids
*/
export function deleteUsers(ids: string) {
return request({
url: '/youlai-admin/api/v1/users/' + ids,
method: 'delete'
});
return request({
url: '/youlai-admin/api/v1/users/' + ids,
method: 'delete'
});
}
/**
@ -103,11 +103,11 @@ export function deleteUsers(ids: string) {
* @returns
*/
export function downloadTemplate() {
return request({
url: '/youlai-admin/api/v1/users/template',
method: 'get',
responseType: 'arraybuffer'
});
return request({
url: '/youlai-admin/api/v1/users/template',
method: 'get',
responseType: 'arraybuffer'
});
}
/**
@ -117,12 +117,12 @@ export function downloadTemplate() {
* @returns
*/
export function exportUser(queryParams: UserQueryParam) {
return request({
url: '/youlai-admin/api/v1/users/_export',
method: 'get',
params: queryParams,
responseType: 'arraybuffer'
});
return request({
url: '/youlai-admin/api/v1/users/_export',
method: 'get',
params: queryParams,
responseType: 'arraybuffer'
});
}
/**
@ -131,16 +131,16 @@ export function exportUser(queryParams: UserQueryParam) {
* @param file
*/
export function importUser(deptId: number, roleIds: string, file: File) {
const formData = new FormData();
formData.append('file', file);
formData.append('deptId', deptId.toString());
formData.append('roleIds', roleIds);
return request({
url: '/youlai-admin/api/v1/users/_import',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
const formData = new FormData();
formData.append('file', file);
formData.append('deptId', deptId.toString());
formData.append('roleIds', roleIds);
return request({
url: '/youlai-admin/api/v1/users/_import',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}

View File

@ -8,13 +8,13 @@ import { AxiosPromise } from 'axios';
* @param queryParams
*/
export function listMemebersPage(
queryParams: MemberQueryParam
queryParams: MemberQueryParam
): AxiosPromise<MemberPageResult> {
return request({
url: '/mall-ums/api/v1/members',
method: 'get',
params: queryParams
});
return request({
url: '/mall-ums/api/v1/members',
method: 'get',
params: queryParams
});
}
/**
@ -23,10 +23,10 @@ export function listMemebersPage(
* @param id
*/
export function getMemberDetail(id: number) {
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'get'
});
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'get'
});
}
/**
@ -35,11 +35,11 @@ export function getMemberDetail(id: number) {
* @param data
*/
export function addMember(data: object) {
return request({
url: '/mall-ums/api/v1/members',
method: 'post',
data: data
});
return request({
url: '/mall-ums/api/v1/members',
method: 'post',
data: data
});
}
/**
@ -49,9 +49,9 @@ export function addMember(data: object) {
* @param data
*/
export function updateMember(id: number, data: object) {
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'put',
data: data
});
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'put',
data: data
});
}

6
src/components.d.ts vendored
View File

@ -2,8 +2,8 @@
import Pagination from '@/components/Pagination/index.vue';
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Pagination: typeof Pagination;
}
export interface GlobalComponents {
Pagination: typeof Pagination;
}
}
export {};

View File

@ -1,20 +1,20 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
<span
v-if="
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
"
class="no-redirect"
>{{ generateTitle(item.meta.title) }}</span
>
<a v-else @click.prevent="handleLink(item)">
{{ generateTitle(item.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
<span
v-if="
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
"
class="no-redirect"
>{{ generateTitle(item.meta.title) }}</span
>
<a v-else @click.prevent="handleLink(item)">
{{ generateTitle(item.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script setup lang="ts">
@ -26,82 +26,82 @@ import { generateTitle } from '@/utils/i18n';
const currentRoute = useRoute();
const pathCompile = (path: string) => {
const { params } = currentRoute;
const toPath = compile(path);
return toPath(params);
const { params } = currentRoute;
const toPath = compile(path);
return toPath(params);
};
const breadcrumbs = ref([] as Array<RouteLocationMatched>);
function getBreadcrumb() {
let matched = currentRoute.matched.filter(
item => item.meta && item.meta.title
);
const first = matched[0];
if (!isDashboard(first)) {
matched = [
{ path: '/dashboard', meta: { title: 'dashboard' } } as any
].concat(matched);
}
breadcrumbs.value = matched.filter(item => {
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
});
let matched = currentRoute.matched.filter(
item => item.meta && item.meta.title
);
const first = matched[0];
if (!isDashboard(first)) {
matched = [
{ path: '/dashboard', meta: { title: 'dashboard' } } as any
].concat(matched);
}
breadcrumbs.value = matched.filter(item => {
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
});
}
function isDashboard(route: RouteLocationMatched) {
const name = route && route.name;
if (!name) {
return false;
}
return (
name.toString().trim().toLocaleLowerCase() ===
'Dashboard'.toLocaleLowerCase()
);
const name = route && route.name;
if (!name) {
return false;
}
return (
name.toString().trim().toLocaleLowerCase() ===
'Dashboard'.toLocaleLowerCase()
);
}
function handleLink(item: any) {
const { redirect, path } = item;
if (redirect) {
router.push(redirect).catch(err => {
console.warn(err);
});
return;
}
router.push(pathCompile(path)).catch(err => {
console.warn(err);
});
const { redirect, path } = item;
if (redirect) {
router.push(redirect).catch(err => {
console.warn(err);
});
return;
}
router.push(pathCompile(path)).catch(err => {
console.warn(err);
});
}
watch(
() => currentRoute.path,
path => {
if (path.startsWith('/redirect/')) {
return;
}
getBreadcrumb();
}
() => currentRoute.path,
path => {
if (path.startsWith('/redirect/')) {
return;
}
getBreadcrumb();
}
);
onBeforeMount(() => {
getBreadcrumb();
getBreadcrumb();
});
</script>
<style lang="scss" scoped>
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
font-weight: 400 !important;
}
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@ -1,59 +1,59 @@
<template>
<a
href="https://github.com/hxrui"
target="_blank"
class="github-corner"
aria-label="View source on Github"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="fill: #40c9c6; color: #fff"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px"
class="octo-arm"
/>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
/>
</svg>
</a>
<a
href="https://github.com/hxrui"
target="_blank"
class="github-corner"
aria-label="View source on Github"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="fill: #40c9c6; color: #fff"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px"
class="octo-arm"
/>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
/>
</svg>
</a>
</template>
<style scoped>
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
}
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
</style>

View File

@ -1,46 +1,46 @@
<template>
<div style="padding: 0 15px" @click="toggleClick">
<svg
:class="{ 'is-active': isActive }"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/>
</svg>
</div>
<div style="padding: 0 15px" @click="toggleClick">
<svg
:class="{ 'is-active': isActive }"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/>
</svg>
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick');
}
}
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick');
}
}
};
</script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburger.is-active {
transform: rotate(180deg);
transform: rotate(180deg);
}
</style>

View File

@ -1,29 +1,29 @@
<template>
<div class="icon-select">
<el-input
v-model="iconName"
clearable
placeholder="请输入图标名称"
@clear="filterIcons"
@input="filterIcons"
>
<template #suffix><i class="el-icon-search el-input__icon" /></template>
</el-input>
<div class="icon-select__list">
<div
v-for="(item, index) in iconList"
:key="index"
@click="selectedIcon(item)"
>
<svg-icon
color="#999"
:icon-class="item"
style="height: 30px; width: 16px; margin-right: 5px"
/>
<span>{{ item }}</span>
</div>
</div>
</div>
<div class="icon-select">
<el-input
v-model="iconName"
clearable
placeholder="请输入图标名称"
@clear="filterIcons"
@input="filterIcons"
>
<template #suffix><i class="el-icon-search el-input__icon" /></template>
</el-input>
<div class="icon-select__list">
<div
v-for="(item, index) in iconList"
:key="index"
@click="selectedIcon(item)"
>
<svg-icon
color="#999"
:icon-class="item"
style="height: 30px; width: 16px; margin-right: 5px"
/>
<span>{{ item }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
@ -33,8 +33,8 @@ import SvgIcon from '@/components/SvgIcon/index.vue';
const icons = [] as string[];
const modules = import.meta.glob('../../assets/icons/*.svg');
for (const path in modules) {
const p = path.split('assets/icons/')[1].split('.svg')[0];
icons.push(p);
const p = path.split('assets/icons/')[1].split('.svg')[0];
icons.push(p);
}
const iconList = ref(icons);
@ -43,51 +43,51 @@ const iconName = ref('');
const emit = defineEmits(['selected']);
function filterIcons() {
iconList.value = icons;
if (iconName.value) {
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1);
}
iconList.value = icons;
if (iconName.value) {
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1);
}
}
function selectedIcon(name: string) {
emit('selected', name);
document.body.click();
emit('selected', name);
document.body.click();
}
function reset() {
iconName.value = '';
iconList.value = icons;
iconName.value = '';
iconList.value = icons;
}
defineExpose({
reset
reset
});
</script>
<style lang="scss" scoped>
.icon-select {
width: 100%;
padding: 10px;
width: 100%;
padding: 10px;
&__list {
height: 200px;
overflow-y: scroll;
&__list {
height: 200px;
overflow-y: scroll;
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
}
}
</style>

View File

@ -1,19 +1,19 @@
<template>
<el-dropdown class="lang-select" trigger="click" @command="handleSetLanguage">
<div class="lang-select__icon">
<svg-icon class-name="international-icon" icon-class="language" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :disabled="language === 'zh-cn'" command="zh-cn">
中文
</el-dropdown-item>
<el-dropdown-item :disabled="language === 'en'" command="en">
English
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown class="lang-select" trigger="click" @command="handleSetLanguage">
<div class="lang-select__icon">
<svg-icon class-name="international-icon" icon-class="language" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :disabled="language === 'zh-cn'" command="zh-cn">
中文
</el-dropdown-item>
<el-dropdown-item :disabled="language === 'en'" command="en">
English
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
@ -30,18 +30,18 @@ import SvgIcon from '@/components/SvgIcon/index.vue';
const { locale } = useI18n();
function handleSetLanguage(lang: string) {
locale.value = lang;
app.setLanguage(lang);
if (lang == 'en') {
ElMessage.success('Switch Language Successful!');
} else {
ElMessage.success('切换语言成功!');
}
locale.value = lang;
app.setLanguage(lang);
if (lang == 'en') {
ElMessage.success('Switch Language Successful!');
} else {
ElMessage.success('切换语言成功!');
}
}
</script>
<style lang="scss" scoped>
.lang-select__icon {
line-height: 50px;
line-height: 50px;
}
</style>

View File

@ -1,16 +1,16 @@
<template>
<div :class="{ hidden: hidden }" class="pagination-container">
<el-pagination
:background="background"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<div :class="{ hidden: hidden }" class="pagination-container">
<el-pagination
:background="background"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup lang="ts">
@ -18,84 +18,84 @@ import { computed, PropType } from 'vue';
import { scrollTo } from '@/utils/scroll-to';
const props = defineProps({
total: {
required: true,
type: Number as PropType<number>,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array as PropType<number[]>,
default() {
return [10, 20, 30, 50];
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
total: {
required: true,
type: Number as PropType<number>,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array as PropType<number[]>,
default() {
return [10, 20, 30, 50];
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
const currentPage = computed<number | undefined>({
get: () => props.page,
set: value => {
emit('update:page', value);
}
get: () => props.page,
set: value => {
emit('update:page', value);
}
});
const pageSize = computed<number | undefined>({
get() {
return props.limit;
},
set(val) {
emit('update:limit', val);
}
get() {
return props.limit;
},
set(val) {
emit('update:limit', val);
}
});
function handleSizeChange(val: number) {
emit('pagination', { page: currentPage, limit: val });
if (props.autoScroll) {
scrollTo(0, 800);
}
emit('pagination', { page: currentPage, limit: val });
if (props.autoScroll) {
scrollTo(0, 800);
}
}
function handleCurrentChange(val: number) {
currentPage.value = val;
emit('pagination', { page: val, limit: props.limit });
if (props.autoScroll) {
scrollTo(0, 800);
}
currentPage.value = val;
emit('pagination', { page: val, limit: props.limit });
if (props.autoScroll) {
scrollTo(0, 800);
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
display: none;
}
</style>

View File

@ -1,26 +1,26 @@
<template>
<div ref="rightPanel" :class="{ show: show }" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div
class="handle-button"
:style="{ top: buttonTop + 'px', 'background-color': theme }"
@click="show = !show"
>
<Close
style="width: 1em; height: 1em; vertical-align: middle"
v-show="show"
/>
<Setting
style="width: 1em; height: 1em; vertical-align: middle"
v-show="!show"
/>
</div>
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
<div ref="rightPanel" :class="{ show: show }" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div
class="handle-button"
:style="{ top: buttonTop + 'px', 'background-color': theme }"
@click="show = !show"
>
<Close
style="width: 1em; height: 1em; vertical-align: middle"
v-show="show"
/>
<Setting
style="width: 1em; height: 1em; vertical-align: middle"
v-show="!show"
/>
</div>
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script setup lang="ts">
@ -40,124 +40,124 @@ const theme = computed(() => setting.theme);
const show = ref(false);
defineProps({
buttonTop: {
default: 250,
type: Number
}
buttonTop: {
default: 250,
type: Number
}
});
watch(show, value => {
if (value) {
addEventClick();
}
if (value) {
addClass(document.body, 'showRightPanel');
} else {
removeClass(document.body, 'showRightPanel');
}
if (value) {
addEventClick();
}
if (value) {
addClass(document.body, 'showRightPanel');
} else {
removeClass(document.body, 'showRightPanel');
}
});
function addEventClick() {
window.addEventListener('click', closeSidebar);
window.addEventListener('click', closeSidebar);
}
function closeSidebar(evt: any) {
//
let parent = evt.target.closest('.theme-picker-dropdown');
if (parent) {
return;
}
//
let parent = evt.target.closest('.theme-picker-dropdown');
if (parent) {
return;
}
parent = evt.target.closest('.rightPanel');
if (!parent) {
show.value = false;
window.removeEventListener('click', closeSidebar);
}
parent = evt.target.closest('.rightPanel');
if (!parent) {
show.value = false;
window.removeEventListener('click', closeSidebar);
}
}
const rightPanel = ref(ElColorPicker);
function insertToBody() {
const elx = rightPanel.value as any;
const body = document.querySelector('body') as any;
body.insertBefore(elx, body.firstChild);
const elx = rightPanel.value as any;
const body = document.querySelector('body') as any;
body.insertBefore(elx, body.firstChild);
}
onMounted(() => {
insertToBody();
insertToBody();
});
onBeforeUnmount(() => {
const elx = rightPanel.value as any;
elx.remove();
const elx = rightPanel.value as any;
elx.remove();
});
</script>
<style>
.showRightPanel {
overflow: hidden;
position: relative;
width: calc(100% - 15px);
overflow: hidden;
position: relative;
width: calc(100% - 15px);
}
</style>
<style lang="scss" scoped>
.rightPanel-background {
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
background: rgba(0, 0, 0, 0.2);
z-index: -1;
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
background: rgba(0, 0, 0, 0.2);
z-index: -1;
}
.rightPanel {
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05);
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05);
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
}
.show {
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
.rightPanel-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.rightPanel-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.rightPanel {
transform: translate(0);
}
.rightPanel {
transform: translate(0);
}
}
.handle-button {
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
i {
font-size: 24px;
line-height: 48px;
}
i {
font-size: 24px;
line-height: 48px;
}
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<div>
<svg-icon
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
@click="toggle"
/>
</div>
<div>
<svg-icon
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
@click="toggle"
/>
</div>
</template>
<script setup lang="ts">

View File

@ -1,21 +1,21 @@
<template>
<el-dropdown class="size-select" trigger="click" @command="handleSetSize">
<div class="size-select__icon">
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item of sizeOptions"
:key="item.value"
:disabled="(size || 'default') == item.value"
:command="item.value"
>
{{ item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown class="size-select" trigger="click" @command="handleSetSize">
<div class="size-select__icon">
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item of sizeOptions"
:key="item.value"
:disabled="(size || 'default') == item.value"
:command="item.value"
>
{{ item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
@ -29,19 +29,19 @@ const { app } = useStore();
const size = computed(() => app.size);
const sizeOptions = ref([
{ label: '默认', value: 'default' },
{ label: '大型', value: 'large' },
{ label: '小型', value: 'small' }
{ label: '默认', value: 'default' },
{ label: '大型', value: 'large' },
{ label: '小型', value: 'small' }
]);
function handleSetSize(size: string) {
app.setSize(size);
ElMessage.success('切换布局大小成功');
app.setSize(size);
ElMessage.success('切换布局大小成功');
}
</script>
<style lang="scss" scoped>
.size-select__icon {
line-height: 50px;
line-height: 50px;
}
</style>

View File

@ -1,25 +1,25 @@
<template>
<svg aria-hidden="true" class="svg-icon">
<use :xlink:href="symbolId" :fill="color" />
</svg>
<svg aria-hidden="true" class="svg-icon">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
prefix: {
type: String,
default: 'icon'
},
iconClass: {
type: String,
required: false
},
color: {
type: String,
default: ''
}
prefix: {
type: String,
default: 'icon'
},
iconClass: {
type: String,
required: false
},
color: {
type: String,
default: ''
}
});
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
@ -27,10 +27,10 @@ const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
overflow: hidden;
fill: currentColor;
width: 1em;
height: 1em;
vertical-align: -0.15em;
overflow: hidden;
fill: currentColor;
}
</style>

View File

@ -1,19 +1,19 @@
<template>
<el-color-picker
v-model="theme"
:predefine="[
'#409EFF',
'#1890ff',
'#304156',
'#212121',
'#11a983',
'#13c2c2',
'#6959CD',
'#f5222d'
]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
<el-color-picker
v-model="theme"
:predefine="[
'#409EFF',
'#1890ff',
'#304156',
'#212121',
'#11a983',
'#13c2c2',
'#6959CD',
'#f5222d'
]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script setup lang="ts">
@ -34,34 +34,34 @@ const { setting } = useStore();
const theme = computed(() => setting.theme);
watch(theme, (color: string) => {
node.style.setProperty('--el-color-primary', color);
localStorage.set('theme', color);
node.style.setProperty('--el-color-primary', color);
localStorage.set('theme', color);
for (let i = 1; i < 10; i += 1) {
node.style.setProperty(
`--el-color-primary-light-${i}`,
mix(color, mixWhite, i * 0.1)
);
}
node.style.setProperty('--el-color-primary-dark', mix(color, mixBlack, 0.1));
for (let i = 1; i < 10; i += 1) {
node.style.setProperty(
`--el-color-primary-light-${i}`,
mix(color, mixWhite, i * 0.1)
);
}
node.style.setProperty('--el-color-primary-dark', mix(color, mixBlack, 0.1));
localStorage.set('style', node.style.cssText);
localStorage.set('style', node.style.cssText);
});
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
display: none;
}
</style>

View File

@ -1,67 +1,67 @@
<template>
<div>
<!-- 上传组件 -->
<el-upload
ref="singleUploadRef"
action=""
class="single-uploader"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:http-request="uploadImage"
>
<img v-if="imgUrl" :src="imgUrl" class="single-uploader__image" />
<div>
<!-- 上传组件 -->
<el-upload
ref="singleUploadRef"
action=""
class="single-uploader"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:http-request="uploadImage"
>
<img v-if="imgUrl" :src="imgUrl" class="single-uploader__image" />
<el-icon v-else class="single-uploader__plus">
<Plus />
</el-icon>
<el-icon v-else class="single-uploader__plus">
<Plus />
</el-icon>
<!-- 删除图标 -->
<el-icon
v-if="props.showClose && imgUrl"
class="single-uploader__remove"
@click.stop="handleRemove(imgUrl)"
>
<Close />
</el-icon>
</el-upload>
</div>
<!-- 删除图标 -->
<el-icon
v-if="props.showClose && imgUrl"
class="single-uploader__remove"
@click.stop="handleRemove(imgUrl)"
>
<Close />
</el-icon>
</el-upload>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { Plus, Close } from '@element-plus/icons-vue';
import {
ElMessage,
ElUpload,
UploadRawFile,
UploadRequestOptions
ElMessage,
ElUpload,
UploadRawFile,
UploadRequestOptions
} from 'element-plus';
import { uploadFile, deleteFile } from '@/api/system/file';
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
modelValue: {
type: String,
default: ''
},
/**
* 是否显示右上角的删除图片按钮
*/
showClose: {
type: Boolean,
default: false
}
modelValue: {
type: String,
default: ''
},
/**
* 是否显示右上角的删除图片按钮
*/
showClose: {
type: Boolean,
default: false
}
});
const imgUrl = computed<string | undefined>({
get() {
return props.modelValue;
},
set(val) {
// imgUrlv-model
emit('update:modelValue', val);
}
get() {
return props.modelValue;
},
set(val) {
// imgUrlv-model
emit('update:modelValue', val);
}
});
/**
@ -70,8 +70,8 @@ const imgUrl = computed<string | undefined>({
* @param params
*/
async function uploadImage(options: UploadRequestOptions): Promise<any> {
const response = await uploadFile(options.file);
imgUrl.value = response.data;
const response = await uploadFile(options.file);
imgUrl.value = response.data;
}
/**
@ -80,60 +80,60 @@ async function uploadImage(options: UploadRequestOptions): Promise<any> {
* @param fileUrl
*/
function handleRemove(fileUrl?: string) {
if (fileUrl) {
deleteFile(fileUrl);
imgUrl.value = undefined; // imgUrlcomputedset
}
if (fileUrl) {
deleteFile(fileUrl);
imgUrl.value = undefined; // imgUrlcomputedset
}
}
/**
* before-upload 钩子中限制用户上传文件的格式和大小
*/
function handleBeforeUpload(file: UploadRawFile) {
// const isJPG = file.type === "image/jpeg";
const isLt2M = file.size / 1024 / 1024 < 2;
// const isJPG = file.type === "image/jpeg";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
ElMessage.warning('上传图片不能大于2M');
}
return true;
if (!isLt2M) {
ElMessage.warning('上传图片不能大于2M');
}
return true;
}
</script>
<style lang="scss" scoped>
.single-uploader {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
text-align: center;
&:hover {
border-color: var(--el-color-primary);
}
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
text-align: center;
&:hover {
border-color: var(--el-color-primary);
}
&__image {
width: 146px;
height: 146px;
display: block;
}
&__image {
width: 146px;
height: 146px;
display: block;
}
&__plus {
width: 146px;
height: 157px;
font-size: 28px;
color: #8c939d;
text-align: center;
}
&__plus {
width: 146px;
height: 157px;
font-size: 28px;
color: #8c939d;
text-align: center;
}
&__remove {
font-size: 12px;
color: #ff4d51 !important;
margin-top: 0px !important;
position: absolute;
right: 0;
top: 0;
color: #409eff;
}
&__remove {
font-size: 12px;
color: #ff4d51 !important;
margin-top: 0px !important;
position: absolute;
right: 0;
top: 0;
color: #409eff;
}
}
</style>

View File

@ -1,22 +1,22 @@
<template>
<div style="border: 1px solid #ccc">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
style="border-bottom: 1px solid #ccc"
:mode="mode"
/>
<!-- 编辑器 -->
<Editor
:defaultConfig="editorConfig"
v-model="defaultHtml"
@onChange="handleChange"
style="height: 500px; overflow-y: hidden"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
<div style="border: 1px solid #ccc">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
style="border-bottom: 1px solid #ccc"
:mode="mode"
/>
<!-- 编辑器 -->
<Editor
:defaultConfig="editorConfig"
v-model="defaultHtml"
@onChange="handleChange"
style="height: 500px; overflow-y: hidden"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script setup lang="ts">
@ -27,10 +27,10 @@ import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import { uploadFile } from '@/api/system/file';
const props = defineProps({
modelValue: {
type: [String],
default: ''
}
modelValue: {
type: [String],
default: ''
}
});
const emit = defineEmits(['update:modelValue']);
@ -39,40 +39,40 @@ const emit = defineEmits(['update:modelValue']);
const editorRef = shallowRef();
const state = reactive({
toolbarConfig: {},
editorConfig: {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
//
async customUpload(file: any, insertFn: any) {
uploadFile(file).then(response => {
const url = response.data;
insertFn(url);
});
}
}
}
},
defaultHtml: props.modelValue,
mode: 'default'
toolbarConfig: {},
editorConfig: {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
//
async customUpload(file: any, insertFn: any) {
uploadFile(file).then(response => {
const url = response.data;
insertFn(url);
});
}
}
}
},
defaultHtml: props.modelValue,
mode: 'default'
});
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state);
const handleCreated = (editor: any) => {
editorRef.value = editor; // editor
editorRef.value = editor; // editor
};
function handleChange(editor: any) {
emit('update:modelValue', editor.getHtml());
emit('update:modelValue', editor.getHtml());
}
//
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
</script>

View File

@ -5,52 +5,52 @@ import { Directive, DirectiveBinding } from 'vue';
*
*/
export const hasPerm: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
// 「超级管理员」拥有所有的按钮权限
const { user } = useStore();
const roles = user.roles;
if (roles.includes('ROOT')) {
return true;
}
// 「其他角色」按钮权限校验
const { value } = binding;
if (value) {
const requiredPerms = value; // DOM绑定需要的按钮权限标识
mounted(el: HTMLElement, binding: DirectiveBinding) {
// 「超级管理员」拥有所有的按钮权限
const { user } = useStore();
const roles = user.roles;
if (roles.includes('ROOT')) {
return true;
}
// 「其他角色」按钮权限校验
const { value } = binding;
if (value) {
const requiredPerms = value; // DOM绑定需要的按钮权限标识
const hasPerm = user.perms?.some(perm => {
return requiredPerms.includes(perm);
});
const hasPerm = user.perms?.some(perm => {
return requiredPerms.includes(perm);
});
if (!hasPerm) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
);
}
}
if (!hasPerm) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
);
}
}
};
/**
*
*/
export const hasRole: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
if (value) {
const requiredRoles = value; // DOM绑定需要的角色编码
const { user } = useStore();
const hasRole = user.roles.some(perm => {
return requiredRoles.includes(perm);
});
if (value) {
const requiredRoles = value; // DOM绑定需要的角色编码
const { user } = useStore();
const hasRole = user.roles.some(perm => {
return requiredRoles.includes(perm);
});
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error("need roles! Like v-has-role=\"['admin','test']\"");
}
}
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error("need roles! Like v-has-role=\"['admin','test']\"");
}
}
};

16
src/env.d.ts vendored
View File

@ -1,19 +1,19 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
import { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 环境变量 TypeScript的智能提示
interface ImportMetaEnv {
VITE_APP_TITLE: string;
VITE_APP_PORT: string;
VITE_APP_BASE_API: string;
VITE_APP_TITLE: string;
VITE_APP_PORT: string;
VITE_APP_BASE_API: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
readonly env: ImportMetaEnv;
}

View File

@ -1,24 +1,24 @@
export default {
// 路由国际化
route: {
dashboard: 'Dashboard',
document: 'Document'
},
// 登录页面国际化
login: {
title: 'vue3-element-admin',
username: 'Username',
password: 'Password',
login: 'Login',
code: 'Verification Code',
copyright: '',
icp: ''
},
// 导航栏国际化
navbar: {
dashboard: 'Dashboard',
logout: 'Logout',
document: 'Document',
gitee: 'Gitee'
}
// 路由国际化
route: {
dashboard: 'Dashboard',
document: 'Document'
},
// 登录页面国际化
login: {
title: 'vue3-element-admin',
username: 'Username',
password: 'Password',
login: 'Login',
code: 'Verification Code',
copyright: '',
icp: ''
},
// 导航栏国际化
navbar: {
dashboard: 'Dashboard',
logout: 'Logout',
document: 'Document',
gitee: 'Gitee'
}
};

View File

@ -7,12 +7,12 @@ import enLocale from './en';
import zhCnLocale from './zh-cn';
const messages = {
'zh-cn': {
...zhCnLocale
},
en: {
...enLocale
}
'zh-cn': {
...zhCnLocale
},
en: {
...enLocale
}
};
/**
@ -21,25 +21,25 @@ const messages = {
* @returns zh-cn|en ...
*/
export const getLanguage = () => {
// 本地缓存获取
let language = localStorage.get('language');
if (language) {
return language;
}
// 浏览器使用语言
language = navigator.language.toLowerCase();
const locales = Object.keys(messages);
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale;
}
}
return 'zh-cn';
// 本地缓存获取
let language = localStorage.get('language');
if (language) {
return language;
}
// 浏览器使用语言
language = navigator.language.toLowerCase();
const locales = Object.keys(messages);
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale;
}
}
return 'zh-cn';
};
const i18n = createI18n({
locale: getLanguage(),
messages: messages
locale: getLanguage(),
messages: messages
});
export default i18n;

View File

@ -1,23 +1,23 @@
export default {
// 路由国际化
route: {
dashboard: '首页',
document: '项目文档'
},
// 登录页面国际化
login: {
title: 'vue3-element-admin',
username: '用户名',
password: '密码',
login: '登 录',
code: '请输入验证码',
copyright: '',
icp: ''
},
navbar: {
dashboard: '首页',
logout: '注销',
document: '项目文档',
gitee: '码云'
}
// 路由国际化
route: {
dashboard: '首页',
document: '项目文档'
},
// 登录页面国际化
login: {
title: 'vue3-element-admin',
username: '用户名',
password: '密码',
login: '登 录',
code: '请输入验证码',
copyright: '',
icp: ''
},
navbar: {
dashboard: '首页',
logout: '注销',
document: '项目文档',
gitee: '码云'
}
};

View File

@ -1,13 +1,13 @@
<template>
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="router-fade" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
</router-view>
</section>
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="router-fade" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
</router-view>
</section>
</template>
<script setup lang="ts">
@ -21,34 +21,34 @@ const cachedViews = computed(() => tagsView.cachedViews);
<style lang="scss" scoped>
.app-main {
/* 50= navbar 50 */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
/* 50= navbar 50 */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header + .app-main {
padding-top: 50px;
padding-top: 50px;
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 84px);
}
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 84px);
}
.fixed-header + .app-main {
padding-top: 84px;
}
.fixed-header + .app-main {
padding-top: 84px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
.fixed-header {
padding-right: 15px;
}
}
</style>

View File

@ -1,56 +1,56 @@
<template>
<div class="navbar">
<hamburger
id="hamburger-container"
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<div class="navbar">
<hamburger
id="hamburger-container"
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<template v-if="device !== 'mobile'">
<!-- <search id="header-search" class="right-menu-item" />
<div class="right-menu">
<template v-if="device !== 'mobile'">
<!-- <search id="header-search" class="right-menu-item" />
<error-log class="errLog-container right-menu-item hover-effect" />-->
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
<lang-select class="right-menu-item hover-effect" />
</template>
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
<lang-select class="right-menu-item hover-effect" />
</template>
<el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper">
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
</div>
<el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper">
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/">
<el-dropdown-item>{{ $t('navbar.dashboard') }}</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/hxrui">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://gitee.com/haoxr">
<el-dropdown-item>{{ $t('navbar.gitee') }}</el-dropdown-item>
</a>
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
<el-dropdown-item>{{ $t('navbar.document') }}</el-dropdown-item>
</a>
<el-dropdown-item divided @click="logout">
{{ $t('navbar.logout') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/">
<el-dropdown-item>{{ $t('navbar.dashboard') }}</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/hxrui">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://gitee.com/haoxr">
<el-dropdown-item>{{ $t('navbar.gitee') }}</el-dropdown-item>
</a>
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
<el-dropdown-item>{{ $t('navbar.document') }}</el-dropdown-item>
</a>
<el-dropdown-item divided @click="logout">
{{ $t('navbar.logout') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
@ -79,102 +79,102 @@ const device = computed(() => app.device);
const avatar = computed(() => user.avatar);
function toggleSideBar() {
app.toggleSidebar();
app.toggleSidebar();
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
user.logout().then(() => {
router.push(`/login?redirect=${route.fullPath}`);
});
});
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
user.logout().then(() => {
router.push(`/login?redirect=${route.fullPath}`);
});
});
}
</script>
<style lang="scss" scoped>
ul {
list-style: none;
margin: 0;
padding: 0;
list-style: none;
margin: 0;
padding: 0;
}
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.breadcrumb-container {
float: left;
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>

View File

@ -1,28 +1,28 @@
<template>
<div class="drawer-container">
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>主题颜色</span>
<div style="float: right; height: 26px; margin: -3px 8px 0 0">
<theme-picker @change="themeChange" />
</div>
</div>
<div class="drawer-container">
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>主题颜色</span>
<div style="float: right; height: 26px; margin: -3px 8px 0 0">
<theme-picker @change="themeChange" />
</div>
</div>
<div class="drawer-item">
<span>开启 Tags-View</span>
<el-switch v-model="tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>开启 Tags-View</span>
<el-switch v-model="tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>固定 Header</span>
<el-switch v-model="fixedHeader" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>固定 Header</span>
<el-switch v-model="fixedHeader" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>侧边栏 Logo</span>
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</div>
</div>
<div class="drawer-item">
<span>侧边栏 Logo</span>
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</div>
</div>
</template>
<script setup lang="ts">
@ -35,69 +35,69 @@ import useStore from '@/store';
const { setting } = useStore();
const state = reactive({
fixedHeader: setting.fixedHeader,
tagsView: setting.tagsView,
sidebarLogo: setting.sidebarLogo
fixedHeader: setting.fixedHeader,
tagsView: setting.tagsView,
sidebarLogo: setting.sidebarLogo
});
const { fixedHeader, tagsView, sidebarLogo } = toRefs(state);
function themeChange(val: any) {
setting.changeSetting({ key: 'theme', value: val });
setting.changeSetting({ key: 'theme', value: val });
}
watch(
() => state.fixedHeader,
value => {
setting.changeSetting({ key: 'fixedHeader', value: value });
}
() => state.fixedHeader,
value => {
setting.changeSetting({ key: 'fixedHeader', value: value });
}
);
watch(
() => state.tagsView,
value => {
setting.changeSetting({ key: 'tagsView', value: value });
}
() => state.tagsView,
value => {
setting.changeSetting({ key: 'tagsView', value: value });
}
);
watch(
() => state.sidebarLogo,
value => {
setting.changeSetting({ key: 'sidebarLogo', value: value });
}
() => state.sidebarLogo,
value => {
setting.changeSetting({ key: 'sidebarLogo', value: value });
}
);
</script>
<style lang="scss" scoped>
.drawer-container {
padding: 24px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
padding: 24px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
.drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
line-height: 22px;
}
.drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
line-height: 22px;
}
.drawer-item {
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
padding: 12px 0;
}
.drawer-item {
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
padding: 12px 0;
}
.drawer-switch {
float: right;
}
.drawer-switch {
float: right;
}
.job-link {
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
}
.job-link {
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
}
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
<slot />
</a>
<div v-else @click="push">
<slot />
</div>
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
<slot />
</a>
<div v-else @click="push">
<slot />
</div>
</template>
<script lang="ts">
@ -20,26 +20,26 @@ const sidebar = computed(() => app.sidebar);
const device = computed(() => app.device);
export default defineComponent({
props: {
to: {
type: String,
required: true
}
},
setup(props) {
const router = useRouter();
const push = () => {
if (device.value === 'mobile' && sidebar.value.opened == true) {
app.closeSideBar(false);
}
router.push(props.to).catch(err => {
console.log(err);
});
};
return {
push,
isExternal
};
}
props: {
to: {
type: String,
required: true
}
},
setup(props) {
const router = useRouter();
const push = () => {
if (device.value === 'mobile' && sidebar.value.opened == true) {
app.closeSideBar(false);
}
router.push(props.to).catch(err => {
console.log(err);
});
};
return {
push,
isExternal
};
}
});
</script>

View File

@ -1,35 +1,35 @@
<template>
<div class="sidebar-logo-container" :class="{ collapse: isCollapse }">
<transition name="sidebarLogoFade">
<router-link
v-if="collapse"
key="collapse"
class="sidebar-logo-link"
to="/"
>
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title">{{ title }}</h1>
</router-link>
</transition>
</div>
<div class="sidebar-logo-container" :class="{ collapse: isCollapse }">
<transition name="sidebarLogoFade">
<router-link
v-if="collapse"
key="collapse"
class="sidebar-logo-link"
to="/"
>
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title">{{ title }}</h1>
</router-link>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, toRefs } from 'vue';
const props = defineProps({
collapse: {
type: Boolean,
required: true
}
collapse: {
type: Boolean,
required: true
}
});
const state = reactive({
isCollapse: props.collapse
isCollapse: props.collapse
});
const { isCollapse } = toRefs(state);
@ -40,50 +40,50 @@ const logo = ref('https://www.youlai.tech/files/blog/logo.png');
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
}
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
margin-left: 12px;
}
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
margin-left: 12px;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>

View File

@ -1,49 +1,49 @@
<template>
<div v-if="!item.meta || !item.meta.hidden">
<template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
(!item.meta || !item.meta.alwaysShow)
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<svg-icon
v-if="onlyOneChild.meta && onlyOneChild.meta.icon"
:icon-class="onlyOneChild.meta.icon"
/>
<template #title>
{{ generateTitle(onlyOneChild.meta.title) }}
</template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
<!-- popper-append-to-body -->
<template #title>
<svg-icon
v-if="item.meta && item.meta.icon"
:icon-class="item.meta.icon"
></svg-icon>
<span v-if="item.meta && item.meta.title">{{
generateTitle(item.meta.title)
}}</span>
</template>
<div v-if="!item.meta || !item.meta.hidden">
<template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
(!item.meta || !item.meta.alwaysShow)
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<svg-icon
v-if="onlyOneChild.meta && onlyOneChild.meta.icon"
:icon-class="onlyOneChild.meta.icon"
/>
<template #title>
{{ generateTitle(onlyOneChild.meta.title) }}
</template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
<!-- popper-append-to-body -->
<template #title>
<svg-icon
v-if="item.meta && item.meta.icon"
:icon-class="item.meta.icon"
></svg-icon>
<span v-if="item.meta && item.meta.title">{{
generateTitle(item.meta.title)
}}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:item="child"
:is-nest="true"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:item="child"
:is-nest="true"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>
<script setup lang="ts">
@ -56,58 +56,58 @@ import { generateTitle } from '@/utils/i18n';
import SvgIcon from '@/components/SvgIcon/index.vue';
const props = defineProps({
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
required: false
},
basePath: {
type: String,
required: true
}
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
required: false
},
basePath: {
type: String,
required: true
}
});
const onlyOneChild = ref();
function hasOneShowingChild(children = [] as any, parent: any) {
if (!children) {
children = [];
}
const showingChildren = children.filter((item: any) => {
if (item.meta && item.meta.hidden) {
return false;
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item;
return true;
}
});
if (!children) {
children = [];
}
const showingChildren = children.filter((item: any) => {
if (item.meta && item.meta.hidden) {
return false;
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item;
return true;
}
});
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true;
}
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true;
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
return true;
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
return true;
}
return false;
return false;
}
function resolvePath(routePath: string) {
if (isExternal(routePath)) {
return routePath;
}
if (isExternal(props.basePath)) {
return props.basePath;
}
return path.resolve(props.basePath, routePath);
if (isExternal(routePath)) {
return routePath;
}
if (isExternal(props.basePath)) {
return props.basePath;
}
return path.resolve(props.basePath, routePath);
}
</script>

View File

@ -1,27 +1,27 @@
<template>
<div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:active-text-color="variables.menuActiveText"
:unique-opened="false"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:item="route"
:key="route.path"
:base-path="route.path"
:is-collapse="isCollapse"
/>
</el-menu>
</el-scrollbar>
</div>
<div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:active-text-color="variables.menuActiveText"
:unique-opened="false"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:item="route"
:key="route.path"
:base-path="route.path"
:is-collapse="isCollapse"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
@ -41,11 +41,11 @@ const showLogo = computed(() => setting.sidebarLogo);
const isCollapse = computed(() => !app.sidebar.opened);
const activeMenu = computed(() => {
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu as string;
}
return path;
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu as string;
}
return path;
});
</script>

View File

@ -1,21 +1,21 @@
<template>
<el-scrollbar
ref="scrollContainerRef"
:vertical="false"
class="scroll-container"
@wheel.prevent="handleScroll"
>
<slot />
</el-scrollbar>
<el-scrollbar
ref="scrollContainerRef"
:vertical="false"
class="scroll-container"
@wheel.prevent="handleScroll"
>
<slot />
</el-scrollbar>
</template>
<script setup lang="ts">
import {
ref,
computed,
onMounted,
onBeforeUnmount,
getCurrentInstance
ref,
computed,
onMounted,
onBeforeUnmount,
getCurrentInstance
} from 'vue';
import { TagView } from '@/types';
import useStore from '@/store';
@ -29,102 +29,102 @@ const visitedViews = computed(() => tagsView.visitedViews);
const { ctx } = getCurrentInstance() as any;
const scrollWrapper = computed(() => {
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement;
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement;
});
onMounted(() => {
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
});
onBeforeUnmount(() => {
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
});
function handleScroll(e: WheelEvent) {
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
scrollWrapper.value.scrollLeft =
scrollWrapper.value.scrollLeft + eventDelta / 4;
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
scrollWrapper.value.scrollLeft =
scrollWrapper.value.scrollLeft + eventDelta / 4;
}
function moveToTarget(currentTag: TagView) {
const $container = ctx.$refs.scrollContainer.$el;
const $containerWidth = $container.offsetWidth;
const $scrollWrapper = scrollWrapper.value;
const $container = ctx.$refs.scrollContainer.$el;
const $containerWidth = $container.offsetWidth;
const $scrollWrapper = scrollWrapper.value;
let firstTag = null;
let lastTag = null;
let firstTag = null;
let lastTag = null;
// find first tag and last tag
if (visitedViews.value.length > 0) {
firstTag = visitedViews.value[0];
lastTag = visitedViews.value[visitedViews.value.length - 1];
}
// find first tag and last tag
if (visitedViews.value.length > 0) {
firstTag = visitedViews.value[0];
lastTag = visitedViews.value[visitedViews.value.length - 1];
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0;
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
} else {
const tagListDom = document.getElementsByClassName('tags-view__item');
const currentIndex = visitedViews.value.findIndex(
item => item === currentTag
);
let prevTag = null;
let nextTag = null;
for (const k in tagListDom) {
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
if (
(tagListDom[k] as any).dataset.path ===
visitedViews.value[currentIndex - 1].path
) {
prevTag = tagListDom[k];
}
if (
(tagListDom[k] as any).dataset.path ===
visitedViews.value[currentIndex + 1].path
) {
nextTag = tagListDom[k];
}
}
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0;
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
} else {
const tagListDom = document.getElementsByClassName('tags-view__item');
const currentIndex = visitedViews.value.findIndex(
item => item === currentTag
);
let prevTag = null;
let nextTag = null;
for (const k in tagListDom) {
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
if (
(tagListDom[k] as any).dataset.path ===
visitedViews.value[currentIndex - 1].path
) {
prevTag = tagListDom[k];
}
if (
(tagListDom[k] as any).dataset.path ===
visitedViews.value[currentIndex + 1].path
) {
nextTag = tagListDom[k];
}
}
}
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft =
(nextTag as any).offsetLeft +
(nextTag as any).offsetWidth +
tagAndTagSpacing.value;
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft =
(nextTag as any).offsetLeft +
(nextTag as any).offsetWidth +
tagAndTagSpacing.value;
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft =
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
}
}
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft =
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
}
}
}
defineExpose({
moveToTarget
moveToTarget
});
</script>
<style lang="scss" scoped>
.scroll-container {
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
}
.el-scrollbar__wrap {
height: 49px;
}
}
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
}
</style>

View File

@ -1,71 +1,71 @@
<template>
<div class="tags-view__container">
<scroll-pane
ref="scrollPaneRef"
class="tags-view__wrapper"
@scroll="handleScroll"
>
<router-link
v-for="tag in visitedViews"
:key="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view__item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ generateTitle(tag.meta.title) }}
<span
v-if="!isAffix(tag)"
class="icon-close"
@click.prevent.stop="closeSelectedTag(tag)"
>
<svg-icon icon-class="close" />
</span>
</router-link>
</scroll-pane>
<ul
v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }"
class="tags-view__menu"
>
<li @click="refreshSelectedTag(selectedTag)">
<svg-icon icon-class="refresh" />
刷新
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<svg-icon icon-class="close" />
关闭
</li>
<li @click="closeOtherTags">
<svg-icon icon-class="close_other" />
关闭其它
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<svg-icon icon-class="close_left" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<svg-icon icon-class="close_right" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<svg-icon icon-class="close_all" />
关闭所有
</li>
</ul>
</div>
<div class="tags-view__container">
<scroll-pane
ref="scrollPaneRef"
class="tags-view__wrapper"
@scroll="handleScroll"
>
<router-link
v-for="tag in visitedViews"
:key="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view__item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ generateTitle(tag.meta.title) }}
<span
v-if="!isAffix(tag)"
class="icon-close"
@click.prevent.stop="closeSelectedTag(tag)"
>
<svg-icon icon-class="close" />
</span>
</router-link>
</scroll-pane>
<ul
v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }"
class="tags-view__menu"
>
<li @click="refreshSelectedTag(selectedTag)">
<svg-icon icon-class="refresh" />
刷新
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<svg-icon icon-class="close" />
关闭
</li>
<li @click="closeOtherTags">
<svg-icon icon-class="close_other" />
关闭其它
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<svg-icon icon-class="close_left" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<svg-icon icon-class="close_right" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<svg-icon icon-class="close_all" />
关闭所有
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import {
computed,
getCurrentInstance,
nextTick,
ref,
watch,
onMounted,
ComponentInternalInstance
computed,
getCurrentInstance,
nextTick,
ref,
watch,
onMounted,
ComponentInternalInstance
} from 'vue';
import path from 'path-browserify';
@ -95,282 +95,282 @@ const left = ref(0);
const top = ref(0);
watch(route, () => {
addTags();
moveToCurrentTag();
addTags();
moveToCurrentTag();
});
watch(visible, value => {
if (value) {
document.body.addEventListener('click', closeMenu);
} else {
document.body.removeEventListener('click', closeMenu);
}
if (value) {
document.body.addEventListener('click', closeMenu);
} else {
document.body.removeEventListener('click', closeMenu);
}
});
function filterAffixTags(routes: RouteRecordRaw[], basePath = '/') {
let tags: TagView[] = [];
let tags: TagView[] = [];
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
});
}
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
});
}
if (route.children) {
const childTags = filterAffixTags(route.children, route.path);
if (childTags.length >= 1) {
tags = tags.concat(childTags);
}
}
});
return tags;
if (route.children) {
const childTags = filterAffixTags(route.children, route.path);
if (childTags.length >= 1) {
tags = tags.concat(childTags);
}
}
});
return tags;
}
function initTags() {
const res = filterAffixTags(routes.value) as [];
affixTags.value = res;
for (const tag of res) {
// Must have tag name
if ((tag as TagView).name) {
tagsView.addVisitedView(tag);
}
}
const res = filterAffixTags(routes.value) as [];
affixTags.value = res;
for (const tag of res) {
// Must have tag name
if ((tag as TagView).name) {
tagsView.addVisitedView(tag);
}
}
}
function addTags() {
if (route.name) {
tagsView.addView(route);
}
if (route.name) {
tagsView.addView(route);
}
}
function moveToCurrentTag() {
const tags = getCurrentInstance()?.refs.tag as any[];
nextTick(() => {
if (tags === null || tags === undefined || !Array.isArray(tags)) {
return;
}
for (const tag of tags) {
if ((tag.to as TagView).path === route.path) {
(scrollPaneRef.value as any).value.moveToTarget(tag);
// when query is different then update
if ((tag.to as TagView).fullPath !== route.fullPath) {
tagsView.updateVisitedView(route);
}
}
}
});
const tags = getCurrentInstance()?.refs.tag as any[];
nextTick(() => {
if (tags === null || tags === undefined || !Array.isArray(tags)) {
return;
}
for (const tag of tags) {
if ((tag.to as TagView).path === route.path) {
(scrollPaneRef.value as any).value.moveToTarget(tag);
// when query is different then update
if ((tag.to as TagView).fullPath !== route.fullPath) {
tagsView.updateVisitedView(route);
}
}
}
});
}
function isActive(tag: TagView) {
return tag.path === route.path;
return tag.path === route.path;
}
function isAffix(tag: TagView) {
return tag.meta && tag.meta.affix;
return tag.meta && tag.meta.affix;
}
function isFirstView() {
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[1].fullPath ||
(selectedTag.value as TagView).fullPath === '/index'
);
} catch (err) {
return false;
}
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[1].fullPath ||
(selectedTag.value as TagView).fullPath === '/index'
);
} catch (err) {
return false;
}
}
function isLastView() {
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[visitedViews.value.length - 1].fullPath
);
} catch (err) {
return false;
}
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[visitedViews.value.length - 1].fullPath
);
} catch (err) {
return false;
}
}
function refreshSelectedTag(view: TagView) {
tagsView.delCachedView(view);
const { fullPath } = view;
nextTick(() => {
router.replace({ path: '/redirect' + fullPath }).catch(err => {
console.warn(err);
});
});
tagsView.delCachedView(view);
const { fullPath } = view;
nextTick(() => {
router.replace({ path: '/redirect' + fullPath }).catch(err => {
console.warn(err);
});
});
}
function toLastView(visitedViews: TagView[], view?: any) {
const latestView = visitedViews.slice(-1)[0];
if (latestView && latestView.fullPath) {
router.push(latestView.fullPath);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
router.replace({ path: '/redirect' + view.fullPath });
} else {
router.push('/');
}
}
const latestView = visitedViews.slice(-1)[0];
if (latestView && latestView.fullPath) {
router.push(latestView.fullPath);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
router.replace({ path: '/redirect' + view.fullPath });
} else {
router.push('/');
}
}
}
function closeSelectedTag(view: TagView) {
tagsView.delView(view).then((res: any) => {
if (isActive(view)) {
toLastView(res.visitedViews, view);
}
});
tagsView.delView(view).then((res: any) => {
if (isActive(view)) {
toLastView(res.visitedViews, view);
}
});
}
function closeLeftTags() {
tagsView.delLeftViews(selectedTag.value).then((res: any) => {
if (
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
) {
toLastView(res.visitedViews);
}
});
tagsView.delLeftViews(selectedTag.value).then((res: any) => {
if (
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
) {
toLastView(res.visitedViews);
}
});
}
function closeRightTags() {
tagsView.delRightViews(selectedTag.value).then((res: any) => {
if (
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
) {
toLastView(res.visitedViews);
}
});
tagsView.delRightViews(selectedTag.value).then((res: any) => {
if (
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
) {
toLastView(res.visitedViews);
}
});
}
function closeOtherTags() {
tagsView.delOtherViews(selectedTag.value).then(() => {
moveToCurrentTag();
});
tagsView.delOtherViews(selectedTag.value).then(() => {
moveToCurrentTag();
});
}
function closeAllTags(view: TagView) {
tagsView.delRightViews(selectedTag.value).then((res: any) => {
if (affixTags.value.some((tag: any) => tag.path === route.path)) {
return;
}
toLastView(res.visitedViews, view);
});
tagsView.delRightViews(selectedTag.value).then((res: any) => {
if (affixTags.value.some((tag: any) => tag.path === route.path)) {
return;
}
toLastView(res.visitedViews, view);
});
}
function openMenu(tag: TagView, e: MouseEvent) {
const menuMinWidth = 105;
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = proxy?.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const l = e.clientX - offsetLeft + 15; // 15: margin right
const menuMinWidth = 105;
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = proxy?.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const l = e.clientX - offsetLeft + 15; // 15: margin right
if (l > maxLeft) {
left.value = maxLeft;
} else {
left.value = l;
}
if (l > maxLeft) {
left.value = maxLeft;
} else {
left.value = l;
}
top.value = e.clientY;
visible.value = true;
selectedTag.value = tag;
top.value = e.clientY;
visible.value = true;
selectedTag.value = tag;
}
function closeMenu() {
visible.value = false;
visible.value = false;
}
function handleScroll() {
closeMenu();
closeMenu();
}
onMounted(() => {
initTags();
addTags();
initTags();
addTags();
});
</script>
<style lang="scss" scoped>
.tags-view__container {
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
.tags-view__wrapper {
.tags-view__item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
.tags-view__wrapper {
.tags-view__item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&:hover {
color: var(--el-color-primary);
}
&:hover {
color: var(--el-color-primary);
}
&.active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
&.active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.icon-close {
border-radius: 50%;
text-align: center;
.icon-close {
border-radius: 50%;
text-align: center;
&:hover {
background-color: #ccc;
color: #fff;
}
}
}
}
&:hover {
background-color: #ccc;
color: #fff;
}
}
}
}
.tags-view__menu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
.tags-view__menu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
&:hover {
background: #eee;
}
}
}
}
</style>

View File

@ -1,22 +1,22 @@
<template>
<div :class="classObj" class="app-wrapper">
<div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
/>
<Sidebar class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<RightPanel v-if="showSettings">
<settings />
</RightPanel>
</div>
</div>
<div :class="classObj" class="app-wrapper">
<div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
/>
<Sidebar class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<RightPanel v-if="showSettings">
<settings />
</RightPanel>
</div>
</div>
</template>
<script setup lang="ts">
@ -40,23 +40,23 @@ const fixedHeader = computed(() => setting.fixedHeader);
const showSettings = computed(() => setting.showSettings);
const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened,
openSidebar: sidebar.value.opened,
withoutAnimation: sidebar.value.withoutAnimation,
mobile: device.value === 'mobile'
hideSidebar: !sidebar.value.opened,
openSidebar: sidebar.value.opened,
withoutAnimation: sidebar.value.withoutAnimation,
mobile: device.value === 'mobile'
}));
watchEffect(() => {
if (width.value < WIDTH) {
app.toggleDevice('mobile');
app.closeSideBar(true);
} else {
app.toggleDevice('desktop');
}
if (width.value < WIDTH) {
app.toggleDevice('mobile');
app.closeSideBar(true);
} else {
app.toggleDevice('desktop');
}
});
function handleClickOutside() {
app.closeSideBar(false);
app.closeSideBar(false);
}
</script>
@ -65,41 +65,41 @@ function handleClickOutside() {
@import '@/styles/variables.module.scss';
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
width: calc(100% - 54px);
}
.mobile .fixed-header {
width: 100%;
width: 100%;
}
</style>

View File

@ -27,7 +27,7 @@ const app = createApp(App);
import * as directive from '@/directive';
Object.keys(directive).forEach(key => {
app.directive(key, (directive as { [key: string]: Directive })[key]);
app.directive(key, (directive as { [key: string]: Directive })[key]);
});
// 全局方法
@ -35,9 +35,9 @@ app.config.globalProperties.$listDictsByCode = listDictsByCode;
// 注册全局组件
app
.component('Pagination', Pagination)
.use(createPinia())
.use(router)
.use(ElementPlus)
.use(i18n)
.mount('#app');
.component('Pagination', Pagination)
.use(createPinia())
.use(router)
.use(ElementPlus)
.use(i18n)
.mount('#app');

View File

@ -9,47 +9,47 @@ NProgress.configure({ showSpinner: false }); // 进度环显示/隐藏
const whiteList = ['/login', '/auth-redirect'];
router.beforeEach(async (to, form, next) => {
NProgress.start();
const { user, permission } = useStore();
const hasToken = user.token;
if (hasToken) {
// 登录成功,跳转到首页
if (to.path === '/login') {
next({ path: '/' });
NProgress.done();
} else {
const hasGetUserInfo = user.roles.length > 0;
if (hasGetUserInfo) {
next();
} else {
try {
await user.getUserInfo();
const roles = user.roles;
const accessRoutes: any = await permission.generateRoutes(roles);
accessRoutes.forEach((route: any) => {
router.addRoute(route);
});
next({ ...to, replace: true });
} catch (error) {
// 移除 token 并跳转登录页
await user.resetToken();
ElMessage.error((error as any) || 'Has Error');
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
// 未登录可以访问白名单页面(登录页面)
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
NProgress.start();
const { user, permission } = useStore();
const hasToken = user.token;
if (hasToken) {
// 登录成功,跳转到首页
if (to.path === '/login') {
next({ path: '/' });
NProgress.done();
} else {
const hasGetUserInfo = user.roles.length > 0;
if (hasGetUserInfo) {
next();
} else {
try {
await user.getUserInfo();
const roles = user.roles;
const accessRoutes: any = await permission.generateRoutes(roles);
accessRoutes.forEach((route: any) => {
router.addRoute(route);
});
next({ ...to, replace: true });
} catch (error) {
// 移除 token 并跳转登录页
await user.resetToken();
ElMessage.error((error as any) || 'Has Error');
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
// 未登录可以访问白名单页面(登录页面)
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
NProgress.done();
});

View File

@ -6,48 +6,48 @@ export const Layout = () => import('@/layout/index.vue');
// 参数说明: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
// 静态路由
export const constantRoutes: Array<RouteRecordRaw> = [
{
path: '/redirect',
component: Layout,
meta: { hidden: true },
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/404',
component: () => import('@/views/error-page/404.vue'),
meta: { hidden: true }
},
{
path: '/401',
component: () => import('@/views/error-page/401.vue'),
meta: { hidden: true }
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'homepage', affix: true }
}
]
}
{
path: '/redirect',
component: Layout,
meta: { hidden: true },
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/404',
component: () => import('@/views/error-page/404.vue'),
meta: { hidden: true }
},
{
path: '/401',
component: () => import('@/views/error-page/401.vue'),
meta: { hidden: true }
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'homepage', affix: true }
}
]
}
// 外部链接
/*{
// 外部链接
/*{
path: '/external-link',
component: Layout,
children: [
@ -57,8 +57,8 @@ export const constantRoutes: Array<RouteRecordRaw> = [
}
]
}*/
// 多级嵌套路由
/* {
// 多级嵌套路由
/* {
path: '/nested',
component: Layout,
redirect: '/nested/level1/level2',
@ -101,21 +101,21 @@ export const constantRoutes: Array<RouteRecordRaw> = [
// 创建路由
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 })
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 })
});
// 重置路由
export function resetRouter() {
const { permission } = useStore();
permission.routes.forEach(route => {
const name = route.name;
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
const { permission } = useStore();
permission.routes.forEach(route => {
const name = route.name;
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
}
export default router;

View File

@ -1,20 +1,20 @@
interface DefaultSettings {
title: string;
showSettings: boolean;
tagsView: boolean;
fixedHeader: boolean;
sidebarLogo: boolean;
errorLog: string;
title: string;
showSettings: boolean;
tagsView: boolean;
fixedHeader: boolean;
sidebarLogo: boolean;
errorLog: string;
}
const defaultSettings: DefaultSettings = {
title: 'vue3-element-admin',
showSettings: true,
tagsView: true,
fixedHeader: false,
// 是否显示Logo
sidebarLogo: true,
errorLog: 'production'
title: 'vue3-element-admin',
showSettings: true,
tagsView: true,
fixedHeader: false,
// 是否显示Logo
sidebarLogo: true,
errorLog: 'production'
};
export default defaultSettings;

View File

@ -5,11 +5,11 @@ import useSettingStore from './modules/settings';
import useTagsViewStore from './modules/tagsView';
const useStore = () => ({
user: useUserStore(),
app: useAppStore(),
permission: usePermissionStore(),
setting: useSettingStore(),
tagsView: useTagsViewStore()
user: useUserStore(),
app: useAppStore(),
permission: usePermissionStore(),
setting: useSettingStore(),
tagsView: useTagsViewStore()
});
export default useStore;

View File

@ -4,45 +4,45 @@ import { defineStore } from 'pinia';
import { getLanguage } from '@/lang/index';
const useAppStore = defineStore({
id: 'app',
state: (): AppState => ({
device: 'desktop',
sidebar: {
opened: localStorage.get('sidebarStatus')
? !!+localStorage.get('sidebarStatus')
: true,
withoutAnimation: false
},
language: getLanguage(),
size: localStorage.get('size') || 'default'
}),
actions: {
toggleSidebar() {
this.sidebar.opened = !this.sidebar.opened;
this.sidebar.withoutAnimation = false;
if (this.sidebar.opened) {
localStorage.set('sidebarStatus', 1);
} else {
localStorage.set('sidebarStatus', 0);
}
},
closeSideBar(withoutAnimation: any) {
localStorage.set('sidebarStatus', 0);
this.sidebar.opened = false;
this.sidebar.withoutAnimation = withoutAnimation;
},
toggleDevice(device: string) {
this.device = device;
},
setSize(size: string) {
this.size = size;
localStorage.set('size', size);
},
setLanguage(language: string) {
this.language = language;
localStorage.set('language', language);
}
}
id: 'app',
state: (): AppState => ({
device: 'desktop',
sidebar: {
opened: localStorage.get('sidebarStatus')
? !!+localStorage.get('sidebarStatus')
: true,
withoutAnimation: false
},
language: getLanguage(),
size: localStorage.get('size') || 'default'
}),
actions: {
toggleSidebar() {
this.sidebar.opened = !this.sidebar.opened;
this.sidebar.withoutAnimation = false;
if (this.sidebar.opened) {
localStorage.set('sidebarStatus', 1);
} else {
localStorage.set('sidebarStatus', 0);
}
},
closeSideBar(withoutAnimation: any) {
localStorage.set('sidebarStatus', 0);
this.sidebar.opened = false;
this.sidebar.withoutAnimation = withoutAnimation;
},
toggleDevice(device: string) {
this.device = device;
},
setSize(size: string) {
this.size = size;
localStorage.set('size', size);
},
setLanguage(language: string) {
this.language = language;
localStorage.set('language', language);
}
}
});
export default useAppStore;

View File

@ -8,73 +8,73 @@ const modules = import.meta.glob('../../views/**/**.vue');
export const Layout = () => import('@/layout/index.vue');
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (route.meta && route.meta.roles) {
if (roles.includes('ROOT')) {
return true;
}
return roles.some(role => {
if (route.meta?.roles !== undefined) {
return (route.meta.roles as string[]).includes(role);
}
});
}
return false;
if (route.meta && route.meta.roles) {
if (roles.includes('ROOT')) {
return true;
}
return roles.some(role => {
if (route.meta?.roles !== undefined) {
return (route.meta.roles as string[]).includes(role);
}
});
}
return false;
};
export const filterAsyncRoutes = (
routes: RouteRecordRaw[],
roles: string[]
routes: RouteRecordRaw[],
roles: string[]
) => {
const res: RouteRecordRaw[] = [];
routes.forEach(route => {
const tmp = { ...route } as any;
if (hasPermission(roles, tmp)) {
if (tmp.component == 'Layout') {
tmp.component = Layout;
} else {
const component = modules[`../../views/${tmp.component}.vue`] as any;
if (component) {
tmp.component = modules[`../../views/${tmp.component}.vue`];
} else {
tmp.component = modules[`../../views/error-page/404.vue`];
}
}
res.push(tmp);
const res: RouteRecordRaw[] = [];
routes.forEach(route => {
const tmp = { ...route } as any;
if (hasPermission(roles, tmp)) {
if (tmp.component == 'Layout') {
tmp.component = Layout;
} else {
const component = modules[`../../views/${tmp.component}.vue`] as any;
if (component) {
tmp.component = modules[`../../views/${tmp.component}.vue`];
} else {
tmp.component = modules[`../../views/error-page/404.vue`];
}
}
res.push(tmp);
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles);
}
}
});
return res;
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles);
}
}
});
return res;
};
const usePermissionStore = defineStore({
id: 'permission',
state: (): PermissionState => ({
routes: [],
addRoutes: []
}),
actions: {
setRoutes(routes: RouteRecordRaw[]) {
this.addRoutes = routes;
this.routes = constantRoutes.concat(routes);
},
generateRoutes(roles: string[]) {
return new Promise((resolve, reject) => {
listRoutes()
.then(response => {
const asyncRoutes = response.data;
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
this.setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch(error => {
reject(error);
});
});
}
}
id: 'permission',
state: (): PermissionState => ({
routes: [],
addRoutes: []
}),
actions: {
setRoutes(routes: RouteRecordRaw[]) {
this.addRoutes = routes;
this.routes = constantRoutes.concat(routes);
},
generateRoutes(roles: string[]) {
return new Promise((resolve, reject) => {
listRoutes()
.then(response => {
const asyncRoutes = response.data;
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
this.setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch(error => {
reject(error);
});
});
}
}
});
export default usePermissionStore;

View File

@ -7,44 +7,44 @@ const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings;
const el = document.documentElement;
export const useSettingStore = defineStore({
id: 'setting',
state: (): SettingState => ({
theme:
localStorage.get('theme') ||
getComputedStyle(el).getPropertyValue(`--el-color-primary`),
showSettings: showSettings,
tagsView:
localStorage.get('tagsView') != null
? localStorage.get('tagsView')
: tagsView,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo
}),
actions: {
async changeSetting(payload: { key: string; value: any }) {
const { key, value } = payload;
switch (key) {
case 'theme':
this.theme = value;
break;
case 'showSettings':
this.showSettings = value;
break;
case 'fixedHeader':
this.fixedHeader = value;
break;
case 'tagsView':
this.tagsView = value;
localStorage.set('tagsView', value);
break;
case 'sidebarLogo':
this.sidebarLogo = value;
break;
default:
break;
}
}
}
id: 'setting',
state: (): SettingState => ({
theme:
localStorage.get('theme') ||
getComputedStyle(el).getPropertyValue(`--el-color-primary`),
showSettings: showSettings,
tagsView:
localStorage.get('tagsView') != null
? localStorage.get('tagsView')
: tagsView,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo
}),
actions: {
async changeSetting(payload: { key: string; value: any }) {
const { key, value } = payload;
switch (key) {
case 'theme':
this.theme = value;
break;
case 'showSettings':
this.showSettings = value;
break;
case 'fixedHeader':
this.fixedHeader = value;
break;
case 'tagsView':
this.tagsView = value;
localStorage.set('tagsView', value);
break;
case 'sidebarLogo':
this.sidebarLogo = value;
break;
default:
break;
}
}
}
});
export default useSettingStore;

View File

@ -2,174 +2,174 @@ import { defineStore } from 'pinia';
import { TagsViewState } from '@/types';
const useTagsViewStore = defineStore({
id: 'tagsView',
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: []
}),
actions: {
addVisitedView(view: any) {
if (this.visitedViews.some(v => v.path === view.path)) return;
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta?.title || 'no-name'
})
);
},
addCachedView(view: any) {
if (this.cachedViews.includes(view.name)) return;
if (!view.meta.noCache) {
this.cachedViews.push(view.name);
}
},
id: 'tagsView',
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: []
}),
actions: {
addVisitedView(view: any) {
if (this.visitedViews.some(v => v.path === view.path)) return;
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta?.title || 'no-name'
})
);
},
addCachedView(view: any) {
if (this.cachedViews.includes(view.name)) return;
if (!view.meta.noCache) {
this.cachedViews.push(view.name);
}
},
delVisitedView(view: any) {
return new Promise(resolve => {
for (const [i, v] of this.visitedViews.entries()) {
if (v.path === view.path) {
this.visitedViews.splice(i, 1);
break;
}
}
resolve([...this.visitedViews]);
});
},
delCachedView(view: any) {
return new Promise(resolve => {
const index = this.cachedViews.indexOf(view.name);
index > -1 && this.cachedViews.splice(index, 1);
resolve([...this.cachedViews]);
});
},
delVisitedView(view: any) {
return new Promise(resolve => {
for (const [i, v] of this.visitedViews.entries()) {
if (v.path === view.path) {
this.visitedViews.splice(i, 1);
break;
}
}
resolve([...this.visitedViews]);
});
},
delCachedView(view: any) {
return new Promise(resolve => {
const index = this.cachedViews.indexOf(view.name);
index > -1 && this.cachedViews.splice(index, 1);
resolve([...this.cachedViews]);
});
},
delOtherVisitedViews(view: any) {
return new Promise(resolve => {
this.visitedViews = this.visitedViews.filter(v => {
return v.meta?.affix || v.path === view.path;
});
resolve([...this.visitedViews]);
});
},
delOtherCachedViews(view: any) {
return new Promise(resolve => {
const index = this.cachedViews.indexOf(view.name);
if (index > -1) {
this.cachedViews = this.cachedViews.slice(index, index + 1);
} else {
// if index = -1, there is no cached tags
this.cachedViews = [];
}
resolve([...this.cachedViews]);
});
},
delOtherVisitedViews(view: any) {
return new Promise(resolve => {
this.visitedViews = this.visitedViews.filter(v => {
return v.meta?.affix || v.path === view.path;
});
resolve([...this.visitedViews]);
});
},
delOtherCachedViews(view: any) {
return new Promise(resolve => {
const index = this.cachedViews.indexOf(view.name);
if (index > -1) {
this.cachedViews = this.cachedViews.slice(index, index + 1);
} else {
// if index = -1, there is no cached tags
this.cachedViews = [];
}
resolve([...this.cachedViews]);
});
},
updateVisitedView(view: any) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view);
break;
}
}
},
addView(view: any) {
this.addVisitedView(view);
this.addCachedView(view);
},
delView(view: any) {
return new Promise(resolve => {
this.delVisitedView(view);
this.delCachedView(view);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
});
});
},
delOtherViews(view: any) {
return new Promise(resolve => {
this.delOtherVisitedViews(view);
this.delOtherCachedViews(view);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
});
});
},
delLeftViews(view: any) {
return new Promise(resolve => {
const currIndex = this.visitedViews.findIndex(
v => v.path === view.path
);
if (currIndex === -1) {
return;
}
this.visitedViews = this.visitedViews.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index >= currIndex || (item.meta && item.meta.affix)) {
return true;
}
updateVisitedView(view: any) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view);
break;
}
}
},
addView(view: any) {
this.addVisitedView(view);
this.addCachedView(view);
},
delView(view: any) {
return new Promise(resolve => {
this.delVisitedView(view);
this.delCachedView(view);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
});
});
},
delOtherViews(view: any) {
return new Promise(resolve => {
this.delOtherVisitedViews(view);
this.delOtherCachedViews(view);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
});
});
},
delLeftViews(view: any) {
return new Promise(resolve => {
const currIndex = this.visitedViews.findIndex(
v => v.path === view.path
);
if (currIndex === -1) {
return;
}
this.visitedViews = this.visitedViews.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index >= currIndex || (item.meta && item.meta.affix)) {
return true;
}
const cacheIndex = this.cachedViews.indexOf(item.name as string);
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...this.visitedViews]
});
});
},
delRightViews(view: any) {
return new Promise(resolve => {
const currIndex = this.visitedViews.findIndex(
v => v.path === view.path
);
if (currIndex === -1) {
return;
}
this.visitedViews = this.visitedViews.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index <= currIndex || (item.meta && item.meta.affix)) {
return true;
}
const cacheIndex = this.cachedViews.indexOf(item.name as string);
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...this.visitedViews]
});
});
},
delRightViews(view: any) {
return new Promise(resolve => {
const currIndex = this.visitedViews.findIndex(
v => v.path === view.path
);
if (currIndex === -1) {
return;
}
this.visitedViews = this.visitedViews.filter((item, index) => {
// affix:true 固定tag例如“首页”
if (index <= currIndex || (item.meta && item.meta.affix)) {
return true;
}
const cacheIndex = this.cachedViews.indexOf(item.name as string);
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...this.visitedViews]
});
});
},
delAllViews() {
return new Promise(resolve => {
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix);
this.visitedViews = affixTags;
this.cachedViews = [];
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
});
});
},
delAllVisitedViews() {
return new Promise(resolve => {
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix);
this.visitedViews = affixTags;
resolve([...this.visitedViews]);
});
},
delAllCachedViews() {
return new Promise(resolve => {
this.cachedViews = [];
resolve([...this.cachedViews]);
});
}
}
const cacheIndex = this.cachedViews.indexOf(item.name as string);
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1);
}
return false;
});
resolve({
visitedViews: [...this.visitedViews]
});
});
},
delAllViews() {
return new Promise(resolve => {
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix);
this.visitedViews = affixTags;
this.cachedViews = [];
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
});
});
},
delAllVisitedViews() {
return new Promise(resolve => {
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix);
this.visitedViews = affixTags;
resolve([...this.visitedViews]);
});
},
delAllCachedViews() {
return new Promise(resolve => {
this.cachedViews = [];
resolve([...this.cachedViews]);
});
}
}
});
export default useTagsViewStore;

View File

@ -6,103 +6,103 @@ import { getUserInfo } from '@/api/system/user';
import { resetRouter } from '@/router';
const useUserStore = defineStore({
id: 'user',
state: (): UserState => ({
token: localStorage.get('token') || '',
nickname: '',
avatar: '',
roles: [],
perms: []
}),
actions: {
async RESET_STATE() {
this.$reset();
},
/**
*
* @param userInfo
* username: 用户名
* password: 密码
* code: 验证码
* uuid: 匹配正确验证码的 key
*/
login(userInfo: LoginFormData) {
const { username, password, code, uuid } = userInfo;
return new Promise((resolve, reject) => {
login({
username: username.trim(),
password: password,
grant_type: 'captcha',
code: code,
uuid: uuid
})
.then(response => {
const { access_token, token_type } = response.data;
const accessToken = token_type + ' ' + access_token;
localStorage.set('token', accessToken);
this.token = accessToken;
resolve(access_token);
})
.catch(error => {
reject(error);
});
});
},
/**
*
*/
getUserInfo() {
return new Promise((resolve, reject) => {
getUserInfo()
.then(({ data }) => {
if (!data) {
return reject('Verification failed, please Login again.');
}
const { nickname, avatar, roles, perms } = data;
if (!roles || roles.length <= 0) {
reject('getUserInfo: roles must be a non-null array!');
}
this.nickname = nickname;
this.avatar = avatar;
this.roles = roles;
this.perms = perms;
resolve(data);
})
.catch(error => {
reject(error);
});
});
},
id: 'user',
state: (): UserState => ({
token: localStorage.get('token') || '',
nickname: '',
avatar: '',
roles: [],
perms: []
}),
actions: {
async RESET_STATE() {
this.$reset();
},
/**
*
* @param userInfo
* username: 用户名
* password: 密码
* code: 验证码
* uuid: 匹配正确验证码的 key
*/
login(userInfo: LoginFormData) {
const { username, password, code, uuid } = userInfo;
return new Promise((resolve, reject) => {
login({
username: username.trim(),
password: password,
grant_type: 'captcha',
code: code,
uuid: uuid
})
.then(response => {
const { access_token, token_type } = response.data;
const accessToken = token_type + ' ' + access_token;
localStorage.set('token', accessToken);
this.token = accessToken;
resolve(access_token);
})
.catch(error => {
reject(error);
});
});
},
/**
*
*/
getUserInfo() {
return new Promise((resolve, reject) => {
getUserInfo()
.then(({ data }) => {
if (!data) {
return reject('Verification failed, please Login again.');
}
const { nickname, avatar, roles, perms } = data;
if (!roles || roles.length <= 0) {
reject('getUserInfo: roles must be a non-null array!');
}
this.nickname = nickname;
this.avatar = avatar;
this.roles = roles;
this.perms = perms;
resolve(data);
})
.catch(error => {
reject(error);
});
});
},
/**
*
*/
logout() {
return new Promise((resolve, reject) => {
logout()
.then(() => {
localStorage.remove('token');
this.RESET_STATE();
resetRouter();
resolve(null);
})
.catch(error => {
reject(error);
});
});
},
/**
*
*/
logout() {
return new Promise((resolve, reject) => {
logout()
.then(() => {
localStorage.remove('token');
this.RESET_STATE();
resetRouter();
resolve(null);
})
.catch(error => {
reject(error);
});
});
},
/**
* Token
*/
resetToken() {
return new Promise(resolve => {
localStorage.remove('token');
this.RESET_STATE();
resolve(null);
});
}
}
/**
* Token
*/
resetToken() {
return new Promise(resolve => {
localStorage.remove('token');
this.RESET_STATE();
resolve(null);
});
}
}
});
export default useUserStore;

View File

@ -1,50 +1,50 @@
:root {
// 这里可以设置你自定义的颜色变量
// 这个是element主要按钮:active的颜色当主题更改后此变量的值也随之更改
--el-color-primary-dark: #0d84ff;
// element plus 2.1.0 禁用文本色值和正常文本色值无法区分问题
--el-text-color-disabled: #ccc;
// 这里可以设置你自定义的颜色变量
// 这个是element主要按钮:active的颜色当主题更改后此变量的值也随之更改
--el-color-primary-dark: #0d84ff;
// element plus 2.1.0 禁用文本色值和正常文本色值无法区分问题
--el-text-color-disabled: #ccc;
}
// 覆盖 element-plus 的样式
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
font-weight: 400 !important;
}
.el-upload {
input[type='file'] {
display: none !important;
}
input[type='file'] {
display: none !important;
}
}
.el-upload__input {
display: none;
display: none;
}
// dropdown
.el-dropdown-menu {
a {
display: block;
}
a {
display: block;
}
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
box-sizing: content-box;
}
// 选中行背景色值
.el-table__body tr.current-row td {
background-color: #e1f3d8b5 !important;
background-color: #e1f3d8b5 !important;
}
// card 的header统一高度
.el-card__header {
height: 60px !important;
height: 60px !important;
}
// 表格表头和表体未对齐
.el-table__header col[name='gutter'] {
display: table-cell !important;
display: table-cell !important;
}

View File

@ -5,64 +5,64 @@
@import './sidebar.scss';
body {
margin: 0;
padding: 0;
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
margin: 0;
padding: 0;
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
height: 100%;
box-sizing: border-box;
}
#app {
height: 100%;
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
box-sizing: inherit;
}
a:focus,
a:active {
outline: none;
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
outline: none;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
}
// main-container global css
.app-container {
padding: 20px;
padding: 20px;
}

View File

@ -1,28 +1,28 @@
@mixin clearfix {
&:after {
content: '';
display: table;
clear: both;
}
&:after {
content: '';
display: table;
clear: both;
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
width: 100%;
height: 100%;
position: relative;
width: 100%;
height: 100%;
}

View File

@ -1,228 +1,228 @@
#app {
.main-container {
min-height: 100%;
transition: margin-left 0.28s;
margin-left: $sideBarWidth;
position: relative;
}
.main-container {
min-height: 100%;
transition: margin-left 0.28s;
margin-left: $sideBarWidth;
position: relative;
}
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
background-color: $menuBg;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out,
0s padding-right ease-in-out;
}
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out,
0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.is-horizontal {
display: none;
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
// menu hover
.submenu-title-noDropdown,
.el-sub-menu__title {
&:hover {
background-color: $menuHover !important;
}
}
// menu hover
.submenu-title-noDropdown,
.el-sub-menu__title {
&:hover {
background-color: $menuHover !important;
}
}
.is-active > .el-sub-menu__title {
color: $subMenuActiveText !important;
}
.is-active > .el-sub-menu__title {
color: $subMenuActiveText !important;
}
& .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-sub-menu .el-menu-item {
min-width: $sideBarWidth !important;
background-color: $subMenuBg !important;
& .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-sub-menu .el-menu-item {
min-width: $sideBarWidth !important;
background-color: $subMenuBg !important;
&:hover {
background-color: $subMenuHover !important;
}
}
}
&:hover {
background-color: $subMenuHover !important;
}
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
.svg-icon {
margin-right: 0px;
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
.svg-icon {
margin-right: 0px;
}
}
.main-container {
margin-left: 54px;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-sub-menu {
overflow: hidden;
.el-sub-menu {
overflow: hidden;
& > .el-sub-menu__title {
padding: 0 !important;
& > .el-sub-menu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-sub-menu__icon-arrow {
display: none;
}
}
}
.el-sub-menu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse {
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-sub-menu {
min-width: $sideBarWidth !important;
}
.el-menu--collapse .el-menu .el-sub-menu {
min-width: $sideBarWidth !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform 0.28s;
width: $sideBarWidth !important;
}
.sidebar-container {
transition: transform 0.28s;
width: $sideBarWidth !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
& > .el-menu {
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
}
& > .el-menu {
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
}
.nest-menu .el-sub-menu > .el-sub-menu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
background-color: $menuHover !important;
}
}
.nest-menu .el-sub-menu > .el-sub-menu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
background-color: $menuHover !important;
}
}
// the scroll bar appears when the subMenu is too long
> .el-menu--popup {
max-height: 100vh;
overflow-y: auto;
// the scroll bar appears when the subMenu is too long
> .el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@ -3,46 +3,46 @@
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
opacity: 0;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.5s;
transition: all 0.5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px);
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
opacity: 0;
transform: translateX(30px);
}
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.5s;
transition: all 0.5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-move {
transition: all 0.5s;
transition: all 0.5s;
}
.breadcrumb-leave-active {
position: absolute;
position: absolute;
}

View File

@ -14,12 +14,12 @@ $sideBarWidth: 210px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuText: $menuText;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
menuText: $menuText;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
}

View File

@ -1,9 +1,9 @@
export interface PageQueryParam {
pageNum: number;
pageSize: number;
pageNum: number;
pageSize: number;
}
export interface PageResult<T> {
list: T;
total: number;
list: T;
total: number;
}

View File

@ -2,8 +2,8 @@
* Seata表单类型声明
*/
export interface SeataFormData {
openTx: boolean;
stockEx: boolean;
accountEx: boolean;
orderEx: boolean;
openTx: boolean;
stockEx: boolean;
accountEx: boolean;
orderEx: boolean;
}

View File

@ -4,35 +4,35 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface OrderQueryParam extends PageQueryParam {
orderSn: string | undefined;
status: number | undefined;
orderSn: string | undefined;
status: number | undefined;
}
/**
*
*/
export interface Order {
id: string;
orderSn: string;
totalAmount: string;
payAmount: string;
payType: number;
status: number;
totalQuantity: number;
gmtCreate: string;
memberId: string;
sourceType: number;
orderItems: OrderItem[];
id: string;
orderSn: string;
totalAmount: string;
payAmount: string;
payType: number;
status: number;
totalQuantity: number;
gmtCreate: string;
memberId: string;
sourceType: number;
orderItems: OrderItem[];
}
export interface OrderItem {
id: string;
orderId: string;
skuId: string;
skuName: string;
picUrl: string;
price: string;
count: number;
totalAmount: number;
id: string;
orderId: string;
skuId: string;
skuName: string;
picUrl: string;
price: string;
count: number;
totalAmount: number;
}
/**
@ -44,13 +44,13 @@ export type OrderPageResult = PageResult<Order[]>;
*
*/
export interface OrderDetail {
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@ -4,17 +4,17 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface BrandQueryParam extends PageQueryParam {
name?: string;
name?: string;
}
/**
*
*/
export interface BrandItem {
id: string;
name: string;
logoUrl: string;
sort: number;
id: string;
name: string;
logoUrl: string;
sort: number;
}
/**
@ -26,8 +26,8 @@ export type BrandPageResult = PageResult<BrandItem[]>;
*
*/
export interface BrandFormData {
id: number | undefined;
name: string;
logoUrl: string;
sort: number;
id: number | undefined;
name: string;
logoUrl: string;
sort: number;
}

View File

@ -4,45 +4,45 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface GoodsQueryParam extends PageQueryParam {
name?: stirng;
categoryId?: number;
name?: stirng;
categoryId?: number;
}
/**
*
*/
export interface GoodsItem {
id: string;
name: string;
categoryId?: any;
brandId?: any;
originPrice: string;
price: string;
sales: number;
picUrl?: any;
album?: any;
unit?: any;
description: string;
detail: string;
status?: any;
categoryName: string;
brandName: string;
skuList: SkuItem[];
id: string;
name: string;
categoryId?: any;
brandId?: any;
originPrice: string;
price: string;
sales: number;
picUrl?: any;
album?: any;
unit?: any;
description: string;
detail: string;
status?: any;
categoryName: string;
brandName: string;
skuList: SkuItem[];
}
/**
*
*/
export interface SkuItem {
id: string;
skuSn?: any;
name: string;
spuId?: any;
specIds: string;
price: string;
stockNum: number;
lockedStockNum?: any;
picUrl?: any;
id: string;
skuSn?: any;
name: string;
spuId?: any;
specIds: string;
price: string;
stockNum: number;
lockedStockNum?: any;
picUrl?: any;
}
/**
@ -54,17 +54,17 @@ export type GoodsPageResult = PageResult<GoodsItem[]>;
*
*/
export interface GoodsDetail {
id?: string;
name?: string;
categoryId?: string;
brandId?: string;
originPrice?: number;
price?: number;
picUrl?: string;
album: string[];
description?: string;
detail?: string;
attrList: any[];
specList: any[];
skuList: any[];
id?: string;
name?: string;
categoryId?: string;
brandId?: string;
originPrice?: number;
price?: number;
picUrl?: string;
album: string[];
description?: string;
detail?: string;
attrList: any[];
specList: any[];
skuList: any[];
}

View File

@ -4,17 +4,17 @@ import { PageQueryParam, PageResult } from '../base';
* 广
*/
export interface AdvertQueryParam extends PageQueryParam {
title?: string;
title?: string;
}
/**
* 广
*/
export interface AdvertItem {
id: string;
name: string;
logoUrl: string;
sort: number;
id: string;
name: string;
logoUrl: string;
sort: number;
}
/**
@ -26,13 +26,13 @@ export type AdvertPageResult = PageResult<AdvertItem[]>;
* 广
*/
export interface AdvertFormData {
id?: number;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
id?: number;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@ -4,27 +4,27 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface ClientQueryParam extends PageQueryParam {
/**
*
*/
clientId: string | undefined;
/**
*
*/
clientId: string | undefined;
}
/**
*
*/
export interface ClientItem {
clientId: string;
clientSecret: string;
resourceIds: string;
scope: string;
authorizedGrantTypes: string;
webServerRedirectUri?: any;
authorities?: any;
accessTokenValidity: number;
refreshTokenValidity: number;
additionalInformation?: any;
autoapprove: string;
clientId: string;
clientSecret: string;
resourceIds: string;
scope: string;
authorizedGrantTypes: string;
webServerRedirectUri?: any;
authorities?: any;
accessTokenValidity: number;
refreshTokenValidity: number;
additionalInformation?: any;
autoapprove: string;
}
/**
@ -36,14 +36,14 @@ export type ClientPageResult = PageResult<ClientItem[]>;
*
*/
export interface ClientFormData {
authorizedGrantTypes: string;
clientId: string;
clientSecret: string;
accessTokenValidity: string;
refreshTokenValidity: string;
webServerRedirectUri: string;
authorities: string;
additionalInformation: string;
autoapprove: string;
scope: string;
authorizedGrantTypes: string;
clientId: string;
clientSecret: string;
accessTokenValidity: string;
refreshTokenValidity: string;
webServerRedirectUri: string;
authorities: string;
additionalInformation: string;
autoapprove: string;
scope: string;
}

View File

@ -2,8 +2,8 @@
*
*/
export interface DeptQueryParam {
name: string | undefined;
status: number | undefined;
name: string | undefined;
status: number | undefined;
}
/**
@ -11,25 +11,25 @@ export interface DeptQueryParam {
*/
export interface DeptItem {
id: string;
name: string;
parentId: string;
treePath: string;
sort: number;
status: number;
leader?: string;
mobile?: string;
email?: string;
children: DeptItem[];
id: string;
name: string;
parentId: string;
treePath: string;
sort: number;
status: number;
leader?: string;
mobile?: string;
email?: string;
children: DeptItem[];
}
/**
*
*/
export interface DeptFormData {
id?: string;
parentId: string;
name: string;
sort: number;
status: number;
id?: string;
parentId: string;
name: string;
sort: number;
status: number;
}

View File

@ -4,21 +4,21 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface DictQueryParam extends PageQueryParam {
/**
*
*/
name: string | undefined;
/**
*
*/
name: string | undefined;
}
/**
*
*/
export interface Dict {
id: number;
code: string;
name: string;
status: number;
remark: string;
id: number;
code: string;
name: string;
status: number;
remark: string;
}
/**
@ -30,39 +30,39 @@ export type DictPageResult = PageResult<Dict[]>;
*
*/
export interface DictFormData {
id: number | undefined;
name: string;
code: string;
status: number;
remark: string;
id: number | undefined;
name: string;
code: string;
status: number;
remark: string;
}
/**
*
*/
export interface DictItemQueryParam extends PageQueryParam {
/**
*
*/
name: string | undefined;
/**
*
*/
dictCode: string | undefined;
/**
*
*/
name: string | undefined;
/**
*
*/
dictCode: string | undefined;
}
/**
*
*/
export interface DictItem {
id: number;
name: string;
value: string;
dictCode: string;
sort: number;
status: number;
defaulted: number;
remark?: string;
id: number;
name: string;
value: string;
dictCode: string;
sort: number;
status: number;
defaulted: number;
remark?: string;
}
/**
@ -74,13 +74,13 @@ export type DictItemPageResult = PageResult<DictItem[]>;
*
*/
export interface DictItemFormData {
id?: number;
dictCode?: string;
dictName?: string;
name: string;
code: string;
value: string;
status: number;
sort: number;
remark: string;
id?: number;
dictCode?: string;
dictName?: string;
name: string;
code: string;
value: string;
status: number;
sort: number;
remark: string;
}

View File

@ -2,25 +2,25 @@
*
*/
export interface LoginFormData {
username: string;
password: string;
grant_type: string;
code: string;
uuid: string;
username: string;
password: string;
grant_type: string;
code: string;
uuid: string;
}
/**
*
*/
export interface LoginResponseData {
access_token: string;
token_type: string;
access_token: string;
token_type: string;
}
/**
*
*/
export interface Captcha {
img: string;
uuid: string;
img: string;
uuid: string;
}

View File

@ -2,7 +2,7 @@
*
*/
export interface MenuQueryParam {
name?: string;
name?: string;
}
/**
@ -10,58 +10,58 @@ export interface MenuQueryParam {
*/
export interface MenuItem {
id: number;
parentId: number;
gmtCreate: string;
gmtModified: string;
name: string;
icon: string;
component: string;
sort: number;
visible: number;
children: MenuItem[];
id: number;
parentId: number;
gmtCreate: string;
gmtModified: string;
name: string;
icon: string;
component: string;
sort: number;
visible: number;
children: MenuItem[];
}
/**
*
*/
export interface MenuFormData {
/**
* ID
*/
id?: string;
/**
* ID
*/
parentId: string;
/**
*
*/
name: string;
/**
* (1:;0:;)
*/
visible: number;
icon?: string;
/**
*
*/
sort: number;
/**
*
*/
component?: string;
/**
*
*/
path: string;
/**
*
*/
redirect?: string;
/**
* ID
*/
id?: string;
/**
* ID
*/
parentId: string;
/**
*
*/
name: string;
/**
* (1:;0:;)
*/
visible: number;
icon?: string;
/**
*
*/
sort: number;
/**
*
*/
component?: string;
/**
*
*/
path: string;
/**
*
*/
redirect?: string;
/**
* (1:菜单23)
*/
type: string;
/**
* (1:菜单23)
*/
type: string;
}

View File

@ -4,20 +4,20 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface PermQueryParam extends PageQueryParam {
menuId: any;
name: string | undefined;
menuId: any;
name: string | undefined;
}
/**
*
*/
export interface PermItem {
id: number;
name: string;
menuId: string;
urlPerm: string;
btnPerm: string;
roles?: string[];
id: number;
name: string;
menuId: string;
urlPerm: string;
btnPerm: string;
roles?: string[];
}
/**
@ -29,9 +29,9 @@ export type PermPageResult = PageResult<PermItem[]>;
*
*/
export interface PermFormData {
id: number | undefined;
name: string;
urlPerm: string;
btnPerm: string;
menuId: string;
id: number | undefined;
name: string;
urlPerm: string;
btnPerm: string;
menuId: string;
}

View File

@ -4,21 +4,21 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface RoleQueryParam extends PageQueryParam {
name?: string;
name?: string;
}
/**
*
*/
export interface RoleItem {
id: string;
name: string;
code: string;
sort: number;
status: number;
deleted: number;
menuIds?: any;
permissionIds?: any;
id: string;
name: string;
code: string;
sort: number;
status: number;
deleted: number;
menuIds?: any;
permissionIds?: any;
}
/**
@ -30,9 +30,9 @@ export type RolePageResult = PageResult<RoleItem[]>;
*
*/
export interface RoleFormData {
id: number | undefined;
name: string;
code: string;
sort: number;
status: number;
id: number | undefined;
name: string;
code: string;
sort: number;
status: number;
}

View File

@ -4,36 +4,36 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface UserInfo {
nickname: string;
avatar: string;
roles: string[];
perms: string[];
nickname: string;
avatar: string;
roles: string[];
perms: string[];
}
/**
*
*/
export interface UserQueryParam extends PageQueryParam {
keywords: string;
status: number;
deptId: number;
keywords: string;
status: number;
deptId: number;
}
/**
*
*/
export interface UserItem {
id: string;
username: string;
nickname: string;
mobile: string;
gender: number;
avatar: string;
email: string;
status: number;
deptName: string;
roleNames: string;
gmtCreate: string;
id: string;
username: string;
nickname: string;
mobile: string;
gender: number;
avatar: string;
email: string;
status: number;
deptName: string;
roleNames: string;
gmtCreate: string;
}
/**
@ -45,23 +45,23 @@ export type UserPageResult = PageResult<UserItem[]>;
*
*/
export interface UserFormData {
id: number | undefined;
deptId: number;
username: string;
nickname: string;
password: string;
mobile: string;
email: string;
gender: number;
status: number;
remark: string;
roleIds: number[];
id: number | undefined;
deptId: number;
username: string;
nickname: string;
password: string;
mobile: string;
email: string;
gender: number;
status: number;
remark: string;
roleIds: number[];
}
/**
*
*/
export interface UserImportFormData {
deptId: number;
roleIds: number[];
deptId: number;
roleIds: number[];
}

View File

@ -4,43 +4,43 @@ import { PageQueryParam, PageResult } from '../base';
*
*/
export interface MemberQueryParam extends PageQueryParam {
nickName?: string;
nickName?: string;
}
/**
*
*/
export interface MemberItem {
id: string;
gender: number;
nickName: string;
mobile: string;
birthday?: any;
avatarUrl: string;
openid: string;
sessionKey?: any;
city: string;
country: string;
language: string;
province: string;
status: number;
balance: string;
deleted: number;
point: number;
addressList: AddressItem[];
id: string;
gender: number;
nickName: string;
mobile: string;
birthday?: any;
avatarUrl: string;
openid: string;
sessionKey?: any;
city: string;
country: string;
language: string;
province: string;
status: number;
balance: string;
deleted: number;
point: number;
addressList: AddressItem[];
}
export interface AddressItem {
id: string;
memberId: string;
consigneeName: string;
consigneeMobile: string;
province: string;
city: string;
area: string;
detailAddress: string;
zipCode?: any;
defaulted: number;
id: string;
memberId: string;
consigneeName: string;
consigneeMobile: string;
province: string;
city: string;
area: string;
detailAddress: string;
zipCode?: any;
defaulted: number;
}
/**
@ -52,13 +52,13 @@ export type MemberPageResult = PageResult<MemberItem[]>;
*
*/
export interface MemberFormData {
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

10
src/types/common.d.ts vendored
View File

@ -6,15 +6,15 @@
*
*/
export interface Dialog {
title: string;
visible: boolean;
title: string;
visible: boolean;
}
/**
*
*/
export interface Option {
value: string;
label: string;
children?: Option[];
value: string;
label: string;
children?: Option[];
}

44
src/types/store.d.ts vendored
View File

@ -3,53 +3,53 @@ import { RouteRecordRaw, RouteLocationNormalized } from 'vue-router';
*
*/
export interface AppState {
device: string;
sidebar: {
opened: boolean;
withoutAnimation: boolean;
};
language: string;
size: string;
device: string;
sidebar: {
opened: boolean;
withoutAnimation: boolean;
};
language: string;
size: string;
}
/**
*
*/
export interface PermissionState {
routes: RouteRecordRaw[];
addRoutes: RouteRecordRaw[];
routes: RouteRecordRaw[];
addRoutes: RouteRecordRaw[];
}
/**
*
*/
export interface SettingState {
theme: string;
tagsView: boolean;
fixedHeader: boolean;
showSettings: boolean;
sidebarLogo: boolean;
theme: string;
tagsView: boolean;
fixedHeader: boolean;
showSettings: boolean;
sidebarLogo: boolean;
}
/**
*
*/
export interface TagView extends Partial<RouteLocationNormalized> {
title?: string;
title?: string;
}
export interface TagsViewState {
visitedViews: TagView[];
cachedViews: string[];
visitedViews: TagView[];
cachedViews: string[];
}
/**
*
*/
export interface UserState {
token: string;
nickname: string;
avatar: string;
roles: string[];
perms: string[];
token: string;
nickname: string;
avatar: string;
roles: string[];
perms: string[];
}

View File

@ -5,24 +5,24 @@
* @return {string}
*/
function pluralize(time: number, label: string) {
if (time === 1) {
return time + label;
}
return time + label + 's';
if (time === 1) {
return time + label;
}
return time + label + 's';
}
/**
* @param {number} time
*/
export function timeAgo(time: number) {
const between = Date.now() / 1000 - Number(time);
if (between < 3600) {
return pluralize(~~(between / 60), ' minute');
} else if (between < 86400) {
return pluralize(~~(between / 3600), ' hour');
} else {
return pluralize(~~(between / 86400), ' day');
}
const between = Date.now() / 1000 - Number(time);
if (between < 3600) {
return pluralize(~~(between / 60), ' minute');
} else if (between < 86400) {
return pluralize(~~(between / 3600), ' hour');
} else {
return pluralize(~~(between / 86400), ' day');
}
}
/**
@ -32,24 +32,24 @@ export function timeAgo(time: number) {
* @param {number} digits
*/
export function numberFormatter(num: number, digits: number) {
const si = [
{ value: 1e18, symbol: 'E' },
{ value: 1e15, symbol: 'P' },
{ value: 1e12, symbol: 'T' },
{ value: 1e9, symbol: 'G' },
{ value: 1e6, symbol: 'M' },
{ value: 1e3, symbol: 'k' }
];
for (let i = 0; i < si.length; i++) {
if (num >= si[i].value) {
return (
(num / si[i].value)
.toFixed(digits)
.replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
);
}
}
return num.toString();
const si = [
{ value: 1e18, symbol: 'E' },
{ value: 1e15, symbol: 'P' },
{ value: 1e12, symbol: 'T' },
{ value: 1e9, symbol: 'G' },
{ value: 1e6, symbol: 'M' },
{ value: 1e3, symbol: 'k' }
];
for (let i = 0; i < si.length; i++) {
if (num >= si[i].value) {
return (
(num / si[i].value)
.toFixed(digits)
.replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
);
}
}
return num.toString();
}
/**
@ -57,9 +57,9 @@ export function numberFormatter(num: number, digits: number) {
* @param {number} num
*/
export function toThousandFilter(num: number) {
return (+num || 0)
.toString()
.replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','));
return (+num || 0)
.toString()
.replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','));
}
/**
@ -67,7 +67,7 @@ export function toThousandFilter(num: number) {
* @param {String} string
*/
export function uppercaseFirst(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
return string.charAt(0).toUpperCase() + string.slice(1);
}
/**
@ -76,5 +76,5 @@ export function uppercaseFirst(string: string) {
* @param {number} num
*/
export function moneyFormatter(num: number) {
return '¥' + (isNaN(num) ? 0.0 : parseFloat((num / 100).toFixed(2)));
return '¥' + (isNaN(num) ? 0.0 : parseFloat((num / 100).toFixed(2)));
}

View File

@ -2,11 +2,11 @@
import i18n from '@/lang/index';
export function generateTitle(title: any) {
// 判断是否存在国际化配置,如果没有原生返回
const hasKey = i18n.global.te('route.' + title);
if (hasKey) {
const translatedTitle = i18n.global.t('route.' + title);
return translatedTitle;
}
return title;
// 判断是否存在国际化配置,如果没有原生返回
const hasKey = i18n.global.te('route.' + title);
if (hasKey) {
const translatedTitle = i18n.global.t('route.' + title);
return translatedTitle;
}
return title;
}

View File

@ -5,7 +5,7 @@
* @returns {boolean}
*/
export function hasClass(ele: HTMLElement, cls: string) {
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
}
/**
@ -14,7 +14,7 @@ export function hasClass(ele: HTMLElement, cls: string) {
* @param {string} cls
*/
export function addClass(ele: HTMLElement, cls: string) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls;
if (!hasClass(ele, cls)) ele.className += ' ' + cls;
}
/**
@ -23,25 +23,25 @@ export function addClass(ele: HTMLElement, cls: string) {
* @param {string} cls
*/
export function removeClass(ele: HTMLElement, cls: string) {
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
ele.className = ele.className.replace(reg, ' ');
}
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
ele.className = ele.className.replace(reg, ' ');
}
}
export function mix(color1: string, color2: string, weight: number) {
weight = Math.max(Math.min(Number(weight), 1), 0);
const r1 = parseInt(color1.substring(1, 3), 16);
const g1 = parseInt(color1.substring(3, 5), 16);
const b1 = parseInt(color1.substring(5, 7), 16);
const r2 = parseInt(color2.substring(1, 3), 16);
const g2 = parseInt(color2.substring(3, 5), 16);
const b2 = parseInt(color2.substring(5, 7), 16);
const r = Math.round(r1 * (1 - weight) + r2 * weight);
const g = Math.round(g1 * (1 - weight) + g2 * weight);
const b = Math.round(b1 * (1 - weight) + b2 * weight);
const rStr = ('0' + (r || 0).toString(16)).slice(-2);
const gStr = ('0' + (g || 0).toString(16)).slice(-2);
const bStr = ('0' + (b || 0).toString(16)).slice(-2);
return '#' + rStr + gStr + bStr;
weight = Math.max(Math.min(Number(weight), 1), 0);
const r1 = parseInt(color1.substring(1, 3), 16);
const g1 = parseInt(color1.substring(3, 5), 16);
const b1 = parseInt(color1.substring(5, 7), 16);
const r2 = parseInt(color2.substring(1, 3), 16);
const g2 = parseInt(color2.substring(3, 5), 16);
const b2 = parseInt(color2.substring(5, 7), 16);
const r = Math.round(r1 * (1 - weight) + r2 * weight);
const g = Math.round(g1 * (1 - weight) + g2 * weight);
const b = Math.round(b1 * (1 - weight) + b2 * weight);
const rStr = ('0' + (r || 0).toString(16)).slice(-2);
const gStr = ('0' + (g || 0).toString(16)).slice(-2);
const bStr = ('0' + (b || 0).toString(16)).slice(-2);
return '#' + rStr + gStr + bStr;
}

View File

@ -5,64 +5,64 @@ import useStore from '@/store';
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' }
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' }
});
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
if (!config.headers) {
throw new Error(
`Expected 'config' and 'config.headers' not to be undefined`
);
}
const { user } = useStore();
if (user.token) {
config.headers.Authorization = `${localStorage.get('token')}`;
}
return config;
},
error => {
return Promise.reject(error);
}
(config: AxiosRequestConfig) => {
if (!config.headers) {
throw new Error(
`Expected 'config' and 'config.headers' not to be undefined`
);
}
const { user } = useStore();
if (user.token) {
config.headers.Authorization = `${localStorage.get('token')}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, msg } = response.data;
if (code === '00000') {
return response.data;
} else {
// 响应数据为二进制流处理(Excel导出)
if (response.data instanceof ArrayBuffer) {
return response;
}
(response: AxiosResponse) => {
const { code, msg } = response.data;
if (code === '00000') {
return response.data;
} else {
// 响应数据为二进制流处理(Excel导出)
if (response.data instanceof ArrayBuffer) {
return response;
}
ElMessage({
message: msg || '系统出错',
type: 'error'
});
return Promise.reject(new Error(msg || 'Error'));
}
},
error => {
const { code, msg } = error.response.data;
if (code === 'A0230') {
// token 过期
localStorage.clear(); // 清除浏览器全部缓存
window.location.href = '/'; // 跳转登录页
ElMessageBox.alert('当前页面已失效,请重新登录', '提示', {});
} else {
ElMessage({
message: msg || '系统出错',
type: 'error'
});
}
return Promise.reject(new Error(msg || 'Error'));
}
ElMessage({
message: msg || '系统出错',
type: 'error'
});
return Promise.reject(new Error(msg || 'Error'));
}
},
error => {
const { code, msg } = error.response.data;
if (code === 'A0230') {
// token 过期
localStorage.clear(); // 清除浏览器全部缓存
window.location.href = '/'; // 跳转登录页
ElMessageBox.alert('当前页面已失效,请重新登录', '提示', {});
} else {
ElMessage({
message: msg || '系统出错',
type: 'error'
});
}
return Promise.reject(new Error(msg || 'Error'));
}
);
// 导出 axios 实例

View File

@ -1,72 +1,72 @@
import { ref } from 'vue';
export default function () {
const chart = ref<any>();
const sidebarElm = ref<Element>();
const chart = ref<any>();
const sidebarElm = ref<Element>();
const chartResizeHandler = () => {
if (chart.value) {
chart.value.resize();
}
};
const chartResizeHandler = () => {
if (chart.value) {
chart.value.resize();
}
};
const sidebarResizeHandler = (e: TransitionEvent) => {
if (e.propertyName === 'width') {
chartResizeHandler();
}
};
const sidebarResizeHandler = (e: TransitionEvent) => {
if (e.propertyName === 'width') {
chartResizeHandler();
}
};
const initResizeEvent = () => {
window.addEventListener('resize', chartResizeHandler);
};
const initResizeEvent = () => {
window.addEventListener('resize', chartResizeHandler);
};
const destroyResizeEvent = () => {
window.removeEventListener('resize', chartResizeHandler);
};
const destroyResizeEvent = () => {
window.removeEventListener('resize', chartResizeHandler);
};
const initSidebarResizeEvent = () => {
sidebarElm.value = document.getElementsByClassName('sidebar-container')[0];
if (sidebarElm.value) {
sidebarElm.value.addEventListener(
'transitionend',
sidebarResizeHandler as EventListener
);
}
};
const initSidebarResizeEvent = () => {
sidebarElm.value = document.getElementsByClassName('sidebar-container')[0];
if (sidebarElm.value) {
sidebarElm.value.addEventListener(
'transitionend',
sidebarResizeHandler as EventListener
);
}
};
const destroySidebarResizeEvent = () => {
if (sidebarElm.value) {
sidebarElm.value.removeEventListener(
'transitionend',
sidebarResizeHandler as EventListener
);
}
};
const destroySidebarResizeEvent = () => {
if (sidebarElm.value) {
sidebarElm.value.removeEventListener(
'transitionend',
sidebarResizeHandler as EventListener
);
}
};
const mounted = () => {
initResizeEvent();
initSidebarResizeEvent();
};
const mounted = () => {
initResizeEvent();
initSidebarResizeEvent();
};
const beforeDestroy = () => {
destroyResizeEvent();
destroySidebarResizeEvent();
};
const beforeDestroy = () => {
destroyResizeEvent();
destroySidebarResizeEvent();
};
const activated = () => {
initResizeEvent();
initSidebarResizeEvent();
};
const activated = () => {
initResizeEvent();
initSidebarResizeEvent();
};
const deactivated = () => {
destroyResizeEvent();
destroySidebarResizeEvent();
};
const deactivated = () => {
destroyResizeEvent();
destroySidebarResizeEvent();
};
return {
chart,
mounted,
beforeDestroy,
activated,
deactivated
};
return {
chart,
mounted,
beforeDestroy,
activated,
deactivated
};
}

View File

@ -1,22 +1,22 @@
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
t /= d / 2;
if (t < 1) {
return (c / 2) * t * t + b;
}
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
t /= d / 2;
if (t < 1) {
return (c / 2) * t * t + b;
}
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
};
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
const requestAnimFrame = (function () {
return (
window.requestAnimationFrame ||
(window as any).webkitRequestAnimationFrame ||
(window as any).mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
return (
window.requestAnimationFrame ||
(window as any).webkitRequestAnimationFrame ||
(window as any).mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
})();
/**
@ -24,17 +24,17 @@ const requestAnimFrame = (function () {
* @param {number} amount
*/
const move = (amount: number) => {
document.documentElement.scrollTop = amount;
(document.body.parentNode as HTMLElement).scrollTop = amount;
document.body.scrollTop = amount;
document.documentElement.scrollTop = amount;
(document.body.parentNode as HTMLElement).scrollTop = amount;
document.body.scrollTop = amount;
};
const position = () => {
return (
document.documentElement.scrollTop ||
(document.body.parentNode as HTMLElement).scrollTop ||
document.body.scrollTop
);
return (
document.documentElement.scrollTop ||
(document.body.parentNode as HTMLElement).scrollTop ||
document.body.scrollTop
);
};
/**
@ -43,27 +43,27 @@ const position = () => {
* @param {Function} callback
*/
export const scrollTo = (to: number, duration: number, callback?: any) => {
const start = position();
const change = to - start;
const increment = 20;
let currentTime = 0;
duration = typeof duration === 'undefined' ? 500 : duration;
const animateScroll = function () {
// increment the time
currentTime += increment;
// find the value with the quadratic in-out easing function
const val = easeInOutQuad(currentTime, start, change, duration);
// move the document.body
move(val);
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll);
} else {
if (callback && typeof callback === 'function') {
// the animation is done so lets callback
callback();
}
}
};
animateScroll();
const start = position();
const change = to - start;
const increment = 20;
let currentTime = 0;
duration = typeof duration === 'undefined' ? 500 : duration;
const animateScroll = function () {
// increment the time
currentTime += increment;
// find the value with the quadratic in-out easing function
const val = easeInOutQuad(currentTime, start, change, duration);
// move the document.body
move(val);
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll);
} else {
if (callback && typeof callback === 'function') {
// the animation is done so lets callback
callback();
}
}
};
animateScroll();
};

View File

@ -2,44 +2,44 @@
* window.localStorage
*/
export const localStorage = {
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
const json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
}
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
const json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
}
};
/**
* window.sessionStorage
*/
export const sessionStorage = {
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
const json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear();
}
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
const json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear();
}
};

View File

@ -7,6 +7,6 @@
* @returns {Boolean}
*/
export function isExternal(path: string) {
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
return isExternal;
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
return isExternal;
}

View File

@ -1,174 +1,174 @@
<!-- 线 + 柱混合图 -->
<template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }" />
</template>
<script setup lang="ts">
import {
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
} from 'vue';
import { init, EChartsOption } from 'echarts';
import * as echarts from 'echarts';
import resize from '@/utils/resize';
const props = defineProps({
id: {
type: String,
default: 'barChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
id: {
type: String,
default: 'barChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
});
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
function initChart() {
const barChart = init(document.getElementById(props.id) as HTMLDivElement);
const barChart = init(document.getElementById(props.id) as HTMLDivElement);
barChart.setOption({
title: {
show: true,
text: '业绩总览',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
legend: {
x: 'center',
y: 'bottom',
data: ['收入', '毛利润', '收入增长率', '利润增长率']
},
xAxis: [
{
type: 'category',
data: ['上海', '北京', '浙江', '广东', '深圳', '四川', '湖北', '安徽'],
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
min: 0,
max: 10000,
interval: 2000,
axisLabel: {
formatter: '{value} '
}
},
{
type: 'value',
min: 0,
max: 100,
interval: 20,
axisLabel: {
formatter: '{value}%'
}
}
],
series: [
{
name: '收入',
type: 'bar',
data: [8000, 8200, 7000, 6200, 6500, 5500, 4500, 4200, 3800],
barWidth: 20,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
])
}
},
{
name: '毛利润',
type: 'bar',
data: [6700, 6800, 6300, 5213, 4500, 4200, 4200, 3800],
barWidth: 20,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#25d73c' },
{ offset: 0.5, color: '#1bc23d' },
{ offset: 1, color: '#179e61' }
])
}
},
{
name: '收入增长率',
type: 'line',
yAxisIndex: 1,
data: [65, 67, 65, 53, 47, 45, 43, 42, 41],
itemStyle: {
color: '#67C23A'
}
},
{
name: '利润增长率',
type: 'line',
yAxisIndex: 1,
data: [80, 81, 78, 67, 65, 60, 56, 51, 45],
itemStyle: {
color: '#409EFF'
}
}
]
} as EChartsOption);
chart.value = barChart;
barChart.setOption({
title: {
show: true,
text: '业绩总览',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
legend: {
x: 'center',
y: 'bottom',
data: ['收入', '毛利润', '收入增长率', '利润增长率']
},
xAxis: [
{
type: 'category',
data: ['上海', '北京', '浙江', '广东', '深圳', '四川', '湖北', '安徽'],
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
min: 0,
max: 10000,
interval: 2000,
axisLabel: {
formatter: '{value} '
}
},
{
type: 'value',
min: 0,
max: 100,
interval: 20,
axisLabel: {
formatter: '{value}%'
}
}
],
series: [
{
name: '收入',
type: 'bar',
data: [8000, 8200, 7000, 6200, 6500, 5500, 4500, 4200, 3800],
barWidth: 20,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
])
}
},
{
name: '毛利润',
type: 'bar',
data: [6700, 6800, 6300, 5213, 4500, 4200, 4200, 3800],
barWidth: 20,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#25d73c' },
{ offset: 0.5, color: '#1bc23d' },
{ offset: 1, color: '#179e61' }
])
}
},
{
name: '收入增长率',
type: 'line',
yAxisIndex: 1,
data: [65, 67, 65, 53, 47, 45, 43, 42, 41],
itemStyle: {
color: '#67C23A'
}
},
{
name: '利润增长率',
type: 'line',
yAxisIndex: 1,
data: [80, 81, 78, 67, 65, 60, 56, 51, 45],
itemStyle: {
color: '#409EFF'
}
}
]
} as EChartsOption);
chart.value = barChart;
}
onBeforeUnmount(() => {
beforeDestroy();
beforeDestroy();
});
onActivated(() => {
activated();
activated();
});
onDeactivated(() => {
deactivated();
deactivated();
});
onMounted(() => {
mounted();
nextTick(() => {
initChart();
});
mounted();
nextTick(() => {
initChart();
});
});
</script>

View File

@ -1,134 +1,134 @@
<!-- 漏斗图 -->
<template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }" />
</template>
<script setup lang="ts">
import {
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
} from 'vue';
import { init, EChartsOption } from 'echarts';
import resize from '@/utils/resize';
const props = defineProps({
id: {
type: String,
default: 'funnelChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
id: {
type: String,
default: 'funnelChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
});
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
function initChart() {
const funnelChart = init(document.getElementById(props.id) as HTMLDivElement);
const funnelChart = init(document.getElementById(props.id) as HTMLDivElement);
funnelChart.setOption({
title: {
show: true,
text: '订单线索转化漏斗图',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
legend: {
x: 'center',
y: 'bottom',
data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
},
funnelChart.setOption({
title: {
show: true,
text: '订单线索转化漏斗图',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
legend: {
x: 'center',
y: 'bottom',
data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
},
series: [
{
name: 'Funnel',
type: 'funnel',
left: '20%',
top: 60,
bottom: 60,
width: '60%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: {
fontSize: 20
}
},
data: [
{ value: 60, name: 'Visit' },
{ value: 40, name: 'Inquiry' },
{ value: 20, name: 'Order' },
{ value: 80, name: 'Click' },
{ value: 100, name: 'Show' }
]
}
]
} as EChartsOption);
chart.value = funnelChart;
series: [
{
name: 'Funnel',
type: 'funnel',
left: '20%',
top: 60,
bottom: 60,
width: '60%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: {
fontSize: 20
}
},
data: [
{ value: 60, name: 'Visit' },
{ value: 40, name: 'Inquiry' },
{ value: 20, name: 'Order' },
{ value: 80, name: 'Click' },
{ value: 100, name: 'Show' }
]
}
]
} as EChartsOption);
chart.value = funnelChart;
}
onBeforeUnmount(() => {
beforeDestroy();
beforeDestroy();
});
onActivated(() => {
activated();
activated();
});
onDeactivated(() => {
deactivated();
deactivated();
});
onMounted(() => {
mounted();
nextTick(() => {
initChart();
});
mounted();
nextTick(() => {
initChart();
});
});
</script>

View File

@ -1,114 +1,114 @@
<!-- 饼图 -->
<template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }" />
</template>
<script setup lang="ts">
import {
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
} from 'vue';
import { init, EChartsOption } from 'echarts';
import resize from '@/utils/resize';
const props = defineProps({
id: {
type: String,
default: 'pieChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
id: {
type: String,
default: 'pieChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
});
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
function initChart() {
const pieChart = init(document.getElementById(props.id) as HTMLDivElement);
const pieChart = init(document.getElementById(props.id) as HTMLDivElement);
pieChart.setOption({
title: {
show: true,
text: '产品分类总览',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
legend: {
top: 'bottom'
},
series: [
{
name: 'Nightingale Chart',
type: 'pie',
radius: [50, 130],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 6,
normal: {
color: function (params: any) {
//
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
return colorList[params.dataIndex];
}
}
},
data: [
{ value: 26, name: '家用电器' },
{ value: 27, name: '户外运动' },
{ value: 24, name: '汽车用品' },
{ value: 23, name: '手机数码' }
]
}
]
} as EChartsOption);
pieChart.setOption({
title: {
show: true,
text: '产品分类总览',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
legend: {
top: 'bottom'
},
series: [
{
name: 'Nightingale Chart',
type: 'pie',
radius: [50, 130],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 6,
normal: {
color: function (params: any) {
//
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
return colorList[params.dataIndex];
}
}
},
data: [
{ value: 26, name: '家用电器' },
{ value: 27, name: '户外运动' },
{ value: 24, name: '汽车用品' },
{ value: 23, name: '手机数码' }
]
}
]
} as EChartsOption);
chart.value = pieChart;
chart.value = pieChart;
}
onBeforeUnmount(() => {
beforeDestroy();
beforeDestroy();
});
onActivated(() => {
activated();
activated();
});
onDeactivated(() => {
deactivated();
deactivated();
});
onMounted(() => {
mounted();
nextTick(() => {
initChart();
});
mounted();
nextTick(() => {
initChart();
});
});
</script>

View File

@ -1,133 +1,133 @@
<!-- 雷达图 -->
<template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }" />
</template>
<script setup lang="ts">
import {
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted
} from 'vue';
import { init, EChartsOption } from 'echarts';
import resize from '@/utils/resize';
const props = defineProps({
id: {
type: String,
default: 'radarChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
id: {
type: String,
default: 'radarChart'
},
className: {
type: String,
default: ''
},
width: {
type: String,
default: '200px',
required: true
},
height: {
type: String,
default: '200px',
required: true
}
});
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
function initChart() {
const radarChart = init(document.getElementById(props.id) as HTMLDivElement);
const radarChart = init(document.getElementById(props.id) as HTMLDivElement);
radarChart.setOption({
title: {
show: true,
text: '订单状态统计',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
legend: {
x: 'center',
y: 'bottom',
data: ['预定数量', '下单数量', '发货数量']
},
radar: {
// shape: 'circle',
radius: '60%',
indicator: [
{ name: '家用电器', max: 6500 },
{ name: '服装箱包', max: 16000 },
{ name: '运动户外', max: 30000 },
{ name: '手机数码', max: 38000 },
{ name: '汽车用品', max: 52000 },
{ name: '家具厨具', max: 25000 }
]
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
itemStyle: {
borderRadius: 6,
normal: {
color: function (params: any) {
//
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
return colorList[params.dataIndex];
}
}
},
data: [
{
value: [4200, 10000, 20000, 35000, 50000, 18000],
name: '预定数量'
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: '下单数量'
},
{
value: [5000, 12000, 23000, 18000, 31000, 11000],
name: '发货数量'
}
]
}
]
} as EChartsOption);
radarChart.setOption({
title: {
show: true,
text: '订单状态统计',
x: 'center',
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
},
legend: {
x: 'center',
y: 'bottom',
data: ['预定数量', '下单数量', '发货数量']
},
radar: {
// shape: 'circle',
radius: '60%',
indicator: [
{ name: '家用电器', max: 6500 },
{ name: '服装箱包', max: 16000 },
{ name: '运动户外', max: 30000 },
{ name: '手机数码', max: 38000 },
{ name: '汽车用品', max: 52000 },
{ name: '家具厨具', max: 25000 }
]
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
itemStyle: {
borderRadius: 6,
normal: {
color: function (params: any) {
//
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
return colorList[params.dataIndex];
}
}
},
data: [
{
value: [4200, 10000, 20000, 35000, 50000, 18000],
name: '预定数量'
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: '下单数量'
},
{
value: [5000, 12000, 23000, 18000, 31000, 11000],
name: '发货数量'
}
]
}
]
} as EChartsOption);
chart.value = radarChart;
chart.value = radarChart;
}
onBeforeUnmount(() => {
beforeDestroy();
beforeDestroy();
});
onActivated(() => {
activated();
activated();
});
onDeactivated(() => {
deactivated();
deactivated();
});
onMounted(() => {
mounted();
nextTick(() => {
initChart();
});
mounted();
nextTick(() => {
initChart();
});
});
</script>

View File

@ -1,119 +1,119 @@
<template>
<div class="component-container">
<el-card class="project-card">
<template #header>
<span class="fw-b">有来项目简介</span>
</template>
<div class="project-card__main">
<!-- 项目简介 -->
<el-link target="_blank" type="primary" href="https://gitee.com/haoxr">
youlai-mall
</el-link>
是基于Spring Boot 2.6Spring Cloud 2021 & Alibaba
2021Vue3Element-Plusuni-app等主流技术栈构建的一整套全栈开源商城项目
涉及
<el-link
target="_blank"
type="primary"
href="https://gitee.com/youlaitech/youlai-mall"
>后端微服务</el-link
>
<el-link
target="_blank"
type="success"
href="https://gitee.com/youlaitech/youlai-mall-admin"
>前端管理</el-link
>
<el-link
target="_blank"
type="warning"
href="https://gitee.com/youlaitech/youlai-mall-weapp"
>微信小程序
</el-link>
<el-link
target="_blank"
type="danger"
href="https://gitee.com/youlaitech/youlai-mall-weapp"
>APP应用</el-link
>
等多端的开发
<el-divider />
<div class="component-container">
<el-card class="project-card">
<template #header>
<span class="fw-b">有来项目简介</span>
</template>
<div class="project-card__main">
<!-- 项目简介 -->
<el-link target="_blank" type="primary" href="https://gitee.com/haoxr">
youlai-mall
</el-link>
是基于Spring Boot 2.6Spring Cloud 2021 & Alibaba
2021Vue3Element-Plusuni-app等主流技术栈构建的一整套全栈开源商城项目
涉及
<el-link
target="_blank"
type="primary"
href="https://gitee.com/youlaitech/youlai-mall"
>后端微服务</el-link
>
<el-link
target="_blank"
type="success"
href="https://gitee.com/youlaitech/youlai-mall-admin"
>前端管理</el-link
>
<el-link
target="_blank"
type="warning"
href="https://gitee.com/youlaitech/youlai-mall-weapp"
>微信小程序
</el-link>
<el-link
target="_blank"
type="danger"
href="https://gitee.com/youlaitech/youlai-mall-weapp"
>APP应用</el-link
>
等多端的开发
<el-divider />
<!-- 源码地址 -->
<el-row :gutter="10">
<el-col :span="6">
<el-badge value="免费开源" class="fw-b"> 项目地址 </el-badge>
</el-col>
<el-col :span="6">
<el-link
target="_blank"
type="warning"
href="http://youlaitech.gitee.io/youlai-mall"
>官方文档(完善中..)</el-link
>
</el-col>
<el-col :span="6">
<el-link
target="_blank"
type="primary"
href="https://github.com/youlaitech"
>Github</el-link
>
</el-col>
<el-col :span="6">
<el-link
target="_blank"
type="success"
href="https://gitee.com/youlaiorg"
>码云</el-link
>
</el-col>
</el-row>
<el-divider />
<!-- 技术栈 -->
<el-row :gutter="10">
<el-col :span="6" class="fw-b"> 后端技术栈 </el-col>
<el-col :span="18">
Spring BootSpring Cloud & AlibabaSpring Security
OAuth2JWTElastic Stack K8s...
</el-col>
</el-row>
<el-divider />
<el-row :gutter="10">
<el-col :span="6" class="fw-b"> 前端技术栈 </el-col>
<el-col :span="18">
Vue3TypeScriptElement-Plusuni-appvue3-element-admin ...
</el-col>
</el-row>
</div>
</el-card>
</div>
<!-- 源码地址 -->
<el-row :gutter="10">
<el-col :span="6">
<el-badge value="免费开源" class="fw-b"> 项目地址 </el-badge>
</el-col>
<el-col :span="6">
<el-link
target="_blank"
type="warning"
href="http://youlaitech.gitee.io/youlai-mall"
>官方文档(完善中..)</el-link
>
</el-col>
<el-col :span="6">
<el-link
target="_blank"
type="primary"
href="https://github.com/youlaitech"
>Github</el-link
>
</el-col>
<el-col :span="6">
<el-link
target="_blank"
type="success"
href="https://gitee.com/youlaiorg"
>码云</el-link
>
</el-col>
</el-row>
<el-divider />
<!-- 技术栈 -->
<el-row :gutter="10">
<el-col :span="6" class="fw-b"> 后端技术栈 </el-col>
<el-col :span="18">
Spring BootSpring Cloud & AlibabaSpring Security
OAuth2JWTElastic Stack K8s...
</el-col>
</el-row>
<el-divider />
<el-row :gutter="10">
<el-col :span="6" class="fw-b"> 前端技术栈 </el-col>
<el-col :span="18">
Vue3TypeScriptElement-Plusuni-appvue3-element-admin ...
</el-col>
</el-row>
</div>
</el-card>
</div>
</template>
<script>
export default {
name: 'index'
name: 'index'
};
</script>
<style lang="scss" scoped>
.component-container {
.project-card {
font-size: 14px;
.project-card {
font-size: 14px;
&__main {
line-height: 28px;
height: 320px;
overflow-y: auto;
overflow-x: hidden;
}
}
&__main {
line-height: 28px;
height: 320px;
overflow-y: auto;
overflow-x: hidden;
}
}
.fw-b {
font-weight: bold;
}
.fw-b {
font-weight: bold;
}
}
</style>

View File

@ -1,75 +1,75 @@
<!-- 团队介绍 -->
<template>
<div class="component-container">
<el-card class="team-card">
<template #header>
<span class="fw-b">有来开源组织 & 技术团队</span>
</template>
<el-tabs v-model="teamActiveName">
<el-tab-pane label="开发者" name="developer">
<div class="developer" ref="dev_wrapper">
<ul class="developer__container">
<li
class="developer__item"
v-for="(item, index) in developers"
:key="index"
>
<div class="developer__inner">
<el-image
class="developer__img"
:src="item.imgUrl"
:preview-src-list="[item.imgUrl]"
></el-image>
<div class="developer__info">
<span class="developer__nickname">{{ item.nickname }}</span>
<div class="developer__position">
<el-tag
v-for="(position, i) in item.positions"
:type="(colors[i % colors.length] as any)"
:class="i !== 0 ? 'f-ml' : ''"
size="small"
:key="i"
>{{ position }}</el-tag
>
</div>
<div class="developer__homepage">
<a :href="item.homepage" target="_blank">个人主页</a>
</div>
</div>
</div>
</li>
</ul>
<el-image class="developer__indicator" :src="indicatorImgUrl" />
</div>
</el-tab-pane>
<div class="component-container">
<el-card class="team-card">
<template #header>
<span class="fw-b">有来开源组织 & 技术团队</span>
</template>
<el-tabs v-model="teamActiveName">
<el-tab-pane label="开发者" name="developer">
<div class="developer" ref="dev_wrapper">
<ul class="developer__container">
<li
class="developer__item"
v-for="(item, index) in developers"
:key="index"
>
<div class="developer__inner">
<el-image
class="developer__img"
:src="item.imgUrl"
:preview-src-list="[item.imgUrl]"
></el-image>
<div class="developer__info">
<span class="developer__nickname">{{ item.nickname }}</span>
<div class="developer__position">
<el-tag
v-for="(position, i) in item.positions"
:type="(colors[i % colors.length] as any)"
:class="i !== 0 ? 'f-ml' : ''"
size="small"
:key="i"
>{{ position }}</el-tag
>
</div>
<div class="developer__homepage">
<a :href="item.homepage" target="_blank">个人主页</a>
</div>
</div>
</div>
</li>
</ul>
<el-image class="developer__indicator" :src="indicatorImgUrl" />
</div>
</el-tab-pane>
<el-tab-pane label="交流群" name="2">
<div class="group">
<el-image
class="group-img"
src="https://www.youlai.tech/files/blog/youlaiqun.png"
:preview-src-list="[
'https://www.youlai.tech/files/blog/youlaiqun.png'
]"
/>
<div class="group-tip">
群二维码过期可添加开发者微信由其拉入群备注有来即可
</div>
</div>
</el-tab-pane>
<el-tab-pane label="交流群" name="2">
<div class="group">
<el-image
class="group-img"
src="https://www.youlai.tech/files/blog/youlaiqun.png"
:preview-src-list="[
'https://www.youlai.tech/files/blog/youlaiqun.png'
]"
/>
<div class="group-tip">
群二维码过期可添加开发者微信由其拉入群备注有来即可
</div>
</div>
</el-tab-pane>
<el-tab-pane label="加入我们" name="3">
<div class="join">
<p>1. 人品良好善于思考执行力强</p>
<p>2. 熟悉项目且至少给项目提交()一个PR</p>
<p>3. Git代码库活跃个人主页或博客完善者优先</p>
<p>4. 过分优秀者我们会主动联系您...</p>
<div class="join__desc">申请加入方式: 添加开发者微信申请即可</div>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
<el-tab-pane label="加入我们" name="3">
<div class="join">
<p>1. 人品良好善于思考执行力强</p>
<p>2. 熟悉项目且至少给项目提交()一个PR</p>
<p>3. Git代码库活跃个人主页或博客完善者优先</p>
<p>4. 过分优秀者我们会主动联系您...</p>
<div class="join__desc">申请加入方式: 添加开发者微信申请即可</div>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup lang="ts">
@ -77,32 +77,32 @@ import { nextTick, onMounted, reactive, ref, toRefs, watchEffect } from 'vue';
import BScroll from 'better-scroll';
const state = reactive({
teamActiveName: 'developer',
developers: [
{
imgUrl: 'https://s2.loli.net/2022/04/06/yRx8uzj4emA5QVr.jpg',
nickname: '郝先瑞',
positions: ['后端', '前端', '文档'],
homepage: 'https://www.cnblogs.com/haoxianrui/'
},
{
imgUrl: 'https://s2.loli.net/2022/04/06/cQihGv9uPsTjXk1.jpg',
nickname: '张川',
positions: ['后端', '前端'],
homepage: 'https://blog.csdn.net/qq_41595149'
},
{
imgUrl: 'https://s2.loli.net/2022/04/07/2IiOYBHnRGKgCSd.jpg',
nickname: '张加林',
positions: ['DevOps'],
homepage: 'https://gitee.com/ximy'
}
],
colors: ['', 'success', 'warning', 'danger'],
indicatorImgUrl: new URL(
`../../../../assets/index/indicator.png`,
import.meta.url
).href
teamActiveName: 'developer',
developers: [
{
imgUrl: 'https://s2.loli.net/2022/04/06/yRx8uzj4emA5QVr.jpg',
nickname: '郝先瑞',
positions: ['后端', '前端', '文档'],
homepage: 'https://www.cnblogs.com/haoxianrui/'
},
{
imgUrl: 'https://s2.loli.net/2022/04/06/cQihGv9uPsTjXk1.jpg',
nickname: '张川',
positions: ['后端', '前端'],
homepage: 'https://blog.csdn.net/qq_41595149'
},
{
imgUrl: 'https://s2.loli.net/2022/04/07/2IiOYBHnRGKgCSd.jpg',
nickname: '张加林',
positions: ['DevOps'],
homepage: 'https://gitee.com/ximy'
}
],
colors: ['', 'success', 'warning', 'danger'],
indicatorImgUrl: new URL(
`../../../../assets/index/indicator.png`,
import.meta.url
).href
});
const { teamActiveName, developers, colors, indicatorImgUrl } = toRefs(state);
@ -112,17 +112,17 @@ let bScroll = reactive({});
const dev_wrapper = ref<HTMLElement | any>(null);
onMounted(() => {
bScroll = new BScroll(dev_wrapper.value, {
mouseWheel: true, //
disableMouse: false, //
scrollX: true //X
});
bScroll = new BScroll(dev_wrapper.value, {
mouseWheel: true, //
disableMouse: false, //
scrollX: true //X
});
});
watchEffect(() => {
nextTick(() => {
bScroll && (bScroll as any).refresh();
});
nextTick(() => {
bScroll && (bScroll as any).refresh();
});
});
// let bScroll = reactive({})
@ -146,116 +146,116 @@ watchEffect(() => {
<style lang="scss" scoped>
.component-container {
.team-card {
font-size: 14px;
.team-card {
font-size: 14px;
.el-tabs__content {
.el-tab-pane {
height: 265px;
}
}
.el-tabs__content {
.el-tab-pane {
height: 265px;
}
}
.developer {
width: 100%;
overflow: hidden;
.developer {
width: 100%;
overflow: hidden;
&__container {
display: inline-flex;
overflow: hidden;
justify-content: flex-start;
padding: 10px;
&__container {
display: inline-flex;
overflow: hidden;
justify-content: flex-start;
padding: 10px;
.developer__item {
&:not(:first-child) {
margin-left: 20px;
}
.developer__item {
&:not(:first-child) {
margin-left: 20px;
}
align-items: center;
list-style: none;
width: 180px;
min-width: 180px;
align-items: center;
list-style: none;
width: 180px;
min-width: 180px;
.developer__inner {
border: 1px solid #cccccc;
border-radius: 5px;
box-shadow: 6px 6px 6px #aaa;
padding: 8px;
text-align: center;
.developer__inner {
border: 1px solid #cccccc;
border-radius: 5px;
box-shadow: 6px 6px 6px #aaa;
padding: 8px;
text-align: center;
.developer__img {
height: 100px;
width: 100px;
}
.developer__img {
height: 100px;
width: 100px;
}
.developer__info {
padding: 6px;
font-size: 14px;
.developer__info {
padding: 6px;
font-size: 14px;
.developer__position {
margin-top: 10px;
}
.developer__position {
margin-top: 10px;
}
.developer__homepage {
margin-top: 16px;
.developer__homepage {
margin-top: 16px;
a {
display: inline-block;
padding: 4px 10px;
color: #409eff;
border: 1px solid #409eff;
border-radius: 5px;
background: #ecf5ff;
a {
display: inline-block;
padding: 4px 10px;
color: #409eff;
border: 1px solid #409eff;
border-radius: 5px;
background: #ecf5ff;
&:hover {
background: #409eff;
color: #ffffff;
}
}
}
}
}
}
}
&:hover {
background: #409eff;
color: #ffffff;
}
}
}
}
}
}
}
&__indicator {
position: absolute;
right: 0;
bottom: 0;
width: 120px;
height: 100px;
}
}
&__indicator {
position: absolute;
right: 0;
bottom: 0;
width: 120px;
height: 100px;
}
}
.join {
height: 240px;
.join {
height: 240px;
p {
font-weight: bold;
}
p {
font-weight: bold;
}
&__desc {
margin-top: 20px;
color: #409eff;
font-weight: bold;
}
}
&__desc {
margin-top: 20px;
color: #409eff;
font-weight: bold;
}
}
.group {
height: 254px;
.group {
height: 254px;
&-img {
height: 200px;
width: 200px;
}
}
}
&-img {
height: 200px;
width: 200px;
}
}
}
.fw-b {
font-weight: bold;
}
.fw-b {
font-weight: bold;
}
.f-ml {
margin-left: 5px;
}
.f-ml {
margin-left: 5px;
}
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<div class="dashboard-container">
<github-corner class="github-corner" />
<div class="dashboard-container">
<github-corner class="github-corner" />
<!-- 数据 -->
<el-row :gutter="40" class="card-panel__col">
<!-- <el-col :xs="24" :lg="6" class="card-panel__col">
<!-- 数据 -->
<el-row :gutter="40" class="card-panel__col">
<!-- <el-col :xs="24" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper" style="margin-top: -10px">
<el-image style="width:200px; height: 100px"
@ -22,99 +22,99 @@
</div>
</el-col>-->
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-people">
<svg-icon icon-class="uv" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">访问数</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-people">
<svg-icon icon-class="uv" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">访问数</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">消息数</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">消息数</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">收入金额</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">订单数</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
</el-row>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">收入金额</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">订单数</div>
<div class="card-panel-num">1000</div>
</div>
</div>
</el-col>
</el-row>
<!-- 项目 + 团队成员介绍 -->
<el-row :gutter="40">
<!-- 项目介绍 -->
<el-col :md="12" :lg="12" class="card-panel__col">
<Project />
</el-col>
<!-- 项目 + 团队成员介绍 -->
<el-row :gutter="40">
<!-- 项目介绍 -->
<el-col :md="12" :lg="12" class="card-panel__col">
<Project />
</el-col>
<!-- 团队介绍 -->
<el-col :md="12" :lg="12" class="card-panel__col">
<Team />
</el-col>
</el-row>
<!-- 团队介绍 -->
<el-col :md="12" :lg="12" class="card-panel__col">
<Team />
</el-col>
</el-row>
<!-- Echarts 图表 -->
<el-row :gutter="40" style="margin-top: 20px">
<el-col :sm="24" :lg="8" class="card-panel__col">
<BarChart
id="barChart"
height="400px"
width="100%"
class="chart-container"
/>
</el-col>
<!-- Echarts 图表 -->
<el-row :gutter="40" style="margin-top: 20px">
<el-col :sm="24" :lg="8" class="card-panel__col">
<BarChart
id="barChart"
height="400px"
width="100%"
class="chart-container"
/>
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="card-panel__col">
<PieChart
id="pieChart"
height="400px"
width="100%"
class="chart-container"
/>
<!--订单漏斗图-->
<!--<FunnelChart id="funnelChart" height="400px" width="100%" class="chart-container"/>-->
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="card-panel__col">
<PieChart
id="pieChart"
height="400px"
width="100%"
class="chart-container"
/>
<!--订单漏斗图-->
<!--<FunnelChart id="funnelChart" height="400px" width="100%" class="chart-container"/>-->
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="card-panel__col">
<RadarChart
id="radarChart"
height="400px"
width="100%"
class="chart-container"
/>
</el-col>
</el-row>
</div>
<el-col :xs="24" :sm="12" :lg="8" class="card-panel__col">
<RadarChart
id="radarChart"
height="400px"
width="100%"
class="chart-container"
/>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
@ -131,155 +131,155 @@ import Team from './components/Team/index.vue';
<style lang="scss" scoped>
.dashboard-container {
padding: 24px;
background-color: rgb(240, 242, 245);
position: relative;
padding: 24px;
background-color: rgb(240, 242, 245);
position: relative;
.github-corner {
position: absolute;
top: 0px;
border: 0;
right: 0;
z-index: 99;
}
.github-corner {
position: absolute;
top: 0px;
border: 0;
right: 0;
z-index: 99;
}
.box-center {
margin: 0 auto;
display: table;
}
.box-center {
margin: 0 auto;
display: table;
}
.user-profile {
.user-name {
}
.user-profile {
.user-name {
}
.box-center {
padding-top: 10px;
}
.box-center {
padding-top: 10px;
}
.user-role {
padding-top: 10px;
font-weight: 400;
font-size: 14px;
}
.user-role {
padding-top: 10px;
font-weight: 400;
font-size: 14px;
}
.box-social {
padding-top: 30px;
.box-social {
padding-top: 30px;
.el-table {
border-top: 1px solid #dfe6ec;
}
}
.el-table {
border-top: 1px solid #dfe6ec;
}
}
.user-follow {
padding-top: 20px;
}
}
.user-follow {
padding-top: 20px;
}
}
.card-panel__col {
margin-bottom: 12px;
}
.card-panel__col {
margin-bottom: 12px;
}
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.05);
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.05);
&:hover {
.card-panel-icon-wrapper {
color: #fff;
}
&:hover {
.card-panel-icon-wrapper {
color: #fff;
}
.icon-user {
background: #e77541;
}
.icon-user {
background: #e77541;
}
.icon-people {
background: #40c9c6;
}
.icon-people {
background: #40c9c6;
}
.icon-message {
background: #36a3f7;
}
.icon-message {
background: #36a3f7;
}
.icon-money {
background: #f4516c;
}
.icon-money {
background: #f4516c;
}
.icon-shopping {
background: #34bfa3;
}
}
.icon-shopping {
background: #34bfa3;
}
}
.icon-people {
color: #40c9c6;
.icon-people {
color: #40c9c6;
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.icon-message {
color: #36a3f7;
.icon-message {
color: #36a3f7;
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.icon-money {
color: #f4516c;
.icon-money {
color: #f4516c;
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.icon-shopping {
color: #34bfa3;
.icon-shopping {
color: #34bfa3;
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.svg-icon {
width: 4em !important;
height: 4em !important;
}
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px 20px 0;
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px 20px 0;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
.card-panel-num {
font-size: 20px;
}
}
}
.chart-container {
background: #ffffff;
}
.chart-container {
background: #ffffff;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More