fix Bug or esLink formatting

This commit is contained in:
Ah jung
2021-07-22 13:47:44 +08:00
parent f6be8f521e
commit 7f81152793
172 changed files with 10553 additions and 9031 deletions

View File

@@ -1,9 +1,11 @@
module.exports = {
// @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
@@ -12,21 +14,46 @@ module.exports = {
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
jsx: true,
},
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended'
'plugin:prettier/recommended',
'plugin:jest/recommended',
],
rules: {
'vue/no-unused-components': 'off',
'vue/no-unused-vars': 'off',
'vue/no-v-for-template-key-on-child': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
// 'vue/attributes-order': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
@@ -34,34 +61,17 @@ module.exports = {
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'space-before-function-paren': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
component: 'always',
},
svg: 'always',
math: 'always'
}
]
}
}
math: 'always',
},
],
},
});

View File

@@ -1,18 +1,18 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
const modules = import.meta.globEager('./**/*.ts');
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...modules[key].default);
});
/**
* Used in a production environment. Need to manually import all modules
*/
export function setupProdMockServer() {
createProdMockServer(mockModules);
}
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
const modules = import.meta.globEager('./**/*.ts');
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...modules[key].default);
});
/**
* Used in a production environment. Need to manually import all modules
*/
export function setupProdMockServer() {
createProdMockServer(mockModules);
}

View File

@@ -1,4 +1,4 @@
import Mock from 'mockjs'
import Mock from 'mockjs';
export function resultSuccess(result, { message = 'ok' } = {}) {
return Mock.mock({
@@ -50,10 +50,10 @@ export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]
* @param {Number} times 回调函数需要执行的次数
* @param {Function} callback 回调函数
*/
export function doCustomTimes (times:number, callback:any) {
let i = -1
export function doCustomTimes(times: number, callback: any) {
let i = -1;
while (++i < times) {
callback(i)
callback(i);
}
}

View File

@@ -1,36 +1,35 @@
import { Random } from 'mockjs'
import { resultSuccess } from '../_util'
import { Random } from 'mockjs';
import { resultSuccess } from '../_util';
const consoleInfo = {
//访问量
visits:{
dayVisits:Random.float(10000,99999,2,2),
rise:Random.float(10,99),
decline:Random.float(10,99),
amount:Random.float(99999,999999,3,5),
visits: {
dayVisits: Random.float(10000, 99999, 2, 2),
rise: Random.float(10, 99),
decline: Random.float(10, 99),
amount: Random.float(99999, 999999, 3, 5),
},
//销售额
saleroom:{
weekSaleroom:Random.float(10000,99999,2,2),
amount:Random.float(99999,999999,2,2),
degree:Random.float(10,99)
saleroom: {
weekSaleroom: Random.float(10000, 99999, 2, 2),
amount: Random.float(99999, 999999, 2, 2),
degree: Random.float(10, 99),
},
//订单量
orderLarge:{
weekLarge:Random.float(10000,99999,2,2),
rise:Random.float(10,99),
decline:Random.float(10,99),
amount:Random.float(99999,999999,2,2),
orderLarge: {
weekLarge: Random.float(10000, 99999, 2, 2),
rise: Random.float(10, 99),
decline: Random.float(10, 99),
amount: Random.float(99999, 999999, 2, 2),
},
//成交额度
volume:{
weekLarge:Random.float(10000,99999,2,2),
rise:Random.float(10,99),
decline:Random.float(10,99),
amount:Random.float(99999,999999,2,2)
volume: {
weekLarge: Random.float(10000, 99999, 2, 2),
rise: Random.float(10, 99),
decline: Random.float(10, 99),
amount: Random.float(99999, 999999, 2, 2),
},
}
};
export default [
//主控台 卡片数据
@@ -41,7 +40,5 @@ export default [
response: () => {
return resultSuccess(consoleInfo);
},
}
]
},
];

View File

@@ -1,93 +1,89 @@
import { resultSuccess } from '../_util'
import { resultSuccess } from '../_util';
const menuList = (() => {
const result: any[] = [
const menuList = () => {
const result: any[] = [
{
label: 'Dashboard',
key: 'dashboard',
type: 1,
subtitle: 'dashboard',
openType: 1,
auth: 'dashboard',
path: '/dashboard',
children: [
{
label: 'Dashboard',
key: 'dashboard',
type: 1,
subtitle:'dashboard',
openType:1,
auth:'dashboard',
path:'/dashboard',
children: [
{
label: '主控台',
key: 'console',
type: 1,
subtitle:'console',
openType:1,
auth:'console',
path:'/dashboard/console',
},
{
label: '工作台',
key: 'workplace',
type: 1,
subtitle:'workplace',
openType:1,
auth:'workplace',
path:'/dashboard/workplace',
}
]
label: '主控台',
key: 'console',
type: 1,
subtitle: 'console',
openType: 1,
auth: 'console',
path: '/dashboard/console',
},
{
label: '表单管理',
key: 'form',
type: 1,
subtitle:'form',
openType:1,
auth:'form',
path:'/form',
children: [
{
label: '基础表单',
key: 'basic-form',
type: 1,
subtitle:'basic-form',
openType:1,
auth:'basic-form',
path:'/form/basic-form',
},
{
label: '分步表单',
key: 'step-form',
type: 1,
subtitle:'step-form',
openType:1,
auth:'step-form',
path:'/form/step-form',
},
{
label: '表单详情',
key: 'detail',
type: 1,
subtitle:'detail',
openType:1,
auth:'detail',
path:'/form/detail',
}
]
}
]
return result
});
label: '工作台',
key: 'workplace',
type: 1,
subtitle: 'workplace',
openType: 1,
auth: 'workplace',
path: '/dashboard/workplace',
},
],
},
{
label: '表单管理',
key: 'form',
type: 1,
subtitle: 'form',
openType: 1,
auth: 'form',
path: '/form',
children: [
{
label: '基础表单',
key: 'basic-form',
type: 1,
subtitle: 'basic-form',
openType: 1,
auth: 'basic-form',
path: '/form/basic-form',
},
{
label: '分步表单',
key: 'step-form',
type: 1,
subtitle: 'step-form',
openType: 1,
auth: 'step-form',
path: '/form/step-form',
},
{
label: '表单详情',
key: 'detail',
type: 1,
subtitle: 'detail',
openType: 1,
auth: 'detail',
path: '/form/detail',
},
],
},
];
return result;
};
export default [
{
url: '/api/menu/list',
timeout: 1000,
method: 'get',
response: () => {
const list = menuList()
return resultSuccess({
list
}
);
},
}
]
{
url: '/api/menu/list',
timeout: 1000,
method: 'get',
response: () => {
const list = menuList();
return resultSuccess({
list,
});
},
},
];

View File

@@ -1,50 +1,45 @@
import { resultSuccess, doCustomTimes } from '../_util'
import { resultSuccess, doCustomTimes } from '../_util';
function getMenuKeys() {
let keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail']
let newKeys = []
doCustomTimes(parseInt(Math.random()*6), () => {
let key = keys[Math.floor(Math.random() * keys.length)];
newKeys.push(key)
})
return Array.from(new Set(newKeys));
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
const newKeys = [];
doCustomTimes(parseInt(Math.random() * 6), () => {
const key = keys[Math.floor(Math.random() * keys.length)];
newKeys.push(key);
});
return Array.from(new Set(newKeys));
}
const roleList = ((pageSize) => {
const result: any[] = []
doCustomTimes(pageSize, () => {
result.push({
id: '@integer(10,100)',
name: '@cname()',
explain: '@cname()',
isDefault: '@boolean()',
menu_keys: getMenuKeys(),
create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
'status|1': ['normal', 'enable', 'disable'],
});
})
return result
});
const roleList = (pageSize) => {
const result: any[] = [];
doCustomTimes(pageSize, () => {
result.push({
id: '@integer(10,100)',
name: '@cname()',
explain: '@cname()',
isDefault: '@boolean()',
menu_keys: getMenuKeys(),
create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
'status|1': ['normal', 'enable', 'disable'],
});
});
return result;
};
export default [
{
url: '/api/role/list',
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 10 } = query;
const list = roleList(Number(pageSize))
return resultSuccess({
page: Number(page),
pageSize: Number(pageSize),
pageCount: 60,
list
}
);
},
}
]
{
url: '/api/role/list',
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 10 } = query;
const list = roleList(Number(pageSize));
return resultSuccess({
page: Number(page),
pageSize: Number(pageSize),
pageCount: 60,
list,
});
},
},
];

View File

@@ -1,9 +1,9 @@
import { Random } from 'mockjs'
import { resultSuccess, doCustomTimes, resultPageSuccess } from '../_util'
import { Random } from 'mockjs';
import { resultSuccess, doCustomTimes } from '../_util';
const tableList = ((pageSize) => {
const result:any[] = []
doCustomTimes(pageSize,()=> {
const tableList = (pageSize) => {
const result: any[] = [];
doCustomTimes(pageSize, () => {
result.push({
id: '@integer(10,100)',
beginTime: '@datetime',
@@ -16,10 +16,9 @@ const tableList = ((pageSize) => {
'no|100000-10000000': 100000,
'status|1': ['normal', 'enable', 'disable'],
});
})
return result
});
});
return result;
};
export default [
//表格数据列表
@@ -29,16 +28,13 @@ export default [
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 10 } = query;
const list = tableList(Number(pageSize))
const list = tableList(Number(pageSize));
return resultSuccess({
page:Number(page),
pageSize:Number(pageSize),
page: Number(page),
pageSize: Number(pageSize),
pageCount: 60,
list
}
);
list,
});
},
}
]
},
];

View File

@@ -1,53 +1,52 @@
import { MockMethod } from 'vite-plugin-mock'
import { resultSuccess, getRequestToken } from '../_util'
const menusList = [
{
path: '/dashboard',
name: 'Dashboard',
component: 'Layout',
redirect: '/dashboard/console',
meta: {
icon: 'DashboardOutlined',
title: 'Dashboard',
},
children: [
{
path: 'console',
name: 'dashboard_console',
component: 'DashboardConsole',
meta: {
title: '主控台',
}
},
{
path: 'monitor',
name: 'dashboard_monitor',
component: 'DashboardMonitor',
meta: {
title: '监控页',
}
},
{
path: 'workplace',
name: 'dashboard_workplace',
component: 'DashboardWorkplace',
meta: {
hidden: true,
title: '工作台',
}
},
],
}
]
export default [
{
url: '/api/menus',
timeout: 1000,
method: 'get',
response: () => {
return resultSuccess(menusList);
},
}
]
import { resultSuccess } from '../_util';
const menusList = [
{
path: '/dashboard',
name: 'Dashboard',
component: 'Layout',
redirect: '/dashboard/console',
meta: {
icon: 'DashboardOutlined',
title: 'Dashboard',
},
children: [
{
path: 'console',
name: 'dashboard_console',
component: 'DashboardConsole',
meta: {
title: '主控台',
},
},
{
path: 'monitor',
name: 'dashboard_monitor',
component: 'DashboardMonitor',
meta: {
title: '监控页',
},
},
{
path: 'workplace',
name: 'dashboard_workplace',
component: 'DashboardWorkplace',
meta: {
hidden: true,
title: '工作台',
},
},
],
},
];
export default [
{
url: '/api/menus',
timeout: 1000,
method: 'get',
response: () => {
return resultSuccess(menusList);
},
},
];

View File

@@ -1,9 +1,9 @@
import Mock from 'mockjs'
import { resultSuccess, getRequestToken } from '../_util'
import Mock from 'mockjs';
import { resultSuccess } from '../_util';
const Random = Mock.Random
const Random = Mock.Random;
const token = Random.string('upper', 32, 32)
const token = Random.string('upper', 32, 32);
const adminInfo = {
userId: '1',
@@ -33,9 +33,9 @@ const adminInfo = {
{
roleName: '基础列表删除',
value: 'basic_list_delete',
}
},
],
}
};
export default [
{
@@ -56,4 +56,4 @@ export default [
return resultSuccess(adminInfo);
},
},
]
];

View File

@@ -62,11 +62,14 @@
"dotenv": "^10.0.0",
"eslint": "^7.28.0",
"eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.9",
"eslint-plugin-jest": "^24.4.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.11.1",
"esno": "^0.7.3",
"gh-pages": "^3.2.0",
"husky": "^6.0.0",
"jest": "^27.0.6",
"less": "^4.1.1",
"less-loader": "^9.0.0",
"lint-staged": "^11.0.0",

View File

@@ -1,105 +1,105 @@
<template>
<NConfigProvider
v-if="!isLock"
:locale="zhCN"
:theme="getDarkTheme"
:theme-overrides="getThemeOverrides"
:date-locale="dateZhCN"
v-if="!isLock"
:locale="zhCN"
:theme="getDarkTheme"
:theme-overrides="getThemeOverrides"
:date-locale="dateZhCN"
>
<AppProvider>
<RouterView/>
<RouterView />
</AppProvider>
</NConfigProvider>
<transition v-if="isLock && $route.name != 'login'" name="slide-up">
<LockScreen/>
<LockScreen />
</transition>
</template>
<script lang="ts">
import { defineComponent, computed, onMounted, onUnmounted } from 'vue'
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui'
import { LockScreen } from '@/components/Lockscreen'
import { AppProvider } from '@/components/Application'
import { useLockscreenStore } from '@/store/modules/lockscreen'
import { useRoute } from 'vue-router'
import { useDesignSettingStore } from '@/store/modules/designSetting'
import { defineComponent, computed, onMounted, onUnmounted } from 'vue';
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui';
import { LockScreen } from '@/components/Lockscreen';
import { AppProvider } from '@/components/Application';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import { useRoute } from 'vue-router';
import { useDesignSettingStore } from '@/store/modules/designSetting';
export default defineComponent({
name: 'App',
components: { LockScreen, AppProvider },
setup() {
const route = useRoute();
const useLockscreen = useLockscreenStore();
const designStore = useDesignSettingStore();
const isLock = computed(() => useLockscreen.isLock);
const lockTime = computed(() => useLockscreen.lockTime);
/**
* @type import('naive-ui').GlobalThemeOverrides
*/
const getThemeOverrides = computed(() => {
return {
common: {
primaryColor: designStore.appTheme,
primaryColorHover: '#57a3f3',
},
};
});
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
let timer;
const timekeeping = () => {
clearInterval(timer);
if (route.name == 'login' || isLock.value) return;
// 设置不锁屏
useLockscreen.setLock(false);
// 重置锁屏时间
useLockscreen.setLockTime();
timer = setInterval(() => {
// 锁屏倒计时递减
useLockscreen.setLockTime(lockTime.value - 1);
if (lockTime.value <= 0) {
// 设置锁屏
useLockscreen.setLock(true);
return clearInterval(timer);
}
}, 1000);
};
onMounted(() => {
document.addEventListener('mousedown', timekeeping);
});
onUnmounted(() => {
document.removeEventListener('mousedown', timekeeping);
});
export default defineComponent({
name: 'App',
components: { LockScreen, AppProvider },
setup() {
const route = useRoute()
const useLockscreen = useLockscreenStore()
const designStore = useDesignSettingStore()
const isLock = computed(() => useLockscreen.isLock)
const lockTime = computed(() => useLockscreen.lockTime)
/**
* @type import('naive-ui').GlobalThemeOverrides
*/
const getThemeOverrides = computed(() => {
return {
common: {
primaryColor: designStore.appTheme,
primaryColorHover: '#57a3f3'
}
}
})
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined))
let timer
const timekeeping = () => {
clearInterval(timer)
if (route.name == 'login' || isLock.value) return
// 设置不锁屏
useLockscreen.setLock(false)
// 重置锁屏时间
useLockscreen.setLockTime()
timer = setInterval(() => {
// 锁屏倒计时递减
useLockscreen.setLockTime(lockTime.value - 1)
if (lockTime.value <= 0) {
// 设置锁屏
useLockscreen.setLock(true)
return clearInterval(timer)
}
}, 1000)
}
onMounted(() => {
document.addEventListener('mousedown', timekeeping)
})
onUnmounted(() => {
document.removeEventListener('mousedown', timekeeping)
})
return {
darkTheme: createTheme([inputDark, datePickerDark]),
getDarkTheme,
zhCN,
dateZhCN,
isLock,
getThemeOverrides
}
}
})
darkTheme: createTheme([inputDark, datePickerDark]),
getDarkTheme,
zhCN,
dateZhCN,
isLock,
getThemeOverrides,
};
},
});
</script>
<style lang="less">
@import 'styles/global.less';
@import 'styles/common.less';
@import 'styles/override.less';
@import 'styles/global.less';
@import 'styles/common.less';
@import 'styles/override.less';
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.35s ease-in;
}
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.35s ease-in;
}
.slide-up-enter-form,
.slide-up-leave-to {
transform: translateY(-100%);
}
.slide-up-enter-form,
.slide-up-leave-to {
transform: translateY(-100%);
}
</style>

View File

@@ -1,11 +1,9 @@
import http from '@/utils/http/axios'
import http from '@/utils/http/axios';
//获取主控台信息
export function getConsoleInfo() {
return http.request(
{
url: '/dashboard/console',
method: 'get'
}
)
return http.request({
url: '/dashboard/console',
method: 'get',
});
}

View File

@@ -1,24 +1,23 @@
import http from '@/utils/http/axios'
import http from '@/utils/http/axios';
/**
* @description: 根据用户id获取用户菜单
*/
export function adminMenus() {
return http.request({
url: '/menus',
method: 'GET'
})
return http.request({
url: '/menus',
method: 'GET',
});
}
/**
* 获取tree菜单列表
* @param params
*/
export function getMenuList(params) {
return http.request({
url: '/menu/list',
method: 'GET',
params
})
return http.request({
url: '/menu/list',
method: 'GET',
params,
});
}

View File

@@ -1,11 +1,11 @@
import http from '@/utils/http/axios'
import http from '@/utils/http/axios';
/**
* @description: 角色列表
*/
export function getRoleList() {
return http.request({
url: '/role/list',
method: 'GET'
})
}
return http.request({
url: '/role/list',
method: 'GET',
});
}

View File

@@ -1,68 +1,66 @@
import http from '@/utils/http/axios'
import http from '@/utils/http/axios';
export interface BasicResponseModel<T = any> {
code: number
message: string
result: T
code: number;
message: string;
result: T;
}
export interface BasicPageParams {
pageNumber: number
pageSize: number
total: number
pageNumber: number;
pageSize: number;
total: number;
}
/**
* @description: 获取用户信息
*/
export function getUserInfo() {
return http.request(
{
url: '/admin_info',
method: 'get'
}
)
return http.request({
url: '/admin_info',
method: 'get',
});
}
/**
* @description: 用户登录
*/
export function login(params) {
return http.request<BasicResponseModel>(
{
url: '/login',
method: 'POST',
params
},
{
isTransformRequestResult: false
}
)
return http.request<BasicResponseModel>(
{
url: '/login',
method: 'POST',
params,
},
{
isTransformRequestResult: false,
}
);
}
/**
* @description: 用户修改密码
*/
export function changePassword(params, uid) {
return http.request(
{
url: `/user/u${ uid }/changepw`,
method: 'POST',
params
},
{
isTransformRequestResult: false
}
)
return http.request(
{
url: `/user/u${uid}/changepw`,
method: 'POST',
params,
},
{
isTransformRequestResult: false,
}
);
}
/**
* @description: 用户登出
*/
export function logout(params) {
return http.request({
url: '/login/logout',
method: 'POST',
params
})
return http.request({
url: '/login/logout',
method: 'POST',
params,
});
}

View File

@@ -1,12 +1,10 @@
import http from '@/utils/http/axios'
import http from '@/utils/http/axios';
//获取table
export function getTableList(params) {
return http.request(
{
url: '/table/list',
method: 'get',
params
}
)
return http.request({
url: '/table/list',
method: 'get',
params,
});
}

View File

@@ -1,9 +1,9 @@
<template>
<n-dialog-provider>
<DialogContent/>
<DialogContent />
<n-notification-provider>
<n-message-provider>
<MessageContent/>
<MessageContent />
<slot slot="default"></slot>
</n-message-provider>
</n-notification-provider>
@@ -11,15 +11,15 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { MessageContent } from '@/components/MessageContent'
import { DialogContent } from '@/components/DialogContent'
import { defineComponent } from 'vue';
import { MessageContent } from '@/components/MessageContent';
import { DialogContent } from '@/components/DialogContent';
export default defineComponent({
name: 'Application',
components: { MessageContent, DialogContent },
setup() {
return {}
}
})
export default defineComponent({
name: 'Application',
components: { MessageContent, DialogContent },
setup() {
return {};
},
});
</script>

View File

@@ -1,3 +1,3 @@
import AppProvider from './Application.vue'
import AppProvider from './Application.vue';
export { AppProvider }
export { AppProvider };

View File

@@ -4,107 +4,107 @@
</span>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
import { useTransition, TransitionPresets } from '@vueuse/core';
import { isNumber } from '@/utils/is';
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
import { useTransition, TransitionPresets } from '@vueuse/core';
import { isNumber } from '@/utils/is';
const props = {
startVal: { type: Number, default: 0 },
endVal: { type: Number, default: 2021 },
duration: { type: Number, default: 1500 },
autoplay: { type: Boolean, default: true },
decimals: {
type: Number,
default: 0,
validator(value: number) {
return value >= 0;
const props = {
startVal: { type: Number, default: 0 },
endVal: { type: Number, default: 2021 },
duration: { type: Number, default: 1500 },
autoplay: { type: Boolean, default: true },
decimals: {
type: Number,
default: 0,
validator(value: number) {
return value >= 0;
},
},
},
prefix: { type: String, default: '' },
suffix: { type: String, default: '' },
separator: { type: String, default: ',' },
decimal: { type: String, default: '.' },
/**
* font color
*/
color: { type: String },
/**
* Turn on digital animation
*/
useEasing: { type: Boolean, default: true },
/**
* Digital animation
*/
transition: { type: String, default: 'linear' },
};
prefix: { type: String, default: '' },
suffix: { type: String, default: '' },
separator: { type: String, default: ',' },
decimal: { type: String, default: '.' },
/**
* font color
*/
color: { type: String },
/**
* Turn on digital animation
*/
useEasing: { type: Boolean, default: true },
/**
* Digital animation
*/
transition: { type: String, default: 'linear' },
};
export default defineComponent({
name: 'CountTo',
props,
emits: ['onStarted', 'onFinished'],
setup(props, { emit }) {
const source = ref(props.startVal);
const disabled = ref(false);
let outputValue = useTransition(source);
export default defineComponent({
name: 'CountTo',
props,
emits: ['onStarted', 'onFinished'],
setup(props, { emit }) {
const source = ref(props.startVal);
const disabled = ref(false);
let outputValue = useTransition(source);
const value = computed(() => formatNumber(unref(outputValue)));
const value = computed(() => formatNumber(unref(outputValue)));
watchEffect(() => {
source.value = props.startVal;
});
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start();
}
});
onMounted(() => {
props.autoplay && start();
});
function start() {
run();
source.value = props.endVal;
}
function reset() {
source.value = props.startVal;
run();
}
function run() {
outputValue = useTransition(source, {
disabled,
duration: props.duration,
onFinished: () => emit('onFinished'),
onStarted: () => emit('onStarted'),
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
watchEffect(() => {
source.value = props.startVal;
});
}
function formatNumber(num: number | string) {
if (!num) {
return '';
}
const { decimals, decimal, separator, suffix, prefix } = props;
num = Number(num).toFixed(decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2');
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start();
}
}
return prefix + x1 + x2 + suffix;
}
});
return { value, start, reset };
},
});
onMounted(() => {
props.autoplay && start();
});
function start() {
run();
source.value = props.endVal;
}
function reset() {
source.value = props.startVal;
run();
}
function run() {
outputValue = useTransition(source, {
disabled,
duration: props.duration,
onFinished: () => emit('onFinished'),
onStarted: () => emit('onStarted'),
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
});
}
function formatNumber(num: number | string) {
if (!num) {
return '';
}
const { decimals, decimal, separator, suffix, prefix } = props;
num = Number(num).toFixed(decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2');
}
}
return prefix + x1 + x2 + suffix;
}
return { value, start, reset };
},
});
</script>

View File

@@ -1,3 +1,3 @@
import DialogContent from './index.vue'
import DialogContent from './index.vue';
export { DialogContent }
export { DialogContent };

View File

