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, root: true,
env: { env: {
browser: true, browser: true,
node: true, node: true,
es6: true es6: true,
}, },
parser: 'vue-eslint-parser', parser: 'vue-eslint-parser',
parserOptions: { parserOptions: {
@@ -12,21 +14,46 @@ module.exports = {
sourceType: 'module', sourceType: 'module',
jsxPragma: 'React', jsxPragma: 'React',
ecmaFeatures: { ecmaFeatures: {
jsx: true jsx: true,
} },
}, },
extends: [ extends: [
'plugin:vue/vue3-recommended', 'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'prettier', 'prettier',
'plugin:prettier/recommended' 'plugin:prettier/recommended',
'plugin:jest/recommended',
], ],
rules: { rules: {
'vue/no-unused-components': 'off', '@typescript-eslint/ban-ts-ignore': 'off',
'vue/no-unused-vars': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'vue/no-v-for-template-key-on-child': '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/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/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off', 'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off', 'vue/max-attributes-per-line': 'off',
@@ -34,34 +61,17 @@ module.exports = {
'vue/singleline-html-element-content-newline': 'off', 'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off', 'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': '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': [ 'vue/html-self-closing': [
'error', 'error',
{ {
html: { html: {
void: 'always', void: 'always',
normal: 'never', normal: 'never',
component: 'always' component: 'always',
}, },
svg: 'always', svg: 'always',
math: 'always' math: 'always',
} },
] ],
} },
} });

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,17 @@
import { resultSuccess, doCustomTimes } from '../_util' import { resultSuccess, doCustomTimes } from '../_util';
function getMenuKeys() { function getMenuKeys() {
let keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'] const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
let newKeys = [] const newKeys = [];
doCustomTimes(parseInt(Math.random()*6), () => { doCustomTimes(parseInt(Math.random() * 6), () => {
let key = keys[Math.floor(Math.random() * keys.length)]; const key = keys[Math.floor(Math.random() * keys.length)];
newKeys.push(key) newKeys.push(key);
}) });
return Array.from(new Set(newKeys)); return Array.from(new Set(newKeys));
} }
const roleList = ((pageSize) => { const roleList = (pageSize) => {
const result: any[] = [] const result: any[] = [];
doCustomTimes(pageSize, () => { doCustomTimes(pageSize, () => {
result.push({ result.push({
id: '@integer(10,100)', id: '@integer(10,100)',
@@ -23,10 +22,9 @@ const roleList = ((pageSize) => {
create_date: `@date('yyyy-MM-dd hh:mm:ss')`, create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
'status|1': ['normal', 'enable', 'disable'], 'status|1': ['normal', 'enable', 'disable'],
}); });
}) });
return result return result;
}); };
export default [ export default [
{ {
@@ -35,16 +33,13 @@ export default [
method: 'get', method: 'get',
response: ({ query }) => { response: ({ query }) => {
const { page = 1, pageSize = 10 } = query; const { page = 1, pageSize = 10 } = query;
const list = roleList(Number(pageSize)) const list = roleList(Number(pageSize));
return resultSuccess({ return resultSuccess({
page: Number(page), page: Number(page),
pageSize: Number(pageSize), pageSize: Number(pageSize),
pageCount: 60, pageCount: 60,
list list,
} });
);
}, },
} },
] ];

View File

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

View File

@@ -1,5 +1,4 @@
import { MockMethod } from 'vite-plugin-mock' import { resultSuccess } from '../_util';
import { resultSuccess, getRequestToken } from '../_util'
const menusList = [ const menusList = [
{ {
@@ -18,7 +17,7 @@ const menusList = [
component: 'DashboardConsole', component: 'DashboardConsole',
meta: { meta: {
title: '主控台', title: '主控台',
} },
}, },
{ {
path: 'monitor', path: 'monitor',
@@ -26,7 +25,7 @@ const menusList = [
component: 'DashboardMonitor', component: 'DashboardMonitor',
meta: { meta: {
title: '监控页', title: '监控页',
} },
}, },
{ {
path: 'workplace', path: 'workplace',
@@ -35,11 +34,11 @@ const menusList = [
meta: { meta: {
hidden: true, hidden: true,
title: '工作台', title: '工作台',
} },
}, },
], ],
} },
] ];
export default [ export default [
{ {
@@ -49,5 +48,5 @@ export default [
response: () => { response: () => {
return resultSuccess(menusList); return resultSuccess(menusList);
}, },
} },
] ];

View File

@@ -1,9 +1,9 @@
import Mock from 'mockjs' import Mock from 'mockjs';
import { resultSuccess, getRequestToken } from '../_util' 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 = { const adminInfo = {
userId: '1', userId: '1',
@@ -33,9 +33,9 @@ const adminInfo = {
{ {
roleName: '基础列表删除', roleName: '基础列表删除',
value: 'basic_list_delete', value: 'basic_list_delete',
} },
], ],
} };
export default [ export default [
{ {
@@ -56,4 +56,4 @@ export default [
return resultSuccess(adminInfo); return resultSuccess(adminInfo);
}, },
}, },
] ];

View File

@@ -62,11 +62,14 @@
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"eslint": "^7.28.0", "eslint": "^7.28.0",
"eslint-config-prettier": "^8.3.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-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.11.1", "eslint-plugin-vue": "^7.11.1",
"esno": "^0.7.3", "esno": "^0.7.3",
"gh-pages": "^3.2.0", "gh-pages": "^3.2.0",
"husky": "^6.0.0", "husky": "^6.0.0",
"jest": "^27.0.6",
"less": "^4.1.1", "less": "^4.1.1",
"less-loader": "^9.0.0", "less-loader": "^9.0.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,27 +1,25 @@
import http from '@/utils/http/axios' import http from '@/utils/http/axios';
export interface BasicResponseModel<T = any> { export interface BasicResponseModel<T = any> {
code: number code: number;
message: string message: string;
result: T result: T;
} }
export interface BasicPageParams { export interface BasicPageParams {
pageNumber: number pageNumber: number;
pageSize: number pageSize: number;
total: number total: number;
} }
/** /**
* @description: 获取用户信息 * @description: 获取用户信息
*/ */
export function getUserInfo() { export function getUserInfo() {
return http.request( return http.request({
{
url: '/admin_info', url: '/admin_info',
method: 'get' method: 'get',
} });
)
} }
/** /**
@@ -32,12 +30,12 @@ export function login(params) {
{ {
url: '/login', url: '/login',
method: 'POST', method: 'POST',
params params,
}, },
{ {
isTransformRequestResult: false isTransformRequestResult: false,
} }
) );
} }
/** /**
@@ -46,14 +44,14 @@ export function login(params) {
export function changePassword(params, uid) { export function changePassword(params, uid) {
return http.request( return http.request(
{ {
url: `/user/u${ uid }/changepw`, url: `/user/u${uid}/changepw`,
method: 'POST', method: 'POST',
params params,
}, },
{ {
isTransformRequestResult: false isTransformRequestResult: false,
} }
) );
} }
/** /**
@@ -63,6 +61,6 @@ export function logout(params) {
return http.request({ return http.request({
url: '/login/logout', url: '/login/logout',
method: 'POST', method: 'POST',
params params,
}) });
} }

View File

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

View File

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

View File

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

View File

@@ -4,11 +4,11 @@
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue'; import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
import { useTransition, TransitionPresets } from '@vueuse/core'; import { useTransition, TransitionPresets } from '@vueuse/core';
import { isNumber } from '@/utils/is'; import { isNumber } from '@/utils/is';
const props = { const props = {
startVal: { type: Number, default: 0 }, startVal: { type: Number, default: 0 },
endVal: { type: Number, default: 2021 }, endVal: { type: Number, default: 2021 },
duration: { type: Number, default: 1500 }, duration: { type: Number, default: 1500 },
@@ -36,9 +36,9 @@ const props = {
* Digital animation * Digital animation
*/ */
transition: { type: String, default: 'linear' }, transition: { type: String, default: 'linear' },
}; };
export default defineComponent({ export default defineComponent({
name: 'CountTo', name: 'CountTo',
props, props,
emits: ['onStarted', 'onFinished'], emits: ['onStarted', 'onFinished'],
@@ -106,5 +106,5 @@ export default defineComponent({
return { value, start, reset }; return { value, start, reset };
}, },
}); });
</script> </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> <template></template>
<script lang="ts"> <script lang="ts">
import { useDialog } from 'naive-ui' import { useDialog } from 'naive-ui';
export default { export default {
name: 'DialogContent', name: 'DialogContent',
setup() { setup() {
//挂载在 window 方便与在js中使用 //挂载在 window 方便与在js中使用
window.$dialog = useDialog() window['$dialog'] = useDialog();
} },
} };
</script> </script>

View File

@@ -7,23 +7,21 @@
@contextmenu.prevent @contextmenu.prevent
> >
<template v-if="!showLogin"> <template v-if="!showLogin">
<div class="lock-box"> <div class="lock-box">
<div class="lock"> <div class="lock">
<span class="lock-icon" title="解锁屏幕" @click="onLockLogin(true)"> <span class="lock-icon" title="解锁屏幕" @click="onLockLogin(true)">
<n-icon> <n-icon>
<lock-outlined/> <lock-outlined />
</n-icon> </n-icon>
</span> </span>
</div> </div>
</div> </div>
<!--充电--> <!--充电-->
<recharge <recharge
:battery="battery" :battery="battery"
:battery-status="batteryStatus" :battery-status="batteryStatus"
:calc-discharging-time="calcDischargingTime" :calc-discharging-time="calcDischargingTime"
></recharge> />
<div class="local-time"> <div class="local-time">
<div class="time">{{ hour }}:{{ minute }}</div> <div class="time">{{ hour }}:{{ minute }}</div>
@@ -31,9 +29,9 @@
</div> </div>
<div class="computer-status"> <div class="computer-status">
<span :class="{ offline: !online }" class="network"> <span :class="{ offline: !online }" class="network">
<wifi-outlined class="network"/> <wifi-outlined class="network" />
</span> </span>
<api-outlined/> <api-outlined />
</div> </div>
</template> </template>
@@ -42,7 +40,7 @@
<div class="login-box"> <div class="login-box">
<n-avatar :size="128"> <n-avatar :size="128">
<n-icon> <n-icon>
<user-outlined/> <user-outlined />
</n-icon> </n-icon>
</n-avatar> </n-avatar>
<div class="username">{{ loginParams.username }}</div> <div class="username">{{ loginParams.username }}</div>
@@ -50,11 +48,12 @@
type="password" type="password"
autofocus autofocus
v-model:value="loginParams.password" v-model:value="loginParams.password"
placeholder="请输入登录密码"> placeholder="请输入登录密码"
>
<template #suffix> <template #suffix>
<n-icon @click="onLogin" style="cursor: pointer;"> <n-icon @click="onLogin" style="cursor: pointer">
<LoadingOutlined v-if="loginLoading"/> <LoadingOutlined v-if="loginLoading" />
<arrow-right-outlined v-else/> <arrow-right-outlined v-else />
</n-icon> </n-icon>
</template> </template>
</n-input> </n-input>
@@ -64,62 +63,60 @@
</div> </div>
<div class="w-full mt-1 flex justify-around"> <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="goLogin">重新登录</a></div>
<div><a @click="onLogin">进入系统</a></div> <div><a @click="onLogin">进入系统</a></div>
</div> </div>
</div> </div>
</template> </template>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, reactive, toRefs, computed } from 'vue' import { defineComponent, reactive, toRefs } from 'vue';
import { ResultEnum } from '@/enums/httpEnum' import { ResultEnum } from '@/enums/httpEnum';
import recharge from './Recharge.vue' import recharge from './Recharge.vue';
import { import {
LockOutlined, LockOutlined,
LoadingOutlined, LoadingOutlined,
UnlockOutlined,
UserOutlined, UserOutlined,
ApiOutlined, ApiOutlined,
ArrowRightOutlined, ArrowRightOutlined,
WifiOutlined, WifiOutlined,
} from '@vicons/antd' } from '@vicons/antd';
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router';
import { useOnline } from '@/hooks/useOnline' import { useOnline } from '@/hooks/useOnline';
import { useTime } from '@/hooks/useTime' import { useTime } from '@/hooks/useTime';
import { useBattery } from '@/hooks/useBattery' import { useBattery } from '@/hooks/useBattery';
import { useLockscreenStore } from '@/store/modules/lockscreen' import { useLockscreenStore } from '@/store/modules/lockscreen';
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user';
export default defineComponent({ export default defineComponent({
name: 'Lockscreen', name: 'Lockscreen',
components: { components: {
LockOutlined, LockOutlined,
LoadingOutlined, LoadingOutlined,
UnlockOutlined,
UserOutlined, UserOutlined,
ArrowRightOutlined, ArrowRightOutlined,
ApiOutlined, ApiOutlined,
WifiOutlined, WifiOutlined,
recharge, recharge,
}, },
setup(props, { emit }) { setup() {
const useLockscreen = useLockscreenStore() const useLockscreen = useLockscreenStore();
const userStore = useUserStore(); const userStore = useUserStore();
// 获取时间 // 获取时间
const { month, day, hour, minute, second, week } = useTime() const { month, day, hour, minute, second, week } = useTime();
const { online } = useOnline() const { online } = useOnline();
const router = useRouter() const router = useRouter();
const route = useRoute() const route = useRoute();
const { battery, batteryStatus, calcDischargingTime } = useBattery() const { battery, batteryStatus, calcDischargingTime } = useBattery();
const { username } = userStore.getUserInfo || {} const userInfo: object = userStore.getUserInfo || {};
const username = userInfo['username'] || '';
const state = reactive({ const state = reactive({
showLogin: false, showLogin: false,
loginLoading: false, // 正在登录 loginLoading: false, // 正在登录
@@ -127,45 +124,45 @@ export default defineComponent({
errorMsg: '密码错误', errorMsg: '密码错误',
loginParams: { loginParams: {
username: username || '', username: username || '',
password: '' password: '',
} },
}) });
// 解锁登录 // 解锁登录
const onLockLogin = (value: boolean) => (state.showLogin = value) const onLockLogin = (value: boolean) => (state.showLogin = value);
// 登录 // 登录
const onLogin = async () => { const onLogin = async () => {
if (!state.loginParams.password.trim()) { if (!state.loginParams.password.trim()) {
return return;
} }
const params = { const params = {
isLock: true, isLock: true,
...state.loginParams ...state.loginParams,
} };
state.loginLoading = true state.loginLoading = true;
const { code, result, message } = await userStore.login(params) const { code, message } = await userStore.login(params);
if (code === ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
onLockLogin(false) onLockLogin(false);
useLockscreen.setLock(false) useLockscreen.setLock(false);
} else { } else {
state.errorMsg = message state.errorMsg = message;
state.isLoginError = true state.isLoginError = true;
}
state.loginLoading = false
} }
state.loginLoading = false;
};
//重新登录 //重新登录
const goLogin = () => { const goLogin = () => {
onLockLogin(false) onLockLogin(false);
useLockscreen.setLock(false) useLockscreen.setLock(false);
router.replace({ router.replace({
path: '/login', path: '/login',
query: { query: {
redirect: route.fullPath redirect: route.fullPath,
} },
}) });
} };
return { return {
...toRefs(state), ...toRefs(state),
@@ -181,14 +178,14 @@ export default defineComponent({
calcDischargingTime, calcDischargingTime,
onLockLogin, onLockLogin,
onLogin, onLogin,
goLogin goLogin,
} };
} },
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.lockscreen { .lockscreen {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@@ -300,5 +297,5 @@ export default defineComponent({
} }
} }
} }
} }
</style> </style>

View File

@@ -20,33 +20,33 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'HuaweiCharge', name: 'HuaweiCharge',
// props: ['batteryStatus', 'battery', 'calcDischargingTime'], // props: ['batteryStatus', 'battery', 'calcDischargingTime'],
props: { props: {
battery: { battery: {
// 电池对象 // 电池对象
type: Object, type: Object,
default: () => ({}) default: () => ({}),
}, },
calcDischargingTime: { calcDischargingTime: {
// 电池剩余时间可用时间 // 电池剩余时间可用时间
type: String, type: String,
default: '' default: '',
}, },
batteryStatus: { batteryStatus: {
// 电池状态 // 电池状态
type: String, type: String,
validator: (val: string) => ['充电中', '已充满', '已断开电源'].includes(val) validator: (val: string) => ['充电中', '已充满', '已断开电源'].includes(val),
} },
} },
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.container { .container {
position: absolute; position: absolute;
bottom: 20vh; bottom: 20vh;
left: 50vw; left: 50vw;
@@ -129,11 +129,11 @@ export default defineComponent({
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
} }
} }
@width: ~`Math.round(Math.random() * 100)` px; @width: ~`Math.round(Math.random() * 100) ` px;
@left: calc(15px + `Math.round(Math.random(70))`); @left: calc(15px + `Math.round(Math.random(70)) `);
each(range(15), { each(range(15), {
.xiaoma-@{value} { .xiaoma-@{value} {
height: (@value * 50px); height: (@value * 50px);
} }
@@ -147,8 +147,7 @@ each(range(15), {
} }
}); });
@keyframes rotate {
@keyframes rotate {
50% { 50% {
border-radius: 45% / 42% 38% 58% 49%; border-radius: 45% / 42% 38% 58% 49%;
} }
@@ -156,9 +155,9 @@ each(range(15), {
100% { 100% {
transform: translate(-50%, -50%) rotate(720deg); transform: translate(-50%, -50%) rotate(720deg);
} }
} }
@keyframes moveToTop { @keyframes moveToTop {
90% { 90% {
opacity: 1; opacity: 1;
} }
@@ -167,11 +166,11 @@ each(range(15), {
opacity: 0.1; opacity: 0.1;
transform: translate(-50%, -180px); transform: translate(-50%, -180px);
} }
} }
@keyframes hueRotate { @keyframes hueRotate {
100% { 100% {
filter: contrast(15) hue-rotate(360deg); filter: contrast(15) hue-rotate(360deg);
} }
} }
</style> </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> <template></template>
<script lang="ts"> <script lang="ts">
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui';
export default { export default {
name: 'MessageContent', name: 'MessageContent',
setup() { setup() {
//挂载在 window 方便与在js中使用 //挂载在 window 方便与在js中使用
window.$message = useMessage() window['$message'] = useMessage();
} },
} };
</script> </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 { default as TableAction } from './src/components/TableAction.vue';
export * from './src/types/table'; export * from './src/types/table';
export * from './src/types/tableAction'; export * from './src/types/tableAction';

View File

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

View File

@@ -12,11 +12,10 @@
> >
<slot name="more"></slot> <slot name="more"></slot>
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right"> <n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
<div class="flex items-center"> <div class="flex items-center">
<span>更多</span> <span>更多</span>
<n-icon size="14" class="ml-1"> <n-icon size="14" class="ml-1">
<DownOutlined/> <DownOutlined />
</n-icon> </n-icon>
</div> </div>
<!-- <template #icon>--> <!-- <template #icon>-->
@@ -29,13 +28,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, computed, toRaw } from 'vue'; import { defineComponent, PropType, computed, toRaw } from 'vue';
import { ActionItem } from '@/components/Table'; import { ActionItem } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission'; import { usePermission } from '@/hooks/web/usePermission';
import { isString, isBoolean, isFunction } from "@/utils/is"; import { isBoolean, isFunction } from '@/utils/is';
import { DownOutlined } from '@vicons/antd' import { DownOutlined } from '@vicons/antd';
export default defineComponent({ export default defineComponent({
name: 'TableAction', name: 'TableAction',
components: { DownOutlined }, components: { DownOutlined },
props: { props: {
@@ -49,44 +48,36 @@ export default defineComponent({
}, },
style: { style: {
type: String as PropType<String>, type: String as PropType<String>,
default: 'button' default: 'button',
}, },
select:{ select: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () =>{ } default: () => {},
}
}, },
setup(props, { emit }) { },
setup(props) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const getTooltip = computed(() => { const actionType =
return (data: string | TooltipProps): TooltipProps => { props.style === 'button' ? 'default' : props.style === 'text' ? 'primary' : 'default';
if (isString(data)) { const actionText =
return { title: data, placement: 'bottom' }; props.style === 'button' ? undefined : props.style === 'text' ? true : undefined;
} 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 getMoreProps = computed(() => { const getMoreProps = computed(() => {
return { return {
text: actionText, text: actionText,
type: actionType, type: actionType,
size: "small" size: 'small',
} };
}) });
const getDropdownList = computed(() => { const getDropdownList = computed(() => {
return (toRaw(props.dropDownActions) || []) return (toRaw(props.dropDownActions) || [])
.filter((action) => { .filter((action) => {
return hasPermission(action.auth) && isIfShow(action); return hasPermission(action.auth) && isIfShow(action);
}) })
.map((action, index) => { .map((action) => {
const { label, popConfirm } = action; const { popConfirm } = action;
return { return {
size: 'small', size: 'small',
text: actionText, text: actionText,
@@ -94,7 +85,7 @@ export default defineComponent({
...action, ...action,
...popConfirm, ...popConfirm,
onConfirm: popConfirm?.confirm, onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel onCancel: popConfirm?.cancel,
}; };
}); });
}); });
@@ -137,9 +128,8 @@ export default defineComponent({
return { return {
getActions, getActions,
getDropdownList, getDropdownList,
getTooltip, getMoreProps,
getMoreProps };
} },
} });
})
</script> </script>

View File

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

View File

@@ -1,10 +1,9 @@
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue'; import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue';
import type { BasicColumn, BasicTableProps } from '../types/table'; import type { BasicColumn, BasicTableProps } from '../types/table';
import { isEqual, cloneDeep } from 'lodash-es'; 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 { 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>) { export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>; const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
@@ -16,7 +15,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
handleActionColumn(propsRef, columns); handleActionColumn(propsRef, columns);
if (!columns) return []; if (!columns) return [];
return columns; return columns;
}) });
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
@@ -38,9 +37,10 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
const pageColumns = unref(getColumnsRef); const pageColumns = unref(getColumnsRef);
const columns = cloneDeep(pageColumns); const columns = cloneDeep(pageColumns);
return columns.filter((column) => { return columns.filter((column) => {
// @ts-ignore
return hasPermission(column.auth) && isIfShow(column); return hasPermission(column.auth) && isIfShow(column);
}) });
}) });
watch( watch(
() => unref(propsRef).columns, () => unref(propsRef).columns,
@@ -53,8 +53,9 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) { function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
const { actionColumn } = unref(propsRef); const { actionColumn } = unref(propsRef);
if (!actionColumn) return; if (!actionColumn) return;
// @ts-ignore
columns.push({ columns.push({
...actionColumn ...actionColumn,
}); });
} }
@@ -72,34 +73,32 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
if (!isString(columns[0])) { if (!isString(columns[0])) {
columnsRef.value = columns; columnsRef.value = columns;
} else { } else {
const newColumns: any[] = [] const newColumns: any[] = [];
cacheColumns.forEach(item => { cacheColumns.forEach((item) => {
if (columnList.includes(item.key)) { if (columnList.includes(item.key)) {
newColumns.push({ ...item }) newColumns.push({ ...item });
} }
}) });
if (!isEqual(cacheKeys, columns)) { if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => { newColumns.sort((prev, next) => {
return ( return cacheKeys.indexOf(prev.key) - cacheKeys.indexOf(next.key);
cacheKeys.indexOf(prev.key) - cacheKeys.indexOf(next.key)
);
}); });
} }
columnsRef.value = newColumns columnsRef.value = newColumns;
} }
} }
//获取 //获取
function getColumns() { function getColumns() {
let columns = toRaw(unref(getColumnsRef)); const columns = toRaw(unref(getColumnsRef));
return columns.map(item => { return columns.map((item) => {
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined } return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined };
}) });
} }
//获取原始 //获取原始
function getCacheColumns(isKey?: boolean): any[] { function getCacheColumns(isKey?: boolean): any[] {
return isKey ? cacheColumns.map(item => item.key) : cacheColumns; return isKey ? cacheColumns.map((item) => item.key) : cacheColumns;
} }
//更新原始数据单个字段 //更新原始数据单个字段
@@ -121,6 +120,6 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
setCacheColumnsField, setCacheColumnsField,
setColumns, setColumns,
getColumns, getColumns,
getPageColumns getPageColumns,
}; };
} }