@@ -1,12 +1,12 @@
<template></template>
<script lang="ts">
import { useDialog } from 'naive-ui'
import { useDialog } from 'naive-ui';
export default {
name: 'DialogContent',
setup() {
//挂载在 window 方便与在js中使用
window.$dialog = useDialog()
}
}
export default {
name: 'DialogContent',
setup() {
//挂载在 window 方便与在js中使用
window['$dialog'] = useDialog();
},
};
</script>

View File

@@ -1,29 +1,27 @@
<template>
<div
:class="{ onLockLogin: showLogin }"
class="lockscreen"
@keyup="onLockLogin(true)"
@mousedown.stop
@contextmenu.prevent
:class="{ onLockLogin: showLogin }"
class="lockscreen"
@keyup="onLockLogin(true)"
@mousedown.stop
@contextmenu.prevent
>
<template v-if="!showLogin">
<div class="lock-box">
<div class="lock">
<span class="lock-icon" title="解锁屏幕" @click="onLockLogin(true)">
<n-icon>
<lock-outlined/>
<lock-outlined />
</n-icon>
</span>
</div>
</div>
<!--充电-->
<recharge
:battery="battery"
:battery-status="batteryStatus"
:calc-discharging-time="calcDischargingTime"
></recharge>
:battery="battery"
:battery-status="batteryStatus"
:calc-discharging-time="calcDischargingTime"
/>
<div class="local-time">
<div class="time">{{ hour }}:{{ minute }}</div>
@@ -31,9 +29,9 @@
</div>
<div class="computer-status">
<span :class="{ offline: !online }" class="network">
<wifi-outlined class="network"/>
<wifi-outlined class="network" />
</span>
<api-outlined/>
<api-outlined />
</div>
</template>
@@ -42,19 +40,20 @@
<div class="login-box">
<n-avatar :size="128">
<n-icon>
<user-outlined/>
<user-outlined />
</n-icon>
</n-avatar>
<div class="username">{{ loginParams.username }}</div>
<n-input
type="password"
autofocus
v-model:value="loginParams.password"
placeholder="请输入登录密码">
type="password"
autofocus
v-model:value="loginParams.password"
placeholder="请输入登录密码"
>
<template #suffix>
<n-icon @click="onLogin" style="cursor: pointer;">
<LoadingOutlined v-if="loginLoading"/>
<arrow-right-outlined v-else/>
<n-icon @click="onLogin" style="cursor: pointer">
<LoadingOutlined v-if="loginLoading" />
<arrow-right-outlined v-else />
</n-icon>
</template>
</n-input>
@@ -64,241 +63,239 @@
</div>
<div class="w-full mt-1 flex justify-around">
<div><a @click="showLogin=false">返回</a></div>
<div><a @click="showLogin = false">返回</a></div>
<div><a @click="goLogin">重新登录</a></div>
<div><a @click="onLogin">进入系统</a></div>
</div>
</div>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, reactive, toRefs, computed } from 'vue'
import { ResultEnum } from '@/enums/httpEnum'
import recharge from './Recharge.vue'
import {
LockOutlined,
LoadingOutlined,
UnlockOutlined,
UserOutlined,
ApiOutlined,
ArrowRightOutlined,
WifiOutlined,
} from '@vicons/antd'
import { useRouter, useRoute } from 'vue-router'
import { useOnline } from '@/hooks/useOnline'
import { useTime } from '@/hooks/useTime'
import { useBattery } from '@/hooks/useBattery'
import { useLockscreenStore } from '@/store/modules/lockscreen'
import { useUserStore } from '@/store/modules/user'
export default defineComponent({
name: 'Lockscreen',
components: {
import { defineComponent, reactive, toRefs } from 'vue';
import { ResultEnum } from '@/enums/httpEnum';
import recharge from './Recharge.vue';
import {
LockOutlined,
LoadingOutlined,
UnlockOutlined,
UserOutlined,
ArrowRightOutlined,
ApiOutlined,
ArrowRightOutlined,
WifiOutlined,
recharge,
},
setup(props, { emit }) {
const useLockscreen = useLockscreenStore()
const userStore = useUserStore();
} from '@vicons/antd';
// 获取时间
const { month, day, hour, minute, second, week } = useTime()
const { online } = useOnline()
import { useRouter, useRoute } from 'vue-router';
import { useOnline } from '@/hooks/useOnline';
import { useTime } from '@/hooks/useTime';
import { useBattery } from '@/hooks/useBattery';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import { useUserStore } from '@/store/modules/user';
const router = useRouter()
const route = useRoute()
export default defineComponent({
name: 'Lockscreen',
components: {
LockOutlined,
LoadingOutlined,
UserOutlined,
ArrowRightOutlined,
ApiOutlined,
WifiOutlined,
recharge,
},
setup() {
const useLockscreen = useLockscreenStore();
const userStore = useUserStore();
const { battery, batteryStatus, calcDischargingTime } = useBattery()
const { username } = userStore.getUserInfo || {}
const state = reactive({
showLogin: false,
loginLoading: false, // 正在登录
isLoginError: false, //密码错误
errorMsg: '密码错误',
loginParams: {
username: username || '',
password: ''
}
})
// 获取时间
const { month, day, hour, minute, second, week } = useTime();
const { online } = useOnline();
// 解锁登录
const onLockLogin = (value: boolean) => (state.showLogin = value)
const router = useRouter();
const route = useRoute();
// 登录
const onLogin = async () => {
if (!state.loginParams.password.trim()) {
return
}
const params = {
isLock: true,
...state.loginParams
}
state.loginLoading = true
const { code, result, message } = await userStore.login(params)
if (code === ResultEnum.SUCCESS) {
onLockLogin(false)
useLockscreen.setLock(false)
} else {
state.errorMsg = message
state.isLoginError = true
}
state.loginLoading = false
}
const { battery, batteryStatus, calcDischargingTime } = useBattery();
const userInfo: object = userStore.getUserInfo || {};
const username = userInfo['username'] || '';
const state = reactive({
showLogin: false,
loginLoading: false, // 正在登录
isLoginError: false, //密码错误
errorMsg: '密码错误',
loginParams: {
username: username || '',
password: '',
},
});
//重新登录
const goLogin = () => {
onLockLogin(false)
useLockscreen.setLock(false)
router.replace({
path: '/login',
query: {
redirect: route.fullPath
// 解锁登录
const onLockLogin = (value: boolean) => (state.showLogin = value);
// 登录
const onLogin = async () => {
if (!state.loginParams.password.trim()) {
return;
}
})
}
const params = {
isLock: true,
...state.loginParams,
};
state.loginLoading = true;
const { code, message } = await userStore.login(params);
if (code === ResultEnum.SUCCESS) {
onLockLogin(false);
useLockscreen.setLock(false);
} else {
state.errorMsg = message;
state.isLoginError = true;
}
state.loginLoading = false;
};
return {
...toRefs(state),
online,
month,
day,
hour,
minute,
second,
week,
battery,
batteryStatus,
calcDischargingTime,
onLockLogin,
onLogin,
goLogin
}
}
})
//重新登录
const goLogin = () => {
onLockLogin(false);
useLockscreen.setLock(false);
router.replace({
path: '/login',
query: {
redirect: route.fullPath,
},
});
};
return {
...toRefs(state),
online,
month,
day,
hour,
minute,
second,
week,
battery,
batteryStatus,
calcDischargingTime,
onLockLogin,
onLogin,
goLogin,
};
},
});
</script>
<style lang="less" scoped>
.lockscreen {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
background: #000;
color: white;
overflow: hidden;
z-index: 9999;
&.onLockLogin {
background-color: rgba(25, 28, 34, 0.88);
backdrop-filter: blur(7px);
}
.login-box {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
.lockscreen {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #000;
color: white;
overflow: hidden;
z-index: 9999;
> * {
margin-bottom: 14px;
&.onLockLogin {
background-color: rgba(25, 28, 34, 0.88);
backdrop-filter: blur(7px);
}
.username {
font-size: 30px;
}
}
.lock-box {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 34px;
z-index: 100;
.tips {
color: white;
cursor: text;
}
.lock {
.login-box {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.lock-icon {
cursor: pointer;
> * {
margin-bottom: 14px;
}
.anticon-unlock {
display: none;
.username {
font-size: 30px;
}
}
.lock-box {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 34px;
z-index: 100;
.tips {
color: white;
cursor: text;
}
.lock {
display: flex;
justify-content: center;
.lock-icon {
cursor: pointer;
.anticon-unlock {
display: none;
}
&:hover .anticon-unlock {
display: initial;
}
&:hover .anticon-lock {
display: none;
}
}
}
}
&:hover .anticon-unlock {
display: initial;
}
.local-time {
position: absolute;
bottom: 60px;
left: 60px;
font-family: helvetica;
&:hover .anticon-lock {
display: none;
.time {
font-size: 70px;
}
.date {
font-size: 40px;
}
}
.computer-status {
position: absolute;
bottom: 60px;
right: 60px;
font-size: 24px;
> * {
margin-left: 14px;
}
.network {
position: relative;
&.offline::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
width: 2px;
height: 28px;
transform: translate(-50%, -50%) rotate(45deg);
background-color: red;
z-index: 10;
}
}
}
}
.local-time {
position: absolute;
bottom: 60px;
left: 60px;
font-family: helvetica;
.time {
font-size: 70px;
}
.date {
font-size: 40px;
}
}
.computer-status {
position: absolute;
bottom: 60px;
right: 60px;
font-size: 24px;
> * {
margin-left: 14px;
}
.network {
position: relative;
&.offline::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
width: 2px;
height: 28px;
transform: translate(-50%, -50%) rotate(45deg);
background-color: red;
z-index: 10;
}
}
}
}
</style>

View File

@@ -20,120 +20,120 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent } from 'vue';
export default defineComponent({
name: 'HuaweiCharge',
// props: ['batteryStatus', 'battery', 'calcDischargingTime'],
props: {
battery: {
// 电池对象
type: Object,
default: () => ({})
export default defineComponent({
name: 'HuaweiCharge',
// props: ['batteryStatus', 'battery', 'calcDischargingTime'],
props: {
battery: {
// 电池对象
type: Object,
default: () => ({}),
},
calcDischargingTime: {
// 电池剩余时间可用时间
type: String,
default: '',
},
batteryStatus: {
// 电池状态
type: String,
validator: (val: string) => ['充电中', '已充满', '已断开电源'].includes(val),
},
},
calcDischargingTime: {
// 电池剩余时间可用时间
type: String,
default: ''
},
batteryStatus: {
// 电池状态
type: String,
validator: (val: string) => ['充电中', '已充满', '已断开电源'].includes(val)
}
}
})
});
</script>
<style lang="less" scoped>
.container {
position: absolute;
bottom: 20vh;
left: 50vw;
width: 300px;
height: 400px;
transform: translateX(-50%);
.number {
.container {
position: absolute;
top: 27%;
z-index: 10;
width: 300px;
font-size: 32px;
color: #fff;
text-align: center;
}
.contrast {
bottom: 20vh;
left: 50vw;
width: 300px;
height: 400px;
overflow: hidden;
background-color: #000;
filter: contrast(15) hue-rotate(0);
animation: hueRotate 10s infinite linear;
transform: translateX(-50%);
.circle {
position: relative;
width: 300px;
height: 300px;
filter: blur(8px);
box-sizing: border-box;
&::after {
position: absolute;
top: 40%;
left: 50%;
width: 200px;
height: 200px;
background-color: #00ff6f;
border-radius: 42% 38% 62% 49% / 45%;
content: '';
transform: translate(-50%, -50%) rotate(0);
animation: rotate 10s infinite linear;
}
&::before {
position: absolute;
top: 40%;
left: 50%;
z-index: 10;
width: 176px;
height: 176px;
background-color: #000;
border-radius: 50%;
content: '';
transform: translate(-50%, -50%);
}
}
.bubbles {
.number {
position: absolute;
bottom: 0;
left: 50%;
width: 100px;
height: 40px;
background-color: #00ff6f;
border-radius: 100px 100px 0 0;
filter: blur(5px);
transform: translate(-50%, 0);
top: 27%;
z-index: 10;
width: 300px;
font-size: 32px;
color: #fff;
text-align: center;
}
li {
position: absolute;
background: #00ff6f;
border-radius: 50%;
.contrast {
width: 300px;
height: 400px;
overflow: hidden;
background-color: #000;
filter: contrast(15) hue-rotate(0);
animation: hueRotate 10s infinite linear;
.circle {
position: relative;
width: 300px;
height: 300px;
filter: blur(8px);
box-sizing: border-box;
&::after {
position: absolute;
top: 40%;
left: 50%;
width: 200px;
height: 200px;
background-color: #00ff6f;
border-radius: 42% 38% 62% 49% / 45%;
content: '';
transform: translate(-50%, -50%) rotate(0);
animation: rotate 10s infinite linear;
}
&::before {
position: absolute;
top: 40%;
left: 50%;
z-index: 10;
width: 176px;
height: 176px;
background-color: #000;
border-radius: 50%;
content: '';
transform: translate(-50%, -50%);
}
}
.bubbles {
position: absolute;
bottom: 0;
left: 50%;
width: 100px;
height: 40px;
background-color: #00ff6f;
border-radius: 100px 100px 0 0;
filter: blur(5px);
transform: translate(-50%, 0);
li {
position: absolute;
background: #00ff6f;
border-radius: 50%;
}
}
}
.charging {
font-size: 20px;
text-align: center;
}
}
.charging {
font-size: 20px;
text-align: center;
}
}
@width: ~`Math.round(Math.random() * 100)` px;
@left: calc(15px + `Math.round(Math.random(70))`);
each(range(15), {
@width: ~`Math.round(Math.random() * 100) ` px;
@left: calc(15px + `Math.round(Math.random(70)) `);
each(range(15), {
.xiaoma-@{value} {
height: (@value * 50px);
}
@@ -147,31 +147,30 @@ each(range(15), {
}
});
@keyframes rotate {
50% {
border-radius: 45% / 42% 38% 58% 49%;
}
@keyframes rotate {
50% {
border-radius: 45% / 42% 38% 58% 49%;
100% {
transform: translate(-50%, -50%) rotate(720deg);
}
}
100% {
transform: translate(-50%, -50%) rotate(720deg);
}
}
@keyframes moveToTop {
90% {
opacity: 1;
}
@keyframes moveToTop {
90% {
opacity: 1;
100% {
opacity: 0.1;
transform: translate(-50%, -180px);
}
}
100% {
opacity: 0.1;
transform: translate(-50%, -180px);
@keyframes hueRotate {
100% {
filter: contrast(15) hue-rotate(360deg);
}
}
}
@keyframes hueRotate {
100% {
filter: contrast(15) hue-rotate(360deg);
}
}
</style>

View File

@@ -1,3 +1,3 @@
import LockScreen from './Lockscreen.vue'
import LockScreen from './Lockscreen.vue';
export { LockScreen }
export { LockScreen };

View File

@@ -1,3 +1,3 @@
import MessageContent from './index.vue'
import MessageContent from './index.vue';
export { MessageContent }
export { MessageContent };

View File

@@ -1,12 +1,12 @@
<template></template>
<script lang="ts">
import { useMessage } from 'naive-ui'
import { useMessage } from 'naive-ui';
export default {
name: 'MessageContent',
setup() {
//挂载在 window 方便与在js中使用
window.$message = useMessage()
}
}
export default {
name: 'MessageContent',
setup() {
//挂载在 window 方便与在js中使用
window['$message'] = useMessage();
},
};
</script>

View File

@@ -2,4 +2,3 @@ export { default as BasicTable } from './src/Table.vue';
export { default as TableAction } from './src/components/TableAction.vue';
export * from './src/types/table';
export * from './src/types/tableAction';

View File

@@ -1,15 +1,14 @@
<template>
<div class="table-toolbar">
<!--顶部左侧区域-->
<div class="flex items-center table-toolbar-left ">
<div class="flex items-center table-toolbar-left">
<template v-if="title">
<div class="table-toolbar-left-title">
{{ title }}
<n-tooltip trigger="hover" v-if="titleTooltip">
<template #trigger>
<n-icon size="18" class="ml-1 cursor-pointer text-gray-400">
<QuestionCircleOutlined/>
<QuestionCircleOutlined />
</n-icon>
</template>
{{ titleTooltip }}
@@ -20,7 +19,6 @@
</div>
<div class="flex items-center table-toolbar-right">
<!--顶部右侧区域-->
<slot name="toolbar"></slot>
@@ -29,7 +27,7 @@
<template #trigger>
<div class="table-toolbar-right-icon" @click="reload">
<n-icon size="18">
<ReloadOutlined/>
<ReloadOutlined />
</n-icon>
</div>
</template>
@@ -40,9 +38,14 @@
<n-tooltip trigger="hover">
<template #trigger>
<div class="table-toolbar-right-icon">
<n-dropdown @select="densitySelect" trigger="click" :options="densityOptions" v-model:value="tableSize">
<n-dropdown
@select="densitySelect"
trigger="click"
:options="densityOptions"
v-model:value="tableSize"
>
<n-icon size="18">
<ColumnHeightOutlined/>
<ColumnHeightOutlined />
</n-icon>
</n-dropdown>
</div>
@@ -51,17 +54,15 @@
</n-tooltip>
<!--表格设置单独抽离成组件-->
<ColumnSetting></ColumnSetting>
<ColumnSetting />
</div>
</div>
<div class="s-table">
<n-data-table
v-bind="getBindValues"
:pagination="pagination"
@update:page="updatePage"
@update:page-size="updatePageSize"
v-bind="getBindValues"
:pagination="pagination"
@update:page="updatePage"
@update:page-size="updatePageSize"
>
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data"></slot>
@@ -71,226 +72,212 @@
</template>
<script lang="ts">
import { NDataTable } from 'naive-ui'
import { ref, defineComponent, reactive, unref, onMounted, toRaw, onBeforeMount, computed, toRefs, watch } from "vue"
import { ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, QuestionCircleOutlined } from '@vicons/antd'
import { createTableContext } from './hooks/useTableContext';
import { NDataTable } from 'naive-ui';
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs } from 'vue';
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
import { createTableContext } from './hooks/useTableContext';
import ColumnSetting from './components/settings/ColumnSetting.vue'
import ColumnSetting from './components/settings/ColumnSetting.vue';
import { useLoading } from './hooks/useLoading';
import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource';
import { usePagination } from './hooks/usePagination';
import { useLoading } from './hooks/useLoading';
import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource';
import { usePagination } from './hooks/usePagination';
import { basicProps } from './props'
import { basicProps } from './props';
import { BasicTableProps } from './types/table'
import { BasicTableProps } from './types/table';
const densityOptions = [
{
type: 'menu',
label: '紧凑',
key: 'small',
},
{
type: 'menu',
label: '默认',
key: 'medium',
},
{
type: 'menu',
label: '宽松',
key: 'large',
},
];
const densityOptions = [
{
type: "menu",
label: '紧凑',
key: 'small',
},
{
type: "menu",
label: '默认',
key: "medium"
},
{
type: "menu",
label: '宽松',
key: 'large'
}
]
export default defineComponent({
components: {
ReloadOutlined,
ColumnHeightOutlined,
ColumnSetting,
QuestionCircleOutlined,
},
props: {
...NDataTable.props, // 这里继承原 UI 组件的 props
...basicProps,
},
emits: ['fetch-success', 'fetch-error', 'update:checked-row-keys'],
setup(props, { emit }) {
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
export default defineComponent({
components: {
ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, ColumnSetting, QuestionCircleOutlined
},
props: {
...NDataTable.props, // 这里继承原 UI 组件的 props
...basicProps
},
emits: [
'fetch-success',
'fetch-error',
'update:checked-row-keys'
],
setup(props, { emit }) {
const tableData = ref<Recordable[]>([]);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
});
const tableData = ref<Recordable[]>([]);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const { getLoading, setLoading } = useLoading(getProps);
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
});
const { getPaginationInfo, setPagination } = usePagination(getProps);
const { getLoading, setLoading } = useLoading(getProps);
const {
getPaginationInfo,
getPagination,
setPagination,
setShowPagination,
getShowPagination,
} = usePagination(getProps)
const { getDataSourceRef, getRowKey, getDataSource, setDataSource, reload } = useDataSource(
getProps, {
const { getDataSourceRef, getRowKey, reload } = useDataSource(
getProps,
{
getPaginationInfo,
setPagination,
tableData,
setLoading
}, emit
)
setLoading,
},
emit
);
const {
getPageColumns,
setColumns,
getColumns,
getCacheColumns,
setCacheColumnsField,
getColumnsRef
} = useColumns(getProps)
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
useColumns(getProps);
const state = reactive({
tableSize: 'medium',
isColumnSetting: false
})
const state = reactive({
tableSize: 'medium',
isColumnSetting: false,
});
//页码切换
function updatePage(page) {
setPagination({ page: page, });
reload()
}
//分页数量切换
function updatePageSize(size) {
setPagination({ page: 1, pageSize: size, });
reload()
}
//密度切换
function densitySelect(e) {
state.tableSize = e
}
//选中行
function updateCheckedRowKeys(rowKeys) {
emit('update:checked-row-keys', rowKeys)
}
//重置 Columns
const resetColumns = () => {
columns.map(item => {
item.isShow = true
})
}
//获取表格大小
const getTableSize = computed(() => state.tableSize)
//组装表格信息
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef);
let propsData = {
...unref(getProps),
loading: unref(getLoading),
columns: toRaw(unref(getPageColumns)),
rowKey: unref(getRowKey),
data: tableData,
size: unref(getTableSize),
remote: true
//页码切换
function updatePage(page) {
setPagination({ page: page });
reload();
}
return propsData
})
//获取分页信息
const pagination = computed(() => toRaw(unref(getPaginationInfo)))
//分页数量切换
function updatePageSize(size) {
setPagination({ page: 1, pageSize: size });
reload();
}
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
//密度切换
function densitySelect(e) {
state.tableSize = e;
}
const tableAction: TableActionType = {
reload,
setColumns,
setLoading,
setProps,
getColumns,
getPageColumns,
getCacheColumns,
setCacheColumnsField,
emit,
getSize: () => {
return unref(getBindValues).size as SizeType;
},
};
//选中行
function updateCheckedRowKeys(rowKeys) {
emit('update:checked-row-keys', rowKeys);
}
createTableContext({ ...tableAction, wrapRef, getBindValues });
//重置 Columns
const resetColumns = () => {
columns.map((item) => {
item.isShow = true;
});
};
return {
...toRefs(state),
getBindValues,
densityOptions,
reload,
densitySelect,
updatePage,
updatePageSize,
updateCheckedRowKeys,
pagination,
resetColumns,
tableAction
}
}
})
//获取表格大小
const getTableSize = computed(() => state.tableSize);
//组装表格信息
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef);
let propsData = {
...unref(getProps),
loading: unref(getLoading),
columns: toRaw(unref(getPageColumns)),
rowKey: unref(getRowKey),
data: tableData,
size: unref(getTableSize),
remote: true,
};
return propsData;
});
//获取分页信息
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
const tableAction: TableActionType = {
reload,
setColumns,
setLoading,
setProps,
getColumns,
getPageColumns,
getCacheColumns,
setCacheColumnsField,
emit,
getSize: () => {
return unref(getBindValues).size as SizeType;
},
};
createTableContext({ ...tableAction, wrapRef, getBindValues });
return {
...toRefs(state),
getBindValues,
densityOptions,
reload,
densitySelect,
updatePage,
updatePageSize,
updateCheckedRowKeys,
pagination,
resetColumns,
tableAction,
};
},
});
</script>
<style lang='less' scoped>
.table-toolbar {
display: flex;
justify-content: space-between;
padding: 0 0 16px 0;
&-left {
<style lang="less" scoped>
.table-toolbar {
display: flex;
align-items: center;
justify-content: flex-start;
flex: 1;
justify-content: space-between;
padding: 0 0 16px 0;
&-title {
&-left {
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 16px;
font-weight: 600;
flex: 1;
&-title {
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 16px;
font-weight: 600;
}
}
}
&-right {
display: flex;
justify-content: flex-end;
flex: 1;
&-right {
display: flex;
justify-content: flex-end;
flex: 1;
&-icon {
margin-left: 12px;
font-size: 16px;
cursor: pointer;
color: var(--text-color);
&-icon {
margin-left: 12px;
font-size: 16px;
cursor: pointer;
color: var(--text-color);
:hover {
color: #1890ff;
:hover {
color: #1890ff;
}
}
}
}
}
.table-toolbar-inner-popover-title {
padding: 2px 0;
}
.table-toolbar-inner-popover-title {
padding: 2px 0;
}
</style>

View File

@@ -5,18 +5,17 @@
<n-button v-bind="action" class="mx-2">{{ action.label }}</n-button>
</template>
<n-dropdown
v-if="dropDownActions && getDropdownList.length"
trigger="hover"
:options="getDropdownList"
@select="select"
v-if="dropDownActions && getDropdownList.length"
trigger="hover"
:options="getDropdownList"
@select="select"
>
<slot name="more"></slot>
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
<div class="flex items-center">
<span>更多</span>
<n-icon size="14" class="ml-1">
<DownOutlined/>
<DownOutlined />
</n-icon>
</div>
<!-- <template #icon>-->
@@ -29,64 +28,56 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed, toRaw } from 'vue';
import { ActionItem } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { isString, isBoolean, isFunction } from "@/utils/is";
import { DownOutlined } from '@vicons/antd'
import { defineComponent, PropType, computed, toRaw } from 'vue';
import { ActionItem } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { isBoolean, isFunction } from '@/utils/is';
import { DownOutlined } from '@vicons/antd';
export default defineComponent({
name: 'TableAction',
components: { DownOutlined },
props: {
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
export default defineComponent({
name: 'TableAction',
components: { DownOutlined },
props: {
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
style: {
type: String as PropType<String>,
default: 'button',
},
select: {
type: Function as PropType<Function>,
default: () => {},
},
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
style: {
type: String as PropType<String>,
default: 'button'
},
select:{
type: Function as PropType<Function>,
default: () =>{ }
}
},
setup(props, { emit }) {
const { hasPermission } = usePermission();
setup(props) {
const { hasPermission } = usePermission();
const getTooltip = computed(() => {
return (data: string | TooltipProps): TooltipProps => {
if (isString(data)) {
return { title: data, placement: 'bottom' };
} else {
return Object.assign({ placement: 'bottom' }, data);
}
};
});
const actionType =
props.style === 'button' ? 'default' : props.style === 'text' ? 'primary' : 'default';
const actionText =
props.style === 'button' ? undefined : props.style === 'text' ? true : undefined;
const actionType = props.style === 'button' ? 'default' : props.style === 'text' ? 'primary' : 'default'
const actionText = props.style === 'button' ? undefined : props.style === 'text' ? true : undefined
const getMoreProps = computed(() => {
const getMoreProps = computed(() => {
return {
text: actionText,
type: actionType,
size: "small"
}
})
size: 'small',
};
});
const getDropdownList = computed(() => {
return (toRaw(props.dropDownActions) || [])
const getDropdownList = computed(() => {
return (toRaw(props.dropDownActions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
.map((action, index) => {
const { label, popConfirm } = action;
.map((action) => {
const { popConfirm } = action;
return {
size: 'small',
text: actionText,
@@ -94,27 +85,27 @@ export default defineComponent({
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel
onCancel: popConfirm?.cancel,
};
});
});
});
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
const getActions = computed(() => {
return (toRaw(props.actions) || [])
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
@@ -132,14 +123,13 @@ export default defineComponent({
enable: !!popConfirm,
};
});
});
});
return {
getActions,
getDropdownList,
getTooltip,
getMoreProps
}
}
})
return {
getActions,
getDropdownList,
getMoreProps,
};
},
});
</script>

View File

@@ -5,46 +5,62 @@
<n-popover trigger="click" :width="230" class="toolbar-popover" placement="bottom-end">
<template #trigger>
<n-icon size="18">
<SettingOutlined/>
<SettingOutlined />
</n-icon>
</template>
<template #header>
<div class="table-toolbar-inner-popover-title">
<n-space>
<n-checkbox v-model:checked="checkAll" @update:checked="onCheckAll">列展示</n-checkbox>
<n-checkbox v-model:checked="selection" @update:checked="onSelection">勾选列</n-checkbox>
<n-button text type="info" size="small" class="mt-1" @click="resetColumns">重置</n-button>
<n-checkbox v-model:checked="checkAll" @update:checked="onCheckAll"
>列展示</n-checkbox
>
<n-checkbox v-model:checked="selection" @update:checked="onSelection"
>勾选列</n-checkbox
>
<n-button text type="info" size="small" class="mt-1" @click="resetColumns"
>重置</n-button
>
</n-space>
</div>
</template>
<div class="table-toolbar-inner">
<n-checkbox-group v-model:value="checkList" @update:value="onChange">
<Draggable v-model="columnsList" animation="300" item-key="key" @end="draggableEnd">
<template #item="{element, index}">
<div class="table-toolbar-inner-checkbox"
:class="{'table-toolbar-inner-checkbox-dark':getDarkTheme === true}">
<template #item="{ element }">
<div
class="table-toolbar-inner-checkbox"
:class="{ 'table-toolbar-inner-checkbox-dark': getDarkTheme === true }"
>
<span class="drag-icon">
<n-icon size="18">
<DragOutlined/>
<DragOutlined />
</n-icon>
</span>
<n-checkbox :value="element.key" :label="element.title"/>
<n-checkbox :value="element.key" :label="element.title" />
<div class="fixed-item">
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-icon size="18" :color="element.fixed === 'left' ? '#2080f0':undefined"
class="cursor-pointer" @click="fixedColumn(element,'left')">
<VerticalRightOutlined/>
<n-icon
size="18"
:color="element.fixed === 'left' ? '#2080f0' : undefined"
class="cursor-pointer"
@click="fixedColumn(element, 'left')"
>
<VerticalRightOutlined />
</n-icon>
</template>
<span>固定到左侧</span>
</n-tooltip>
<n-divider vertical/>
<n-divider vertical />
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-icon size="18" :color="element.fixed === 'right' ? '#2080f0':undefined"
class="cursor-pointer" @click="fixedColumn(element,'right')">
<VerticalLeftOutlined/>
<n-icon
size="18"
:color="element.fixed === 'right' ? '#2080f0' : undefined"
class="cursor-pointer"
@click="fixedColumn(element, 'right')"
>
<VerticalLeftOutlined />
</n-icon>
</template>
<span>固定到右侧</span>
@@ -63,221 +79,226 @@
</template>
<script lang="ts">
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from "vue"
import { useTableContext } from '../../hooks/useTableContext';
import { ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, VerticalRightOutlined, VerticalLeftOutlined } from '@vicons/antd'
import Draggable from 'vuedraggable/src/vuedraggable'
import { useDesignSetting } from "@/hooks/setting/useDesignSetting";
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from 'vue';
import { useTableContext } from '../../hooks/useTableContext';
import {
SettingOutlined,
DragOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
} from '@vicons/antd';
import Draggable from 'vuedraggable/src/vuedraggable';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
interface Options {
title: string;
key: string;
fixed?: boolean | 'left' | 'right';
}
export default defineComponent({
name: 'ColumnSetting',
components: {
ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, Draggable,
VerticalRightOutlined, VerticalLeftOutlined
},
setup(props, { emit }) {
const { getDarkTheme } = useDesignSetting()
const table = useTableContext();
const columnsList = ref<Options[]>([]);
const cacheColumnsList = ref<Options[]>([]);
const state = reactive({
selection: false,
checkAll: true,
checkList: [],
defaultCheckList: []
})
const getSelection = computed(() => {
return state.selection
})
watchEffect(() => {
const columns = table.getColumns();
if (columns.length) {
init();
}
});
//初始化
function init() {
const columns = getColumns();
const checkList = columns.map(item => item.key)
state.checkList = checkList
state.defaultCheckList = checkList
columnsList.value = columns
cacheColumnsList.value = columns
}
//切换
function onChange(checkList) {
if (state.selection) {
checkList.unshift('selection')
}
setColumns(checkList)
}
//设置
function setColumns(columns) {
table.setColumns(columns)
}
//获取
function getColumns() {
let newRet = []
table.getColumns().forEach(item => {
newRet.push({ ...item })
})
return newRet
}
//重置
function resetColumns() {
state.checkList = [...state.defaultCheckList]
state.checkAll = true;
let cacheColumnsKeys: any[] = table.getCacheColumns()
let newColumns = cacheColumnsKeys.map(item => {
return {
...item,
fixed: undefined
}
})
setColumns(newColumns);
columnsList.value = newColumns
}
//全选
function onCheckAll(e) {
let checkList = table.getCacheColumns(true)
if (e) {
setColumns(checkList);
state.checkList = checkList
} else {
setColumns([]);
state.checkList = []
}
}
//拖拽排序
function draggableEnd() {
const newColumns = toRaw(unref(columnsList))
columnsList.value = newColumns
setColumns(newColumns);
}
//勾选列
function onSelection(e) {
let checkList = table.getCacheColumns()
if (e) {
checkList.unshift({ type: 'selection', key: 'selection' })
setColumns(checkList);
} else {
checkList.splice(0, 1)
setColumns(checkList);
}
}
//固定
function fixedColumn(item, fixed) {
console.log('item', item)
if (!state.checkList.includes(item.key)) return;
let columns = getColumns();
const isFixed = item.fixed === fixed ? undefined : fixed
let index = columns.findIndex(res => res.key === item.key)
console.log('index', index)
if (index !== -1) {
columns[index].fixed = isFixed;
}
table.setCacheColumnsField(item.key, { fixed: isFixed })
columnsList.value[index].fixed = isFixed
console.log('columnsList', columnsList.value)
setColumns(columns);
}
return {
...toRefs(state),
columnsList,
getDarkTheme,
onChange,
onCheckAll,
onSelection,
resetColumns,
fixedColumn,
draggableEnd,
getSelection
}
interface Options {
title: string;
key: string;
fixed?: boolean | 'left' | 'right';
}
})
export default defineComponent({
name: 'ColumnSetting',
components: {
SettingOutlined,
DragOutlined,
Draggable,
VerticalRightOutlined,
VerticalLeftOutlined,
},
setup() {
const { getDarkTheme } = useDesignSetting();
const table = useTableContext();
const columnsList = ref<Options[]>([]);
const cacheColumnsList = ref<Options[]>([]);
const state = reactive({
selection: false,
checkAll: true,
checkList: [],
defaultCheckList: [],
});
const getSelection = computed(() => {
return state.selection;
});
watchEffect(() => {
const columns = table.getColumns();
if (columns.length) {
init();
}
});
//初始化
function init() {
const columns: any[] = getColumns();
const checkList: any = columns.map((item) => item.key);
state.checkList = checkList;
state.defaultCheckList = checkList;
columnsList.value = columns;
cacheColumnsList.value = columns;
}
//切换
function onChange(checkList) {
if (state.selection) {
checkList.unshift('selection');
}
setColumns(checkList);
}
//设置
function setColumns(columns) {
table.setColumns(columns);
}
//获取
function getColumns() {
let newRet = [];
table.getColumns().forEach((item) => {
newRet.push({ ...item });
});
return newRet;
}
//重置
function resetColumns() {
state.checkList = [...state.defaultCheckList];
state.checkAll = true;
let cacheColumnsKeys: any[] = table.getCacheColumns();
let newColumns = cacheColumnsKeys.map((item) => {
return {
...item,
fixed: undefined,
};
});
setColumns(newColumns);
columnsList.value = newColumns;
}
//全选
function onCheckAll(e) {
let checkList = table.getCacheColumns(true);
if (e) {
setColumns(checkList);
state.checkList = checkList;
} else {
setColumns([]);
state.checkList = [];
}
}
//拖拽排序
function draggableEnd() {
const newColumns = toRaw(unref(columnsList));
columnsList.value = newColumns;
setColumns(newColumns);
}
//勾选列
function onSelection(e) {
let checkList = table.getCacheColumns();
if (e) {
checkList.unshift({ type: 'selection', key: 'selection' });
setColumns(checkList);
} else {
checkList.splice(0, 1);
setColumns(checkList);
}
}
//固定
function fixedColumn(item, fixed) {
if (!state.checkList.includes(item.key)) return;
let columns = getColumns();
const isFixed = item.fixed === fixed ? undefined : fixed;
let index = columns.findIndex((res) => res.key === item.key);
if (index !== -1) {
columns[index].fixed = isFixed;
}
table.setCacheColumnsField(item.key, { fixed: isFixed });
columnsList.value[index].fixed = isFixed;
setColumns(columns);
}
return {
...toRefs(state),
columnsList,
getDarkTheme,
onChange,
onCheckAll,
onSelection,
resetColumns,
fixedColumn,
draggableEnd,
getSelection,
};
},
});
</script>
<style lang="less">
.table-toolbar {
&-inner-popover-title {
padding: 3px 0;
}
.table-toolbar {
&-inner-popover-title {
padding: 3px 0;
}
&-right {
&-icon {
margin-left: 12px;
font-size: 16px;
color: var(--text-color);
cursor: pointer;
&-right {
&-icon {
margin-left: 12px;
font-size: 16px;
color: var(--text-color);
cursor: pointer;
:hover {
color: #1890ff;
:hover {
color: #1890ff;
}
}
}
}
}
.table-toolbar-inner {
&-checkbox {
display: flex;
align-items: center;
padding: 10px 14px;
&:hover {
background: #e6f7ff;
}
.drag-icon {
display: inline-flex;
margin-right: 8px;
cursor: move;
}
.fixed-item {
.table-toolbar-inner {
&-checkbox {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: auto;
}
.ant-checkbox-wrapper {
flex: 1;
padding: 10px 14px;
&:hover {
color: #1890ff !important;
background: #e6f7ff;
}
.drag-icon {
display: inline-flex;
margin-right: 8px;
cursor: move;
}
.fixed-item {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: auto;
}
.ant-checkbox-wrapper {
flex: 1;
&:hover {
color: #1890ff !important;
}
}
}
&-checkbox-dark {
&:hover {
background: hsla(0, 0%, 100%, 0.08);
}
}
}
&-checkbox-dark {
&:hover {
background: hsla(0, 0%, 100%, .08);
.toolbar-popover {
.n-popover__content {
padding: 0;
}
}
}
.toolbar-popover {
.n-popover__content {
padding: 0;
}
}
</style>

View File

@@ -1,6 +1,6 @@
import componentSetting from '@/settings/componentSetting'
import componentSetting from '@/settings/componentSetting';
const { table } = componentSetting
const { table } = componentSetting;
const { apiSetting, defaultPageSize, pageSizes } = table;
@@ -9,7 +9,3 @@ export const DEFAULTPAGESIZE = defaultPageSize;
export const APISETTING = apiSetting;
export const PAGESIZES = pageSizes;

View File

@@ -1,126 +1,125 @@
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue';
import type { BasicColumn, BasicTableProps } from '../types/table';
import { isEqual, cloneDeep } from 'lodash-es';
import { isArray, isString } from '@/utils/is';
import { isArray, isString, isBoolean, isFunction } from '@/utils/is';
import { usePermission } from '@/hooks/web/usePermission';
import { isString, isBoolean, isFunction } from "@/utils/is";
import { ActionItem } from "@/components/Table";
import { ActionItem } from '@/components/Table';
export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
let cacheColumns = unref(propsRef).columns;
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
let cacheColumns = unref(propsRef).columns;
const getColumnsRef = computed(() => {
const columns = cloneDeep(unref(columnsRef));
const getColumnsRef = computed(() => {
const columns = cloneDeep(unref(columnsRef));
handleActionColumn(propsRef, columns);
if (!columns) return [];
return columns;
})
handleActionColumn(propsRef, columns);
if (!columns) return [];
return columns;
});
const { hasPermission } = usePermission();
const { hasPermission } = usePermission();
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
const getPageColumns = computed(() => {
const pageColumns = unref(getColumnsRef);
const columns = cloneDeep(pageColumns);
return columns.filter((column) => {
return hasPermission(column.auth) && isIfShow(column);
})
})
const getPageColumns = computed(() => {
const pageColumns = unref(getColumnsRef);
const columns = cloneDeep(pageColumns);
return columns.filter((column) => {
// @ts-ignore
return hasPermission(column.auth) && isIfShow(column);
});
});
watch(
() => unref(propsRef).columns,
(columns) => {
columnsRef.value = columns;
cacheColumns = columns;
watch(
() => unref(propsRef).columns,
(columns) => {
columnsRef.value = columns;
cacheColumns = columns;
}
);
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
const { actionColumn } = unref(propsRef);
if (!actionColumn) return;
// @ts-ignore
columns.push({
...actionColumn,
});
}
//设置
function setColumns(columnList: string[]) {
const columns: any[] = cloneDeep(columnList);
if (!isArray(columns)) return;
if (!columns.length) {
columnsRef.value = [];
return;
}
const cacheKeys = cacheColumns.map((item) => item.key);
//针对拖拽排序
if (!isString(columns[0])) {
columnsRef.value = columns;
} else {
const newColumns: any[] = [];
cacheColumns.forEach((item) => {
if (columnList.includes(item.key)) {
newColumns.push({ ...item });
}
);
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
const { actionColumn } = unref(propsRef);
if (!actionColumn) return;
columns.push({
...actionColumn
});
if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => {
return cacheKeys.indexOf(prev.key) - cacheKeys.indexOf(next.key);
});
}
columnsRef.value = newColumns;
}
}
//设置
function setColumns(columnList: string[]) {
const columns: any[] = cloneDeep(columnList);
if (!isArray(columns)) return;
//获取
function getColumns() {
const columns = toRaw(unref(getColumnsRef));
return columns.map((item) => {
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined };
});
}
if (!columns.length) {
columnsRef.value = [];
return;
}
const cacheKeys = cacheColumns.map((item) => item.key);
//针对拖拽排序
if (!isString(columns[0])) {
columnsRef.value = columns;
} else {
const newColumns: any[] = []
cacheColumns.forEach(item => {
if (columnList.includes(item.key)) {
newColumns.push({ ...item })
}
})
if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => {
return (
cacheKeys.indexOf(prev.key) - cacheKeys.indexOf(next.key)
);
});
}
columnsRef.value = newColumns
}
//获取原始
function getCacheColumns(isKey?: boolean): any[] {
return isKey ? cacheColumns.map((item) => item.key) : cacheColumns;
}
//更新原始数据单个字段
function setCacheColumnsField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
if (!dataIndex || !value) {
return;
}
cacheColumns.forEach((item) => {
if (item.key === dataIndex) {
Object.assign(item, value);
return;
}
});
}
//获取
function getColumns() {
let columns = toRaw(unref(getColumnsRef));
return columns.map(item => {
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined }
})
}
//获取原始
function getCacheColumns(isKey?: boolean): any[] {
return isKey ? cacheColumns.map(item => item.key) : cacheColumns;
}
//更新原始数据单个字段
function setCacheColumnsField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
if (!dataIndex || !value) {
return;
}
cacheColumns.forEach((item) => {
if (item.key === dataIndex) {
Object.assign(item, value);
return;
}
});
}
return {
getColumnsRef,
getCacheColumns,
setCacheColumnsField,
setColumns,
getColumns,
getPageColumns
};
return {
getColumnsRef,
getCacheColumns,
setCacheColumnsField,
setColumns,
getColumns,
getPageColumns,
};
}

View File

@@ -5,137 +5,134 @@ import { isBoolean } from '@/utils/is';
import { APISETTING } from '../const';
export function useDataSource(
propsRef: ComputedRef<BasicTableProps>,
{
getPaginationInfo,
setPagination,
setLoading,
tableData
},
emit
propsRef: ComputedRef<BasicTableProps>,
{ getPaginationInfo, setPagination, setLoading, tableData },
emit
) {
const dataSourceRef = ref([]);
const dataSourceRef = ref([]);
watchEffect(() => {
tableData.value = unref(dataSourceRef);
});
watchEffect(() => {
tableData.value = unref(dataSourceRef);
});
watch(
() => unref(propsRef).dataSource,
() => {
const { dataSource }: any = unref(propsRef);
dataSource && (dataSourceRef.value = dataSource);
},
{
immediate: true,
}
);
watch(
() => unref(propsRef).dataSource,
() => {
const { dataSource }: any = unref(propsRef);
dataSource && (dataSourceRef.value = dataSource);
},
{
immediate: true,
}
);
const getRowKey = computed(() => {
const { rowKey }: any = unref(propsRef);
return rowKey ? rowKey : () => {
return 'key'
const getRowKey = computed(() => {
const { rowKey }: any = unref(propsRef);
return rowKey
? rowKey
: () => {
return 'key';
};
});
});
const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
return unref(dataSourceRef);
}
return unref(dataSourceRef);
});
async function fetch(opt?) {
try {
setLoading(true);
const { request, pagination }: any = unref(propsRef);
//组装分页信息
const pageField = APISETTING.pageField
const sizeField = APISETTING.sizeField
const totalField = APISETTING.totalField
const listField = APISETTING.listField
let pageParams = {};
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
pageParams = {};
} else {
pageParams[pageField] = (opt && opt[pageField]) || page;
pageParams[sizeField] = pageSize;
}
let params = {
...pageParams,
}
const res = await request(params);
const resultTotal = res[totalField] || 0
const currentPage = res[pageField]
// 如果数据异常,需获取正确的页码再次执行
if (resultTotal) {
const currentTotalPage = Math.ceil(resultTotal / pageSize);
if (page > currentTotalPage) {
setPagination({
[pageField]: currentTotalPage,
});
fetch(opt);
}
}
let resultInfo = res[listField] ? res[listField] : []
dataSourceRef.value = resultInfo;
setPagination({
[pageField]: currentPage,
[totalField]: resultTotal,
});
if (opt && opt[pageField]) {
setPagination({
[pageField]: opt[pageField] || 1,
});
}
emit('fetch-success', {
items: unref(resultInfo),
resultTotal
});
} catch (error) {
console.error(error)
emit('fetch-error', error);
dataSourceRef.value = [];
// setPagination({
// pageCount: 0,
// });
} finally {
setLoading(false);
const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
return unref(dataSourceRef);
}
return unref(dataSourceRef);
});
async function fetch(opt?) {
try {
setLoading(true);
const { request, pagination }: any = unref(propsRef);
//组装分页信息
const pageField = APISETTING.pageField;
const sizeField = APISETTING.sizeField;
const totalField = APISETTING.totalField;
const listField = APISETTING.listField;
let pageParams = {};
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
pageParams = {};
} else {
pageParams[pageField] = (opt && opt[pageField]) || page;
pageParams[sizeField] = pageSize;
}
const params = {
...pageParams,
};
const res = await request(params);
const resultTotal = res[totalField] || 0;
const currentPage = res[pageField];
// 如果数据异常,需获取正确的页码再次执行
if (resultTotal) {
const currentTotalPage = Math.ceil(resultTotal / pageSize);
if (page > currentTotalPage) {
setPagination({
[pageField]: currentTotalPage,
});
fetch(opt);
}
}
const resultInfo = res[listField] ? res[listField] : [];
dataSourceRef.value = resultInfo;
setPagination({
[pageField]: currentPage,
[totalField]: resultTotal,
});
if (opt && opt[pageField]) {
setPagination({
[pageField]: opt[pageField] || 1,
});
}
emit('fetch-success', {
items: unref(resultInfo),
resultTotal,
});
} catch (error) {
console.error(error);
emit('fetch-error', error);
dataSourceRef.value = [];
// setPagination({
// pageCount: 0,
// });
} finally {
setLoading(false);
}
}
onMounted(() => {
setTimeout(() => {
fetch();
}, 16)
});
onMounted(() => {
setTimeout(() => {
fetch();
}, 16);
});
function setTableData(values) {
dataSourceRef.value = values;
}
function setTableData(values) {
dataSourceRef.value = values;
}
function getDataSource(): any[] {
return getDataSourceRef.value;
}
function getDataSource(): any[] {
return getDataSourceRef.value;
}
async function reload(opt?) {
await fetch(opt);
}
async function reload(opt?) {
await fetch(opt);
}
return {
fetch,
getRowKey,
getDataSourceRef,
getDataSource,
setTableData,
reload
}
return {
fetch,
getRowKey,
getDataSourceRef,
getDataSource,
setTableData,
reload,
};
}

View File

@@ -2,20 +2,20 @@ import { ref, ComputedRef, unref, computed, watch } from 'vue';
import type { BasicTableProps } from '../types/table';
export function useLoading(props: ComputedRef<BasicTableProps>) {
const loadingRef = ref(unref(props).loading);
const loadingRef = ref(unref(props).loading);
watch(
() => unref(props).loading,
(loading) => {
loadingRef.value = loading;
}
);
const getLoading = computed(() => unref(loadingRef));
function setLoading(loading: boolean) {
loadingRef.value = loading;
watch(
() => unref(props).loading,
(loading) => {
loadingRef.value = loading;
}
);
return { getLoading, setLoading };
const getLoading = computed(() => unref(loadingRef));
function setLoading(loading: boolean) {
loadingRef.value = loading;
}
return { getLoading, setLoading };
}

View File

@@ -6,43 +6,43 @@ import { isBoolean } from '@/utils/is';
import { DEFAULTPAGESIZE, PAGESIZES } from '../const';
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({});
const show = ref(true);
const configRef = ref<PaginationProps>({});
const show = ref(true);
const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps);
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
return false;
}
return {
pageSize: DEFAULTPAGESIZE,
pageSizes: PAGESIZES,
showSizePicker: true,
showQuickJumper: true,
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
};
});
function setPagination(info: Partial<PaginationProps>) {
const paginationInfo = unref(getPaginationInfo);
configRef.value = {
...(!isBoolean(paginationInfo) ? paginationInfo : {}),
...info,
};
const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps);
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
return false;
}
return {
pageSize: DEFAULTPAGESIZE,
pageSizes: PAGESIZES,
showSizePicker: true,
showQuickJumper: true,
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
};
});
function getPagination() {
return unref(getPaginationInfo);
}
function setPagination(info: Partial<PaginationProps>) {
const paginationInfo = unref(getPaginationInfo);
configRef.value = {
...(!isBoolean(paginationInfo) ? paginationInfo : {}),
...info,
};
}
function getShowPagination() {
return unref(show);
}
function getPagination() {
return unref(getPaginationInfo);
}
async function setShowPagination(flag: boolean) {
show.value = flag;
}
function getShowPagination() {
return unref(show);
}
return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination };
async function setShowPagination(flag: boolean) {
show.value = flag;
}
return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination };
}