View File

@@ -6,12 +6,7 @@ import { APISETTING } from '../const';
export function useDataSource( export function useDataSource(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
{ { getPaginationInfo, setPagination, setLoading, tableData },
getPaginationInfo,
setPagination,
setLoading,
tableData
},
emit emit
) { ) {
const dataSourceRef = ref([]); const dataSourceRef = ref([]);
@@ -33,8 +28,10 @@ export function useDataSource(
const getRowKey = computed(() => { const getRowKey = computed(() => {
const { rowKey }: any = unref(propsRef); const { rowKey }: any = unref(propsRef);
return rowKey ? rowKey : () => { return rowKey
return 'key' ? rowKey
: () => {
return 'key';
}; };
}); });
@@ -52,10 +49,10 @@ export function useDataSource(
const { request, pagination }: any = unref(propsRef); const { request, pagination }: any = unref(propsRef);
//组装分页信息 //组装分页信息
const pageField = APISETTING.pageField const pageField = APISETTING.pageField;
const sizeField = APISETTING.sizeField const sizeField = APISETTING.sizeField;
const totalField = APISETTING.totalField const totalField = APISETTING.totalField;
const listField = APISETTING.listField const listField = APISETTING.listField;
let pageParams = {}; let pageParams = {};
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps; const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
@@ -67,13 +64,13 @@ export function useDataSource(
pageParams[sizeField] = pageSize; pageParams[sizeField] = pageSize;
} }
let params = { const params = {
...pageParams, ...pageParams,
} };
const res = await request(params); const res = await request(params);
const resultTotal = res[totalField] || 0 const resultTotal = res[totalField] || 0;
const currentPage = res[pageField] const currentPage = res[pageField];
// 如果数据异常,需获取正确的页码再次执行 // 如果数据异常,需获取正确的页码再次执行
if (resultTotal) { if (resultTotal) {
@@ -85,7 +82,7 @@ export function useDataSource(
fetch(opt); fetch(opt);
} }
} }
let resultInfo = res[listField] ? res[listField] : [] const resultInfo = res[listField] ? res[listField] : [];
dataSourceRef.value = resultInfo; dataSourceRef.value = resultInfo;
setPagination({ setPagination({
[pageField]: currentPage, [pageField]: currentPage,
@@ -98,10 +95,10 @@ export function useDataSource(
} }
emit('fetch-success', { emit('fetch-success', {
items: unref(resultInfo), items: unref(resultInfo),
resultTotal resultTotal,
}); });
} catch (error) { } catch (error) {
console.error(error) console.error(error);
emit('fetch-error', error); emit('fetch-error', error);
dataSourceRef.value = []; dataSourceRef.value = [];
// setPagination({ // setPagination({
@@ -115,7 +112,7 @@ export function useDataSource(
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
fetch(); fetch();
}, 16) }, 16);
}); });
function setTableData(values) { function setTableData(values) {
@@ -136,6 +133,6 @@ export function useDataSource(
getDataSourceRef, getDataSourceRef,
getDataSource, getDataSource,
setTableData, setTableData,
reload reload,
} };
} }

View File

@@ -1,5 +1,5 @@
import type { PropType } from 'vue' import type { PropType } from 'vue';
import { BasicColumn } from './types/table' import { BasicColumn } from './types/table';
export const basicProps = { export const basicProps = {
title: { title: {
@@ -16,8 +16,7 @@ export const basicProps = {
}, },
tableData: { tableData: {
type: [Object], type: [Object],
default: () => { default: () => {},
},
}, },
columns: { columns: {
type: [Array] as PropType<BasicColumn[]>, type: [Array] as PropType<BasicColumn[]>,
@@ -27,7 +26,7 @@ export const basicProps = {
request: { request: {
type: Function as PropType<(...arg: any[]) => Promise<any>>, type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null, default: null,
required: true required: true,
}, },
rowKey: { rowKey: {
type: [String, Function] as PropType<string | ((record) => string)>, type: [String, Function] as PropType<string | ((record) => string)>,
@@ -35,15 +34,14 @@ export const basicProps = {
}, },
pagination: { pagination: {
type: [Object, Boolean], type: [Object, Boolean],
default: () => { default: () => {},
}
}, },
showPagination: { showPagination: {
type: [String, Boolean], type: [String, Boolean],
default: 'auto' default: 'auto',
}, },
actionColumn: { actionColumn: {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
}, },
} };

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { NButton, NTooltip } from 'naive-ui'; // @ts-ignore
import { RoleEnum } from '/@/enums/roleEnum'; import { NButton } from 'naive-ui';
import { RoleEnum } from '@/enums/roleEnum';
// @ts-ignore
export interface ActionItem extends NButton.props { export interface ActionItem extends NButton.props {
onClick?: Fn; onClick?: Fn;
label?: string; label?: string;
@@ -13,7 +14,6 @@ export interface ActionItem extends NButton.props {
auth?: RoleEnum | RoleEnum[] | string | string[]; auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示 // 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean); ifShow?: boolean | ((action: ActionItem) => boolean);
tooltip?: string | TooltipProps;
} }
export interface PopConfirm { export interface PopConfirm {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ export enum ResultEnum {
SUCCESS = 200, SUCCESS = 200,
ERROR = -1, ERROR = -1,
TIMEOUT = 10042, TIMEOUT = 10042,
TYPE = 'success' TYPE = 'success',
} }
/** /**
@@ -16,7 +16,7 @@ export enum RequestEnum {
POST = 'POST', POST = 'POST',
PATCH = 'PATCH', PATCH = 'PATCH',
PUT = 'PUT', PUT = 'PUT',
DELETE = 'DELETE' DELETE = 'DELETE',
} }
/** /**
@@ -30,5 +30,5 @@ export enum ContentTypeEnum {
// form-data 一般配合qs // form-data 一般配合qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data 上传 // form-data 上传
FORM_DATA = 'multipart/form-data;charset=UTF-8' FORM_DATA = 'multipart/form-data;charset=UTF-8',
} }

View File

@@ -3,5 +3,5 @@ export enum RoleEnum {
ADMIN = 'admin', ADMIN = 'admin',
// 普通用户 // 普通用户
NORMAL = 'normal' NORMAL = 'normal',
} }

View File

@@ -23,7 +23,7 @@ export function useEventListener({
autoRemove = true, autoRemove = true,
isDebounce = true, isDebounce = true,
wait = 80, wait = 80,
}: UseEventParams): { removeEvent: RemoveEventFn } { }: UseEventParams): { removeEvent: RemoveEventFn } {
/* eslint-disable-next-line */ /* eslint-disable-next-line */
let remove: RemoveEventFn = () => { let remove: RemoveEventFn = () => {
}; };

View File

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

View File

@@ -11,7 +11,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
VITE_GLOB_API_URL_PREFIX, VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK, VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL VITE_GLOB_IMG_URL,
} = getAppEnvConfig(); } = getAppEnvConfig();
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
@@ -28,8 +28,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
urlPrefix: VITE_GLOB_API_URL_PREFIX, urlPrefix: VITE_GLOB_API_URL_PREFIX,
uploadUrl: VITE_GLOB_UPLOAD_URL, uploadUrl: VITE_GLOB_UPLOAD_URL,
prodMock: VITE_GLOB_PROD_MOCK, prodMock: VITE_GLOB_PROD_MOCK,
imgUrl: VITE_GLOB_IMG_URL imgUrl: VITE_GLOB_IMG_URL,
}; };
return glob as Readonly<GlobConfig>; return glob as Readonly<GlobConfig>;
}; };

View File

@@ -13,7 +13,6 @@ export function useDesignSetting() {
return { return {
getDarkTheme, getDarkTheme,
getAppTheme, getAppTheme,
getAppThemeList getAppThemeList,
} };
} }

View File

@@ -2,7 +2,6 @@ import { computed } from 'vue';
import { useProjectSettingStore } from '@/store/modules/projectSetting'; import { useProjectSettingStore } from '@/store/modules/projectSetting';
export function useProjectSetting() { export function useProjectSetting() {
const projectStore = useProjectSettingStore(); const projectStore = useProjectSettingStore();
const getNavMode = computed(() => projectStore.navMode); const getNavMode = computed(() => projectStore.navMode);
@@ -29,6 +28,6 @@ export function useProjectSetting() {
getMenuSetting, getMenuSetting,
getCrumbsSetting, getCrumbsSetting,
getPermissionMode, getPermissionMode,
getShowFooter getShowFooter,
} };
} }

View File

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

View File