View File

@@ -5,18 +5,18 @@ import { provide, inject, ComputedRef } from 'vue';
const key = Symbol('s-table');
type Instance = TableActionType & {
wrapRef: Ref<Nullable<HTMLElement>>;
getBindValues: ComputedRef<Recordable>;
wrapRef: Ref<Nullable<HTMLElement>>;
getBindValues: ComputedRef<Recordable>;
};
type RetInstance = Omit<Instance, 'getBindValues'> & {
getBindValues: ComputedRef<BasicTableProps>;
getBindValues: ComputedRef<BasicTableProps>;
};
export function createTableContext(instance: Instance) {
provide(key, instance);
provide(key, instance);
}
export function useTableContext(): RetInstance {
return inject(key) as RetInstance;
return inject(key) as RetInstance;
}

View File

@@ -1,49 +1,47 @@
import type { PropType } from 'vue'
import { BasicColumn } from './types/table'
import type { PropType } from 'vue';
import { BasicColumn } from './types/table';
export const basicProps = {
title: {
type: String,
default: null,
},
titleTooltip: {
type: String,
default: null,
},
size: {
type: String,
default: 'medium',
},
tableData: {
type: [Object],
default: () => {
},
},
columns: {
type: [Array] as PropType<BasicColumn[]>,
default: () => [],
required: true,
},
request: {
type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null,
required: true
},
rowKey: {
type: [String, Function] as PropType<string | ((record) => string)>,
default: undefined,
},
pagination: {
type: [Object, Boolean],
default: () => {
}
},
showPagination: {
type: [String, Boolean],
default: 'auto'
},
actionColumn: {
type: Object as PropType<BasicColumn>,
default: null,
},
}
title: {
type: String,
default: null,
},
titleTooltip: {
type: String,
default: null,
},
size: {
type: String,
default: 'medium',
},
tableData: {
type: [Object],
default: () => {},
},
columns: {
type: [Array] as PropType<BasicColumn[]>,
default: () => [],
required: true,
},
request: {
type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null,
required: true,
},
rowKey: {
type: [String, Function] as PropType<string | ((record) => string)>,
default: undefined,
},
pagination: {
type: [Object, Boolean],
default: () => {},
},
showPagination: {
type: [String, Boolean],
default: 'auto',
},
actionColumn: {
type: Object as PropType<BasicColumn>,
default: null,
},
};

View File

@@ -1,11 +1,8 @@
import Pagination from 'naive-ui/lib/pagination';
import { VNodeChild } from 'vue';
export interface PaginationProps {
page?: number;
pageCount?: number,
pageSize?: number,
pageSizes?: number[],
showSizePicker?: boolean,
showQuickJumper?: boolean,
page?: number;
pageCount?: number;
pageSize?: number;
pageSizes?: number[];
showSizePicker?: boolean;
showQuickJumper?: boolean;
}

View File

@@ -1,22 +1,19 @@
import type {
TableBaseColumn,
} from 'naive-ui/lib/data-table/src/interface';
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
export interface BasicColumn extends TableBaseColumn {
}
export type BasicColumn = TableBaseColumn;
export interface TableActionType {
reload: (opt) => Promise<void>;
emit?: any;
getColumns: (opt) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
reload: (opt) => Promise<void>;
emit?: any;
getColumns: (opt?) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
}
export interface BasicTableProps<T = any> {
title?: string,
dataSource: Function,
columns: any[],
pagination: object,
showPagination: boolean
export interface BasicTableProps {
title?: string;
dataSource: Function;
columns: any[];
pagination: object;
showPagination: boolean;
actionColumn: any[];
}

View File

@@ -1,26 +1,26 @@
import { NButton, NTooltip } from 'naive-ui';
import { RoleEnum } from '/@/enums/roleEnum';
// @ts-ignore
import { NButton } from 'naive-ui';
import { RoleEnum } from '@/enums/roleEnum';
// @ts-ignore
export interface ActionItem extends NButton.props {
onClick?: Fn;
label?: string;
color?: 'success' | 'error' | 'warning';
icon?: string;
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
tooltip?: string | TooltipProps;
onClick?: Fn;
label?: string;
color?: 'success' | 'error' | 'warning';
icon?: string;
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
}
export interface PopConfirm {
title: string;
okText?: string;
cancelText?: string;
confirm: Fn;
cancel?: Fn;
icon?: string;
title: string;
okText?: string;
cancelText?: string;
confirm: Fn;
cancel?: Fn;
icon?: string;
}

View File

@@ -2,42 +2,48 @@
<div class="w-full">
<div class="upload">
<div class="upload-card">
<!--图片列表-->
<div class="upload-card-item" :style="getCSSProperties" v-for="(item,index) in imgList">
<div
class="upload-card-item"
:style="getCSSProperties"
v-for="(item, index) in imgList"
:key="`img_${index}`"
>
<div class="upload-card-item-info">
<div class="img-box">
<img :src="item"/>
<img :src="item" />
</div>
<div class="img-box-actions">
<n-icon size="18" class="action-icon mx-2" @click="preview(item)">
<EyeOutlined/>
<EyeOutlined />
</n-icon>
<n-icon size="18" class="action-icon mx-2" @click="remove(index)">
<DeleteOutlined/>
<DeleteOutlined />
</n-icon>
</div>
</div>
</div>
<!--上传图片-->
<div class="upload-card-item upload-card-item-select-picture" :style="getCSSProperties"
v-if="imgList.length < maxNumber">
<div
class="upload-card-item upload-card-item-select-picture"
:style="getCSSProperties"
v-if="imgList.length < maxNumber"
>
<n-upload
v-bind="$props"
:file-list-style="{ display:'none'}"
@before-upload="beforeUpload"
@finish="finish"
v-bind="$props"
:file-list-style="{ display: 'none' }"
@before-upload="beforeUpload"
@finish="finish"
>
<div class="flex justify-center flex-col">
<n-icon size="18" class="m-auto">
<PlusOutlined/>
<PlusOutlined />
</n-icon>
<span class="upload-title">上传图片</span>
</div>
</n-upload>
</div>
</div>
</div>
@@ -47,267 +53,254 @@
{{ helpText }}
</n-alert>
</n-space>
</div>
<!--预览图片-->
<n-modal
v-model:show="showModal"
preset="card"
title="预览"
:bordered="false"
:style="{width: '520px'}"
v-model:show="showModal"
preset="card"
title="预览"
:bordered="false"
:style="{ width: '520px' }"
>
<img :src="previewUrl"/>
<img :src="previewUrl" />
</n-modal>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, computed } from "vue";
import { EyeOutlined, DeleteOutlined, PlusOutlined } from "@vicons/antd";
import { NUpload } from 'naive-ui'
import { basicProps } from "./props";
import { useMessage, useDialog } from 'naive-ui'
import { ResultEnum } from '@/enums/httpEnum'
import componentSetting from '@/settings/componentSetting'
import { useGlobSetting } from '@/hooks/setting'
import { isString } from '@/utils/is'
import { defineComponent, toRefs, reactive, computed } from 'vue';
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
import { NUpload } from 'naive-ui';
import { basicProps } from './props';
import { useMessage, useDialog } from 'naive-ui';
import { ResultEnum } from '@/enums/httpEnum';
import componentSetting from '@/settings/componentSetting';
import { useGlobSetting } from '@/hooks/setting';
import { isString } from '@/utils/is';
const globSetting = useGlobSetting()
const globSetting = useGlobSetting();
export default defineComponent({
name: 'BasicUpload',
export default defineComponent({
name: 'BasicUpload',
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
props: {
...NUpload.props, // 这里继承原 UI 组件的 props
...basicProps
},
emits: ['uploadChange', 'delete'],
setup(props, { emit }) {
const { value, width, height } = props
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
props: {
...NUpload.props, // 这里继承原 UI 组件的 props
...basicProps,
},
emits: ['uploadChange', 'delete'],
setup(props, { emit }) {
const getCSSProperties = computed(() => {
return {
width: `${props.width}px`,
height: `${props.height}px`,
};
});
const getCSSProperties = computed(() => {
return {
width: `${ width }px`,
height: `${ height }px`,
const message = useMessage();
const dialog = useDialog();
const state = reactive({
showModal: false,
previewUrl: '',
originalImgList: [],
imgList: [],
});
//赋值默认图片显示
if (props.value.length) {
state.imgList = props.value.map((item) => {
return getImgUrl(item);
});
}
})
const message = useMessage()
const dialog = useDialog()
//预览
function preview(url: string) {
state.showModal = true;
state.previewUrl = url;
}
const state = reactive({
showModal: false,
previewUrl: '',
originalImgList: [],
imgList: []
})
//删除
function remove(index: number) {
dialog.info({
title: '提示',
content: '你确定要删除吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
state.imgList.splice(index, 1);
state.originalImgList.splice(index, 1);
emit('uploadChange', state.originalImgList);
emit('delete', state.originalImgList);
},
onNegativeClick: () => {},
});
}
//赋值默认图片显示
if (value.length) {
state.imgList = value.map(item => {
return getImgUrl(item)
})
}
//组装完整图片地址
function getImgUrl(url: string): string {
const { imgUrl } = globSetting;
return /(^http|https:\/\/)/g.test(url) ? url : `${imgUrl}${url}`;
}
//预览
function preview(url: string) {
state.showModal = true
state.previewUrl = url
}
function checkFileType(fileType: string) {
return componentSetting.upload.fileType.includes(fileType);
}
//删除
function remove(index: number) {
dialog.info({
title: '提示',
content: '你确定要删除吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
state.imgList.splice(index, 1)
state.originalImgList.splice(index, 1)
emit('uploadChange', state.originalImgList)
emit('delete', state.originalImgList)
},
onNegativeClick: () => {
//上传之前
function beforeUpload({ file }) {
const fileInfo = file.file;
const { maxSize, accept } = props;
const acceptRef = (isString(accept) && accept.split(',')) || [];
// 设置最大值,则判断
if (maxSize && fileInfo.size / 1024 / 1024 >= maxSize) {
message.error(`上传文件最大值不能超过${maxSize}M`);
return false;
}
})
}
//组装完整图片地址
function getImgUrl(url: string) {
const { imgUrl } = globSetting
return (/(^http|https:\/\/)/g).test(url) ? url : `${ imgUrl }${ url }`
}
// 设置类型,则判断
const fileType = componentSetting.upload.fileType;
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
message.error(`只能上传文件类型为${fileType.join(',')}`);
return false;
}
function checkFileType(fileType: string) {
return componentSetting.upload.fileType.includes(fileType)
}
//上传之前
function beforeUpload({ file, fileList }) {
const fileInfo = file.file;
const { maxSize, accept, maxNumber } = props;
const acceptRef = (isString(accept) && accept.split(',')) || [];
// 设置最大值,则判断
if (maxSize && fileInfo.size / 1024 / 1024 >= maxSize) {
message.error(`上传文件最大值不能超过${ maxSize }M`);
return false;
return true;
}
// 设置类型,则判断
const fileType = componentSetting.upload.fileType
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
message.error(`只能上传文件类型为${ fileType.join(',') }`);
return false;
//上传结束
function finish({ event: Event }) {
const res = eval('(' + Event.target.response + ')');
const infoField = componentSetting.upload.apiSetting.infoField;
const { code } = res;
const message = res.msg || res.message || '上传失败';
const result = res[infoField];
//成功
if (code === ResultEnum.SUCCESS) {
let imgUrl = getImgUrl(result.photo);
state.imgList.push(imgUrl);
state.originalImgList.push(result.photo);
emit('uploadChange', state.originalImgList);
} else message.error(message);
}
return true
}
//上传结束
function finish({ event: Event }) {
const res = eval('(' + Event.target.response + ')');
const infoField = componentSetting.upload.apiSetting.infoField
const { code } = res
const message = (res.msg || res.message) || '上传失败'
const result = res[infoField]
//成功
if (code === ResultEnum.SUCCESS) {
let imgUrl: string = getImgUrl(result.photo)
state.imgList.push(imgUrl)
state.originalImgList.push(result.photo)
emit('uploadChange', state.originalImgList)
} else message.error(message)
}
return {
...toRefs(state),
finish,
preview,
remove,
beforeUpload,
getCSSProperties
}
}
})
return {
...toRefs(state),
finish,
preview,
remove,
beforeUpload,
getCSSProperties,
};
},
});
</script>
<style lang="less">
.upload {
width: 100%;
overflow: hidden;
.upload {
width: 100%;
overflow: hidden;
&-card {
width: auto;
height: auto;
display: flex;
flex-wrap: wrap;
align-items: center;
&-item {
margin: 0 8px 8px 0;
position: relative;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 2px;
&-card {
width: auto;
height: auto;
display: flex;
justify-content: center;
flex-direction: column;
flex-wrap: wrap;
align-items: center;
&:hover {
background: 0 0;
&-info::before {
opacity: 1
}
}
&-info {
&-item {
margin: 0 8px 8px 0;
position: relative;
height: 100%;
padding: 0;
overflow: hidden;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 2px;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
&:hover {
.img-box-actions {
background: 0 0;
.upload-card-item-info::before {
opacity: 1;
}
&-info::before {
opacity: 1;
}
}
&::before {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
opacity: 0;
transition: all .3s;
content: ' ';
}
.img-box {
&-info {
position: relative;
//padding: 8px;
//border: 1px solid #d9d9d9;
border-radius: 2px;
}
.img-box-actions {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
white-space: nowrap;
transform: translate(-50%, -50%);
opacity: 0;
transition: all .3s;
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0;
overflow: hidden;
&:hover {
background: 0 0;
.img-box-actions {
opacity: 1;
}
}
.action-icon {
color: rgba(255, 255, 255, .85);
&::before {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: all 0.3s;
content: ' ';
}
.img-box {
position: relative;
//padding: 8px;
//border: 1px solid #d9d9d9;
border-radius: 2px;
}
.img-box-actions {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
white-space: nowrap;
transform: translate(-50%, -50%);
opacity: 0;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: space-between;
&:hover {
cursor: pointer;
color: #fff
background: 0 0;
}
.action-icon {
color: rgba(255, 255, 255, 0.85);
&:hover {
cursor: pointer;
color: #fff;
}
}
}
}
}
}
&-item-select-picture {
border: 1px dashed #d9d9d9;
border-radius: 2px;
cursor: pointer;
background: #fafafa;
color: #666;
.upload-title {
&-item-select-picture {
border: 1px dashed #d9d9d9;
border-radius: 2px;
cursor: pointer;
background: #fafafa;
color: #666;
}
}
&-item:hover {
background: 0 0;
.upload-card-item-info::before {
opacity: 1
.upload-title {
color: #666;
}
}
}
}
}
</style>

View File

@@ -1,34 +1,34 @@
import type { PropType } from 'vue'
import { NUpload } from 'naive-ui'
import type { PropType } from 'vue';
import { NUpload } from 'naive-ui';
export const basicProps = {
...NUpload.props,
accept: {
type: String,
default: '.jpg,.png,.jpeg,.svg,.gif',
},
helpText: {
type: String as PropType<string>,
default: '',
},
maxSize: {
type: Number as PropType<number>,
default: 2
},
maxNumber: {
type: Number as PropType<number>,
default: Infinity
},
value: {
type: Array as PropType<string[]>,
default: () => []
},
width: {
type: Number as PropType<number>,
default: 104
},
height: {
type: Number as PropType<number>,
default: 104 //建议不小于这个尺寸 太小页面可能显示有异常
},
}
...NUpload.props,
accept: {
type: String,
default: '.jpg,.png,.jpeg,.svg,.gif',
},
helpText: {
type: String as PropType<string>,
default: '',
},
maxSize: {
type: Number as PropType<number>,
default: 2,
},
maxNumber: {
type: Number as PropType<number>,
default: Infinity,
},
value: {
type: Array as PropType<string[]>,
default: () => [],
},
width: {
type: Number as PropType<number>,
default: 104,
},
height: {
type: Number as PropType<number>,
default: 104, //建议不小于这个尺寸 太小页面可能显示有异常
},
};

View File

@@ -1,7 +1,7 @@
export interface BasicProps<T = any> {
title?: string,
dataSource: Function,
columns: any[],
pagination: object,
showPagination: boolean
export interface BasicProps {
title?: string;
dataSource: Function;
columns: any[];
pagination: object;
showPagination: boolean;
}

View File

@@ -1,19 +1,19 @@
import { ObjectDirective } from 'vue'
import { usePermission } from "@/hooks/web/usePermission";
import { ObjectDirective } from 'vue';
import { usePermission } from '@/hooks/web/usePermission';
export const permission: ObjectDirective = {
mounted(el: HTMLButtonElement, binding, vnode) {
if (binding.value == undefined) return
const { action, effect } = binding.value
const { hasPermission } = usePermission()
if (!hasPermission(action)) {
if (effect == 'disabled') {
el.disabled = true
el.style["disabled"] = 'disabled'
el.classList.add("n-button--disabled")
} else {
el.remove()
}
}
mounted(el: HTMLButtonElement, binding) {
if (binding.value == undefined) return;
const { action, effect } = binding.value;
const { hasPermission } = usePermission();
if (!hasPermission(action)) {
if (effect == 'disabled') {
el.disabled = true;
el.style['disabled'] = 'disabled';
el.classList.add('n-button--disabled');
} else {
el.remove();
}
}
}
},
};

View File

@@ -1,19 +1,19 @@
export enum sizeEnum {
XS = 'XS',
SM = 'SM',
MD = 'MD',
LG = 'LG',
XL = 'XL',
XXL = 'XXL',
XS = 'XS',
SM = 'SM',
MD = 'MD',
LG = 'LG',
XL = 'XL',
XXL = 'XXL',
}
export enum screenEnum {
XS = 480,
SM = 576,
MD = 768,
LG = 992,
XL = 1200,
XXL = 1600,
XS = 480,
SM = 576,
MD = 768,
LG = 992,
XL = 1200,
XXL = 1600,
}
const screenMap = new Map<sizeEnum, number>();

View File

@@ -1,20 +1,20 @@
// token key
export const TOKEN_KEY = 'TOKEN'
export const TOKEN_KEY = 'TOKEN';
// user info key
export const USER_INFO_KEY = 'USER__INFO__'
export const USER_INFO_KEY = 'USER__INFO__';
// role info key
export const ROLES_KEY = 'ROLES__KEY__'
export const ROLES_KEY = 'ROLES__KEY__';
// project config key
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
// lock info
export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'
export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
// base global local key
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__';
// base global session key
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';

View File

@@ -2,33 +2,33 @@
* @description: 请求结果集
*/
export enum ResultEnum {
SUCCESS = 200,
ERROR = -1,
TIMEOUT = 10042,
TYPE = 'success'
SUCCESS = 200,
ERROR = -1,
TIMEOUT = 10042,
TYPE = 'success',
}
/**
* @description: 请求方法
*/
export enum RequestEnum {
GET = 'GET',
POST = 'POST',
PATCH = 'PATCH',
PUT = 'PUT',
DELETE = 'DELETE'
GET = 'GET',
POST = 'POST',
PATCH = 'PATCH',
PUT = 'PUT',
DELETE = 'DELETE',
}
/**
* @description: 常用的contentTyp类型
*/
export enum ContentTypeEnum {
// json
JSON = 'application/json;charset=UTF-8',
// json
TEXT = 'text/plain;charset=UTF-8',
// form-data 一般配合qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data 上传
FORM_DATA = 'multipart/form-data;charset=UTF-8'
// json
JSON = 'application/json;charset=UTF-8',
// json
TEXT = 'text/plain;charset=UTF-8',
// form-data 一般配合qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data 上传
FORM_DATA = 'multipart/form-data;charset=UTF-8',
}

View File

@@ -1,14 +1,14 @@
export enum PageEnum {
// 登录
BASE_LOGIN = '/login',
BASE_LOGIN_NAME = 'Login',
//重定向
REDIRECT = '/redirect',
REDIRECT_NAME = 'Redirect',
// 首页
BASE_HOME = '/dashboard',
//首页跳转默认路由
BASE_HOME_REDIRECT = '/dashboard/console',
// 错误
ERROR_PAGE_NAME = 'ErrorPage',
// 登录
BASE_LOGIN = '/login',
BASE_LOGIN_NAME = 'Login',
//重定向
REDIRECT = '/redirect',
REDIRECT_NAME = 'Redirect',
// 首页
BASE_HOME = '/dashboard',
//首页跳转默认路由
BASE_HOME_REDIRECT = '/dashboard/console',
// 错误
ERROR_PAGE_NAME = 'ErrorPage',
}

View File

@@ -1,7 +1,7 @@
export enum RoleEnum {
// 管理员
ADMIN = 'admin',
// 管理员
ADMIN = 'admin',
// 普通用户
NORMAL = 'normal'
// 普通用户
NORMAL = 'normal',
}

View File

@@ -3,45 +3,45 @@ import { tryOnUnmounted } from '@vueuse/core';
import { isFunction } from '@/utils/is';
export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) {
if (!isFunction(handle)) {
throw new Error('handle is not Function!');
}
if (!isFunction(handle)) {
throw new Error('handle is not Function!');
}
const { readyRef, stop, start } = useTimeoutRef(wait);
if (native) {
handle();
} else {
watch(
readyRef,
(maturity) => {
maturity && handle();
},
{ immediate: false }
);
}
return { readyRef, stop, start };
const { readyRef, stop, start } = useTimeoutRef(wait);
if (native) {
handle();
} else {
watch(
readyRef,
(maturity) => {
maturity && handle();
},
{ immediate: false }
);
}
return { readyRef, stop, start };
}
export function useTimeoutRef(wait: number) {
const readyRef = ref(false);
const readyRef = ref(false);
let timer: TimeoutHandle;
let timer: TimeoutHandle;
function stop(): void {
readyRef.value = false;
timer && window.clearTimeout(timer);
}
function stop(): void {
readyRef.value = false;
timer && window.clearTimeout(timer);
}
function start(): void {
stop();
timer = setTimeout(() => {
readyRef.value = true;
}, wait);
}
function start(): void {
stop();
timer = setTimeout(() => {
readyRef.value = true;
}, wait);
}
start();
start();
tryOnUnmounted(stop);
tryOnUnmounted(stop);
return { readyRef, stop, start };
return { readyRef, stop, start };
}

View File

@@ -7,83 +7,83 @@ let globalWidthRef: ComputedRef<number>;
let globalRealWidthRef: ComputedRef<number>;
export interface CreateCallbackParams {
screen: ComputedRef<sizeEnum | undefined>;
width: ComputedRef<number>;
realWidth: ComputedRef<number>;
screenEnum: typeof screenEnum;
screenMap: Map<sizeEnum, number>;
sizeEnum: typeof sizeEnum;
screen: ComputedRef<sizeEnum | undefined>;
width: ComputedRef<number>;
realWidth: ComputedRef<number>;
screenEnum: typeof screenEnum;
screenMap: Map<sizeEnum, number>;
sizeEnum: typeof sizeEnum;
}
export function useBreakpoint() {
return {
screenRef: computed(() => unref(globalScreenRef)),
widthRef: globalWidthRef,
screenEnum,
realWidthRef: globalRealWidthRef,
};
return {
screenRef: computed(() => unref(globalScreenRef)),
widthRef: globalWidthRef,
screenEnum,
realWidthRef: globalRealWidthRef,
};
}
// Just call it once
export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) {
const screenRef = ref<sizeEnum>(sizeEnum.XL);
const realWidthRef = ref(window.innerWidth);
const screenRef = ref<sizeEnum>(sizeEnum.XL);
const realWidthRef = ref(window.innerWidth);
function getWindowWidth() {
const width = document.body.clientWidth;
const xs = screenMap.get(sizeEnum.XS)!;
const sm = screenMap.get(sizeEnum.SM)!;
const md = screenMap.get(sizeEnum.MD)!;
const lg = screenMap.get(sizeEnum.LG)!;
const xl = screenMap.get(sizeEnum.XL)!;
if (width < xs) {
screenRef.value = sizeEnum.XS;
} else if (width < sm) {
screenRef.value = sizeEnum.SM;
} else if (width < md) {
screenRef.value = sizeEnum.MD;
} else if (width < lg) {
screenRef.value = sizeEnum.LG;
} else if (width < xl) {
screenRef.value = sizeEnum.XL;
} else {
screenRef.value = sizeEnum.XXL;
}
realWidthRef.value = width;
function getWindowWidth() {
const width = document.body.clientWidth;
const xs = screenMap.get(sizeEnum.XS)!;
const sm = screenMap.get(sizeEnum.SM)!;
const md = screenMap.get(sizeEnum.MD)!;
const lg = screenMap.get(sizeEnum.LG)!;
const xl = screenMap.get(sizeEnum.XL)!;
if (width < xs) {
screenRef.value = sizeEnum.XS;
} else if (width < sm) {
screenRef.value = sizeEnum.SM;
} else if (width < md) {
screenRef.value = sizeEnum.MD;
} else if (width < lg) {
screenRef.value = sizeEnum.LG;
} else if (width < xl) {
screenRef.value = sizeEnum.XL;
} else {
screenRef.value = sizeEnum.XXL;
}
realWidthRef.value = width;
}
useEventListener({
el: window,
name: 'resize',
useEventListener({
el: window,
name: 'resize',
listener: () => {
getWindowWidth();
resizeFn();
},
// wait: 100,
listener: () => {
getWindowWidth();
resizeFn();
},
// wait: 100,
});
getWindowWidth();
globalScreenRef = computed(() => unref(screenRef));
globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!);
globalRealWidthRef = computed((): number => unref(realWidthRef));
function resizeFn() {
fn?.({
screen: globalScreenRef,
width: globalWidthRef,
realWidth: globalRealWidthRef,
screenEnum,
screenMap,
sizeEnum,
});
}
getWindowWidth();
globalScreenRef = computed(() => unref(screenRef));
globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!);
globalRealWidthRef = computed((): number => unref(realWidthRef));
function resizeFn() {
fn?.({
screen: globalScreenRef,
width: globalWidthRef,
realWidth: globalRealWidthRef,
screenEnum,
screenMap,
sizeEnum,
});
}
resizeFn();
return {
screenRef: globalScreenRef,
screenEnum,
widthRef: globalWidthRef,
realWidthRef: globalRealWidthRef,
};
resizeFn();
return {
screenRef: globalScreenRef,
screenEnum,
widthRef: globalWidthRef,
realWidthRef: globalRealWidthRef,
};
}

View File

@@ -6,57 +6,57 @@ import { useThrottleFn, useDebounceFn } from '@vueuse/core';
export type RemoveEventFn = () => void;
export interface UseEventParams {
el?: Element | Ref<Element | undefined> | Window | any;
name: string;
listener: EventListener;
options?: boolean | AddEventListenerOptions;
autoRemove?: boolean;
isDebounce?: boolean;
wait?: number;
el?: Element | Ref<Element | undefined> | Window | any;
name: string;
listener: EventListener;
options?: boolean | AddEventListenerOptions;
autoRemove?: boolean;
isDebounce?: boolean;
wait?: number;
}
export function useEventListener({
el = window,
name,
listener,
options,
autoRemove = true,
isDebounce = true,
wait = 80,
}: UseEventParams): { removeEvent: RemoveEventFn } {
/* eslint-disable-next-line */
el = window,
name,
listener,
options,
autoRemove = true,
isDebounce = true,
wait = 80,
}: UseEventParams): { removeEvent: RemoveEventFn } {
/* eslint-disable-next-line */
let remove: RemoveEventFn = () => {
};
const isAddRef = ref(false);
const isAddRef = ref(false);
if (el) {
const element: Ref<Element> = ref(el as Element);
if (el) {
const element: Ref<Element> = ref(el as Element);
const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait);
const realHandler = wait ? handler : listener;
const removeEventListener = (e: Element) => {
isAddRef.value = true;
e.removeEventListener(name, realHandler, options);
};
const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options);
const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait);
const realHandler = wait ? handler : listener;
const removeEventListener = (e: Element) => {
isAddRef.value = true;
e.removeEventListener(name, realHandler, options);
};
const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options);
const removeWatch = watch(
element,
(v, _ov, cleanUp) => {
if (v) {
!unref(isAddRef) && addEventListener(v);
cleanUp(() => {
autoRemove && removeEventListener(v);
});
}
},
{ immediate: true }
);
const removeWatch = watch(
element,
(v, _ov, cleanUp) => {
if (v) {
!unref(isAddRef) && addEventListener(v);
cleanUp(() => {
autoRemove && removeEventListener(v);
});
}
},
{ immediate: true }
);
remove = () => {
removeEventListener(element.value);
removeWatch();
};
}
return { removeEvent: remove };
remove = () => {
removeEventListener(element.value);
removeWatch();
};
}
return { removeEvent: remove };
}

View File

@@ -1,3 +1,3 @@
import { useAsync } from './use-async'
import { useAsync } from './use-async';
export { useAsync }
export { useAsync };

View File

@@ -4,32 +4,31 @@ import { warn } from '@/utils/log';
import { getAppEnvConfig } from '@/utils/env';
export const useGlobSetting = (): Readonly<GlobConfig> => {
const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL
} = getAppEnvConfig();
const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL,
} = getAppEnvConfig();
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
);
}
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
);
}
// Take global configuration
const glob: Readonly<GlobConfig> = {
title: VITE_GLOB_APP_TITLE,
apiUrl: VITE_GLOB_API_URL,
shortName: VITE_GLOB_APP_SHORT_NAME,
urlPrefix: VITE_GLOB_API_URL_PREFIX,
uploadUrl: VITE_GLOB_UPLOAD_URL,
prodMock: VITE_GLOB_PROD_MOCK,
imgUrl: VITE_GLOB_IMG_URL
};
return glob as Readonly<GlobConfig>;
// Take global configuration
const glob: Readonly<GlobConfig> = {
title: VITE_GLOB_APP_TITLE,
apiUrl: VITE_GLOB_API_URL,
shortName: VITE_GLOB_APP_SHORT_NAME,
urlPrefix: VITE_GLOB_API_URL_PREFIX,
uploadUrl: VITE_GLOB_UPLOAD_URL,
prodMock: VITE_GLOB_PROD_MOCK,
imgUrl: VITE_GLOB_IMG_URL,
};
return glob as Readonly<GlobConfig>;
};