@@ -1,23 +1,23 @@
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue';
import { debounce } from 'lodash' import { debounce } from 'lodash';
/** /**
* description: 获取页面宽度 * description: 获取页面宽度
*/ */
export function useDomWidth() { export function useDomWidth() {
const domWidth = ref(window.innerWidth) const domWidth = ref(window.innerWidth);
function resize() { function resize() {
domWidth.value = document.body.clientWidth domWidth.value = document.body.clientWidth;
} }
onMounted(() => { onMounted(() => {
window.addEventListener('resize', debounce(resize, 80)) window.addEventListener('resize', debounce(resize, 80));
}) });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resize) 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 用户网络是否可用 * @description 用户网络是否可用
* */ * */
export function useOnline() { export function useOnline() {
const online = ref(true) const online = ref(true);
const showStatus = (val) => { const showStatus = (val) => {
online.value = typeof val == 'boolean' ? val : val.target.online online.value = typeof val == 'boolean' ? val : val.target.online;
} };
// 在页面加载后,设置正确的网络状态 // 在页面加载后,设置正确的网络状态
navigator.onLine ? showStatus(true) : showStatus(false) navigator.onLine ? showStatus(true) : showStatus(false);
onMounted(() => { onMounted(() => {
// 开始监听网络状态的变化 // 开始监听网络状态的变化
window.addEventListener('online', showStatus) window.addEventListener('online', showStatus);
window.addEventListener('offline', showStatus) window.addEventListener('offline', showStatus);
}) });
onUnmounted(() => { onUnmounted(() => {
// 移除监听网络状态的变化 // 移除监听网络状态的变化
window.removeEventListener('online', showStatus) window.removeEventListener('online', showStatus);
window.removeEventListener('offline', showStatus) window.removeEventListener('offline', showStatus);
}) });
return { online } return { online };
} }

View File

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

View File

@@ -12,18 +12,16 @@ import echarts from '@/utils/lib/echarts';
// import { useRootSetting } from '@/hooks/setting/useRootSetting'; // import { useRootSetting } from '@/hooks/setting/useRootSetting';
export function useECharts( export function useECharts(
elRef: Ref<HTMLDivElement>, elRef: Ref<HTMLDivElement>,
theme: 'light' | 'dark' | 'default' = 'light' theme: 'light' | 'dark' | 'default' = 'light'
) { ) {
// const { getDarkMode } = useRootSetting(); // const { getDarkMode } = useRootSetting();
const getDarkMode = 'light' const getDarkMode = 'light';
let chartInstance: echarts.ECharts | null = null; let chartInstance: echarts.ECharts | null = null;
let resizeFn: Fn = resize; let resizeFn: Fn = resize;
const cacheOptions = ref<EChartsOption>({}); const cacheOptions = ref<EChartsOption>({});
let removeResizeFn: Fn = () => { let removeResizeFn: Fn = () => {};
};
resizeFn = useDebounceFn(resize, 200); resizeFn = useDebounceFn(resize, 200);

View File

@@ -1,4 +1,4 @@
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user';
export function usePermission() { export function usePermission() {
const userStore = useUserStore(); const userStore = useUserStore();
@@ -8,10 +8,10 @@ export function usePermission() {
* @param accesses * @param accesses
*/ */
function _someRoles(accesses: string[]) { function _someRoles(accesses: string[]) {
return userStore.getRoles.some(item => { return userStore.getRoles.some((item) => {
const { value }: any = item const { value }: any = item;
return accesses.includes(value) return accesses.includes(value);
}) });
} }
/** /**
@@ -19,8 +19,8 @@ export function usePermission() {
* 可用于 v-if 显示逻辑 * 可用于 v-if 显示逻辑
* */ * */
function hasPermission(accesses: string[]): boolean { function hasPermission(accesses: string[]): boolean {
if (!accesses ||!accesses.length) return true if (!accesses || !accesses.length) return true;
return _someRoles(accesses) return _someRoles(accesses);
} }
/** /**
@@ -28,11 +28,11 @@ export function usePermission() {
* @param accesses * @param accesses
*/ */
function hasEveryPermission(accesses: string[]): boolean { function hasEveryPermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles const rolesList = userStore.getRoles;
if (Array.isArray(accesses)) { if (Array.isArray(accesses)) {
return accesses.every((access) => !!rolesList[access]) return accesses.every((access) => !!rolesList[access]);
} }
throw new Error(`[hasEveryPermission]: ${ accesses } should be a array !`) throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
} }
/** /**
@@ -41,11 +41,11 @@ export function usePermission() {
* @param accessMap * @param accessMap
*/ */
function hasSomePermission(accesses: string[]): boolean { function hasSomePermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles const rolesList = userStore.getRoles;
if (Array.isArray(accesses)) { if (Array.isArray(accesses)) {
return accesses.some((access) => !!rolesList[access]) return accesses.some((access) => !!rolesList[access]);
} }
throw new Error(`[hasSomePermission]: ${ accesses } should be a array !`) 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,39 +1,28 @@
<template> <template>
<div class="page-footer"> <div class="page-footer">
<div class="page-footer-link"> <div class="page-footer-link">
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> <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> <a href="https://github.com/jekip/naive-ui-admin/issues" 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>
<div class="copyright"> <div class="copyright"> naive-ui-admin 1.4 · Made by Ah jung </div>
naive-ui-admin 1.4 · Made by Ah jung
</div> </div>
</div>
</template> </template>
<script> <script>
import { GithubOutlined, CopyrightOutlined } from '@vicons/antd' export default {
export default {
name: 'PageFooter', name: 'PageFooter',
components: { GithubOutlined, CopyrightOutlined }, components: {},
props: { props: {
collapsed: { collapsed: {
type: Boolean type: Boolean,
} },
} },
} };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.page-footer { .page-footer {
margin: 48px 0 24px 0; margin: 48px 0 24px 0;
padding: 0 16px; padding: 0 16px;
text-align: center; text-align: center;
@@ -41,8 +30,8 @@ export default {
a { a {
font-size: 14px; font-size: 14px;
color: #808695; color: #808695;
-webkit-transition: all .2s ease-in-out; -webkit-transition: all 0.2s ease-in-out;
transition: all .2s ease-in-out; transition: all 0.2s ease-in-out;
&:hover { &:hover {
color: #515a6e; color: #515a6e;
@@ -63,5 +52,5 @@ export default {
color: #808695; color: #808695;
font-size: 14px; font-size: 14px;
} }
} }
</style> </style>

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,23 @@
<template> <template>
<div class="logo"> <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> <h2 v-show="!collapsed" class="title">&nbsp;NaiveUiAdmin</h2>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Index', name: 'Index',
props: { props: {
collapsed: { collapsed: {
type: Boolean type: Boolean,
} },
} },
} };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.logo { .logo {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -35,5 +35,5 @@ export default {
color: white; color: white;
margin-bottom: 0; margin-bottom: 0;
} }
} }
</style> </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 }"> <template #default="{ Component, route }">
<transition name="zoom-fade" mode="out-in" appear> <transition name="zoom-fade" mode="out-in" appear>
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents"> <keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath"/> <component :is="Component" :key="route.fullPath" />
</keep-alive> </keep-alive>
<component v-else :is="Component" :key="route.fullPath"/> <component v-else :is="Component" :key="route.fullPath" />
</transition> </transition>
</template> </template>
</RouterView> </RouterView>
</template> </template>
<script> <script>
import { defineComponent, computed } from 'vue' import { defineComponent, computed } from 'vue';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute' import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
export default defineComponent({ export default defineComponent({
name: 'MainView', name: 'MainView',
components: {}, components: {},
props: { props: {
notNeedKey: { notNeedKey: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
animate: { animate: {
type: Boolean, type: Boolean,
default: true default: true,
} },
}, },
setup() { setup() {
const asyncRouteStore = useAsyncRouteStore() const asyncRouteStore = useAsyncRouteStore();
// 需要缓存的路由组件 // 需要缓存的路由组件
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents) const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
return { return {
keepAliveComponents keepAliveComponents,
} };
} },
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped></style>
</style>

View File

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

View File

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

View File

@@ -5,9 +5,9 @@ import {
VerticalRightOutlined, VerticalRightOutlined,
VerticalLeftOutlined, VerticalLeftOutlined,
ColumnWidthOutlined, ColumnWidthOutlined,
MinusOutlined MinusOutlined,
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue';
import { Dropdown, Tabs, Card } from 'ant-design-vue' import { Dropdown, Tabs, Card } from 'ant-design-vue';
export default { export default {
[Tabs.name]: Tabs, [Tabs.name]: Tabs,
@@ -20,5 +20,5 @@ export default {
CloseOutlined, CloseOutlined,
VerticalRightOutlined, VerticalRightOutlined,
VerticalLeftOutlined, VerticalLeftOutlined,
ColumnWidthOutlined ColumnWidthOutlined,
} };

View File

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

View File

@@ -1,33 +1,49 @@
<template> <template>
<div class="tabs-view" <div
class="tabs-view"
:class="{ :class="{
'tabs-view-fix':multiTabsSetting.fixed, 'tabs-view-fix': multiTabsSetting.fixed,
'tabs-view-fixed-header':isMultiHeaderFixed, 'tabs-view-fixed-header': isMultiHeaderFixed,
'tabs-view-default-background':getDarkTheme === false 'tabs-view-default-background': getDarkTheme === false,
}" }"
:style="getChangeStyle"> :style="getChangeStyle"
>
<div class="tabs-view-main"> <div class="tabs-view-main">
<div ref="navWrap" class="tabs-card" :class="{'tabs-card-scrollable': scrollable }"> <div ref="navWrap" class="tabs-card" :class="{ 'tabs-card-scrollable': scrollable }">
<span class="tabs-card-prev" :class="{'tabs-card-prev-hide': !scrollable }" @click="scrollPrev"> <span
class="tabs-card-prev"
:class="{ 'tabs-card-prev-hide': !scrollable }"
@click="scrollPrev"
>
<n-icon size="16" color="#515a6e"> <n-icon size="16" color="#515a6e">
<LeftOutlined/> <LeftOutlined />
</n-icon> </n-icon>
</span> </span>
<span class="tabs-card-next" :class="{'tabs-card-next-hide': !scrollable }" @click="scrollNext"> <span
class="tabs-card-next"
:class="{ 'tabs-card-next-hide': !scrollable }"
@click="scrollNext"
>
<n-icon size="16" color="#515a6e"> <n-icon size="16" color="#515a6e">
<RightOutlined/> <RightOutlined />
</n-icon> </n-icon>
</span> </span>
<div ref="navScroll" class="tabs-card-scroll"> <div ref="navScroll" class="tabs-card-scroll">
<div ref="navRef" class="tabs-card-nav" :style="getNavStyle"> <div ref="navRef" class="tabs-card-nav" :style="getNavStyle">
<Draggable :list="tabsList" animation="300" item-key="fullPath" class="flex"> <Draggable :list="tabsList" animation="300" item-key="fullPath" class="flex">
<template #item="{element}"> <template #item="{ element }">
<div class="tabs-card-scroll-item" <div
:class="{'active-item':activeKey === element.path }" class="tabs-card-scroll-item"
:class="{ 'active-item': activeKey === element.path }"
@click.stop="goPage(element)" @click.stop="goPage(element)"
@contextmenu="handleContextMenu($event,element)"> @contextmenu="handleContextMenu($event, element)"
>
<span>{{ element.meta.title }}</span> <span>{{ element.meta.title }}</span>
<n-icon size="14" @click.stop="closeTabItem(element)" v-if="element.path != baseHome"> <n-icon
size="14"
@click.stop="closeTabItem(element)"
v-if="element.path != baseHome"
>
<CloseOutlined /> <CloseOutlined />
</n-icon> </n-icon>
</div> </div>
@@ -37,22 +53,34 @@
</div> </div>
</div> </div>
<div class="tabs-close"> <div class="tabs-close">
<n-dropdown trigger="hover" @select="closeHandleSelect" placement="bottom-end" :options="TabsMenuOptions"> <n-dropdown
trigger="hover"
@select="closeHandleSelect"
placement="bottom-end"
:options="TabsMenuOptions"
>
<div class="tabs-close-btn" @click.prevent> <div class="tabs-close-btn" @click.prevent>
<n-icon size="16" color="#515a6e"> <n-icon size="16" color="#515a6e">
<DownOutlined/> <DownOutlined />
</n-icon> </n-icon>
</div> </div>
</n-dropdown> </n-dropdown>
</div> </div>
<n-dropdown :show="showDropdown" :x="dropdownX" :y="dropdownY" @clickoutside="onClickOutside" <n-dropdown
placement="bottom-start" @select="closeHandleSelect" :options="TabsMenuOptions"/> :show="showDropdown"
:x="dropdownX"
:y="dropdownY"
@clickoutside="onClickOutside"
placement="bottom-start"
@select="closeHandleSelect"
:options="TabsMenuOptions"
/>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import {
defineComponent, defineComponent,
reactive, reactive,
computed, computed,
@@ -63,35 +91,20 @@ import {
provide, provide,
watch, watch,
onMounted, onMounted,
nextTick nextTick,
} from 'vue' } from 'vue';
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router';
import { storage } from '@/utils/Storage' import { storage } from '@/utils/Storage';
import { TABS_ROUTES } from '@/store/mutation-types' import { TABS_ROUTES } from '@/store/mutation-types';
import { useTabsViewStore } from '@/store/modules/tabsView' import { useTabsViewStore } from '@/store/modules/tabsView';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute' import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { RouteItem } from '@/store/modules/tabsView' import { RouteItem } from '@/store/modules/tabsView';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting' import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui';
// @ts-ignore // @ts-ignore
import Draggable from 'vuedraggable/src/vuedraggable' import Draggable from 'vuedraggable/src/vuedraggable';
import { PageEnum } from '@/enums/pageEnum' import { PageEnum } from '@/enums/pageEnum';
import { import {
DownOutlined,
ReloadOutlined,
CloseOutlined,
ColumnWidthOutlined,
MinusOutlined,
LeftOutlined,
RightOutlined
} from '@vicons/antd'
import { renderIcon } from '@/utils/index'
import elementResizeDetectorMaker from 'element-resize-detector'
import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
export default defineComponent({
name: 'TabsView',
components: {
DownOutlined, DownOutlined,
ReloadOutlined, ReloadOutlined,
CloseOutlined, CloseOutlined,
@@ -99,325 +112,346 @@ export default defineComponent({
MinusOutlined, MinusOutlined,
LeftOutlined, LeftOutlined,
RightOutlined, RightOutlined,
Draggable } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
import elementResizeDetectorMaker from 'element-resize-detector';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
export default defineComponent({
name: 'TabsView',
components: {
DownOutlined,
CloseOutlined,
LeftOutlined,
RightOutlined,
Draggable,
}, },
props: { props: {
collapsed: { collapsed: {
type: Boolean type: Boolean,
} },
}, },
setup(props) { setup(props) {
const { getDarkTheme } = useDesignSetting() const { getDarkTheme } = useDesignSetting();
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } = useProjectSetting() const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } =
useProjectSetting();
const message = useMessage() const message = useMessage();
const route = useRoute() const route = useRoute();
const router = useRouter() const router = useRouter();
const tabsViewStore = useTabsViewStore() const tabsViewStore = useTabsViewStore();
const asyncRouteStore = useAsyncRouteStore() const asyncRouteStore = useAsyncRouteStore();
const navRef: any = ref(null) const navRef: any = ref(null);
const navScroll: any = ref(null) const navScroll: any = ref(null);
const navWrap: any = ref(null) const navWrap: any = ref(null);
const isCurrent = ref(false) const isCurrent = ref(false);
const state = reactive({ const state = reactive({
activeKey: route.fullPath, activeKey: route.fullPath,
scrollable: false, scrollable: false,
navStyle: { navStyle: {
transform: '' transform: '',
}, },
dropdownX: 0, dropdownX: 0,
dropdownY: 0, dropdownY: 0,
showDropdown: false, showDropdown: false,
isMultiHeaderFixed: false, isMultiHeaderFixed: false,
multiTabsSetting: getMultiTabsSetting, multiTabsSetting: getMultiTabsSetting,
}) });
// 获取简易的路由对象 // 获取简易的路由对象
const getSimpleRoute = (route): RouteItem => { const getSimpleRoute = (route): RouteItem => {
const { fullPath, hash, meta, name, params, path, query } = route const { fullPath, hash, meta, name, params, path, query } = route;
return { fullPath, hash, meta, name, params, path, query } return { fullPath, hash, meta, name, params, path, query };
} };
//动态组装样式 菜单缩进 //动态组装样式 菜单缩进
const getChangeStyle = computed(() => { const getChangeStyle = computed(() => {
const { collapsed } = props const { collapsed } = props;
const navMode = unref(getNavMode) const navMode = unref(getNavMode);
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting) const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
const { fixed }: any = unref(getMultiTabsSetting) const { fixed }: any = unref(getMultiTabsSetting);
let lenNum = navMode === 'horizontal' ? '0px' : collapsed ? `${ minMenuWidth }px` : `${ menuWidth }px` let lenNum =
navMode === 'horizontal' ? '0px' : collapsed ? `${minMenuWidth}px` : `${menuWidth}px`;
return { return {
left: lenNum, left: lenNum,
width: `calc(100% - ${ !fixed ? '0px' : lenNum })` width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
} };
}) });
//tags 右侧下拉菜单 //tags 右侧下拉菜单
const TabsMenuOptions = computed(() => { const TabsMenuOptions = computed(() => {
const isDisabled = unref(tabsList).length <= 1 ? true : false const isDisabled = unref(tabsList).length <= 1 ? true : false;
return [ return [
{ {
label: '刷新当前', label: '刷新当前',
key: '1', key: '1',
icon: renderIcon(ReloadOutlined) icon: renderIcon(ReloadOutlined),
}, },
{ {
label: `关闭当前`, label: `关闭当前`,
key: '2', key: '2',
disabled: unref(isCurrent) || isDisabled, disabled: unref(isCurrent) || isDisabled,
icon: renderIcon(CloseOutlined) icon: renderIcon(CloseOutlined),
}, },
{ {
label: '关闭其他', label: '关闭其他',
key: '3', key: '3',
disabled:isDisabled, disabled: isDisabled,
icon: renderIcon(ColumnWidthOutlined) icon: renderIcon(ColumnWidthOutlined),
}, },
{ {
label: '关闭全部', label: '关闭全部',
key: '4', key: '4',
disabled:isDisabled, disabled: isDisabled,
icon: renderIcon(MinusOutlined) icon: renderIcon(MinusOutlined),
} },
] ];
}) });
let routes: RouteItem[] = [] let routes: RouteItem[] = [];
try { try {
const routesStr = storage.get(TABS_ROUTES) as string | null | undefined const routesStr = storage.get(TABS_ROUTES) as string | null | undefined;
routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)] routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)];
} catch (e) { } catch (e) {
routes = [getSimpleRoute(route)] routes = [getSimpleRoute(route)];
} }
// 初始化标签页 // 初始化标签页
tabsViewStore.initTabs(routes) tabsViewStore.initTabs(routes);
//监听滚动条 //监听滚动条
function onScroll(e) { function onScroll(e) {
let scrollTop = e.target.scrollTop || (document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop); // 滚动条偏移量 let scrollTop =
e.target.scrollTop ||
document.documentElement.scrollTop ||
window.pageYOffset ||
document.body.scrollTop; // 滚动条偏移量
if (!getHeaderSetting.fixed && getMultiTabsSetting.fixed && scrollTop >= 64) { if (!getHeaderSetting.fixed && getMultiTabsSetting.fixed && scrollTop >= 64) {
state.isMultiHeaderFixed = true state.isMultiHeaderFixed = true;
} else { } else {
state.isMultiHeaderFixed = false state.isMultiHeaderFixed = false;
} }
} }
window.addEventListener('scroll', onScroll, true) window.addEventListener('scroll', onScroll, true);
// 移除缓存组件名称 // 移除缓存组件名称
const delKeepAliveCompName = () => { const delKeepAliveCompName = () => {
if (route.meta.keepAlive) { if (route.meta.keepAlive) {
const name = router.currentRoute.value.matched.find((item) => item.name == route.name) const name = router.currentRoute.value.matched.find((item) => item.name == route.name)
?.components?.default.name ?.components?.default.name;
if (name) { if (name) {
asyncRouteStore.keepAliveComponents = asyncRouteStore.keepAliveComponents.filter( asyncRouteStore.keepAliveComponents = asyncRouteStore.keepAliveComponents.filter(
(item) => item != name (item) => item != name
) );
}
} }
} }
};
// 标签页列表 // 标签页列表
const tabsList: any = computed(() => tabsViewStore.tabsList) const tabsList: any = computed(() => tabsViewStore.tabsList);
const whiteList: string[] = [PageEnum.BASE_LOGIN_NAME, PageEnum.REDIRECT_NAME, PageEnum.ERROR_PAGE_NAME] const whiteList: string[] = [
PageEnum.BASE_LOGIN_NAME,
PageEnum.REDIRECT_NAME,
PageEnum.ERROR_PAGE_NAME,
];
watch( watch(
() => route.fullPath, () => route.fullPath,
(to) => { (to) => {
if (whiteList.includes(route.name as string)) return if (whiteList.includes(route.name as string)) return;
state.activeKey = to state.activeKey = to;
tabsViewStore.addTabs(getSimpleRoute(route)) tabsViewStore.addTabs(getSimpleRoute(route));
updateNavScroll() updateNavScroll();
}, },
{ immediate: true } { immediate: true }
) );
// 在页面关闭或刷新之前,保存数据 // 在页面关闭或刷新之前,保存数据
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
storage.set(TABS_ROUTES, JSON.stringify(tabsList.value)) storage.set(TABS_ROUTES, JSON.stringify(tabsList.value));
}) });
// 关闭当前页面 // 关闭当前页面
const removeTab = (route) => { const removeTab = (route) => {
if (tabsList.value.length === 1) { if (tabsList.value.length === 1) {
return message.warning('这已经是最后一页,不能再关闭了!') return message.warning('这已经是最后一页,不能再关闭了!');
} }
delKeepAliveCompName() delKeepAliveCompName();
tabsViewStore.closeCurrentTab(route) tabsViewStore.closeCurrentTab(route);
// 如果关闭的是当前页 // 如果关闭的是当前页
if (state.activeKey === route.fullPath) { if (state.activeKey === route.fullPath) {
const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)] const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)];
state.activeKey = currentRoute.fullPath state.activeKey = currentRoute.fullPath;
router.push(currentRoute) router.push(currentRoute);
}
updateNavScroll()
} }
updateNavScroll();
};
// 刷新页面 // 刷新页面
const reloadPage = () => { const reloadPage = () => {
delKeepAliveCompName() delKeepAliveCompName();
router.push({ router.push({
path: '/redirect' + unref(route).fullPath path: '/redirect' + unref(route).fullPath,
}) });
} };
// 注入刷新页面方法 // 注入刷新页面方法
provide('reloadPage', reloadPage) provide('reloadPage', reloadPage);
// 关闭左侧 // 关闭左侧
const closeLeft = (route) => { const closeLeft = (route) => {
tabsViewStore.closeLeftTabs(route) tabsViewStore.closeLeftTabs(route);
state.activeKey = route.fullPath state.activeKey = route.fullPath;
router.replace(route.fullPath) router.replace(route.fullPath);
updateNavScroll() updateNavScroll();
} };
// 关闭右侧 // 关闭右侧
const closeRight = (route) => { const closeRight = (route) => {
tabsViewStore.closeRightTabs(route) tabsViewStore.closeRightTabs(route);
state.activeKey = route.fullPath state.activeKey = route.fullPath;
router.replace(route.fullPath) router.replace(route.fullPath);
updateNavScroll() updateNavScroll();
} };
// 关闭其他 // 关闭其他
const closeOther = (route) => { const closeOther = (route) => {
tabsViewStore.closeOtherTabs(route) tabsViewStore.closeOtherTabs(route);
state.activeKey = route.fullPath state.activeKey = route.fullPath;
router.replace(route.fullPath) router.replace(route.fullPath);
updateNavScroll() updateNavScroll();
} };
// 关闭全部 // 关闭全部
const closeAll = () => { const closeAll = () => {
localStorage.removeItem('routes') localStorage.removeItem('routes');
tabsViewStore.closeAllTabs() tabsViewStore.closeAllTabs();
router.replace(PageEnum.BASE_HOME) router.replace(PageEnum.BASE_HOME);
updateNavScroll() updateNavScroll();
} };
//tab 操作 //tab 操作
const closeHandleSelect = (key) => { const closeHandleSelect = (key) => {
switch (key) { switch (key) {
//刷新 //刷新
case '1': case '1':
reloadPage() reloadPage();
break break;
//关闭 //关闭
case '2': case '2':
removeTab(route) removeTab(route);
break break;
//关闭其他 //关闭其他
case '3': case '3':
closeOther(route) closeOther(route);
break break;
//关闭所有 //关闭所有
case '4': case '4':
closeAll() closeAll();
break break;
}
updateNavScroll()
} }
updateNavScroll();
};
function getCurrentScrollOffset() { function getCurrentScrollOffset() {
const { navStyle } = state const { navStyle } = state;
const transform: any = toRaw(navStyle.transform) const transform: any = toRaw(navStyle.transform);
return transform ? Number(transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) : 0 return transform ? Number(transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) : 0;
} }
function setOffset(value) { function setOffset(value) {
state.navStyle.transform = `translateX(-${ value }px)` state.navStyle.transform = `translateX(-${value}px)`;
} }
function scrollPrev() { function scrollPrev() {
const containerWidth = navScroll.value.offsetWidth const containerWidth = navScroll.value.offsetWidth;
const currentOffset = getCurrentScrollOffset() const currentOffset = getCurrentScrollOffset();
if (!currentOffset) return if (!currentOffset) return;
let newOffset = currentOffset > containerWidth ? currentOffset - containerWidth : 0 let newOffset = currentOffset > containerWidth ? currentOffset - containerWidth : 0;
setOffset(newOffset) setOffset(newOffset);
} }
function scrollNext() { function scrollNext() {
const navWidth = navRef.value.scrollWidth const navWidth = navRef.value.scrollWidth;
const containerWidth = navScroll.value.offsetWidth const containerWidth = navScroll.value.offsetWidth;
const currentOffset = getCurrentScrollOffset() const currentOffset = getCurrentScrollOffset();
if (navWidth - currentOffset <= containerWidth) return if (navWidth - currentOffset <= containerWidth) return;
let newOffset = let newOffset =
navWidth - currentOffset > containerWidth * 2 navWidth - currentOffset > containerWidth * 2
? currentOffset + containerWidth ? currentOffset + containerWidth
: navWidth - containerWidth : navWidth - containerWidth;
setOffset(newOffset) setOffset(newOffset);
} }
function updateNavScroll() { function updateNavScroll() {
if (!navRef.value) return if (!navRef.value) return;
let navWidth = navRef.value.scrollWidth let navWidth = navRef.value.scrollWidth;
let containerWidth = navScroll.value.offsetWidth let containerWidth = navScroll.value.offsetWidth;
const currentOffset = getCurrentScrollOffset() const currentOffset = getCurrentScrollOffset();
if (containerWidth < navWidth) { if (containerWidth < navWidth) {
state.scrollable = true state.scrollable = true;
if (navWidth - currentOffset < containerWidth) { if (navWidth - currentOffset < containerWidth) {
setOffset(navWidth - containerWidth) setOffset(navWidth - containerWidth);
} }
} else { } else {
state.scrollable = false state.scrollable = false;
if (currentOffset > 0) { if (currentOffset > 0) {
setOffset(0) setOffset(0);
} }
} }
} }
function handleResize() { function handleResize() {
updateNavScroll() updateNavScroll();
} }
const getNavStyle = computed(() => { const getNavStyle = computed(() => {
return state.navStyle return state.navStyle;
}) });
function handleContextMenu(e,item) { function handleContextMenu(e, item) {
e.preventDefault() e.preventDefault();
isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path;
state.showDropdown = false state.showDropdown = false;
nextTick().then(() => { nextTick().then(() => {
state.showDropdown = true state.showDropdown = true;
state.dropdownX = e.clientX state.dropdownX = e.clientX;
state.dropdownY = e.clientY state.dropdownY = e.clientY;
}) });
} }
function onClickOutside() { function onClickOutside() {
state.showDropdown = false state.showDropdown = false;
} }
//tags 跳转页面 //tags 跳转页面
function goPage(e) { function goPage(e) {
const { fullPath } = e const { fullPath } = e;
if (fullPath === route.fullPath) return if (fullPath === route.fullPath) return;
state.activeKey = fullPath state.activeKey = fullPath;
router.push({ path: fullPath }) router.push({ path: fullPath });
} }
//删除tab //删除tab
function closeTabItem(e) { function closeTabItem(e) {
const { fullPath } = e const { fullPath } = e;
const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath) const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath);
removeTab(routeInfo) removeTab(routeInfo);
} }
onMounted(() => { onMounted(() => {
onElementResize() onElementResize();
}) });
function onElementResize() { function onElementResize() {
let observer let observer;
observer = elementResizeDetectorMaker() observer = elementResizeDetectorMaker();
observer.listenTo(navWrap.value, handleResize) observer.listenTo(navWrap.value, handleResize);
} }
return { return {
@@ -427,7 +461,7 @@ export default defineComponent({
navScroll, navScroll,
route, route,
tabsList, tabsList,
baseHome:PageEnum.BASE_HOME_REDIRECT, baseHome: PageEnum.BASE_HOME_REDIRECT,
goPage, goPage,
closeTabItem, closeTabItem,
closeLeft, closeLeft,
@@ -443,14 +477,14 @@ export default defineComponent({
getNavStyle, getNavStyle,
handleContextMenu, handleContextMenu,
onClickOutside, onClickOutside,
getDarkTheme getDarkTheme,
} };
} },
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.tabs-view { .tabs-view {
width: 100%; width: 100%;
padding: 6px 0; padding: 6px 0;
display: flex; display: flex;
@@ -554,7 +588,7 @@ export default defineComponent({
} }
.active-item { .active-item {
color: #2d8cf0 color: #2d8cf0;
} }
} }
} }
@@ -584,20 +618,20 @@ export default defineComponent({
justify-content: center; justify-content: center;
} }
} }
} }
.tabs-view-default-background { .tabs-view-default-background {
background: #f5f7f9; background: #f5f7f9;
} }
.tabs-view-fix { .tabs-view-fix {
position: fixed; position: fixed;
z-index: 5; z-index: 5;
padding: 6px 19px 6px 10px; padding: 6px 19px 6px 10px;
left: 200px; left: 200px;
} }
.tabs-view-fixed-header { .tabs-view-fixed-header {
top: 0; top: 0;
} }
</style> </style>

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { App } from 'vue' import { App } from 'vue';
import { permission } from '@/directives/permission' import { permission } from '@/directives/permission';
/** /**
* 注册全局自定义指令 * 注册全局自定义指令
@@ -8,5 +8,5 @@ import { permission } from '@/directives/permission'
*/ */
export function setupDirectives(app: 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 * @param app
*/ */
export function setupGlobalMethods(app: App) { export function setupGlobalMethods() {}
}

View File

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

View File

@@ -1,4 +1,4 @@
import type { App } from 'vue' import type { App } from 'vue';
import { import {
create, create,
NConfigProvider, NConfigProvider,
@@ -62,8 +62,8 @@ import {
NModal, NModal,
NUpload, NUpload,
NTree, NTree,
NSpin NSpin,
} from 'naive-ui' } from 'naive-ui';
const naive = create({ const naive = create({
components: [ components: [
@@ -128,10 +128,10 @@ const naive = create({
NModal, NModal,
NUpload, NUpload,
NTree, NTree,
NSpin NSpin,
] ],
}) });
export function setupNaive(app: App<Element>) { export function setupNaive(app: App<Element>) {
app.use(naive) app.use(naive);
} }

View File

@@ -3,4 +3,3 @@ export const RedirectName = 'Redirect';
export const ErrorPage = () => import('@/views/exception/404.vue'); export const ErrorPage = () => import('@/views/exception/404.vue');
export const Layout = () => import('@/layout/index.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 { renderIcon } from '@/utils/index' import { DashboardOutlined } from '@vicons/antd';
import { DashboardOutlined } from '@vicons/antd'
// import { RouterTransition } from '@/components/transition' // import { RouterTransition } from '@/components/transition'
//前端路由映射表 //前端路由映射表
export const constantRouterComponents = { export const constantRouterComponents = {
'Layout': () => import('@/layout/index.vue'), //布局 Layout: () => import('@/layout/index.vue'), //布局
'DashboardConsole': () => import('@/views/dashboard/console/console.vue'), // 主控台 DashboardConsole: () => import('@/views/dashboard/console/console.vue'), // 主控台
'DashboardMonitor': () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页 DashboardMonitor: () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
'DashboardWorkplace': () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台 DashboardWorkplace: () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
} };
//前端路由图标映射表 //前端路由图标映射表
export const constantRouterIcon = { export const constantRouterIcon = {
'DashboardOutlined': renderIcon(DashboardOutlined) DashboardOutlined: renderIcon(DashboardOutlined),
} };

View File

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

View File

@@ -1,9 +1,9 @@
import { App } from 'vue' import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { ErrorPageRoute, RedirectRoute } from '@/router/base'; import { ErrorPageRoute, RedirectRoute } from '@/router/base';
import { PageEnum } from '@/enums/pageEnum'; import { PageEnum } from '@/enums/pageEnum';
import { createRouterGuards } from './router-guards' import { createRouterGuards } from './router-guards';
import 'nprogress/css/nprogress.css' // 进度条样式 import 'nprogress/css/nprogress.css'; // 进度条样式
// @ts-ignore // @ts-ignore
const modules = import.meta.globEager('./modules/**/*.ts'); const modules = import.meta.globEager('./modules/**/*.ts');
@@ -17,10 +17,10 @@ Object.keys(modules).forEach((key) => {
}); });
function sortRoute(a, b) { 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 = { export const RootRoute: RouteRecordRaw = {
path: '/', path: '/',
@@ -43,21 +43,20 @@ export const LoginRoute: RouteRecordRaw = {
//需要验证权限 //需要验证权限
export const asyncRoutes = [ErrorPageRoute, ...routeModuleList]; export const asyncRoutes = [ErrorPageRoute, ...routeModuleList];
//普通路由 无需验证权限 //普通路由 无需验证权限
export const constantRouter:any[] = [LoginRoute, RootRoute, RedirectRoute] export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(''), history: createWebHashHistory(''),
routes: constantRouter, routes: constantRouter,
strict: true, strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
}) });
export function setupRouter(app: App) { export function setupRouter(app: App) {
app.use(router) app.use(router);
// 创建路由守卫 // 创建路由守卫
createRouterGuards(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 { Layout } from '@/router/constant';
import { WalletOutlined } from '@vicons/antd' import { WalletOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
const routeName = 'comp';
const routeName = 'comp'
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -26,28 +25,27 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '组件示例', title: '组件示例',
icon: renderIcon(WalletOutlined), icon: renderIcon(WalletOutlined),
sort: 8 sort: 8,
}, },
children: [ children: [
{ {
path: 'table', path: 'table',
name: `${ routeName }_table`, name: `${routeName}_table`,
meta: { meta: {
title: '表格', title: '表格',
}, },
component: () => import('@/views/comp/table/list.vue') component: () => import('@/views/comp/table/list.vue'),
}, },
{ {
path: 'upload', path: 'upload',
name: `${ routeName }_upload`, name: `${routeName}_upload`,
meta: { meta: {
title: '上传', title: '上传',
}, },
component: () => import('@/views/comp/upload/index.vue') 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 { Layout } from '@/router/constant';
import { DashboardOutlined } from '@vicons/antd' import { DashboardOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
const routeName = 'dashboard' const routeName = 'dashboard';
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -25,17 +25,17 @@ const routes: Array<RouteRecordRaw> = [
title: 'Dashboard', title: 'Dashboard',
icon: renderIcon(DashboardOutlined), icon: renderIcon(DashboardOutlined),
permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'], permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
sort: 0 sort: 0,
}, },
children: [ children: [
{ {
path: 'console', path: 'console',
name: `${ routeName }_console`, name: `${routeName}_console`,
meta: { meta: {
title: '主控台', title: '主控台',
permission: ['dashboard_console'] permission: ['dashboard_console'],
}, },
component: () => import('@/views/dashboard/console/console.vue') component: () => import('@/views/dashboard/console/console.vue'),
}, },
// { // {
// path: 'monitor', // path: 'monitor',
@@ -48,16 +48,16 @@ const routes: Array<RouteRecordRaw> = [
// }, // },
{ {
path: 'workplace', path: 'workplace',
name: `${ routeName }_workplace`, name: `${routeName}_workplace`,
meta: { meta: {
title: '工作台', title: '工作台',
keepAlive: true, keepAlive: true,
permission: ['dashboard_workplace'] permission: ['dashboard_workplace'],
}, },
component: () => import('@/views/dashboard/workplace/workplace.vue') 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 { Layout } from '@/router/constant';
import { ExclamationCircleOutlined } from '@vicons/antd' import { ExclamationCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -23,7 +23,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '异常页面', title: '异常页面',
icon: renderIcon(ExclamationCircleOutlined), icon: renderIcon(ExclamationCircleOutlined),
sort: 3 sort: 3,
}, },
children: [ children: [
{ {
@@ -32,7 +32,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '403', title: '403',
}, },
component: () => import('@/views/exception/403.vue') component: () => import('@/views/exception/403.vue'),
}, },
{ {
path: '404', path: '404',
@@ -40,7 +40,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '404', title: '404',
}, },
component: () => import('@/views/exception/404.vue') component: () => import('@/views/exception/404.vue'),
}, },
{ {
path: '500', path: '500',
@@ -48,10 +48,10 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '500', title: '500',
}, },
component: () => import('@/views/exception/500.vue') 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 { Layout } from '@/router/constant';
import { ProfileOutlined } from '@vicons/antd' import { ProfileOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -23,7 +23,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '表单页面', title: '表单页面',
icon: renderIcon(ProfileOutlined), icon: renderIcon(ProfileOutlined),
sort: 1 sort: 3,
}, },
children: [ children: [
{ {
@@ -32,7 +32,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '基础表单', title: '基础表单',
}, },
component: () => import('@/views/form/basicForm/index.vue') component: () => import('@/views/form/basicForm/index.vue'),
}, },
{ {
path: 'step-form', path: 'step-form',
@@ -40,7 +40,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '分步表单', title: '分步表单',
}, },
component: () => import('@/views/form/stepForm/stepForm.vue') component: () => import('@/views/form/stepForm/stepForm.vue'),
}, },
{ {
path: 'detail', path: 'detail',
@@ -48,11 +48,10 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '表单详情', title: '表单详情',
}, },
component: () => import('@/views/form/detail/index.vue') 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 { Layout } from '@/router/constant';
import { TableOutlined } from '@vicons/antd' import { TableOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -23,7 +23,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '列表页面', title: '列表页面',
icon: renderIcon(TableOutlined), icon: renderIcon(TableOutlined),
sort: 1 sort: 2,
}, },
children: [ children: [
{ {
@@ -32,19 +32,19 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '基础列表', title: '基础列表',
}, },
component: () => import('@/views/list/basicList/index.vue') component: () => import('@/views/list/basicList/index.vue'),
}, },
{ {
path: 'basic-info/:id?', path: 'basic-info/:id?',
name: 'basic-info', name: 'basic-info',
meta: { meta: {
title: '基础详情', title: '基础详情',
hidden:true hidden: true,
},
component: () => import('@/views/list/basicList/info.vue'),
}, },
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 { Layout } from '@/router/constant';
import { CheckCircleOutlined } from '@vicons/antd' import { CheckCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -23,7 +23,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '结果页面', title: '结果页面',
icon: renderIcon(CheckCircleOutlined), icon: renderIcon(CheckCircleOutlined),
sort: 4 sort: 4,
}, },
children: [ children: [
{ {
@@ -32,7 +32,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '成功页', title: '成功页',
}, },
component: () => import('@/views/result/success.vue') component: () => import('@/views/result/success.vue'),
}, },
{ {
path: 'fail', path: 'fail',
@@ -40,7 +40,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '失败页', title: '失败页',
}, },
component: () => import('@/views/result/fail.vue') component: () => import('@/views/result/fail.vue'),
}, },
{ {
path: 'info', path: 'info',
@@ -48,10 +48,10 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '信息页', title: '信息页',
}, },
component: () => import('@/views/result/info.vue') 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 { Layout } from '@/router/constant';
import { SettingOutlined } from '@vicons/antd' import { SettingOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index' import { renderIcon } from '@/utils/index';
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -23,7 +23,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '设置页面', title: '设置页面',
icon: renderIcon(SettingOutlined), icon: renderIcon(SettingOutlined),
sort: 5 sort: 5,
}, },
children: [ children: [
{ {
@@ -32,7 +32,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '个人设置', title: '个人设置',
}, },
component: () => import('@/views/setting/account/account.vue') component: () => import('@/views/setting/account/account.vue'),
}, },
{ {
path: 'system', path: 'system',
@@ -40,10 +40,10 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '系统设置', title: '系统设置',
}, },
component: () => import('@/views/setting/system/system.vue') 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 { Layout } from '@/router/constant';
import { ToolOutlined } from '@vicons/antd' import { OptionsSharp } from '@vicons/ionicons5';
import { OptionsSharp } from '@vicons/ionicons5' import { renderIcon } from '@/utils/index';
import { renderIcon } from '@/utils/index'
/** /**
* @param name 路由名称, 必须设置,且不能重名 * @param name 路由名称, 必须设置,且不能重名
@@ -24,7 +23,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '系统设置', title: '系统设置',
icon: renderIcon(OptionsSharp), icon: renderIcon(OptionsSharp),
sort: 1 sort: 1,
}, },
children: [ children: [
{ {
@@ -33,7 +32,7 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '菜单权限管理', title: '菜单权限管理',
}, },
component: () => import('@/views/system/menu/menu.vue') component: () => import('@/views/system/menu/menu.vue'),
}, },
{ {
path: 'role', path: 'role',
@@ -41,10 +40,10 @@ const routes: Array<RouteRecordRaw> = [
meta: { meta: {
title: '角色权限管理', title: '角色权限管理',
}, },
component: () => import('@/views/system/role/role.vue') component: () => import('@/views/system/role/role.vue'),
} },
], ],
} },
] ];
export default routes export default routes;

View File

@@ -1,23 +1,22 @@
import { isNavigationFailure, Router } from 'vue-router' import { isNavigationFailure, Router } from 'vue-router';
import { useUserStoreWidthOut } from '@/store/modules/user' import { useUserStoreWidthOut } from '@/store/modules/user';
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute' import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
import NProgress from 'nprogress' // progress bar import NProgress from 'nprogress'; // progress bar
import { ACCESS_TOKEN } from '@/store/mutation-types' import { ACCESS_TOKEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage' import { storage } from '@/utils/Storage';
import { PageEnum } from '@/enums/pageEnum' 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) { export function createRouterGuards(router: Router) {
const userStore = useUserStoreWidthOut(); const userStore = useUserStoreWidthOut();
const asyncRouteStore = useAsyncRouteStoreWidthOut(); const asyncRouteStore = useAsyncRouteStoreWidthOut();
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start() NProgress.start();
if (from.path === LOGIN_PATH && to.name === 'errorPage') { if (from.path === LOGIN_PATH && to.name === 'errorPage') {
next(PageEnum.BASE_HOME); next(PageEnum.BASE_HOME);
return; return;
@@ -29,10 +28,9 @@ export function createRouterGuards(router: Router) {
return; return;
} }
const token = storage.get(ACCESS_TOKEN) const token = storage.get(ACCESS_TOKEN);
if (!token) { if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true // You can access without permission. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) { if (to.meta.ignoreAuth) {
next(); next();
@@ -58,13 +56,13 @@ export function createRouterGuards(router: Router) {
return; return;
} }
const userInfo = await userStore.GetInfo() const userInfo = await userStore.GetInfo();
const routes = await asyncRouteStore.generateRoutes(userInfo) const routes = await asyncRouteStore.generateRoutes(userInfo);
// 动态添加可访问路由表 // 动态添加可访问路由表
routes.forEach((item) => { routes.forEach((item) => {
router.addRoute(item) router.addRoute(item);
}); });
const redirectPath = (from.query.redirect || to.path) as string; const redirectPath = (from.query.redirect || to.path) as string;
@@ -72,35 +70,33 @@ export function createRouterGuards(router: Router) {
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
asyncRouteStore.setDynamicAddedRoute(true); asyncRouteStore.setDynamicAddedRoute(true);
next(nextData); next(nextData);
NProgress.done() NProgress.done();
}) });
router.afterEach((to, _, failure) => { router.afterEach((to, _, failure) => {
document.title = (to?.meta?.title as string) || document.title document.title = (to?.meta?.title as string) || document.title;
if (isNavigationFailure(failure)) { if (isNavigationFailure(failure)) {
//console.log('failed navigation', failure) //console.log('failed navigation', failure)
} }
const asyncRouteStore = useAsyncRouteStoreWidthOut(); const asyncRouteStore = useAsyncRouteStoreWidthOut();
// 在这里设置需要缓存的组件名称 // 在这里设置需要缓存的组件名称
const keepAliveComponents = asyncRouteStore.keepAliveComponents const keepAliveComponents = asyncRouteStore.keepAliveComponents;
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) { if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件 // 需要缓存的组件
keepAliveComponents.push(currentComName) keepAliveComponents.push(currentComName);
} else if (!to.meta?.keepAlive || to.name == 'Redirect') { } else if (!to.meta?.keepAlive || to.name == 'Redirect') {
// 不需要缓存的组件 // 不需要缓存的组件
const index = asyncRouteStore.keepAliveComponents.findIndex( const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
(name) => name == currentComName
)
if (index != -1) { if (index != -1) {
keepAliveComponents.splice(index, 1) keepAliveComponents.splice(index, 1);
} }
} }
asyncRouteStore.setKeepAliveComponents(keepAliveComponents) asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
NProgress.done() // finish progress bar NProgress.done(); // finish progress bar
}) });
router.onError((error) => { router.onError((error) => {
console.log(error, '路由错误') console.log(error, '路由错误');
}) });
} }

View File

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

View File

@@ -21,11 +21,11 @@ export default {
// 集合字段名 // 集合字段名
infoField: 'data', infoField: 'data',
// 图片地址字段名 // 图片地址字段名
imgField: 'photo' imgField: 'photo',
}, },
//最大上传图片大小 //最大上传图片大小
maxSize: 2, maxSize: 2,
//图片上传类型 //图片上传类型
fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'], fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
} },
} };

View File

@@ -18,7 +18,7 @@ const setting = {
//系统主题色 //系统主题色
appTheme: '#2d8cf0', appTheme: '#2d8cf0',
//系统内置主题色列表 //系统内置主题色列表
appThemeList appThemeList,
} };
export default setting; export default setting;

View File

@@ -10,7 +10,7 @@ const setting = {
//固定顶部 //固定顶部
fixed: true, fixed: true,
//显示重载按钮 //显示重载按钮
isReload: true isReload: true,
}, },
//页脚 //页脚
showFooter: true, showFooter: true,
@@ -30,7 +30,7 @@ const setting = {
//菜单宽度 //菜单宽度
menuWidth: 200, menuWidth: 200,
//固定菜单 //固定菜单
fixed: true fixed: true,
}, },
//面包屑 //面包屑
crumbsSetting: { crumbsSetting: {
@@ -41,5 +41,5 @@ const setting = {
}, },
//菜单权限模式 ROLE 前端固定角色 BACK 动态获取 //菜单权限模式 ROLE 前端固定角色 BACK 动态获取
permissionMode: 'ROLE', permissionMode: 'ROLE',
} };
export default setting; export default setting;

View File

@@ -1,10 +1,10 @@
import { toRaw, unref } from 'vue' import { toRaw, unref } from 'vue';
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { RouteRecordRaw } from 'vue-router' import { RouteRecordRaw } from 'vue-router';
import { store } from '@/store' import { store } from '@/store';
import { asyncRoutes, constantRouter } from '@/router/index' import { asyncRoutes, constantRouter } from '@/router/index';
import { generatorDynamicRouter } from '@/router/generator-routers' import { generatorDynamicRouter } from '@/router/generator-routers';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting' import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
interface TreeHelperConfig { interface TreeHelperConfig {
id: string; id: string;
@@ -21,14 +21,13 @@ const DEFAULT_CONFIG: TreeHelperConfig = {
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config); const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
interface AsyncRouteState { interface AsyncRouteState {
menus: RouteRecordRaw[], menus: RouteRecordRaw[];
routers: any[], routers: any[];
addRouters: any[], addRouters: any[];
keepAliveComponents: string[], keepAliveComponents: string[];
isDynamicAddedRoute: boolean isDynamicAddedRoute: boolean;
} }
function filter<T = any>( function filter<T = any>(
tree: T[], tree: T[],
func: (n: T) => boolean, func: (n: T) => boolean,
@@ -61,7 +60,7 @@ export const useAsyncRouteStore = defineStore({
}), }),
getters: { getters: {
getMenus(): RouteRecordRaw[] { getMenus(): RouteRecordRaw[] {
return this.menus return this.menus;
}, },
getIsDynamicAddedRoute(): boolean { getIsDynamicAddedRoute(): boolean {
return this.isDynamicAddedRoute; return this.isDynamicAddedRoute;
@@ -69,57 +68,57 @@ export const useAsyncRouteStore = defineStore({
}, },
actions: { actions: {
getRouters() { getRouters() {
return toRaw(this.addRouters) return toRaw(this.addRouters);
}, },
setDynamicAddedRoute(added: boolean) { setDynamicAddedRoute(added: boolean) {
this.isDynamicAddedRoute = added; this.isDynamicAddedRoute = added;
}, },
// 设置动态路由 // 设置动态路由
setRouters(routers) { setRouters(routers) {
this.addRouters = routers this.addRouters = routers;
this.routers = constantRouter.concat(routers) this.routers = constantRouter.concat(routers);
}, },
setMenus(menus) { setMenus(menus) {
// 设置动态路由 // 设置动态路由
this.menus = menus this.menus = menus;
}, },
setKeepAliveComponents(compNames) { setKeepAliveComponents(compNames) {
// 设置需要缓存的组件 // 设置需要缓存的组件
this.keepAliveComponents = compNames this.keepAliveComponents = compNames;
}, },
async generateRoutes(data) { async generateRoutes(data) {
let accessedRouters let accessedRouters;
const roleList = data.roles || [] const roleList = data.roles || [];
const routeFilter = (route) => { const routeFilter = (route) => {
const { meta } = route; const { meta } = route;
const { permission } = meta || {}; const { permission } = meta || {};
if (!permission) return true; if (!permission) return true;
return roleList.some((role) => permission.includes(role.value)); return roleList.some((role) => permission.includes(role.value));
}; };
const { getPermissionMode } = useProjectSetting() const { getPermissionMode } = useProjectSetting();
const permissionMode = unref(getPermissionMode) const permissionMode = unref(getPermissionMode);
if (permissionMode === 'BACK') { if (permissionMode === 'BACK') {
// 动态获取菜单 // 动态获取菜单
try { try {
accessedRouters = await generatorDynamicRouter() accessedRouters = await generatorDynamicRouter();
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} else { } else {
try { try {
//过滤账户是否拥有某一个权限,并将菜单从加载列表移除 //过滤账户是否拥有某一个权限,并将菜单从加载列表移除
accessedRouters = filter([...asyncRoutes, ...constantRouter], routeFilter); accessedRouters = filter([...asyncRoutes, ...constantRouter], routeFilter);
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} }
accessedRouters = accessedRouters.filter(routeFilter); accessedRouters = accessedRouters.filter(routeFilter);
this.setRouters(accessedRouters) this.setRouters(accessedRouters);
this.setMenus(accessedRouters) this.setMenus(accessedRouters);
return toRaw(accessedRouters) return toRaw(accessedRouters);
} },
} },
}) });
// Need to be used outside the setup // Need to be used outside the setup
export function useAsyncRouteStoreWidthOut() { export function useAsyncRouteStoreWidthOut() {

View File

@@ -1,16 +1,16 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { store } from "@/store" import { store } from '@/store';
import designSetting from '@/settings/designSetting' import designSetting from '@/settings/designSetting';
const { darkTheme, appTheme, appThemeList } = designSetting const { darkTheme, appTheme, appThemeList } = designSetting;
interface DesignSettingState { interface DesignSettingState {
//深色主题 //深色主题
darkTheme: boolean, darkTheme: boolean;
//系统风格 //系统风格
appTheme: string, appTheme: string;
//系统内置风格 //系统内置风格
appThemeList: string[], appThemeList: string[];
} }
export const useDesignSettingStore = defineStore({ export const useDesignSettingStore = defineStore({
@@ -18,21 +18,21 @@ export const useDesignSettingStore = defineStore({
state: (): DesignSettingState => ({ state: (): DesignSettingState => ({
darkTheme, darkTheme,
appTheme, appTheme,
appThemeList appThemeList,
}), }),
getters: { getters: {
getDarkTheme(): boolean { getDarkTheme(): boolean {
return this.darkTheme return this.darkTheme;
}, },
getAppTheme(): string { getAppTheme(): string {
return this.appTheme return this.appTheme;
}, },
getAppThemeList(): string[] { getAppThemeList(): string[] {
return this.appThemeList return this.appThemeList;
}, },
}, },
actions: {} actions: {},
}) });
// Need to be used outside the setup // Need to be used outside the setup
export function useDesignSettingWithOut() { export function useDesignSettingWithOut() {

View File

@@ -1,19 +1,19 @@
const allModules = import.meta.globEager('./*/index.ts') const allModules = import.meta.globEager('./*/index.ts');
const modules = {} as any const modules = {} as any;
Object.keys(allModules).forEach((path) => { Object.keys(allModules).forEach((path) => {
const fileName = path.split('/')[1] const fileName = path.split('/')[1];
modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path] modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path];
}) });
// export default modules // export default modules
import asyncRoute from './async-route' import asyncRoute from './async-route';
import user from './user' import user from './user';
import tabsView from './tabs-view' import tabsView from './tabs-view';
import lockscreen from './lockscreen' import lockscreen from './lockscreen';
export default { export default {
asyncRoute, asyncRoute,
user, user,
tabsView, tabsView,
lockscreen lockscreen,
} };

View File

@@ -1,31 +1,31 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { IS_LOCKSCREEN } from '@/store/mutation-types' import { IS_LOCKSCREEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage' import { storage } from '@/utils/Storage';
// 长时间不操作默认锁屏时间 // 长时间不操作默认锁屏时间
const initTime = 60 * 60 const initTime = 60 * 60;
const isLock = storage.get(IS_LOCKSCREEN, false) const isLock = storage.get(IS_LOCKSCREEN, false);
export type ILockscreenState = { export type ILockscreenState = {
isLock: boolean // 是否锁屏 isLock: boolean; // 是否锁屏
lockTime: number lockTime: number;
} };
export const useLockscreenStore = defineStore({ export const useLockscreenStore = defineStore({
id: 'app-lockscreen', id: 'app-lockscreen',
state: (): ILockscreenState => ({ state: (): ILockscreenState => ({
isLock: isLock === true, // 是否锁屏 isLock: isLock === true, // 是否锁屏
lockTime: isLock == 'true' ? initTime : 0 lockTime: isLock == 'true' ? initTime : 0,
}), }),
getters: {}, getters: {},
actions: { actions: {
setLock(payload) { setLock(payload) {
this.isLock = payload this.isLock = payload;
storage.set(IS_LOCKSCREEN, this.isLock) storage.set(IS_LOCKSCREEN, this.isLock);
}, },
setLockTime(payload = initTime) { setLockTime(payload = initTime) {
this.lockTime = payload this.lockTime = payload;
} },
} },
}) });

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { store } from "@/store" import { store } from '@/store';
import projectSetting from '@/settings/projectSetting' import projectSetting from '@/settings/projectSetting';
const { const {
navMode, navMode,
@@ -10,18 +10,18 @@ const {
menuSetting, menuSetting,
multiTabsSetting, multiTabsSetting,
crumbsSetting, crumbsSetting,
permissionMode permissionMode,
} = projectSetting } = projectSetting;
interface ProjectSettingState { interface ProjectSettingState {
navMode: string,//导航模式 navMode: string; //导航模式
navTheme: string,//导航风格 navTheme: string; //导航风格
headerSetting: object,//顶部设置 headerSetting: object; //顶部设置
showFooter: boolean, //页脚 showFooter: boolean; //页脚
menuSetting: object, //多标签 menuSetting: object; //多标签
multiTabsSetting: object,//多标签 multiTabsSetting: object; //多标签
crumbsSetting: object,//面包屑 crumbsSetting: object; //面包屑
permissionMode: string//权限模式 permissionMode: string; //权限模式
} }
export const useProjectSettingStore = defineStore({ export const useProjectSettingStore = defineStore({
@@ -34,7 +34,7 @@ export const useProjectSettingStore = defineStore({
menuSetting, menuSetting,
multiTabsSetting, multiTabsSetting,
crumbsSetting, crumbsSetting,
permissionMode permissionMode,
}), }),
getters: { getters: {
getNavMode(): string { getNavMode(): string {
@@ -44,30 +44,30 @@ export const useProjectSettingStore = defineStore({
return this.navTheme; return this.navTheme;
}, },
getHeaderSetting(): object { getHeaderSetting(): object {
return this.headerSetting return this.headerSetting;
}, },
getShowFooter(): boolean { getShowFooter(): boolean {
return this.showFooter return this.showFooter;
}, },
getMenuSetting(): object { getMenuSetting(): object {
return this.menuSetting return this.menuSetting;
}, },
getMultiTabsSetting(): object { getMultiTabsSetting(): object {
return this.multiTabsSetting return this.multiTabsSetting;
}, },
getCrumbsSetting(): object { getCrumbsSetting(): object {
return this.multiTabsSetting return this.multiTabsSetting;
}, },
getPermissionMode(): string { getPermissionMode(): string {
return this.permissionMode return this.permissionMode;
} },
}, },
actions: { actions: {
setNavTheme(value: string): void { setNavTheme(value: string): void {
this.navTheme = value this.navTheme = value;
}, },
} },
}) });
// Need to be used outside the setup // Need to be used outside the setup
export function useProjectSettingStoreWithOut() { export function useProjectSettingStoreWithOut() {

View File

@@ -1,62 +1,62 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { RouteLocationNormalized } from 'vue-router' import { RouteLocationNormalized } from 'vue-router';
import { TABS_ROUTES } from '../mutation-types' import { TABS_ROUTES } from '../mutation-types';
// 不需要出现在标签页中的路由 // 不需要出现在标签页中的路由
const whiteList = ['Redirect', 'login'] const whiteList = ['Redirect', 'login'];
export type RouteItem = Partial<RouteLocationNormalized> & { export type RouteItem = Partial<RouteLocationNormalized> & {
fullPath: string fullPath: string;
name: string name: string;
} };
export type ITabsViewState = { export type ITabsViewState = {
tabsList: RouteItem[] // 标签页 tabsList: RouteItem[]; // 标签页
} };
export const useTabsViewStore = defineStore({ export const useTabsViewStore = defineStore({
id: 'app-tabs-view', id: 'app-tabs-view',
state: (): ITabsViewState => ({ state: (): ITabsViewState => ({
tabsList: [] tabsList: [],
}), }),
getters: {}, getters: {},
actions: { actions: {
initTabs(routes) { initTabs(routes) {
// 初始化标签页 // 初始化标签页
this.tabsList = routes this.tabsList = routes;
}, },
addTabs(route): boolean { addTabs(route): boolean {
// 添加标签页 // 添加标签页
if (whiteList.includes(route.name)) return false if (whiteList.includes(route.name)) return false;
const isExists = this.tabsList.some((item) => item.fullPath == route.fullPath) const isExists = this.tabsList.some((item) => item.fullPath == route.fullPath);
if (!isExists) { if (!isExists) {
this.tabsList.push(route) this.tabsList.push(route);
} }
return true return true;
}, },
closeLeftTabs(route) { closeLeftTabs(route) {
// 关闭左侧 // 关闭左侧
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath) const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
this.tabsList.splice(0, index) this.tabsList.splice(0, index);
}, },
closeRightTabs(route) { closeRightTabs(route) {
// 关闭右侧 // 关闭右侧
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath) const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
this.tabsList.splice(index + 1) this.tabsList.splice(index + 1);
}, },
closeOtherTabs(route) { closeOtherTabs(route) {
// 关闭其他 // 关闭其他
this.tabsList = this.tabsList.filter((item) => item.fullPath == route.fullPath) this.tabsList = this.tabsList.filter((item) => item.fullPath == route.fullPath);
}, },
closeCurrentTab(route) { closeCurrentTab(route) {
// 关闭当前页 // 关闭当前页
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath) const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
this.tabsList.splice(index, 1) this.tabsList.splice(index, 1);
}, },
closeAllTabs() { closeAllTabs() {
// 关闭全部 // 关闭全部
this.tabsList = [] this.tabsList = [];
localStorage.removeItem(TABS_ROUTES) localStorage.removeItem(TABS_ROUTES);
} },
} },
}) });

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