View File

@@ -2,18 +2,17 @@ import { computed } from 'vue';
import { useDesignSettingStore } from '@/store/modules/designSetting';
export function useDesignSetting() {
const designStore = useDesignSettingStore();
const designStore = useDesignSettingStore();
const getDarkTheme = computed(() => designStore.darkTheme);
const getDarkTheme = computed(() => designStore.darkTheme);
const getAppTheme = computed(() => designStore.appTheme);
const getAppTheme = computed(() => designStore.appTheme);
const getAppThemeList = computed(() => designStore.appThemeList);
return {
getDarkTheme,
getAppTheme,
getAppThemeList
}
const getAppThemeList = computed(() => designStore.appThemeList);
return {
getDarkTheme,
getAppTheme,
getAppThemeList,
};
}

View File

@@ -2,33 +2,32 @@ import { computed } from 'vue';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
export function useProjectSetting() {
const projectStore = useProjectSettingStore();
const projectStore = useProjectSettingStore();
const getNavMode = computed(() => projectStore.navMode);
const getNavMode = computed(() => projectStore.navMode);
const getNavTheme = computed(() => projectStore.navTheme);
const getNavTheme = computed(() => projectStore.navTheme);
const getHeaderSetting = computed(() => projectStore.headerSetting);
const getHeaderSetting = computed(() => projectStore.headerSetting);
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
const getMenuSetting = computed(() => projectStore.menuSetting);
const getMenuSetting = computed(() => projectStore.menuSetting);
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
const getPermissionMode = computed(() => projectStore.permissionMode);
const getPermissionMode = computed(() => projectStore.permissionMode);
const getShowFooter = computed(() => projectStore.showFooter);
const getShowFooter = computed(() => projectStore.showFooter);
return {
getNavMode,
getNavTheme,
getHeaderSetting,
getMultiTabsSetting,
getMenuSetting,
getCrumbsSetting,
getPermissionMode,
getShowFooter
}
return {
getNavMode,
getNavTheme,
getHeaderSetting,
getMultiTabsSetting,
getMenuSetting,
getCrumbsSetting,
getPermissionMode,
getShowFooter,
};
}

View File

@@ -1,15 +1,15 @@
import { Ref, isReactive, isRef } from 'vue'
import { isReactive, isRef } from 'vue';
function setLoading(loading, val) {
if (loading != undefined && isRef(loading)) {
loading.value = val
} else if (loading != undefined && isReactive(loading)) {
loading.loading = val
}
if (loading != undefined && isRef(loading)) {
loading.value = val;
} else if (loading != undefined && isReactive(loading)) {
loading.loading = val;
}
}
export const useAsync = async (func: Promise<any>, loading: any): Promise<any> => {
setLoading(loading, true)
setLoading(loading, true);
return await func.finally(() => setLoading(loading, false))
}
return await func.finally(() => setLoading(loading, false));
};

View File

@@ -1,84 +1,84 @@
import { computed, onMounted, reactive, toRefs } from 'vue'
import { computed, onMounted, reactive, toRefs } from 'vue';
interface Battery {
charging: boolean // 当前电池是否正在充电
chargingTime: number // 距离充电完毕还需多少秒如果为0则充电完毕
dischargingTime: number // 代表距离电池耗电至空且挂起需要多少秒
level: number // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
[key: string]: any
charging: boolean; // 当前电池是否正在充电
chargingTime: number; // 距离充电完毕还需多少秒如果为0则充电完毕
dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒
level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
[key: string]: any;
}
export const useBattery = () => {
const state = reactive({
battery: {
charging: false,
chargingTime: 0,
dischargingTime: 0,
level: 100
}
})
const state = reactive({
battery: {
charging: false,
chargingTime: 0,
dischargingTime: 0,
level: 100,
},
});
// 更新电池使用状态
const updateBattery = (target) => {
for (const key in state.battery) {
state.battery[key] = target[key]
}
state.battery.level = state.battery.level * 100
// 更新电池使用状态
const updateBattery = (target) => {
for (const key in state.battery) {
state.battery[key] = target[key];
}
state.battery.level = state.battery.level * 100;
};
// 计算电池剩余可用时间
const calcDischargingTime = computed(() => {
const hour = state.battery.dischargingTime / 3600
const minute = (state.battery.dischargingTime / 60) % 60
return `${ ~~hour }小时${ ~~minute }分钟`
})
// 计算电池剩余可用时间
const calcDischargingTime = computed(() => {
const hour = state.battery.dischargingTime / 3600;
const minute = (state.battery.dischargingTime / 60) % 60;
return `${~~hour}小时${~~minute}分钟`;
});
// 电池状态
const batteryStatus = computed(() => {
if (state.battery.charging && state.battery.level >= 100) {
return '已充满'
} else if (state.battery.charging) {
return '充电中'
} else {
return '已断开电源'
}
})
onMounted(async () => {
const BatteryManager: Battery = await (window.navigator as any).getBattery()
updateBattery(BatteryManager)
// 电池充电状态更新时被调用
BatteryManager.onchargingchange = ({ target }) => {
updateBattery(target)
}
// 电池充电时间更新时被调用
BatteryManager.onchargingtimechange = ({ target }) => {
updateBattery(target)
}
// 电池断开充电时间更新时被调用
BatteryManager.ondischargingtimechange = ({ target }) => {
updateBattery(target)
}
// 电池电量更新时被调用
BatteryManager.onlevelchange = ({ target }) => {
updateBattery(target)
}
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
})
return {
...toRefs(state),
batteryStatus,
calcDischargingTime
// 电池状态
const batteryStatus = computed(() => {
if (state.battery.charging && state.battery.level >= 100) {
return '已充满';
} else if (state.battery.charging) {
return '充电中';
} else {
return '已断开电源';
}
}
});
onMounted(async () => {
const BatteryManager: Battery = await (window.navigator as any).getBattery();
updateBattery(BatteryManager);
// 电池充电状态更新时被调用
BatteryManager.onchargingchange = ({ target }) => {
updateBattery(target);
};
// 电池充电时间更新时被调用
BatteryManager.onchargingtimechange = ({ target }) => {
updateBattery(target);
};
// 电池断开充电时间更新时被调用
BatteryManager.ondischargingtimechange = ({ target }) => {
updateBattery(target);
};
// 电池电量更新时被调用
BatteryManager.onlevelchange = ({ target }) => {
updateBattery(target);
};
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
});
return {
...toRefs(state),
batteryStatus,
calcDischargingTime,
};
};

View File

@@ -1,23 +1,23 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { debounce } from 'lodash'
import { ref, onMounted, onUnmounted } from 'vue';
import { debounce } from 'lodash';
/**
* description: 获取页面宽度
*/
export function useDomWidth() {
const domWidth = ref(window.innerWidth)
const domWidth = ref(window.innerWidth);
function resize() {
domWidth.value = document.body.clientWidth
}
function resize() {
domWidth.value = document.body.clientWidth;
}
onMounted(() => {
window.addEventListener('resize', debounce(resize, 80))
})
onUnmounted(() => {
window.removeEventListener('resize', resize)
})
onMounted(() => {
window.addEventListener('resize', debounce(resize, 80));
});
onUnmounted(() => {
window.removeEventListener('resize', resize);
});
return domWidth
return domWidth;
}

View File

@@ -1,30 +1,30 @@
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue';
/**
* @description 用户网络是否可用
* */
export function useOnline() {
const online = ref(true)
const online = ref(true);
const showStatus = (val) => {
online.value = typeof val == 'boolean' ? val : val.target.online
}
const showStatus = (val) => {
online.value = typeof val == 'boolean' ? val : val.target.online;
};
// 在页面加载后,设置正确的网络状态
navigator.onLine ? showStatus(true) : showStatus(false)
// 在页面加载后,设置正确的网络状态
navigator.onLine ? showStatus(true) : showStatus(false);
onMounted(() => {
// 开始监听网络状态的变化
window.addEventListener('online', showStatus)
onMounted(() => {
// 开始监听网络状态的变化
window.addEventListener('online', showStatus);
window.addEventListener('offline', showStatus)
})
onUnmounted(() => {
// 移除监听网络状态的变化
window.removeEventListener('online', showStatus)
window.addEventListener('offline', showStatus);
});
onUnmounted(() => {
// 移除监听网络状态的变化
window.removeEventListener('online', showStatus);
window.removeEventListener('offline', showStatus)
})
window.removeEventListener('offline', showStatus);
});
return { online }
return { online };
}

View File

@@ -1,55 +1,55 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue';
/**
* @description 获取本地时间
*/
export function useTime() {
let timer // 定时器
const year = ref(0) // 年份
const month = ref(0) // 月份
const week = ref('') // 星期几
const day = ref(0) // 天数
const hour = ref<number | string>(0) // 小时
const minute = ref<number | string>(0) // 分钟
const second = ref(0) // 秒
let timer; // 定时器
const year = ref(0); // 年份
const month = ref(0); // 月份
const week = ref(''); // 星期几
const day = ref(0); // 天数
const hour = ref<number | string>(0); // 小时
const minute = ref<number | string>(0); // 分钟
const second = ref(0); // 秒
// 更新时间
const updateTime = () => {
const date = new Date()
year.value = date.getFullYear()
month.value = date.getMonth() + 1
week.value = '日一二三四五六'.charAt(date.getDay())
day.value = date.getDate()
hour.value =
(date.getHours() + '')?.padStart(2, '0') ||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
minute.value =
(date.getMinutes() + '')?.padStart(2, '0') ||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
second.value = date.getSeconds()
}
// 更新时间
const updateTime = () => {
const date = new Date();
year.value = date.getFullYear();
month.value = date.getMonth() + 1;
week.value = '日一二三四五六'.charAt(date.getDay());
day.value = date.getDate();
hour.value =
(date.getHours() + '')?.padStart(2, '0') ||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours());
minute.value =
(date.getMinutes() + '')?.padStart(2, '0') ||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes());
second.value = date.getSeconds();
};
// 原生时间格式化
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
// 原生时间格式化
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
updateTime()
updateTime();
onMounted(() => {
clearInterval(timer)
timer = setInterval(() => updateTime(), 1000)
})
onMounted(() => {
clearInterval(timer);
timer = setInterval(() => updateTime(), 1000);
});
onUnmounted(() => {
clearInterval(timer)
})
onUnmounted(() => {
clearInterval(timer);
});
return { month, day, hour, minute, second, week }
return { month, day, hour, minute, second, week };
}

View File

@@ -12,107 +12,105 @@ import echarts from '@/utils/lib/echarts';
// import { useRootSetting } from '@/hooks/setting/useRootSetting';
export function useECharts(
elRef: Ref<HTMLDivElement>,
theme: 'light' | 'dark' | 'default' = 'light'
elRef: Ref<HTMLDivElement>,
theme: 'light' | 'dark' | 'default' = 'light'
) {
// const { getDarkMode } = useRootSetting();
const getDarkMode = 'light'
let chartInstance: echarts.ECharts | null = null;
let resizeFn: Fn = resize;
const cacheOptions = ref<EChartsOption>({});
let removeResizeFn: Fn = () => {
};
// const { getDarkMode } = useRootSetting();
const getDarkMode = 'light';
let chartInstance: echarts.ECharts | null = null;
let resizeFn: Fn = resize;
const cacheOptions = ref<EChartsOption>({});
let removeResizeFn: Fn = () => {};
resizeFn = useDebounceFn(resize, 200);
resizeFn = useDebounceFn(resize, 200);
const getOptions = computed((): EChartsOption => {
if (getDarkMode !== 'dark') {
return cacheOptions.value;
}
return {
backgroundColor: 'transparent',
...cacheOptions.value,
};
});
function initCharts(t = theme) {
const el = unref(elRef);
if (!el || !unref(el)) {
return;
}
chartInstance = echarts.init(el, t);
const { removeEvent } = useEventListener({
el: window,
name: 'resize',
listener: resizeFn,
});
removeResizeFn = removeEvent;
const { widthRef, screenEnum } = useBreakpoint();
if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
useTimeoutFn(() => {
resizeFn();
}, 30);
}
const getOptions = computed((): EChartsOption => {
if (getDarkMode !== 'dark') {
return cacheOptions.value;
}
function setOptions(options: EChartsOption, clear = true) {
cacheOptions.value = options;
if (unref(elRef)?.offsetHeight === 0) {
useTimeoutFn(() => {
setOptions(unref(getOptions));
}, 30);
return;
}
nextTick(() => {
useTimeoutFn(() => {
if (!chartInstance) {
initCharts(getDarkMode.value as 'default');
if (!chartInstance) return;
}
clear && chartInstance?.clear();
chartInstance?.setOption(unref(getOptions));
}, 30);
});
}
function resize() {
chartInstance?.resize();
}
watch(
() => getDarkMode.value,
(theme) => {
if (chartInstance) {
chartInstance.dispose();
initCharts(theme as 'default');
setOptions(cacheOptions.value);
}
}
);
tryOnUnmounted(() => {
if (!chartInstance) return;
removeResizeFn();
chartInstance.dispose();
chartInstance = null;
});
function getInstance(): echarts.ECharts | null {
if (!chartInstance) {
initCharts(getDarkMode.value as 'default');
}
return chartInstance;
}
return {
setOptions,
resize,
echarts,
getInstance,
backgroundColor: 'transparent',
...cacheOptions.value,
};
});
function initCharts(t = theme) {
const el = unref(elRef);
if (!el || !unref(el)) {
return;
}
chartInstance = echarts.init(el, t);
const { removeEvent } = useEventListener({
el: window,
name: 'resize',
listener: resizeFn,
});
removeResizeFn = removeEvent;
const { widthRef, screenEnum } = useBreakpoint();
if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
useTimeoutFn(() => {
resizeFn();
}, 30);
}
}
function setOptions(options: EChartsOption, clear = true) {
cacheOptions.value = options;
if (unref(elRef)?.offsetHeight === 0) {
useTimeoutFn(() => {
setOptions(unref(getOptions));
}, 30);
return;
}
nextTick(() => {
useTimeoutFn(() => {
if (!chartInstance) {
initCharts(getDarkMode.value as 'default');
if (!chartInstance) return;
}
clear && chartInstance?.clear();
chartInstance?.setOption(unref(getOptions));
}, 30);
});
}
function resize() {
chartInstance?.resize();
}
watch(
() => getDarkMode.value,
(theme) => {
if (chartInstance) {
chartInstance.dispose();
initCharts(theme as 'default');
setOptions(cacheOptions.value);
}
}
);
tryOnUnmounted(() => {
if (!chartInstance) return;
removeResizeFn();
chartInstance.dispose();
chartInstance = null;
});
function getInstance(): echarts.ECharts | null {
if (!chartInstance) {
initCharts(getDarkMode.value as 'default');
}
return chartInstance;
}
return {
setOptions,
resize,
echarts,
getInstance,
};
}

View File

@@ -1,52 +1,52 @@
import { useUserStore } from '@/store/modules/user'
import { useUserStore } from '@/store/modules/user';
export function usePermission() {
const userStore = useUserStore();
const userStore = useUserStore();
/**
* 检查权限
* @param accesses
*/
function _someRoles(accesses: string[]) {
return userStore.getRoles.some(item => {
const { value }: any = item
return accesses.includes(value)
})
/**
* 检查权限
* @param accesses
*/
function _someRoles(accesses: string[]) {
return userStore.getRoles.some((item) => {
const { value }: any = item;
return accesses.includes(value);
});
}
/**
* 判断是否存在权限
* 可用于 v-if 显示逻辑
* */
function hasPermission(accesses: string[]): boolean {
if (!accesses || !accesses.length) return true;
return _someRoles(accesses);
}
/**
* 是否包含指定的所有权限
* @param accesses
*/
function hasEveryPermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles;
if (Array.isArray(accesses)) {
return accesses.every((access) => !!rolesList[access]);
}
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
}
/**
* 判断是否存在权限
* 可用于 v-if 显示逻辑
* */
function hasPermission(accesses: string[]): boolean {
if (!accesses ||!accesses.length) return true
return _someRoles(accesses)
/**
* 是否包含其中某个权限
* @param accesses
* @param accessMap
*/
function hasSomePermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles;
if (Array.isArray(accesses)) {
return accesses.some((access) => !!rolesList[access]);
}
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
}
/**
* 是否包含指定的所有权限
* @param accesses
*/
function hasEveryPermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles
if (Array.isArray(accesses)) {
return accesses.every((access) => !!rolesList[access])
}
throw new Error(`[hasEveryPermission]: ${ accesses } should be a array !`)
}
/**
* 是否包含其中某个权限
* @param accesses
* @param accessMap
*/
function hasSomePermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles
if (Array.isArray(accesses)) {
return accesses.some((access) => !!rolesList[access])
}
throw new Error(`[hasSomePermission]: ${ accesses } should be a array !`)
}
return { hasPermission, hasEveryPermission, hasSomePermission };
return { hasPermission, hasEveryPermission, hasSomePermission };
}

View File

@@ -1,3 +1,3 @@
import PageFooter from './index.vue'
import PageFooter from './index.vue';
export { PageFooter }
export { PageFooter };

View File

@@ -1,67 +1,56 @@
<template>
<div class="page-footer">
<div class="page-footer-link">
<a href="https://github.com/jekip/naive-ui-admin" target="_blank">
官网
</a>
<a href="https://github.com/jekip/naive-ui-admin" target="_blank">
社区
</a>
<a href="https://github.com/jekip/naive-ui-admin/issues" target="_blank">
交流
</a>
</div>
<div class="copyright">
naive-ui-admin 1.4 · Made by Ah jung
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 官网 </a>
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 社区 </a>
<a href="https://github.com/jekip/naive-ui-admin/issues" target="_blank"> 交流 </a>
</div>
<div class="copyright"> naive-ui-admin 1.4 · Made by Ah jung </div>
</div>
</template>
<script>
import { GithubOutlined, CopyrightOutlined } from '@vicons/antd'
export default {
name: 'PageFooter',
components: { GithubOutlined, CopyrightOutlined },
props: {
collapsed: {
type: Boolean
}
}
}
export default {
name: 'PageFooter',
components: {},
props: {
collapsed: {
type: Boolean,
},
},
};
</script>
<style lang="less" scoped>
.page-footer {
margin: 48px 0 24px 0;
padding: 0 16px;
text-align: center;
.page-footer {
margin: 48px 0 24px 0;
padding: 0 16px;
text-align: center;
a {
font-size: 14px;
color: #808695;
-webkit-transition: all .2s ease-in-out;
transition: all .2s ease-in-out;
a {
font-size: 14px;
color: #808695;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
&:hover {
color: #515a6e;
&:hover {
color: #515a6e;
}
}
&-link {
display: flex;
justify-content: center;
margin-bottom: 8px;
a:not(:last-child) {
margin-right: 40px;
}
}
.copyright {
color: #808695;
font-size: 14px;
}
}
&-link {
display: flex;
justify-content: center;
margin-bottom: 8px;
a:not(:last-child) {
margin-right: 40px;
}
}
.copyright {
color: #808695;
font-size: 14px;
}
}
</style>

View File

@@ -7,24 +7,24 @@
<div class="drawer-setting-item justify-center dark-switch">
<n-tooltip placement="bottom">
<template #trigger>
<n-switch v-model:value="designStore.darkTheme"/>
<n-switch v-model:value="designStore.darkTheme" />
</template>
<span>深色主题</span>
</n-tooltip>
</div>
<n-divider title-placement="center">系统主题</n-divider>
<div class="drawer-setting-item align-items-top">
<span class="theme-item"
v-for="(item, index) in appThemeList"
:key="index"
:style="{'background-color':item}"
@click="togTheme(item)"
<span
class="theme-item"
v-for="(item, index) in appThemeList"
:key="index"
:style="{ 'background-color': item }"
@click="togTheme(item)"
>
<n-icon size="12" v-if="item === designStore.appTheme">
<CheckOutlined/>
<CheckOutlined />
</n-icon>
</span>
</div>
@@ -35,46 +35,45 @@
<div class="drawer-setting-item-style align-items-top">
<n-tooltip placement="top">
<template #trigger>
<img src="~@/assets/images/nav-theme-dark.svg" @click="togNavMode('vertical')"/>
<img src="~@/assets/images/nav-theme-dark.svg" @click="togNavMode('vertical')" />
</template>
<span>左侧菜单模式</span>
</n-tooltip>
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'vertical'"/>
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'vertical'" />
</div>
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img src="~@/assets/images/nav-horizontal.svg" @click="togNavMode('horizontal')"/>
<img src="~@/assets/images/nav-horizontal.svg" @click="togNavMode('horizontal')" />
</template>
<span>顶部菜单模式</span>
</n-tooltip>
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'horizontal'"/>
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'horizontal'" />
</div>
</div>
<n-divider title-placement="center">导航栏风格</n-divider>
<div class="drawer-setting-item align-items-top">
<div class="drawer-setting-item-style align-items-top">
<n-tooltip placement="top">
<template #trigger>
<img src="~@/assets/images/nav-theme-dark.svg" @click="togNavTheme('dark')"/>
<img src="~@/assets/images/nav-theme-dark.svg" @click="togNavTheme('dark')" />
</template>
<span>暗色侧边栏</span>
</n-tooltip>
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'dark'"/>
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'dark'" />
</div>
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img src="~@/assets/images/nav-theme-light.svg" @click="togNavTheme('light')"/>
<img src="~@/assets/images/nav-theme-light.svg" @click="togNavTheme('light')" />
</template>
<span>白色侧边栏</span>
</n-tooltip>
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'light'"/>
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'light'" />
</div>
</div>
@@ -82,22 +81,23 @@
<div class="drawer-setting-item-style">
<n-tooltip placement="top">
<template #trigger>
<img src="~@/assets/images/header-theme-dark.svg" @click="togNavTheme('header-dark')"/>
<img
src="~@/assets/images/header-theme-dark.svg"
@click="togNavTheme('header-dark')"
/>
</template>
<span>暗色顶栏</span>
</n-tooltip>
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'header-dark'"/>
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'header-dark'" />
</div>
</div>
<n-divider title-placement="center">界面功能</n-divider>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
固定顶栏
</div>
<div class="drawer-setting-item-title"> 固定顶栏 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.headerSetting.fixed"/>
<n-switch v-model:value="settingStore.headerSetting.fixed" />
</div>
</div>
@@ -111,214 +111,200 @@
<!-- </div>-->
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
固定多页签
</div>
<div class="drawer-setting-item-title"> 固定多页签 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.multiTabsSetting.fixed"/>
<n-switch v-model:value="settingStore.multiTabsSetting.fixed" />
</div>
</div>
<n-divider title-placement="center">界面显示</n-divider>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
显示重载页面按钮
</div>
<div class="drawer-setting-item-title"> 显示重载页面按钮 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.headerSetting.isReload"/>
<n-switch v-model:value="settingStore.headerSetting.isReload" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
显示面包屑导航
</div>
<div class="drawer-setting-item-title"> 显示面包屑导航 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.crumbsSetting.show"/>
<n-switch v-model:value="settingStore.crumbsSetting.show" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
显示面包屑显示图标
</div>
<div class="drawer-setting-item-title"> 显示面包屑显示图标 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.crumbsSetting.showIcon"/>
<n-switch v-model:value="settingStore.crumbsSetting.showIcon" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
显示多页签
</div>
<div class="drawer-setting-item-title"> 显示多页签 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.multiTabsSetting.show"/>
<n-switch v-model:value="settingStore.multiTabsSetting.show" />
</div>
</div>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title">
显示页脚
</div>
<div class="drawer-setting-item-title"> 显示页脚 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.showFooter"/>
<n-switch v-model:value="settingStore.showFooter" />
</div>
</div>
<div class="drawer-setting-item">
<n-alert type="warning" :showIcon="false">
<p>{{ alertText }}</p>
</n-alert>
</div>
</div>
</n-drawer-content>
</n-drawer>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, watch, createVNode, computed, unref } from 'vue'
import { useProjectSettingStore } from "@/store/modules/projectSetting";
import { useDesignSettingStore } from "@/store/modules/designSetting";
import { CheckOutlined } from '@vicons/antd'
import { darkTheme } from 'naive-ui'
import { defineComponent, reactive, toRefs, watch } from 'vue';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useDesignSettingStore } from '@/store/modules/designSetting';
import { CheckOutlined } from '@vicons/antd';
import { darkTheme } from 'naive-ui';
export default defineComponent({
name: 'ProjectSetting',
props: {
title: {
type: String,
default: '项目配置'
export default defineComponent({
name: 'ProjectSetting',
components: { CheckOutlined },
props: {
title: {
type: String,
default: '项目配置',
},
width: {
type: Number,
default: 280,
},
},
width: {
type: Number,
default: 280
},
},
components: { CheckOutlined },
setup(props, { emit }) {
const settingStore = useProjectSettingStore()
const designStore = useDesignSettingStore()
const { width, title } = props
const state = reactive({
width,
title,
isDrawer: false,
placement: "right",
alertText: '该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
appThemeList: designStore.appThemeList
})
setup(props) {
const settingStore = useProjectSettingStore();
const designStore = useDesignSettingStore();
const state = reactive({
width: props.width,
title: props.title,
isDrawer: false,
placement: 'right',
alertText:
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
appThemeList: designStore.appThemeList,
});
watch(
watch(
() => designStore.darkTheme,
(to) => {
settingStore.navTheme = to ? 'header-dark' : 'dark'
settingStore.navTheme = to ? 'header-dark' : 'dark';
}
)
);
function openDrawer(isDrawer) {
state.isDrawer = true
}
function closeDrawer() {
state.isDrawer = false
}
function togNavTheme(theme) {
settingStore.navTheme = theme
if (settingStore.navMode === 'horizontal' && theme === 'light') {
designStore.navTheme = 'dark'
function openDrawer() {
state.isDrawer = true;
}
}
function togTheme(color) {
designStore.appTheme = color
}
function togNavMode(mode) {
settingStore.navMode = mode
if (mode === 'horizontal') {
settingStore.setNavTheme('light')
} else {
settingStore.setNavTheme('dark')
function closeDrawer() {
state.isDrawer = false;
}
}
return {
...toRefs(state),
settingStore,
designStore,
togNavTheme,
togNavMode,
togTheme,
darkTheme,
openDrawer,
closeDrawer,
}
}
})
function togNavTheme(theme) {
settingStore.navTheme = theme;
if (settingStore.navMode === 'horizontal' && theme === 'light') {
settingStore.navTheme = 'dark';
}
}
function togTheme(color) {
designStore.appTheme = color;
}
function togNavMode(mode) {
settingStore.navMode = mode;
if (mode === 'horizontal') {
settingStore.setNavTheme('light');
} else {
settingStore.setNavTheme('dark');
}
}
return {
...toRefs(state),
settingStore,
designStore,
togNavTheme,
togNavMode,
togTheme,
darkTheme,
openDrawer,
closeDrawer,
};
},
});
</script>
<style lang="less" scoped>
.drawer {
.n-divider:not(.n-divider--vertical) {
margin: 10px 0;
}
&-setting-item {
display: flex;
align-items: center;
padding: 12px 0;
flex-wrap: wrap;
&-style {
display: inline-block;
position: relative;
margin-right: 16px;
cursor: pointer;
text-align: center;
.drawer {
.n-divider:not(.n-divider--vertical) {
margin: 10px 0;
}
&-title {
flex: 1 1;
font-size: 14px;
&-setting-item {
display: flex;
align-items: center;
padding: 12px 0;
flex-wrap: wrap;
&-style {
display: inline-block;
position: relative;
margin-right: 16px;
cursor: pointer;
text-align: center;
}
&-title {
flex: 1 1;
font-size: 14px;
}
&-action {
flex: 0 0 auto;
}
.theme-item {
width: 20px;
min-width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid #eee;
border-radius: 2px;
margin: 0 5px 5px 0;
text-align: center;
.n-icon {
color: #fff;
}
}
}
&-action {
flex: 0 0 auto;
.align-items-top {
align-items: flex-start;
padding: 2px 0;
}
.theme-item {
width: 20px;
min-width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid #eee;
border-radius: 2px;
margin: 0 5px 5px 0;
text-align: center;
.justify-center {
justify-content: center;
}
.n-icon {
color: #fff
.dark-switch .n-switch--active {
::v-deep(.n-switch__rail) {
background-color: #000e1c;
}
}
}
.align-items-top {
align-items: flex-start;
padding: 2px 0;
}
.justify-center {
justify-content: center;
}
.dark-switch .n-switch--active {
::v-deep(.n-switch__rail) {
background-color: #000e1c;
}
}
}
</style>

View File

@@ -1,31 +1,31 @@
import {
SettingOutlined,
SearchOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
PoweroffOutlined,
GithubOutlined,
LockOutlined,
ReloadOutlined,
LogoutOutlined,
UserOutlined,
CheckOutlined
} from '@vicons/antd'
SettingOutlined,
SearchOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
PoweroffOutlined,
GithubOutlined,
LockOutlined,
ReloadOutlined,
LogoutOutlined,
UserOutlined,
CheckOutlined,
} from '@vicons/antd';
export default {
SettingOutlined,
LockOutlined,
GithubOutlined,
SearchOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
PoweroffOutlined,
ReloadOutlined,
LogoutOutlined,
UserOutlined,
CheckOutlined
}
SettingOutlined,
LockOutlined,
GithubOutlined,
SearchOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
PoweroffOutlined,
ReloadOutlined,
LogoutOutlined,
UserOutlined,
CheckOutlined,
};

View File

@@ -1,3 +1,3 @@
import PageHeader from './index.vue'
import PageHeader from './index.vue';
export { PageHeader }
export { PageHeader };

View File

@@ -1,26 +1,31 @@
<template>
<div class="layout-header" :class="{'layout-header-light':!(navTheme == 'header-dark')}">
<div class="layout-header" :class="{ 'layout-header-light': !(navTheme == 'header-dark') }">
<!--顶部菜单-->
<div class="layout-header-left" v-if="navMode==='horizontal'">
<AsideMenu v-model:collapsed="collapsed" mode="horizontal" class="n-menu-horizontal-light"/>
<div class="layout-header-left" v-if="navMode === 'horizontal'">
<AsideMenu v-model:collapsed="collapsed" mode="horizontal" class="n-menu-horizontal-light" />
</div>
<!--左侧菜单-->
<div class="layout-header-left" v-else>
<!-- 菜单收起 -->
<div class="ml-1 layout-header-trigger layout-header-trigger-min"
@click="() => $emit('update:collapsed', !collapsed)">
<div
class="ml-1 layout-header-trigger layout-header-trigger-min"
@click="() => $emit('update:collapsed', !collapsed)"
>
<n-icon size="18" v-if="collapsed">
<MenuUnfoldOutlined/>
<MenuUnfoldOutlined />
</n-icon>
<n-icon size="18" v-else>
<MenuFoldOutlined/>
<MenuFoldOutlined />
</n-icon>
</div>
<!-- 刷新 -->
<div class="mr-1 layout-header-trigger layout-header-trigger-min" v-if="headerSetting.isReload"
@click="reloadPage">
<div
class="mr-1 layout-header-trigger layout-header-trigger-min"
v-if="headerSetting.isReload"
@click="reloadPage"
>
<n-icon size="18">
<ReloadOutlined/>
<ReloadOutlined />
</n-icon>
</div>
<!-- 面包屑 -->
@@ -28,29 +33,39 @@
<template v-for="routeItem in breadcrumbList" :key="routeItem.name">
<n-breadcrumb-item>
<n-dropdown
v-if="routeItem.children.length"
:options="routeItem.children"
@select="dropdownSelect"
v-if="routeItem.children.length"
:options="routeItem.children"
@select="dropdownSelect"
>
<span class="link-text">
<component v-if="crumbsSetting.showIcon && routeItem.meta.icon" :is="routeItem.meta.icon"></component>
{{ routeItem.meta.title }}
</span>
</n-dropdown>
<span class="link-text" v-else>
<component v-if="crumbsSetting.showIcon && routeItem.meta.icon" :is="routeItem.meta.icon"></component>
<span class="link-text">
<component
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
:is="routeItem.meta.icon"
/>
{{ routeItem.meta.title }}
</span>
</n-dropdown>
<span class="link-text" v-else>
<component
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
:is="routeItem.meta.icon"
/>
{{ routeItem.meta.title }}
</span>
</n-breadcrumb-item>
</template>
</n-breadcrumb>
</div>
<div class="layout-header-right">
<div class="layout-header-trigger layout-header-trigger-min" v-for="item in iconList" :key="item.icon.name">
<div
class="layout-header-trigger layout-header-trigger-min"
v-for="item in iconList"
:key="item.icon.name"
>
<n-tooltip placement="bottom">
<template #trigger>
<n-icon size="18">
<component :is="item.icon" v-on="item.eventObject || {}"/>
<component :is="item.icon" v-on="item.eventObject || {}" />
</n-icon>
</template>
<span>{{ item.tips }}</span>
@@ -61,7 +76,7 @@
<n-tooltip placement="bottom">
<template #trigger>
<n-icon size="18">
<component :is="fullscreenIcon" @click="toggleFullScreen"/>
<component :is="fullscreenIcon" @click="toggleFullScreen" />
</n-icon>
</template>
<span>全屏</span>
@@ -74,7 +89,7 @@
<n-avatar>
{{ username }}
<template #icon>
<UserOutlined/>
<UserOutlined />
</template>
</n-avatar>
</div>
@@ -85,7 +100,7 @@
<n-tooltip placement="bottom-end">
<template #trigger>
<n-icon size="18" style="font-weight: bold">
<SettingOutlined/>
<SettingOutlined />
</n-icon>
</template>
<span>项目配置</span>
@@ -94,330 +109,322 @@
</div>
</div>
<!--项目配置-->
<ProjectSetting ref="drawerSetting"/>
<ProjectSetting ref="drawerSetting" />
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import components from './components'
import { NDialogProvider, useDialog, useMessage, useNotification } from 'naive-ui'
import { TABS_ROUTES } from '@/store/mutation-types'
import { useUserStore } from '@/store/modules/user'
import { useLockscreenStore } from '@/store/modules/lockscreen'
import ProjectSetting from './ProjectSetting.vue'
import { AsideMenu } from '@/layout/components/Menu'
import { useProjectSetting } from "@/hooks/setting/useProjectSetting";
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import components from './components';
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
import { TABS_ROUTES } from '@/store/mutation-types';
import { useUserStore } from '@/store/modules/user';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import ProjectSetting from './ProjectSetting.vue';
import { AsideMenu } from '@/layout/components/Menu';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
export default defineComponent({
name: 'PageHeader',
components: { ...components, NDialogProvider, ProjectSetting, AsideMenu },
props: {
collapsed: {
type: Boolean
}
},
setup(props) {
const userStore = useUserStore()
const useLockscreen = useLockscreenStore()
const message = useMessage()
const notification = useNotification()
const dialog = useDialog()
const {
getNavMode,
getNavTheme,
getHeaderSetting,
getMenuSetting,
getCrumbsSetting
} = useProjectSetting()
export default defineComponent({
name: 'PageHeader',
components: { ...components, NDialogProvider, ProjectSetting, AsideMenu },
props: {
collapsed: {
type: Boolean,
},
},
setup(props) {
const userStore = useUserStore();
const useLockscreen = useLockscreenStore();
const message = useMessage();
const dialog = useDialog();
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
useProjectSetting();
const { username } = userStore?.info || {}
const { username } = userStore?.info || {};
const drawerSetting = ref()
const drawerSetting = ref();
const state = reactive({
username: username || '',
fullscreenIcon: 'FullscreenOutlined',
navMode: getNavMode,
navTheme: getNavTheme,
headerSetting: getHeaderSetting,
crumbsSetting: getCrumbsSetting,
})
const state = reactive({
username: username || '',
fullscreenIcon: 'FullscreenOutlined',
navMode: getNavMode,
navTheme: getNavTheme,
headerSetting: getHeaderSetting,
crumbsSetting: getCrumbsSetting,
});
const getChangeStyle = computed(() => {
const { collapsed } = props
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting)
return {
'left': collapsed ? `${ minMenuWidth }px` : `${ menuWidth }px`,
'width': `calc(100% - ${ collapsed ? `${ minMenuWidth }px` : `${ menuWidth }px` })`
}
})
const getChangeStyle = computed(() => {
const { collapsed } = props;
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
return {
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
width: `calc(100% - ${collapsed ? `${minMenuWidth}px` : `${menuWidth}px`})`,
};
});
const router = useRouter()
const route = useRoute()
const router = useRouter();
const route = useRoute();
const generator: any = (routerMap, parent) => {
return routerMap.map((item, key) => {
const currentMenu = {
...item,
label: item.meta.title,
key: item.name,
disabled: item.path === '/',
}
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentMenu.children = generator(item.children, currentMenu)
}
return currentMenu
})
}
const generator: any = (routerMap) => {
return routerMap.map((item) => {
const currentMenu = {
...item,
label: item.meta.title,
key: item.name,
disabled: item.path === '/',
};
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentMenu.children = generator(item.children, currentMenu);
}
return currentMenu;
});
};
const breadcrumbList = computed(() => {
return generator(route.matched)
})
const breadcrumbList = computed(() => {
return generator(route.matched);
});
const dropdownSelect = (key) => {
router.push({ name: key })
}
const dropdownSelect = (key) => {
router.push({ name: key });
};
// 刷新页面
const reloadPage = () => {
router.push({
path: '/redirect' + unref(route).fullPath
})
}
// 刷新页面
const reloadPage = () => {
router.push({
path: '/redirect' + unref(route).fullPath,
});
};
// 退出登录
const doLogout = () => {
dialog.info({
title: '提示',
content: '您确定要退出登录吗',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
userStore.logout().then((res) => {
message.success('成功退出登录')
// 移除标签页
localStorage.removeItem(TABS_ROUTES)
router
// 退出登录
const doLogout = () => {
dialog.info({
title: '提示',
content: '您确定要退出登录吗',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
userStore.logout().then(() => {
message.success('成功退出登录');
// 移除标签页
localStorage.removeItem(TABS_ROUTES);
router
.replace({
name: 'Login',
query: {
redirect: route.fullPath
}
redirect: route.fullPath,
},
})
.finally(() => location.reload())
})
},
onNegativeClick: () => {
.finally(() => location.reload());
});
},
onNegativeClick: () => {},
});
};
}
})
}
// 切换全屏图标
const toggleFullscreenIcon = () =>
// 切换全屏图标
const toggleFullscreenIcon = () =>
(state.fullscreenIcon =
document.fullscreenElement !== null ? 'FullscreenExitOutlined' : 'FullscreenOutlined')
document.fullscreenElement !== null ? 'FullscreenExitOutlined' : 'FullscreenOutlined');
// 监听全屏切换事件
document.addEventListener('fullscreenchange', toggleFullscreenIcon)
// 监听全屏切换事件
document.addEventListener('fullscreenchange', toggleFullscreenIcon);
// 全屏切换
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
// 全屏切换
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
}
};
// 图标列表
const iconList = [
{
icon: 'SearchOutlined',
tips: '搜索'
},
{
icon: 'GithubOutlined',
tips: 'github',
eventObject: {
click: () => window.open('https://github.com/jekip/naive-ui-admin')
}
},
{
icon: 'LockOutlined',
tips: '锁屏',
eventObject: {
click: () => useLockscreen.setLock(true)
// 图标列表
const iconList = [
{
icon: 'SearchOutlined',
tips: '搜索',
},
{
icon: 'GithubOutlined',
tips: 'github',
eventObject: {
click: () => window.open('https://github.com/jekip/naive-ui-admin'),
},
},
{
icon: 'LockOutlined',
tips: '锁屏',
eventObject: {
click: () => useLockscreen.setLock(true),
},
},
];
const avatarOptions = [
{
label: '个人设置',
key: 1,
},
{
label: '退出登录',
key: 2,
},
];
//头像下拉菜单
const avatarSelect = (key) => {
switch (key) {
case 1:
router.push({ name: 'Setting' });
break;
case 2:
doLogout();
break;
}
};
function openSetting() {
const { openDrawer } = drawerSetting.value;
openDrawer();
}
]
const avatarOptions = [
{
label: '个人设置',
key: 1
},
{
label: '退出登录',
key: 2
},
]
//头像下拉菜单
const avatarSelect = (key) => {
switch (key) {
case 1:
router.push({ name: 'Setting' })
break;
case 2:
doLogout()
break;
}
}
function openSetting() {
const { openDrawer } = drawerSetting.value
openDrawer()
}
return {
...toRefs(state),
iconList,
toggleFullScreen,
doLogout,
route,
dropdownSelect,
avatarOptions,
getChangeStyle,
avatarSelect,
breadcrumbList,
reloadPage,
drawerSetting,
openSetting,
}
}
})
return {
...toRefs(state),
iconList,
toggleFullScreen,
doLogout,
route,
dropdownSelect,
avatarOptions,
getChangeStyle,
avatarSelect,
breadcrumbList,
reloadPage,
drawerSetting,
openSetting,
};
},
});
</script>
<style lang="less" scoped>
.layout-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
height: @header-height;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: all .2s ease-in-out;
width: 100%;
z-index: 11;
//color: #fff;
//.n-icon {
// color: #fff
//}
&-left {
.layout-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
height: @header-height;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: all 0.2s ease-in-out;
width: 100%;
z-index: 11;
//color: #fff;
::v-deep(.ant-breadcrumb span:last-child .link-text) {
color: #515a6e;
}
//.n-icon {
// color: #fff
//}
::v-deep(.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link) {
color: #fff;
}
.n-breadcrumb {
display: inline-block;
}
}
&-right {
display: flex;
align-items: center;
margin-right: 20px;
.avatar {
&-left {
display: flex;
align-items: center;
height: 64px;
::v-deep(.ant-breadcrumb span:last-child .link-text) {
color: #515a6e;
}
::v-deep(.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link) {
color: #fff;
}
.n-breadcrumb {
display: inline-block;
}
}
> * {
&-right {
display: flex;
align-items: center;
margin-right: 20px;
.avatar {
display: flex;
align-items: center;
height: 64px;
}
> * {
cursor: pointer;
}
}
&-trigger {
display: inline-block;
width: 64px;
height: 64px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease-in-out;
.n-icon {
display: flex;
align-items: center;
height: 64px;
line-height: 64px;
}
&:hover {
background: hsla(0, 0%, 100%, 0.08);
}
.anticon {
font-size: 16px;
color: #515a6e;
}
}
&-trigger-min {
width: auto;
padding: 0 12px;
}
}
&-trigger {
display: inline-block;
width: 64px;
height: 64px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease-in-out;
.layout-header-light {
background: #fff;
color: #515a6e;
.n-icon {
display: flex;
align-items: center;
height: 64px;
line-height: 64px;
}
&:hover {
background: hsla(0, 0%, 100%, .08);
}
.anticon {
font-size: 16px;
color: #515a6e;
}
}
&-trigger-min {
width: auto;
padding: 0 12px;
}
}
.layout-header-left {
::v-deep(.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link) {
color: #515a6e;
}
}
.layout-header-light {
background: #fff;
color: #515a6e;
.n-icon {
color: #515a6e
}
.layout-header-left {
::v-deep(.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link) {
color: #515a6e;
.layout-header-trigger {
&:hover {
background: #f8f8f9;
}
}
}
.layout-header-trigger {
&:hover {
background: #f8f8f9;
}
.layout-header-fix {
position: fixed;
top: 0;
right: 0;
left: 200px;
z-index: 11;
}
}
.layout-header-fix {
position: fixed;
top: 0;
right: 0;
left: 200px;
z-index: 11;
}
//::v-deep(.menu-router-link) {
// color: #515a6e;
//
// &:hover {
// color: #1890ff;
// }
//}
//::v-deep(.menu-router-link) {
// color: #515a6e;
//
// &:hover {
// color: #1890ff;
// }
//}
</style>

View File

@@ -1,3 +1,3 @@
import Logo from './index.vue'
import Logo from './index.vue';
export { Logo }
export { Logo };

View File

@@ -1,39 +1,39 @@
<template>
<div class="logo">
<img src="~@/assets/images/logo.png" alt=""/>
<img src="~@/assets/images/logo.png" alt="" />
<h2 v-show="!collapsed" class="title">&nbsp;NaiveUiAdmin</h2>
</div>
</template>
<script>
export default {
name: 'Index',
props: {
collapsed: {
type: Boolean
}
}
}
export default {
name: 'Index',
props: {
collapsed: {
type: Boolean,
},
},
};
</script>
<style lang="less" scoped>
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 64px;
line-height: 64px;
overflow: hidden;
white-space: nowrap;
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 64px;
line-height: 64px;
overflow: hidden;
white-space: nowrap;
img {
width: auto;
height: 32px;
}
img {
width: auto;
height: 32px;
}
.title {
color: white;
margin-bottom: 0;
.title {
color: white;
margin-bottom: 0;
}
}
}
</style>

View File

@@ -1,3 +1,3 @@
import MainView from './index.vue'
import MainView from './index.vue';
export { MainView }
export { MainView };

View File

@@ -3,41 +3,40 @@
<template #default="{ Component, route }">
<transition name="zoom-fade" mode="out-in" appear>
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath"/>
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-else :is="Component" :key="route.fullPath"/>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
</RouterView>
</template>
<script>
import { defineComponent, computed } from 'vue'
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'
import { defineComponent, computed } from 'vue';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
export default defineComponent({
name: 'MainView',
components: {},
props: {
notNeedKey: {
type: Boolean,
default: false
export default defineComponent({
name: 'MainView',
components: {},
props: {
notNeedKey: {
type: Boolean,
default: false,
},
animate: {
type: Boolean,
default: true,
},
},
animate: {
type: Boolean,
default: true
}
},
setup() {
const asyncRouteStore = useAsyncRouteStore()
// 需要缓存的路由组件
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents)
return {
keepAliveComponents
}
}
})
setup() {
const asyncRouteStore = useAsyncRouteStore();
// 需要缓存的路由组件
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
return {
keepAliveComponents,
};
},
});
</script>
<style lang="less" scoped>
</style>
<style lang="less" scoped></style>

View File

@@ -1,3 +1,3 @@
import AsideMenu from './index.vue'
import AsideMenu from './index.vue';
export { AsideMenu }
export { AsideMenu };

View File

@@ -1,111 +1,108 @@
<template>
<NMenu
:options="menus"
:inverted="inverted"
:mode="mode"
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="20"
:expanded-keys="openKeys"
v-model:value="selectedKeys"
@update:value="clickMenuItem"
@update:expanded-keys="menuExpanded"
>
</NMenu>
:options="menus"
:inverted="inverted"
:mode="mode"
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="20"
:expanded-keys="openKeys"
v-model:value="selectedKeys"
@update:value="clickMenuItem"
@update:expanded-keys="menuExpanded"
/>
</template>
<script lang="ts">
import { defineComponent, reactive, computed, watch, toRefs, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'
import { generatorMenu } from '@/utils/index'
import { useProjectSettingStore } from "@/store/modules/projectSetting";
import { defineComponent, reactive, computed, watch, toRefs } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { generatorMenu } from '@/utils/index';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
export default defineComponent({
name: 'Menu',
components: {},
props: {
mode: {
// 菜单模式
type: String,
default: 'vertical'
export default defineComponent({
name: 'Menu',
components: {},
props: {
mode: {
// 菜单模式
type: String,
default: 'vertical',
},
collapsed: {
// 侧边栏菜单是否收起
type: Boolean,
},
},
collapsed: {
// 侧边栏菜单是否收起
type: Boolean
}
},
setup(props) {
// 当前路由
const currentRoute = useRoute()
const router = useRouter()
const asyncRouteStore = useAsyncRouteStore()
const settingStore = useProjectSettingStore()
const { mode } = props
setup(props) {
// 当前路由
const currentRoute = useRoute();
const router = useRouter();
const asyncRouteStore = useAsyncRouteStore();
const settingStore = useProjectSettingStore();
// 获取当前打开的子菜单
const matched = currentRoute.matched
// 获取当前打开的子菜单
const matched = currentRoute.matched;
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : []
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : [];
const state = reactive({
openKeys: getOpenKeys,
selectedKeys: currentRoute.name,
})
const state = reactive({
openKeys: getOpenKeys,
selectedKeys: currentRoute.name,
});
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(settingStore.navTheme)
})
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(settingStore.navTheme);
});
const menus = computed(() => {
return generatorMenu(asyncRouteStore.getMenus)
})
const menus = computed(() => {
return generatorMenu(asyncRouteStore.getMenus);
});
// 监听菜单收缩状态
watch(
// 监听菜单收缩状态
watch(
() => props.collapsed,
(newVal) => {
state.openKeys = newVal ? [] : getOpenKeys
state.selectedKeys = currentRoute.name
state.openKeys = newVal ? [] : getOpenKeys;
state.selectedKeys = currentRoute.name;
}
)
);
// 跟随页面路由变化,切换菜单选中状态
watch(
// 跟随页面路由变化,切换菜单选中状态
watch(
() => currentRoute.fullPath,
() => {
const matched = currentRoute.matched
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : []
state.openKeys = getOpenKeys
state.selectedKeys = currentRoute.name
const matched = currentRoute.matched;
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : [];
state.openKeys = getOpenKeys;
state.selectedKeys = currentRoute.name;
}
)
);
// 点击菜单
function clickMenuItem(key: string) {
if (/http(s)?:/.test(key)) {
window.open(key)
} else {
router.push({ name: key })
// 点击菜单
function clickMenuItem(key: string) {
if (/http(s)?:/.test(key)) {
window.open(key);
} else {
router.push({ name: key });
}
}
}
//展开菜单
function menuExpanded(openKeys: string[]) {
console.log(openKeys)
if (!openKeys) return
const latestOpenKey = openKeys.pop();
state.openKeys = latestOpenKey ? [latestOpenKey] : []
}
//展开菜单
function menuExpanded(openKeys: string[]) {
console.log(openKeys);
if (!openKeys) return;
const latestOpenKey = openKeys.pop();
state.openKeys = latestOpenKey ? [latestOpenKey] : [];
}
return {
...toRefs(state),
inverted,
menus,
mode,
clickMenuItem,
menuExpanded
}
}
})
return {
...toRefs(state),
inverted,
menus,
clickMenuItem,
menuExpanded,
};
},
});
</script>

View File

@@ -1,24 +1,24 @@
import {
DownOutlined,
ReloadOutlined,
CloseOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
ColumnWidthOutlined,
MinusOutlined
} from '@ant-design/icons-vue'
import { Dropdown, Tabs, Card } from 'ant-design-vue'
DownOutlined,
ReloadOutlined,
CloseOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
ColumnWidthOutlined,
MinusOutlined,
} from '@ant-design/icons-vue';
import { Dropdown, Tabs, Card } from 'ant-design-vue';
export default {
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
[Dropdown.name]: Dropdown,
[Card.name]: Card,
MinusOutlined,
DownOutlined,
ReloadOutlined,
CloseOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
ColumnWidthOutlined
}
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
[Dropdown.name]: Dropdown,
[Card.name]: Card,
MinusOutlined,
DownOutlined,
ReloadOutlined,
CloseOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
ColumnWidthOutlined,
};

View File

@@ -1,3 +1,3 @@
import TabsView from './index.vue'
import TabsView from './index.vue';
export { TabsView }
export { TabsView };

File diff suppressed because it is too large Load Diff

View File

@@ -1,217 +1,227 @@
<template>
<NLayout class="layout" :position="fixedMenu" has-sider>
<NLayoutSider
v-if="navMode ==='vertical'"
show-trigger
@collapse="collapsed = true"
:position="fixedMenu"
@expand="collapsed = false"
:collapsed="collapsed"
collapse-mode="width"
:collapsed-width="64"
:width="leftMenuWidth"
:native-scrollbar="false"
:inverted="inverted" class="layout-sider">
<Logo :collapsed="collapsed"/>
<AsideMenu v-model:collapsed="collapsed"/>
v-if="navMode === 'vertical'"
show-trigger
@collapse="collapsed = true"
:position="fixedMenu"
@expand="collapsed = false"
:collapsed="collapsed"
collapse-mode="width"
:collapsed-width="64"
:width="leftMenuWidth"
:native-scrollbar="false"
:inverted="inverted"
class="layout-sider"
>
<Logo :collapsed="collapsed" />
<AsideMenu v-model:collapsed="collapsed" />
</NLayoutSider>
<NLayout :inverted="inverted">
<NLayoutHeader :inverted="inverted" :position="fixedHeader">
<PageHeader v-model:collapsed="collapsed"/>
<PageHeader v-model:collapsed="collapsed" />
</NLayoutHeader>
<NLayoutContent class="layout-content" :class="{'layout-default-background':getDarkTheme === false}">
<div class="layout-content-main"
:class="{'layout-content-main-fix':fixedMulti,'fluid-header':fixedHeader === 'static'}">
<TabsView v-if="isMultiTabs" v-model:collapsed="collapsed"/>
<div class="main-view" :class="{'main-view-fix':fixedMulti,'noMultiTabs':!isMultiTabs,'mt-3':!isMultiTabs}">
<MainView/>
<NLayoutContent
class="layout-content"
:class="{ 'layout-default-background': getDarkTheme === false }"
>
<div
class="layout-content-main"
:class="{
'layout-content-main-fix': fixedMulti,
'fluid-header': fixedHeader === 'static',
}"
>
<TabsView v-if="isMultiTabs" v-model:collapsed="collapsed" />
<div
class="main-view"
:class="{
'main-view-fix': fixedMulti,
noMultiTabs: !isMultiTabs,
'mt-3': !isMultiTabs,
}"
>
<MainView />
</div>
</div>
<NLayoutFooter v-if="getShowFooter">
<PageFooter/>
<PageFooter />
</NLayoutFooter>
</NLayoutContent>
</NLayout>
</NLayout>
</template>
<script lang="ts">
import { defineComponent, ref, unref, h, watch, computed, onMounted } from 'vue'
import { Logo } from './components/Logo'
import { TabsView } from './components/TagsView'
import { MainView } from './components/Main'
import { AsideMenu } from './components/Menu'
import { PageHeader } from './components/Header'
import { PageFooter } from './components/Footer'
import { useProjectSetting } from "@/hooks/setting/useProjectSetting";
import { useDesignSetting } from "@/hooks/setting/useDesignSetting";
import { defineComponent, ref, unref, computed, onMounted } from 'vue';
import { Logo } from './components/Logo';
import { TabsView } from './components/TagsView';
import { MainView } from './components/Main';
import { AsideMenu } from './components/Menu';
import { PageHeader } from './components/Header';
import { PageFooter } from './components/Footer';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
export default defineComponent({
name: 'Layout',
components: {
TabsView,
MainView,
PageHeader,
AsideMenu,
Logo,
PageFooter
},
setup() {
const { getDarkTheme } = useDesignSetting()
export default defineComponent({
name: 'Layout',
components: {
TabsView,
MainView,
PageHeader,
AsideMenu,
Logo,
PageFooter,
},
setup() {
const { getDarkTheme } = useDesignSetting();
const {
getShowFooter,
getNavMode,
getNavTheme,
getHeaderSetting,
getMenuSetting,
getMultiTabsSetting
} = useProjectSetting()
const {
getShowFooter,
getNavMode,
getNavTheme,
getHeaderSetting,
getMenuSetting,
getMultiTabsSetting,
} = useProjectSetting();
const navMode = getNavMode
const navMode = getNavMode;
const collapsed = ref<boolean>(false)
const collapsed = ref<boolean>(false);
const fixedHeader = computed(() => {
const { fixed } = unref(getHeaderSetting)
return fixed ? 'absolute' : 'static'
})
const fixedHeader = computed(() => {
const { fixed } = unref(getHeaderSetting);
return fixed ? 'absolute' : 'static';
});
const fixedMenu = computed(() => {
const { fixed } = unref(getHeaderSetting)
return fixed ? 'absolute' : 'static'
})
const fixedMenu = computed(() => {
const { fixed } = unref(getHeaderSetting);
return fixed ? 'absolute' : 'static';
});
const isMultiTabs = computed(() => {
return unref(getMultiTabsSetting).show
})
const isMultiTabs = computed(() => {
return unref(getMultiTabsSetting).show;
});
const fixedMulti = computed(() => {
return unref(getMultiTabsSetting).fixed
})
const fixedMulti = computed(() => {
return unref(getMultiTabsSetting).fixed;
});
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(unref(getNavTheme))
})
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(unref(getNavTheme));
});
const leftMenuWidth = computed(() => {
const { minMenuWidth, menuWidth } = unref(getMenuSetting)
return collapsed.value ? minMenuWidth : menuWidth
})
const leftMenuWidth = computed(() => {
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
return collapsed.value ? minMenuWidth : menuWidth;
});
const getChangeStyle = computed(() => {
const { minMenuWidth, menuWidth } = unref(getMenuSetting)
return {
'padding-left': collapsed.value ? `${ minMenuWidth }px` : `${ menuWidth }px`
const getChangeStyle = computed(() => {
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
return {
'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
};
});
function watchWidth() {
const Width = document.body.clientWidth;
if (Width <= 950) {
collapsed.value = true;
} else collapsed.value = false;
}
})
function watchWidth() {
const Width = document.body.clientWidth
if (Width <= 950) {
collapsed.value = true
} else collapsed.value = false
}
onMounted(() => {
window.addEventListener('resize', watchWidth);
});
onMounted(() => {
window.addEventListener('resize', watchWidth);
})
return {
fixedMenu,
fixedMulti,
fixedHeader,
collapsed,
inverted,
isMultiTabs,
leftMenuWidth,
getChangeStyle,
navMode,
getShowFooter,
getDarkTheme
}
}
})
return {
fixedMenu,
fixedMulti,
fixedHeader,
collapsed,
inverted,
isMultiTabs,
leftMenuWidth,
getChangeStyle,
navMode,
getShowFooter,
getDarkTheme,
};
},
});
</script>
<style lang="less" scoped>
.layout {
display: flex;
flex-direction: row;
flex: auto;
&-default-background {
background: #f5f7f9;
}
.layout-sider {
min-height: 100vh;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
position: relative;
z-index: 13;
transition: all .2s ease-in-out;
}
.layout-sider-fix {
position: fixed;
top: 0;
left: 0;
}
.ant-layout {
overflow: hidden;
}
.layout-right-fix {
overflow-x: hidden;
padding-left: 200px;
min-height: 100vh;
transition: all 0.2s ease-in-out;
}
.layout-content {
.layout {
display: flex;
flex-direction: row;
flex: auto;
min-height: 100vh;
&-default-background {
background: #f5f7f9;
}
.layout-sider {
min-height: 100vh;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
position: relative;
z-index: 13;
transition: all 0.2s ease-in-out;
}
.layout-sider-fix {
position: fixed;
top: 0;
left: 0;
}
.ant-layout {
overflow: hidden;
}
.layout-right-fix {
overflow-x: hidden;
padding-left: 200px;
min-height: 100vh;
transition: all 0.2s ease-in-out;
}
.layout-content {
flex: auto;
min-height: 100vh;
}
.n-layout-header.n-layout-header--absolute-positioned {
z-index: 11;
}
.n-layout-footer {
background: none;
}
}
.n-layout-header.n-layout-header--absolute-positioned {
z-index: 11;
.layout-content-main {
margin: 0 10px 10px;
position: relative;
padding-top: 64px;
}
.n-layout-footer {
background: none;
.layout-content-main-fix {
padding-top: 64px;
}
}
.fluid-header {
padding-top: 0px;
}
.layout-content-main {
margin: 0 10px 10px;
position: relative;
padding-top: 64px;
}
.main-view-fix {
padding-top: 44px;
}
.layout-content-main-fix {
padding-top: 64px;
}
.fluid-header {
padding-top: 0px;
}
.main-view-fix {
padding-top: 44px;
}
.noMultiTabs {
padding-top: 0;
}
.noMultiTabs {
padding-top: 0;
}
</style>

View File

@@ -1,39 +1,39 @@
import './styles/tailwind.css'
import { createApp } from 'vue'
import App from './App.vue'
import router, { setupRouter } from './router'
import { setupStore } from '@/store'
import MakeitCaptcha from 'makeit-captcha'
import 'makeit-captcha/dist/captcha.min.css'
import { setupNaive, setupDirectives, setupGlobalMethods, setupCustomComponents } from '@/plugins'
import './styles/tailwind.css';
import { createApp } from 'vue';
import App from './App.vue';
import router, { setupRouter } from './router';
import { setupStore } from '@/store';
import MakeitCaptcha from 'makeit-captcha';
import 'makeit-captcha/dist/captcha.min.css';
import { setupNaive, setupDirectives, setupGlobalMethods, setupCustomComponents } from '@/plugins';
async function bootstrap() {
const app = createApp(App)
const app = createApp(App);
app.use(MakeitCaptcha)
app.use(MakeitCaptcha);
// 注册全局常用的 naive-ui 组件
setupNaive(app)
// 注册全局常用的 naive-ui 组件
setupNaive(app);
// 注册全局自定义组件,如:<svg-icon />
setupCustomComponents(app)
// 注册全局自定义组件,如:<svg-icon />
setupCustomComponents(app);
// 注册全局自定义指令v-permission权限指令
setupDirectives(app)
// 注册全局自定义指令v-permission权限指令
setupDirectives(app);
// 注册全局方法app.config.globalProperties.$message = message
setupGlobalMethods(app)
// 注册全局方法app.config.globalProperties.$message = message
setupGlobalMethods(app);
// 挂载状态管理
setupStore(app)
// 挂载状态管理
setupStore(app);
// 挂载路由
await setupRouter(app)
// 挂载路由
await setupRouter(app);
// 路由准备就绪后挂载APP实例
await router.isReady();
// 路由准备就绪后挂载APP实例
await router.isReady();
app.mount('#app', true);
app.mount('#app', true);
}
void bootstrap()
void bootstrap();

View File

@@ -1,9 +1,7 @@
import { App } from 'vue'
/**
* 全局注册自定义组件 待完善
* @param app
*/
export function setupCustomComponents(app: App) {
// app.component()
export function setupCustomComponents() {
// app.component()
}

View File

@@ -1,12 +1,12 @@
import { App } from 'vue'
import { App } from 'vue';
import { permission } from '@/directives/permission'
import { permission } from '@/directives/permission';
/**
* 注册全局自定义指令
* @param app
*/
export function setupDirectives(app: App) {
// 权限控制指令(演示)
app.directive('permission', permission)
// 权限控制指令(演示)
app.directive('permission', permission);
}

View File

@@ -1,9 +1,5 @@
import { App } from 'vue'
/**
* 注册全局方法 待完善
* @param app
*/
export function setupGlobalMethods(app: App) {
}
export function setupGlobalMethods() {}

View File

@@ -1,4 +1,4 @@
export { setupNaive } from '@/plugins/naive'
export { setupDirectives } from '@/plugins/directives'
export { setupCustomComponents } from '@/plugins/customComponents'
export { setupGlobalMethods } from '@/plugins/globalMethods'
export { setupNaive } from '@/plugins/naive';
export { setupDirectives } from '@/plugins/directives';
export { setupCustomComponents } from '@/plugins/customComponents';
export { setupGlobalMethods } from '@/plugins/globalMethods';

View File

@@ -1,9 +1,75 @@
import type { App } from 'vue'
import type { App } from 'vue';
import {
create,
NConfigProvider,
create,
NConfigProvider,
NMessageProvider,
NDialogProvider,
NInput,
NButton,
NForm,
NFormItem,
NCheckboxGroup,
NCheckbox,
NIcon,
NLayout,
NLayoutHeader,
NLayoutContent,
NLayoutFooter,
NLayoutSider,
NMenu,
NBreadcrumb,
NBreadcrumbItem,
NDropdown,
NSpace,
NTooltip,
NAvatar,
NTabs,
NTabPane,
NCard,
NRow,
NCol,
NDrawer,
NDrawerContent,
NDivider,
NSwitch,
NBadge,
NAlert,
NElement,
NTag,
NNotificationProvider,
NProgress,
NDatePicker,
NGrid,
NGridItem,
NList,
NListItem,
NThing,
NDataTable,
NPopover,
NPagination,
NSelect,
NRadioGroup,
NRadio,
NSteps,
NStep,
NInputGroup,
NResult,
NDescriptions,
NDescriptionsItem,
NTable,
NInputNumber,
NLoadingBarProvider,
NModal,
NUpload,
NTree,
NSpin,
} from 'naive-ui';
const naive = create({
components: [
NMessageProvider,
NDialogProvider,
NConfigProvider,
NInput,
NButton,
NForm,
@@ -62,76 +128,10 @@ import {
NModal,
NUpload,
NTree,
NSpin
} from 'naive-ui'
const naive = create({
components: [
NMessageProvider,
NDialogProvider,
NConfigProvider,
NInput,
NButton,
NForm,
NFormItem,
NCheckboxGroup,
NCheckbox,
NIcon,
NLayout,
NLayoutHeader,
NLayoutContent,
NLayoutFooter,
NLayoutSider,
NMenu,
NBreadcrumb,
NBreadcrumbItem,
NDropdown,
NSpace,
NTooltip,
NAvatar,
NTabs,
NTabPane,
NCard,
NRow,
NCol,
NDrawer,
NDrawerContent,
NDivider,
NSwitch,
NBadge,
NAlert,
NElement,
NTag,
NNotificationProvider,
NProgress,
NDatePicker,
NGrid,
NGridItem,
NList,
NListItem,
NThing,
NDataTable,
NPopover,
NPagination,
NSelect,
NRadioGroup,
NRadio,
NSteps,
NStep,
NInputGroup,
NResult,
NDescriptions,
NDescriptionsItem,
NTable,
NInputNumber,
NLoadingBarProvider,
NModal,
NUpload,
NTree,
NSpin
]
})
NSpin,
],
});
export function setupNaive(app: App<Element>) {
app.use(naive)
app.use(naive);
}

View File

@@ -3,43 +3,43 @@ import { ErrorPage, RedirectName, Layout } from '@/router/constant';
// 404 on a page
export const ErrorPageRoute: AppRouteRecordRaw = {
path: '/:path(.*)*',
name: 'ErrorPage',
component: Layout,
meta: {
path: '/:path(.*)*',
name: 'ErrorPage',
component: Layout,
meta: {
title: 'ErrorPage',
hideBreadcrumb: true,
},
children: [
{
path: '/:path(.*)*',
name: 'ErrorPage',
component: ErrorPage,
meta: {
title: 'ErrorPage',
hideBreadcrumb: true,
},
},
children: [
{
path: '/:path(.*)*',
name: 'ErrorPage',
component: ErrorPage,
meta: {
title: 'ErrorPage',
hideBreadcrumb: true,
},
},
],
],
};
export const RedirectRoute: AppRouteRecordRaw = {
path: '/redirect',
name: RedirectName,
component: Layout,
meta: {
path: '/redirect',
name: RedirectName,
component: Layout,
meta: {
title: RedirectName,
hideBreadcrumb: true,
},
children: [
{
path: '/redirect/:path(.*)',
name: RedirectName,
component: () => import('@/views/redirect/index.vue'),
meta: {
title: RedirectName,
hideBreadcrumb: true,
},
},
children: [
{
path: '/redirect/:path(.*)',
name: RedirectName,
component: () => import('@/views/redirect/index.vue'),
meta: {
title: RedirectName,
hideBreadcrumb: true,
},
},
],
],
};

View File

@@ -3,4 +3,3 @@ export const RedirectName = 'Redirect';
export const ErrorPage = () => import('@/views/exception/404.vue');
export const Layout = () => import('@/layout/index.vue');

View File

@@ -1,17 +1,16 @@
import { RouterView } from 'vue-router'
import { renderIcon } from '@/utils/index'
import { DashboardOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index';
import { DashboardOutlined } from '@vicons/antd';
// import { RouterTransition } from '@/components/transition'
//前端路由映射表
export const constantRouterComponents = {
'Layout': () => import('@/layout/index.vue'), //布局
'DashboardConsole': () => import('@/views/dashboard/console/console.vue'), // 主控台
'DashboardMonitor': () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
'DashboardWorkplace': () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
}
Layout: () => import('@/layout/index.vue'), //布局
DashboardConsole: () => import('@/views/dashboard/console/console.vue'), // 主控台
DashboardMonitor: () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
DashboardWorkplace: () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
};
//前端路由图标映射表
export const constantRouterIcon = {
'DashboardOutlined': renderIcon(DashboardOutlined)
}
DashboardOutlined: renderIcon(DashboardOutlined),
};

View File

@@ -1,8 +1,8 @@
import { adminMenus } from '@/api/system/menu'
import { constantRouterComponents, constantRouterIcon } from './constantRouterComponents'
import router from '@/router/index'
import { constantRouter } from '@/router/index'
import { RouteRecordRaw } from 'vue-router'
import { adminMenus } from '@/api/system/menu';
import { constantRouterComponents, constantRouterIcon } from './constantRouterComponents';
import router from '@/router/index';
import { constantRouter } from '@/router/index';
import { RouteRecordRaw } from 'vue-router';
/**
* 格式化 后端 结构信息并递归生成层级路由表
@@ -12,35 +12,34 @@ import { RouteRecordRaw } from 'vue-router'
* @returns {*}
*/
export const routerGenerator = (routerMap, parent?): any[] => {
return routerMap.map(item => {
const currentRouter: any = {
// 路由地址 动态拼接生成如 /dashboard/workplace
path: `${ parent && parent.path || '' }/${ item.path }`,
// 路由名称,建议唯一
name: item.name || '',
// 该路由对应页面的 组件
component: constantRouterComponents[item.component],
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: {
...item.meta,
label: item.meta.title,
icon: constantRouterIcon[item.meta.icon] || null,
permission: item.meta.permission || null
}
}
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
currentRouter.path = currentRouter.path.replace('//', '/')
// 重定向
item.redirect && (currentRouter.redirect = item.redirect)
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = routerGenerator(item.children, currentRouter)
}
return currentRouter
})
}
return routerMap.map((item) => {
const currentRouter: any = {
// 路由地址 动态拼接生成如 /dashboard/workplace
path: `${(parent && parent.path) || ''}/${item.path}`,
// 路由名称,建议唯一
name: item.name || '',
// 该路由对应页面的 组件
component: constantRouterComponents[item.component],
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: {
...item.meta,
label: item.meta.title,
icon: constantRouterIcon[item.meta.icon] || null,
permission: item.meta.permission || null,
},
};
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
currentRouter.path = currentRouter.path.replace('//', '/');
// 重定向
item.redirect && (currentRouter.redirect = item.redirect);
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = routerGenerator(item.children, currentRouter);
}
return currentRouter;
});
};
/**
* 动态生成菜单
@@ -48,18 +47,18 @@ export const routerGenerator = (routerMap, parent?): any[] => {
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
return new Promise((resolve, reject) => {
adminMenus()
.then((result) => {
const routeList = routerGenerator(result)
const asyncRoutesList = [...constantRouter, ...routeList]
asyncRoutesList.forEach(item => {
router.addRoute(item)
})
resolve(asyncRoutesList)
})
.catch((err) => {
reject(err)
})
})
}
return new Promise((resolve, reject) => {
adminMenus()
.then((result) => {
const routeList = routerGenerator(result);
const asyncRoutesList = [...constantRouter, ...routeList];
asyncRoutesList.forEach((item) => {
router.addRoute(item);
});
resolve(asyncRoutesList);
})
.catch((err) => {
reject(err);
});
});
};

View File

@@ -1,9 +1,9 @@
import { App } from 'vue'
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { ErrorPageRoute, RedirectRoute } from '@/router/base';
import { PageEnum } from '@/enums/pageEnum';
import { createRouterGuards } from './router-guards'
import 'nprogress/css/nprogress.css' // 进度条样式
import { createRouterGuards } from './router-guards';
import 'nprogress/css/nprogress.css'; // 进度条样式
// @ts-ignore
const modules = import.meta.globEager('./modules/**/*.ts');
@@ -11,53 +11,52 @@ const modules = import.meta.globEager('./modules/**/*.ts');
const routeModuleList: RouteRecordRaw[] = [];
Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
routeModuleList.push(...modList);
const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
routeModuleList.push(...modList);
});
function sortRoute(a, b) {
return (a.meta.sort || 0) - (b.meta.sort || 0)
return (a.meta.sort || 0) - (b.meta.sort || 0);
}
routeModuleList.sort(sortRoute)
routeModuleList.sort(sortRoute);
export const RootRoute: RouteRecordRaw = {
path: '/',
name: 'Root',
redirect: PageEnum.BASE_HOME,
meta: {
title: 'Root',
},
path: '/',
name: 'Root',
redirect: PageEnum.BASE_HOME,
meta: {
title: 'Root',
},
};
export const LoginRoute: RouteRecordRaw = {
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
},
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
},
};
//需要验证权限
export const asyncRoutes = [ErrorPageRoute, ...routeModuleList];
//普通路由 无需验证权限
export const constantRouter:any[] = [LoginRoute, RootRoute, RedirectRoute]
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
const router = createRouter({
history: createWebHashHistory(''),
routes: constantRouter,
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
})
history: createWebHashHistory(''),
routes: constantRouter,
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
export function setupRouter(app: App) {
app.use(router)
// 创建路由守卫
createRouterGuards(router)
app.use(router);
// 创建路由守卫
createRouterGuards(router);
}
export default router
export default router;

View File

@@ -1,10 +1,9 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { WalletOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { WalletOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
const routeName = 'comp'
const routeName = 'comp';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -18,36 +17,35 @@ const routeName = 'comp'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/comp',
name: routeName,
redirect: '/comp/console',
component: Layout,
{
path: '/comp',
name: routeName,
redirect: '/comp/console',
component: Layout,
meta: {
title: '组件示例',
icon: renderIcon(WalletOutlined),
sort: 8,
},
children: [
{
path: 'table',
name: `${routeName}_table`,
meta: {
title: '组件示例',
icon: renderIcon(WalletOutlined),
sort: 8
title: '表格',
},
children: [
{
path: 'table',
name: `${ routeName }_table`,
meta: {
title: '表格',
},
component: () => import('@/views/comp/table/list.vue')
},
{
component: () => import('@/views/comp/table/list.vue'),
},
{
path: 'upload',
name: `${routeName}_upload`,
meta: {
title: '上传',
},
component: () => import('@/views/comp/upload/index.vue'),
},
],
},
];
path: 'upload',
name: `${ routeName }_upload`,
meta: {
title: '上传',
},
component: () => import('@/views/comp/upload/index.vue')
}
],
}
]
export default routes
export default routes;

View File

@@ -1,9 +1,9 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DashboardOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { DashboardOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
const routeName = 'dashboard'
const routeName = 'dashboard';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -16,48 +16,48 @@ const routeName = 'dashboard'
* @param meta.sort 排序越小越排前
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/dashboard',
name: routeName,
redirect: '/dashboard/console',
component: Layout,
{
path: '/dashboard',
name: routeName,
redirect: '/dashboard/console',
component: Layout,
meta: {
title: 'Dashboard',
icon: renderIcon(DashboardOutlined),
permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
sort: 0,
},
children: [
{
path: 'console',
name: `${routeName}_console`,
meta: {
title: 'Dashboard',
icon: renderIcon(DashboardOutlined),
permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
sort: 0
title: '主控台',
permission: ['dashboard_console'],
},
children: [
{
path: 'console',
name: `${ routeName }_console`,
meta: {
title: '主控台',
permission: ['dashboard_console']
},
component: () => import('@/views/dashboard/console/console.vue')
},
// {
// path: 'monitor',
// name: `${ routeName }_monitor`,
// meta: {
// title: '监控页',
// permission: ['dashboard_monitor']
// },
// component: () => import('@/views/dashboard/monitor/monitor.vue')
// },
{
path: 'workplace',
name: `${ routeName }_workplace`,
meta: {
title: '工作台',
keepAlive: true,
permission: ['dashboard_workplace']
},
component: () => import('@/views/dashboard/workplace/workplace.vue')
}
]
}
]
component: () => import('@/views/dashboard/console/console.vue'),
},
// {
// path: 'monitor',
// name: `${ routeName }_monitor`,
// meta: {
// title: '监控页',
// permission: ['dashboard_monitor']
// },
// component: () => import('@/views/dashboard/monitor/monitor.vue')
// },
{
path: 'workplace',
name: `${routeName}_workplace`,
meta: {
title: '工作台',
keepAlive: true,
permission: ['dashboard_workplace'],
},
component: () => import('@/views/dashboard/workplace/workplace.vue'),
},
],
},
];
export default routes
export default routes;

View File

@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ExclamationCircleOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { ExclamationCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -15,43 +15,43 @@ import { renderIcon } from '@/utils/index'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/exception',
name: 'Exception',
redirect: '/exception/403',
component: Layout,
{
path: '/exception',
name: 'Exception',
redirect: '/exception/403',
component: Layout,
meta: {
title: '异常页面',
icon: renderIcon(ExclamationCircleOutlined),
sort: 3,
},
children: [
{
path: '403',
name: 'exception-403',
meta: {
title: '异常页面',
icon: renderIcon(ExclamationCircleOutlined),
sort: 3
title: '403',
},
children: [
{
path: '403',
name: 'exception-403',
meta: {
title: '403',
},
component: () => import('@/views/exception/403.vue')
},
{
path: '404',
name: 'exception-404',
meta: {
title: '404',
},
component: () => import('@/views/exception/404.vue')
},
{
path: '500',
name: 'exception-500',
meta: {
title: '500',
},
component: () => import('@/views/exception/500.vue')
},
],
}
]
component: () => import('@/views/exception/403.vue'),
},
{
path: '404',
name: 'exception-404',
meta: {
title: '404',
},
component: () => import('@/views/exception/404.vue'),
},
{
path: '500',
name: 'exception-500',
meta: {
title: '500',
},
component: () => import('@/views/exception/500.vue'),
},
],
},
];
export default routes
export default routes;

View File

@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ProfileOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { ProfileOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -15,44 +15,43 @@ import { renderIcon } from '@/utils/index'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/form',
name: 'Form',
redirect: '/form/basic-form',
component: Layout,
{
path: '/form',
name: 'Form',
redirect: '/form/basic-form',
component: Layout,
meta: {
title: '表单页面',
icon: renderIcon(ProfileOutlined),
sort: 3,
},
children: [
{
path: 'basic-form',
name: 'form-basic-form',
meta: {
title: '表单页面',
icon: renderIcon(ProfileOutlined),
sort: 1
title: '基础表单',
},
children: [
{
path: 'basic-form',
name: 'form-basic-form',
meta: {
title: '基础表单',
},
component: () => import('@/views/form/basicForm/index.vue')
},
{
path: 'step-form',
name: 'form-step-form',
meta: {
title: '分步表单',
},
component: () => import('@/views/form/stepForm/stepForm.vue')
},
{
path: 'detail',
name: 'form-detail',
meta: {
title: '表单详情',
},
component: () => import('@/views/form/detail/index.vue')
},
component: () => import('@/views/form/basicForm/index.vue'),
},
{
path: 'step-form',
name: 'form-step-form',
meta: {
title: '分步表单',
},
component: () => import('@/views/form/stepForm/stepForm.vue'),
},
{
path: 'detail',
name: 'form-detail',
meta: {
title: '表单详情',
},
component: () => import('@/views/form/detail/index.vue'),
},
],
},
];
],
}
]
export default routes
export default routes;

View File

@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { TableOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { TableOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -15,36 +15,36 @@ import { renderIcon } from '@/utils/index'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/list',
name: 'List',
redirect: '/list/basic-list',
component: Layout,
{
path: '/list',
name: 'List',
redirect: '/list/basic-list',
component: Layout,
meta: {
title: '列表页面',
icon: renderIcon(TableOutlined),
sort: 2,
},
children: [
{
path: 'basic-list',
name: 'basic-list',
meta: {
title: '列表页面',
icon: renderIcon(TableOutlined),
sort: 1
title: '基础列表',
},
children: [
{
path: 'basic-list',
name: 'basic-list',
meta: {
title: '基础列表',
},
component: () => import('@/views/list/basicList/index.vue')
},
{
path: 'basic-info/:id?',
name: 'basic-info',
meta: {
title: '基础详情',
hidden:true
},
component: () => import('@/views/list/basicList/info.vue')
}
],
}
]
component: () => import('@/views/list/basicList/index.vue'),
},
{
path: 'basic-info/:id?',
name: 'basic-info',
meta: {
title: '基础详情',
hidden: true,
},
component: () => import('@/views/list/basicList/info.vue'),
},
],
},
];
export default routes
export default routes;

View File

@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { CheckCircleOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { CheckCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -15,43 +15,43 @@ import { renderIcon } from '@/utils/index'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/result',
name: 'Result',
redirect: '/result/success',
component: Layout,
{
path: '/result',
name: 'Result',
redirect: '/result/success',
component: Layout,
meta: {
title: '结果页面',
icon: renderIcon(CheckCircleOutlined),
sort: 4,
},
children: [
{
path: 'success',
name: 'result-success',
meta: {
title: '结果页面',
icon: renderIcon(CheckCircleOutlined),
sort: 4
title: '成功页',
},
children: [
{
path: 'success',
name: 'result-success',
meta: {
title: '成功页',
},
component: () => import('@/views/result/success.vue')
},
{
path: 'fail',
name: 'result-fail',
meta: {
title: '失败页',
},
component: () => import('@/views/result/fail.vue')
},
{
path: 'info',
name: 'result-info',
meta: {
title: '信息页',
},
component: () => import('@/views/result/info.vue')
},
],
}
]
component: () => import('@/views/result/success.vue'),
},
{
path: 'fail',
name: 'result-fail',
meta: {
title: '失败页',
},
component: () => import('@/views/result/fail.vue'),
},
{
path: 'info',
name: 'result-info',
meta: {
title: '信息页',
},
component: () => import('@/views/result/info.vue'),
},
],
},
];
export default routes
export default routes;

View File

@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { SettingOutlined } from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import { SettingOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -15,35 +15,35 @@ import { renderIcon } from '@/utils/index'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/setting',
name: 'Setting',
redirect: '/setting/account',
component: Layout,
{
path: '/setting',
name: 'Setting',
redirect: '/setting/account',
component: Layout,
meta: {
title: '设置页面',
icon: renderIcon(SettingOutlined),
sort: 5,
},
children: [
{
path: 'account',
name: 'setting-account',
meta: {
title: '设置页面',
icon: renderIcon(SettingOutlined),
sort: 5
title: '个人设置',
},
children: [
{
path: 'account',
name: 'setting-account',
meta: {
title: '个人设置',
},
component: () => import('@/views/setting/account/account.vue')
},
{
path: 'system',
name: 'setting-system',
meta: {
title: '系统设置',
},
component: () => import('@/views/setting/system/system.vue')
}
]
}
]
component: () => import('@/views/setting/account/account.vue'),
},
{
path: 'system',
name: 'setting-system',
meta: {
title: '系统设置',
},
component: () => import('@/views/setting/system/system.vue'),
},
],
},
];
export default routes
export default routes;

View File

@@ -1,8 +1,7 @@
import { RouteRecordRaw } from 'vue-router'
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ToolOutlined } from '@vicons/antd'
import { OptionsSharp } from '@vicons/ionicons5'
import { renderIcon } from '@/utils/index'
import { OptionsSharp } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
@@ -16,35 +15,35 @@ import { renderIcon } from '@/utils/index'
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/system',
name: 'System',
redirect: '/system/menu',
component: Layout,
{
path: '/system',
name: 'System',
redirect: '/system/menu',
component: Layout,
meta: {
title: '系统设置',
icon: renderIcon(OptionsSharp),
sort: 1,
},
children: [
{
path: 'menu',
name: 'system_menu',
meta: {
title: '系统设置',
icon: renderIcon(OptionsSharp),
sort: 1
title: '菜单权限管理',
},
children: [
{
path: 'menu',
name: 'system_menu',
meta: {
title: '菜单权限管理',
},
component: () => import('@/views/system/menu/menu.vue')
},
{
path: 'role',
name: 'system_role',
meta: {
title: '角色权限管理',
},
component: () => import('@/views/system/role/role.vue')
}
],
}
]
component: () => import('@/views/system/menu/menu.vue'),
},
{
path: 'role',
name: 'system_role',
meta: {
title: '角色权限管理',
},
component: () => import('@/views/system/role/role.vue'),
},
],
},
];
export default routes
export default routes;

View File

@@ -1,106 +1,102 @@
import { isNavigationFailure, Router } from 'vue-router'
import { useUserStoreWidthOut } from '@/store/modules/user'
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute'
import NProgress from 'nprogress' // progress bar
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { storage } from '@/utils/Storage'
import { PageEnum } from '@/enums/pageEnum'
import { isNavigationFailure, Router } from 'vue-router';
import { useUserStoreWidthOut } from '@/store/modules/user';
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
import NProgress from 'nprogress'; // progress bar
import { ACCESS_TOKEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage';
import { PageEnum } from '@/enums/pageEnum';
NProgress.configure({ showSpinner: false }); // NProgress Configuration
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const LOGIN_PATH = PageEnum.BASE_LOGIN;
const LOGIN_PATH = PageEnum.BASE_LOGIN
const whitePathList = [LOGIN_PATH] // no redirect whitelist
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
export function createRouterGuards(router: Router) {
const userStore = useUserStoreWidthOut();
const userStore = useUserStoreWidthOut();
const asyncRouteStore = useAsyncRouteStoreWidthOut();
router.beforeEach(async (to, from, next) => {
NProgress.start();
if (from.path === LOGIN_PATH && to.name === 'errorPage') {
next(PageEnum.BASE_HOME);
return;
}
// Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) {
next();
return;
}
const token = storage.get(ACCESS_TOKEN);
if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) {
next();
return;
}
// redirect login page
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH,
replace: true,
};
if (to.path) {
redirectData.query = {
...redirectData.query,
redirect: to.path,
};
}
next(redirectData);
return;
}
if (asyncRouteStore.getIsDynamicAddedRoute) {
next();
return;
}
const userInfo = await userStore.GetInfo();
const routes = await asyncRouteStore.generateRoutes(userInfo);
// 动态添加可访问路由表
routes.forEach((item) => {
router.addRoute(item);
});
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
asyncRouteStore.setDynamicAddedRoute(true);
next(nextData);
NProgress.done();
});
router.afterEach((to, _, failure) => {
document.title = (to?.meta?.title as string) || document.title;
if (isNavigationFailure(failure)) {
//console.log('failed navigation', failure)
}
const asyncRouteStore = useAsyncRouteStoreWidthOut();
router.beforeEach(async (to, from, next) => {
NProgress.start()
if (from.path === LOGIN_PATH && to.name === 'errorPage') {
next(PageEnum.BASE_HOME);
return;
}
// 在这里设置需要缓存的组件名称
const keepAliveComponents = asyncRouteStore.keepAliveComponents;
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件
keepAliveComponents.push(currentComName);
} else if (!to.meta?.keepAlive || to.name == 'Redirect') {
// 不需要缓存的组件
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
if (index != -1) {
keepAliveComponents.splice(index, 1);
}
}
asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
NProgress.done(); // finish progress bar
});
// Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) {
next();
return;
}
const token = storage.get(ACCESS_TOKEN)
if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) {
next();
return;
}
// redirect login page
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH,
replace: true,
};
if (to.path) {
redirectData.query = {
...redirectData.query,
redirect: to.path,
};
}
next(redirectData);
return;
}
if (asyncRouteStore.getIsDynamicAddedRoute) {
next();
return;
}
const userInfo = await userStore.GetInfo()
const routes = await asyncRouteStore.generateRoutes(userInfo)
// 动态添加可访问路由表
routes.forEach((item) => {
router.addRoute(item)
});
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
asyncRouteStore.setDynamicAddedRoute(true);
next(nextData);
NProgress.done()
})
router.afterEach((to, _, failure) => {
document.title = (to?.meta?.title as string) || document.title
if (isNavigationFailure(failure)) {
//console.log('failed navigation', failure)
}
const asyncRouteStore = useAsyncRouteStoreWidthOut();
// 在这里设置需要缓存的组件名称
const keepAliveComponents = asyncRouteStore.keepAliveComponents
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件
keepAliveComponents.push(currentComName)
} else if (!to.meta?.keepAlive || to.name == 'Redirect') {
// 不需要缓存的组件
const index = asyncRouteStore.keepAliveComponents.findIndex(
(name) => name == currentComName
)
if (index != -1) {
keepAliveComponents.splice(index, 1)
}
}
asyncRouteStore.setKeepAliveComponents(keepAliveComponents)
NProgress.done() // finish progress bar
})
router.onError((error) => {
console.log(error, '路由错误')
})
router.onError((error) => {
console.log(error, '路由错误');
});
}

View File

@@ -1,37 +1,37 @@
import type { RouteRecordRaw, RouteMeta } from 'vue-router';
import { defineComponent } from 'vue';
import { RoleEnum } from '@/enums/roleEnum'
import { RoleEnum } from '@/enums/roleEnum';
export type Component<T extends any = any> =
| ReturnType<typeof defineComponent>
| (() => Promise<typeof import('*.vue')>)
| (() => Promise<T>);
| ReturnType<typeof defineComponent>
| (() => Promise<typeof import('*.vue')>)
| (() => Promise<T>);
// @ts-ignore
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
name: string;
meta: RouteMeta;
component?: Component | string;
components?: Component;
children?: AppRouteRecordRaw[];
props?: Recordable;
fullPath?: string;
name: string;
meta: RouteMeta;
component?: Component | string;
components?: Component;
children?: AppRouteRecordRaw[];
props?: Recordable;
fullPath?: string;
}
export interface Meta {
// 名称
title: string
// 是否忽略权限
ignoreAuth?: boolean
roles?: RoleEnum[]
// 是否不缓存
noKeepAlive?: boolean
// 是否固定在tab上
affix?: boolean
// tab上的图标
icon?: string
// 跳转地址
frameSrc?: string
// 外链跳转地址
externalLink?: string
// 名称
title: string;
// 是否忽略权限
ignoreAuth?: boolean;
roles?: RoleEnum[];
// 是否不缓存
noKeepAlive?: boolean;
// 是否固定在tab上
affix?: boolean;
// tab上的图标
icon?: string;
// 跳转地址
frameSrc?: string;
// 外链跳转地址
externalLink?: string;
}

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