style(project): 项目代码风格格式化

This commit is contained in:
Coffee-crocodile 2023-03-27 17:40:49 +08:00
parent b8cf5dc13c
commit 34371ded9d
138 changed files with 4192 additions and 4200 deletions

View File

@ -1,14 +0,0 @@
*.sh
node_modules
lib
*.md
*.woff
*.ttf
.vscode
.idea
/dist/
/public
/docs
.vscode
.local
components.d.ts

View File

@ -1,64 +1,65 @@
module.exports = { module.exports = {
//https://eslint.org/docs/latest/ extends: '@antfu',
root: true, // //https://eslint.org/docs/latest/
// 环境变量 https://eslint.org/docs/latest/user-guide/configuring/language-options#specifying-environments // root: true,
env: { // // 环境变量 https://eslint.org/docs/latest/user-guide/configuring/language-options#specifying-environments
browser: true, //浏览器全局变量。 // env: {
node: true, // Node.js 全局变量和 Node.js 范围。 // browser: true, //浏览器全局变量。
es2021: true, // 所有的ECMAScript6的特性除了模块 // node: true, // Node.js 全局变量和 Node.js 范围。
}, // es2021: true, // 所有的ECMAScript6的特性除了模块
// 全局变量 // },
globals: {}, // // 全局变量
// 指定解析器与解析器配置 // globals: {},
parser: 'vue-eslint-parser', // // 指定解析器与解析器配置
parserOptions: { // parser: 'vue-eslint-parser',
ecmaVersion: 12, // parserOptions: {
parser: '@typescript-eslint/parser', // ecmaVersion: 12,
sourceType: 'module', // parser: '@typescript-eslint/parser',
}, // sourceType: 'module',
// 想要Linting规则的插件 https://eslint.org/docs/latest/user-guide/configuring/plugins // },
plugins: ['vue', '@typescript-eslint'], // // 想要Linting规则的插件 https://eslint.org/docs/latest/user-guide/configuring/plugins
// 指定扩展的配置,配置支持递归扩展,支持规则的覆盖和聚合。 // plugins: ['vue', '@typescript-eslint'],
extends: [ // // 指定扩展的配置,配置支持递归扩展,支持规则的覆盖和聚合。
'eslint:recommended', // extends: [
'plugin:vue/vue3-recommended', // 'eslint:recommended',
'@vue/eslint-config-typescript/recommended', // 'plugin:vue/vue3-recommended',
'@vue/typescript/recommended', // '@vue/eslint-config-typescript/recommended',
], // '@vue/typescript/recommended',
overrides: [ // ],
{ // overrides: [
files: ['*.vue'], // {
parser: 'vue-eslint-parser', // files: ['*.vue'],
parserOptions: { // parser: 'vue-eslint-parser',
parser: '@typescript-eslint/parser' // parserOptions: {
}, // parser: '@typescript-eslint/parser'
rules: { // },
'no-undef': 'off' // rules: {
// 'no-undef': 'off'
} // }
}, // },
{ // {
files: ['*.html'], // files: ['*.html'],
rules: { // rules: {
'vue/comment-directive': 'off' // 'vue/comment-directive': 'off'
} // }
} // }
], // ],
rules: { // rules: {
// TSESLint docs https://typescript-eslint.io/rules/ // // TSESLint docs https://typescript-eslint.io/rules/
'no-var': 'error', // 禁止使用var // 'no-var': 'error', // 禁止使用var
'no-unused-vars': 'off', // 允许声明不使用的值 // 'no-unused-vars': 'off', // 允许声明不使用的值
'no-console': 'off', // 允许出现console // 'no-console': 'off', // 允许出现console
'no-debugger': 'off', // 关闭debugger警告 // 'no-debugger': 'off', // 关闭debugger警告
'vue/multi-word-component-names': 0, // 关闭文件名多单词 // 'vue/multi-word-component-names': 0, // 关闭文件名多单词
// 'import/no-unresolved': ['error', { ignore: ['~icons/*'] }], // // 'import/no-unresolved': ['error', { ignore: ['~icons/*'] }],
"@typescript-eslint/no-explicit-any": ["off"], // 允许使用any // "@typescript-eslint/no-explicit-any": ["off"], // 允许使用any
"@typescript-eslint/no-empty-function": 'off', // 允许空函数 // "@typescript-eslint/no-empty-function": 'off', // 允许空函数
'@typescript-eslint/no-empty-interface': [ // '@typescript-eslint/no-empty-interface': [
'error', // 'error',
{ // {
allowSingleExtends: true // allowSingleExtends: true
} // }
], // ],
}, // },
}; }

16
.vscode/settings.json vendored
View File

@ -1,10 +1,10 @@
{ {
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.formatOnSave": false, "editor.formatOnSave": false,
"eslint.format.enable": false, "eslint.format.enable": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"eslint.validate": ["typescript", "javascript", "vue", "html"], "eslint.validate": ["typescript", "javascript", "vue", "html"],
"eslint.options": { "configFile": ".eslintrc.js" }, "eslint.options": { "configFile": ".eslintrc.js" }
} }

View File

@ -1 +1 @@
export * from './proxy'; export * from './proxy'

View File

@ -1,21 +1,21 @@
import type { ProxyOptions } from 'vite'; import type { ProxyOptions } from 'vite'
/** /**
* @description: vite代理字段 * @description: vite代理字段
* @param {*} env - * @param {*} env -
*/ */
export function createViteProxy(envConfig: ServiceEnvConfig) { export function createViteProxy(envConfig: ServiceEnvConfig) {
const proxy: Record<string, string | ProxyOptions> = { const proxy: Record<string, string | ProxyOptions> = {
[envConfig.urlPattern]: { [envConfig.urlPattern]: {
target: envConfig.url, target: envConfig.url,
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${envConfig.urlPattern}`), ''), rewrite: path => path.replace(new RegExp(`^${envConfig.urlPattern}`), ''),
}, },
[envConfig.secondUrlPattern]: { [envConfig.secondUrlPattern]: {
target: envConfig.secondUrl, target: envConfig.secondUrl,
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${envConfig.secondUrlPattern}`), ''), rewrite: path => path.replace(new RegExp(`^${envConfig.secondUrlPattern}`), ''),
}, },
}; }
return proxy; return proxy
} }

View File

@ -1,2 +1,2 @@
export * from './config'; export * from './config'
export * from './plugins'; export * from './plugins'

View File

@ -1,9 +1,9 @@
import viteCompression from 'vite-plugin-compression'; //https://github.com/vbenjs/vite-plugin-compression/blob/main/README.zh_CN.md import viteCompression from 'vite-plugin-compression' // https://github.com/vbenjs/vite-plugin-compression/blob/main/README.zh_CN.md
export default (env: ImportMetaEnv) => { export default (env: ImportMetaEnv) => {
// 默认使用gzip压缩 // 默认使用gzip压缩
const { VITE_COMPRESS_TYPE = 'gzip' } = env; const { VITE_COMPRESS_TYPE = 'gzip' } = env
return viteCompression({ return viteCompression({
algorithm: VITE_COMPRESS_TYPE, // 压缩算法 algorithm: VITE_COMPRESS_TYPE, // 压缩算法
}); })
}; }

View File

@ -1,10 +1,11 @@
import type { PluginOption } from 'vite'; import type { PluginOption } from 'vite'
import vue from './vue'; import unocss from '@unocss/vite'
import compress from './compress'; import vueSetupExtend from 'vite-plugin-vue-setup-extend'
import unocss from '@unocss/vite'; import vue from './vue'
import visualizer from './visualizer'; import compress from './compress'
import unplugin from './unplugin'; import visualizer from './visualizer'
import mock from './mock'; import unplugin from './unplugin'
import mock from './mock'
/** /**
* @description: vite插件配置 * @description: vite插件配置
@ -12,14 +13,14 @@ import mock from './mock';
* @return {*} * @return {*}
*/ */
export function setVitePlugins(env: ImportMetaEnv) { export function setVitePlugins(env: ImportMetaEnv) {
const plugins = [...vue, unocss(), ...unplugin, mock]; const plugins = [...vue, unocss(), ...unplugin, mock, vueSetupExtend()]
// 是否压缩 // 是否压缩
if (env.VITE_COMPRESS_OPEN) { if (env.VITE_COMPRESS_OPEN)
plugins.push(compress(env)); plugins.push(compress(env))
}
// 是否依赖分析 // 是否依赖分析
if (env.VITE_VISUALIZER) { if (env.VITE_VISUALIZER)
plugins.push(visualizer as PluginOption); plugins.push(visualizer as PluginOption)
}
return plugins; return plugins
} }

View File

@ -1,9 +1,6 @@
import { viteMockServe } from 'vite-plugin-mock'; // https://github.com/vbenjs/vite-plugin-mock/blob/main/README.zh_CN.md import { viteMockServe } from 'vite-plugin-mock' // https://github.com/vbenjs/vite-plugin-mock/blob/main/README.zh_CN.md
export default viteMockServe({ export default viteMockServe({
mockPath: 'mock', mockPath: 'mock',
injectCode: ` injectCode: 'import { setupMockServer } from \'../mock\';setupMockServer();',
import { setupMockServer } from '../mock'; })
setupMockServer();
`,
});

View File

@ -1,9 +1,9 @@
import Components from 'unplugin-vue-components/vite'; import path from 'node:path'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; import Components from 'unplugin-vue-components/vite'
import Icons from 'unplugin-icons/vite'; // https://github.com/antfu/unplugin-icons import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import IconsResolver from 'unplugin-icons/resolver'; import Icons from 'unplugin-icons/vite' // https://github.com/antfu/unplugin-icons
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; // https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md import IconsResolver from 'unplugin-icons/resolver'
import path from 'path'; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' // https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md
export default [ export default [
Components({ Components({
@ -23,4 +23,4 @@ export default [
// inject: 'body-last', // inject: 'body-last',
// customDomId: '__svg__icons__dom__', // customDomId: '__svg__icons__dom__',
}), }),
]; ]

View File

@ -1,6 +1,6 @@
import { visualizer } from 'rollup-plugin-visualizer'; // https://github.com/btd/rollup-plugin-visualizer import { visualizer } from 'rollup-plugin-visualizer' // https://github.com/btd/rollup-plugin-visualizer
export default visualizer({ export default visualizer({
gzipSize: true, gzipSize: true,
brotliSize: true, brotliSize: true,
open: true, open: true,
}); })

View File

@ -1,6 +1,6 @@
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'; // https://github.com/vitejs/vite/tree/main/packages/plugin-vue-jsx import vueJsx from '@vitejs/plugin-vue-jsx' // https://github.com/vitejs/vite/tree/main/packages/plugin-vue-jsx
const plugins = [vue(), vueJsx()]; const plugins = [vue(), vueJsx()]
export default plugins; export default plugins

View File

@ -1 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] }; module.exports = { extends: ['@commitlint/config-conventional'] }

View File

@ -1,6 +1,6 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
import api from './module'; import api from './module'
export function setupMockServer() { export function setupMockServer() {
createProdMockServer(api); createProdMockServer(api)
} }

View File

@ -1,3 +1,3 @@
import user from './user'; import user from './user'
export default [...user]; export default [...user]

View File

@ -1,20 +1,20 @@
import { mock } from 'mockjs'; import { mock } from 'mockjs'
import { resultSuccess } from '../utils'; import { resultSuccess } from '../utils'
const userList = mock({ const userList = mock({
'list|20': [ 'list|20': [
{ {
id: '@id', 'id': '@id',
name: '@cname', 'name': '@cname',
'age|20-36': 36, 'age|20-36': 36,
'gender|1': ['0', '1', null], 'gender|1': ['0', '1', null],
email: '@email("qq.com")', 'email': '@email("qq.com")',
address: '@county(true) ', 'address': '@county(true)',
'role|1': ['super', 'admin', 'user'], 'role|1': ['super', 'admin', 'user'],
'disabled|1': true, 'disabled|1': true,
}, },
], ],
}); })
export default [ export default [
{ {
@ -22,7 +22,7 @@ export default [
timeout: 1000, timeout: 1000,
method: 'get', method: 'get',
response: () => { response: () => {
return resultSuccess(userList.list); return resultSuccess(userList.list)
}, },
}, },
]; ]

View File

@ -1,494 +1,493 @@
import Mock from 'mockjs'; import Mock from 'mockjs'
import { resultSuccess, resultFailed } from '../utils'; import { resultFailed, resultSuccess } from '../utils'
const Random = Mock.Random; const Random = Mock.Random
const token = () => Random.string('upper', 32, 32); const token = () => Random.string('upper', 32, 32)
const userData = [ const userData = [
{ {
userId: 1, userId: 1,
userName: 'super', userName: 'super',
password: '123456', password: '123456',
nickName: '超级管理员大人', nickName: '超级管理员大人',
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg', avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
role: 'super', role: 'super',
}, },
{ {
userId: 2, userId: 2,
userName: 'admin', userName: 'admin',
password: '123456', password: '123456',
nickName: '管理员大人', nickName: '管理员大人',
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg', avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
role: 'admin', role: 'admin',
}, },
{ {
userId: 3, userId: 3,
userName: 'user', userName: 'user',
password: '123456', password: '123456',
nickName: '用户大人', nickName: '用户大人',
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg', avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
role: 'user', role: 'user',
}, },
]; ]
const userRoutes = [ const userRoutes = [
{ {
name: 'dashboard', name: 'dashboard',
path: '/dashboard', path: '/dashboard',
meta: { meta: {
title: '仪表盘', title: '仪表盘',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:analysis', icon: 'icon-park-outline:analysis',
}, },
children: [ children: [
{ {
name: 'dashboard_workbench', name: 'dashboard_workbench',
path: '/dashboard/workbench', path: '/dashboard/workbench',
meta: { meta: {
title: '工作台', title: '工作台',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:alarm', icon: 'icon-park-outline:alarm',
}, },
}, },
{ {
name: 'dashboard_monitor', name: 'dashboard_monitor',
path: '/dashboard/monitor', path: '/dashboard/monitor',
meta: { meta: {
title: '监控页', title: '监控页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:anchor', icon: 'icon-park-outline:anchor',
}, },
}, },
], ],
}, },
{ {
name: 'test', name: 'test',
path: '/test', path: '/test',
meta: { meta: {
title: '多级菜单演示', title: '多级菜单演示',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list', icon: 'icon-park-outline:list',
}, },
children: [ children: [
{ {
name: 'test1', name: 'test1',
path: '/test/test1', path: '/test/test1',
meta: { meta: {
title: '接口功能测试', title: '接口功能测试',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list', icon: 'icon-park-outline:list',
}, },
}, },
{ {
name: 'test2', name: 'test2',
path: '/test/test2', path: '/test/test2',
meta: { meta: {
title: '多级菜单子页', title: '多级菜单子页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list', icon: 'icon-park-outline:list',
}, },
children: [ children: [
{ {
name: 'test2_detail', name: 'test2_detail',
path: '/test/test2/detail', path: '/test/test2/detail',
meta: { meta: {
title: '多级菜单的详情页', title: '多级菜单的详情页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list', icon: 'icon-park-outline:list',
hide: true, hide: true,
activeMenu: '/test/test2', activeMenu: '/test/test2',
}, },
}, },
], ],
}, },
{ {
name: 'test3', name: 'test3',
path: '/test/test3', path: '/test/test3',
meta: { meta: {
title: '多级菜单', title: '多级菜单',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list', icon: 'icon-park-outline:list',
}, },
children: [ children: [
{ {
name: 'test4', name: 'test4',
path: '/test/test3/test4', path: '/test/test3/test4',
meta: { meta: {
title: '多级菜单3-1', title: '多级菜单3-1',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list', icon: 'icon-park-outline:list',
}, },
}, },
], ],
}, },
], ],
}, },
{ {
name: 'list', name: 'list',
path: '/list', path: '/list',
meta: { meta: {
title: '列表页', title: '列表页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list-two', icon: 'icon-park-outline:list-two',
}, },
children: [ children: [
{ {
name: 'list_commonList', name: 'list_commonList',
path: '/list/commonList', path: '/list/commonList',
meta: { meta: {
title: '常用列表', title: '常用列表',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:list-view', icon: 'icon-park-outline:list-view',
}, },
}, },
{ {
name: 'list_cardList', name: 'list_cardList',
path: '/list/cardList', path: '/list/cardList',
meta: { meta: {
title: '卡片列表', title: '卡片列表',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:view-grid-list', icon: 'icon-park-outline:view-grid-list',
}, },
}, },
], ],
}, },
{ {
name: 'plugin', name: 'plugin',
path: '/plugin', path: '/plugin',
meta: { meta: {
title: '组件示例', title: '组件示例',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:application-one', icon: 'icon-park-outline:application-one',
}, },
children: [ children: [
{ {
name: 'plugin_charts', name: 'plugin_charts',
path: '/plugin/charts', path: '/plugin/charts',
meta: { meta: {
title: '图表', title: '图表',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:chart-line', icon: 'icon-park-outline:chart-line',
}, },
children: [ children: [
{ {
name: 'plugin_echarts', name: 'plugin_echarts',
path: '/plugin/charts/echarts', path: '/plugin/charts/echarts',
meta: { meta: {
title: 'ECharts', title: 'ECharts',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:chart-proportion', icon: 'icon-park-outline:chart-proportion',
}, },
}, },
{ {
name: 'plugin_antV', name: 'plugin_antV',
path: '/plugin/charts/antV', path: '/plugin/charts/antV',
meta: { meta: {
title: 'antV', title: 'antV',
requiresAuth: true, requiresAuth: true,
icon: 'ant-design:ant-design-outlined', icon: 'ant-design:ant-design-outlined',
}, },
}, },
], ],
}, },
{ {
name: 'plugin_map', name: 'plugin_map',
path: '/plugin/map', path: '/plugin/map',
meta: { meta: {
title: '地图', title: '地图',
requiresAuth: true, requiresAuth: true,
icon: 'carbon:map', icon: 'carbon:map',
keepAlive: true, keepAlive: true,
}, },
}, },
{ {
name: 'plugin_editor', name: 'plugin_editor',
path: '/plugin/editor', path: '/plugin/editor',
meta: { meta: {
title: '编辑器', title: '编辑器',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:editor', icon: 'icon-park-outline:editor',
}, },
children: [ children: [
{ {
name: 'plugin_md', name: 'plugin_md',
path: '/plugin/editor/md', path: '/plugin/editor/md',
meta: { meta: {
title: 'MarkDown', title: 'MarkDown',
requiresAuth: true, requiresAuth: true,
icon: 'ri:markdown-line', icon: 'ri:markdown-line',
}, },
}, },
{ {
name: 'plugin_rich', name: 'plugin_rich',
path: '/plugin/editor/rich', path: '/plugin/editor/rich',
meta: { meta: {
title: '富文本', title: '富文本',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:edit-one', icon: 'icon-park-outline:edit-one',
}, },
}, },
], ],
}, },
{ {
name: 'plugin_clipboard', name: 'plugin_clipboard',
path: '/plugin/clipboard', path: '/plugin/clipboard',
meta: { meta: {
title: '剪贴板', title: '剪贴板',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:clipboard', icon: 'icon-park-outline:clipboard',
}, },
}, },
{ {
name: 'plugin_icons', name: 'plugin_icons',
path: '/plugin/icons', path: '/plugin/icons',
meta: { meta: {
title: '图标', title: '图标',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:winking-face-with-open-eyes', icon: 'icon-park-outline:winking-face-with-open-eyes',
}, },
}, },
{ {
name: 'plugin_QRCode', name: 'plugin_QRCode',
path: '/plugin/QRCode', path: '/plugin/QRCode',
meta: { meta: {
title: '二维码', title: '二维码',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:two-dimensional-code', icon: 'icon-park-outline:two-dimensional-code',
}, },
}, },
], ],
}, },
{ {
name: 'docments', name: 'docments',
path: '/docments', path: '/docments',
meta: { meta: {
title: '外链文档', title: '外链文档',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:file-doc', icon: 'icon-park-outline:file-doc',
}, },
children: [ children: [
{ {
name: 'docments_vue', name: 'docments_vue',
path: '/docments/vue', path: '/docments/vue',
meta: { meta: {
title: 'vue', title: 'vue',
requiresAuth: true, requiresAuth: true,
icon: 'logos:vue', icon: 'logos:vue',
}, },
}, },
{ {
name: 'docments_vite', name: 'docments_vite',
path: '/docments/vite', path: '/docments/vite',
meta: { meta: {
title: 'vite', title: 'vite',
requiresAuth: true, requiresAuth: true,
icon: 'logos:vitejs', icon: 'logos:vitejs',
}, },
}, },
{ {
name: 'docments_vueuse', name: 'docments_vueuse',
path: '/docments/vueuse', path: '/docments/vueuse',
meta: { meta: {
title: 'VueUse外链', title: 'VueUse外链',
requiresAuth: true, requiresAuth: true,
icon: 'logos:vueuse', icon: 'logos:vueuse',
herf: 'https://vueuse.org/guide/', herf: 'https://vueuse.org/guide/',
}, },
}, },
], ],
}, },
{ {
name: 'permission', name: 'permission',
path: '/permission', path: '/permission',
meta: { meta: {
title: '权限示例', title: '权限示例',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:people-safe', icon: 'icon-park-outline:people-safe',
}, },
children: [ children: [
{ {
name: 'permission_permission', name: 'permission_permission',
path: '/permission/permission', path: '/permission/permission',
meta: { meta: {
title: '权限示例', title: '权限示例',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:right-user', icon: 'icon-park-outline:right-user',
}, },
}, },
{ {
name: 'permission_justSuper', name: 'permission_justSuper',
path: '/permission/justSuper', path: '/permission/justSuper',
meta: { meta: {
title: '超管super可见', title: '超管super可见',
requiresAuth: true, requiresAuth: true,
roles: ['super'], roles: ['super'],
icon: 'icon-park-outline:wrong-user', icon: 'icon-park-outline:wrong-user',
}, },
}, },
], ],
}, },
{ {
name: 'error', name: 'error',
path: '/error', path: '/error',
meta: { meta: {
title: '异常页', title: '异常页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:error-computer', icon: 'icon-park-outline:error-computer',
}, },
children: [ children: [
{ {
name: '403', name: '403',
path: '/error/403', path: '/error/403',
meta: { meta: {
title: '403页', title: '403页',
requiresAuth: true, requiresAuth: true,
icon: 'carbon:error', icon: 'carbon:error',
order: 3, order: 3,
}, },
}, },
{ {
name: '404', name: '404',
path: '/error/404', path: '/error/404',
meta: { meta: {
title: '404页', title: '404页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:error', icon: 'icon-park-outline:error',
order: 2, order: 2,
}, },
}, },
{ {
name: '500', name: '500',
path: '/error/500', path: '/error/500',
meta: { meta: {
title: '500页', title: '500页',
requiresAuth: true, requiresAuth: true,
icon: 'carbon:data-error', icon: 'carbon:data-error',
order: 1, order: 1,
}, },
}, },
], ],
}, },
{ {
name: 'setting', name: 'setting',
path: '/setting', path: '/setting',
meta: { meta: {
title: '系统设置', title: '系统设置',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:setting', icon: 'icon-park-outline:setting',
}, },
children: [ children: [
{ {
name: 'setting_account', name: 'setting_account',
path: '/setting/account', path: '/setting/account',
meta: { meta: {
title: '用户设置', title: '用户设置',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:every-user', icon: 'icon-park-outline:every-user',
}, },
}, },
{ {
name: 'setting_dictionary', name: 'setting_dictionary',
path: '/setting/dictionary', path: '/setting/dictionary',
meta: { meta: {
title: '字典设置', title: '字典设置',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:book-one', icon: 'icon-park-outline:book-one',
}, },
}, },
{ {
name: 'setting_menu', name: 'setting_menu',
path: '/setting/menu', path: '/setting/menu',
meta: { meta: {
title: '菜单设置', title: '菜单设置',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:application-menu', icon: 'icon-park-outline:application-menu',
}, },
}, },
{ {
name: 'setting_system', name: 'setting_system',
path: '/setting/system', path: '/setting/system',
meta: { meta: {
title: '系统配置', title: '系统配置',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:coordinate-system', icon: 'icon-park-outline:coordinate-system',
}, },
}, },
], ],
}, },
{ {
name: 'userCenter', name: 'userCenter',
path: '/userCenter', path: '/userCenter',
meta: { meta: {
title: '个人中心', title: '个人中心',
requiresAuth: true, requiresAuth: true,
icon: 'carbon:user-avatar-filled-alt', icon: 'carbon:user-avatar-filled-alt',
}, },
}, },
{ {
name: 'about', name: 'about',
path: '/about', path: '/about',
meta: { meta: {
title: '关于', title: '关于',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:info', icon: 'icon-park-outline:info',
}, },
}, },
]; ]
export default [ export default [
{ {
url: '/mock/login', url: '/mock/login',
method: 'post', method: 'post',
response: (options: any) => { response: (options: any) => {
const { userName = undefined, password = undefined } = options.body; const { userName = undefined, password = undefined } = options.body
if (!userName || !password) { if (!userName || !password)
return resultFailed(null, '账号密码不全'); return resultFailed(null, '账号密码不全')
}
const userInfo = userData.find((item) => item.userName === userName && item.password === password); const userInfo = userData.find(item => item.userName === userName && item.password === password)
if (userInfo) { if (userInfo) {
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: { data: {
userId: userInfo.userId, userId: userInfo.userId,
token: token(), token: token(),
refreshToken: token(), refreshToken: token(),
}, },
}; }
} }
return resultFailed(null, '账号密码错误'); return resultFailed(null, '账号密码错误')
}, },
}, },
{ {
url: '/mock/updateToken', url: '/mock/updateToken',
method: 'post', method: 'post',
response: () => { response: () => {
return resultSuccess({ token: token(), refreshToken: token() }); return resultSuccess({ token: token(), refreshToken: token() })
}, },
}, },
{ {
url: '/mock/getUserInfo', url: '/mock/getUserInfo',
method: 'get', method: 'get',
response: (options: any) => { response: (options: any) => {
const { userId = undefined } = options.query; const { userId = undefined } = options.query
if (!userId) { if (!userId)
return resultFailed(null, '未传入用户id'); return resultFailed(null, '未传入用户id')
}
const userInfo = userData.find((item) => item.userId == userId); const userInfo = userData.find(item => item.userId === userId)
if (userInfo) { if (userInfo)
return resultSuccess(userInfo); return resultSuccess(userInfo)
}
return resultFailed(null, '未找到用户信息,请检查提交参数'); return resultFailed(null, '未找到用户信息,请检查提交参数')
}, },
}, },
{ {
url: '/mock/getUserRoutes', url: '/mock/getUserRoutes',
method: 'post', method: 'post',
response: () => { response: () => {
return resultSuccess(userRoutes); return resultSuccess(userRoutes)
}, },
}, },
]; ]

View File

@ -1,16 +1,16 @@
import Mock from 'mockjs'; import Mock from 'mockjs'
export function resultSuccess(data: any, msg?:string ) { export function resultSuccess(data: any, msg?: string) {
return Mock.mock({ return Mock.mock({
code: 200, code: 200,
data, data,
msg: msg || 'success', msg: msg || 'success',
}); })
} }
export function resultFailed(data: any, msg?: string ) { export function resultFailed(data: any, msg?: string) {
return Mock.mock({ return Mock.mock({
code: 500, code: 500,
data, data,
msg: msg || 'failed', msg: msg || 'failed',
}); })
} }

View File

@ -1,87 +1,89 @@
{ {
"name": "ench-admin", "name": "ench-admin",
"private": true, "version": "0.0.1",
"version": "0.0.1", "private": true,
"description": "", "description": "",
"author": "iam-see <chen.dev@foxmail.com> (https://github.com/iam-see/)", "author": "iam-see <chen.dev@foxmail.com> (https://github.com/iam-see/)",
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/iam-see/Ench-admin", "homepage": "https://github.com/iam-see/Ench-admin",
"keywords": [ "keywords": [
"Vue", "Vue",
"Vue3", "Vue3",
"admin" "admin"
], ],
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"dev:test": "vite --mode test", "dev:test": "vite --mode test",
"dev:prod": "vite --mode production", "dev:prod": "vite --mode production",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"prepare": "husky install", "prepare": "husky install",
"commit": "cz", "commit": "cz",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md" "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
"path": "./node_modules/cz-customizable" "path": "./node_modules/cz-customizable"
} }
}, },
"lint-staged": { "dependencies": {
"*.{vue,js,jsx,ts,tsx,json}": "eslint --fix" "@vueuse/core": "^9.13.0",
}, "@wangeditor/editor": "^5.1.23",
"dependencies": { "@wangeditor/editor-for-vue": "^5.1.12",
"@vueuse/core": "^9.13.0", "axios": "^1.3.4",
"@wangeditor/editor": "^5.1.23", "crypto-js": "^4.1.1",
"@wangeditor/editor-for-vue": "^5.1.12", "echarts": "^5.4.1",
"axios": "^1.3.4", "md-editor-v3": "^2.9.3",
"crypto-js": "^4.1.1", "pinia": "^2.0.33",
"echarts": "^5.4.1", "pinia-plugin-persist": "^1.0.0",
"md-editor-v3": "^2.9.3", "qs": "^6.11.1",
"pinia": "^2.0.33", "vue": "^3.2.47",
"pinia-plugin-persist": "^1.0.0", "vue-qr": "^4.0.9",
"qs": "^6.11.1", "vue-router": "^4.1.6"
"vue": "^3.2.47", },
"vue-qr": "^4.0.9", "devDependencies": {
"vue-router": "^4.1.6" "@antfu/eslint-config": "^0.37.0",
}, "@commitlint/cli": "^17.4.1",
"devDependencies": { "@commitlint/config-conventional": "^17.4.0",
"@commitlint/cli": "^17.4.1", "@iconify-json/icon-park-outline": "^1.1.9",
"@commitlint/config-conventional": "^17.4.0", "@iconify/vue": "^4.0.2",
"@iconify-json/icon-park-outline": "^1.1.9", "@types/crypto-js": "^4.1.1",
"@iconify/vue": "^4.0.2", "@types/mockjs": "^1.0.7",
"@types/crypto-js": "^4.1.1", "@types/node": "^18.15.3",
"@types/mockjs": "^1.0.7", "@types/qs": "^6.9.7",
"@types/node": "^18.15.3", "@typescript-eslint/eslint-plugin": "^5.55.0",
"@types/qs": "^6.9.7", "@typescript-eslint/parser": "^5.55.0",
"@typescript-eslint/eslint-plugin": "^5.55.0", "@unocss/preset-attributify": "^0.50.4",
"@typescript-eslint/parser": "^5.55.0", "@unocss/preset-uno": "^0.50.4",
"@unocss/preset-attributify": "^0.50.4", "@unocss/vite": "^0.50.4",
"@unocss/preset-uno": "^0.50.4", "@vitejs/plugin-vue": "^4.0.0",
"@unocss/vite": "^0.50.4", "@vitejs/plugin-vue-jsx": "^3.0.0",
"@vitejs/plugin-vue": "^4.0.0", "@vue/eslint-config-typescript": "^11.0.2",
"@vitejs/plugin-vue-jsx": "^3.0.0", "commitizen": "^4.2.6",
"@vue/eslint-config-typescript": "^11.0.2", "cz-conventional-changelog": "^3.3.0",
"commitizen": "^4.2.6", "cz-customizable": "^7.0.0",
"cz-conventional-changelog": "^3.3.0", "eslint": "^8.36.0",
"cz-customizable": "^7.0.0", "eslint-import-resolver-alias": "^1.1.2",
"eslint": "^8.31.0", "eslint-plugin-import": "^2.27.5",
"eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-vue": "^9.9.0",
"eslint-plugin-import": "^2.27.5", "husky": "^8.0.3",
"eslint-plugin-vue": "^9.9.0", "less": "^4.1.3",
"husky": "^8.0.3", "lint-staged": "^13.1.0",
"less": "^4.1.3", "mockjs": "^1.1.0",
"lint-staged": "^13.1.0", "naive-ui": "^2.34.3",
"mockjs": "^1.1.0", "rollup-plugin-visualizer": "^5.9.0",
"naive-ui": "^2.34.3", "typescript": "^5.0.2",
"rollup-plugin-visualizer": "^5.9.0", "unplugin-icons": "^0.15.3",
"typescript": "^5.0.2", "unplugin-vue-components": "^0.24.1",
"unplugin-icons": "^0.15.3", "vite": "^4.2.1",
"unplugin-vue-components": "^0.24.1", "vite-plugin-compression": "^0.5.1",
"vite": "^4.2.1", "vite-plugin-mock": "^2.9.6",
"vite-plugin-compression": "^0.5.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-mock": "^2.9.6", "vite-plugin-vue-setup-extend": "^0.4.0",
"vite-plugin-svg-icons": "^2.0.1", "vue-tsc": "^1.2.0"
"vue-tsc": "^1.2.0" },
} "lint-staged": {
"*.{vue,js,jsx,ts,tsx,json}": "eslint --fix"
}
} }

View File

@ -1,3 +1,19 @@
<script setup lang="ts">
import type { GlobalThemeOverrides } from 'naive-ui'
import { dateZhCN, useOsTheme, zhCN } from 'naive-ui'
import { useAppStore } from './store'
// import themeConfig from './theme.json';
const locale = zhCN
const dateLocale = dateZhCN
const appStore = useAppStore()
const osThemeRef = useOsTheme()
appStore.setDarkMode(osThemeRef.value === 'dark')
const themeOverrides: GlobalThemeOverrides = {}
</script>
<template> <template>
<n-config-provider <n-config-provider
class="wh-full" class="wh-full"
@ -10,19 +26,4 @@
</n-config-provider> </n-config-provider>
</template> </template>
<script setup lang="ts">
import { useAppStore } from './store';
import { zhCN, dateZhCN, GlobalThemeOverrides, useOsTheme } from 'naive-ui';
// import themeConfig from './theme.json';
const locale = zhCN;
const dateLocale = dateZhCN;
const appStore = useAppStore();
const osThemeRef = useOsTheme();
appStore.setDarkMode(osThemeRef.value === 'dark');
const themeOverrides: GlobalThemeOverrides = {};
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,8 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
const appStore = useAppStore()
</script>
<template> <template>
<div <div
class="cursor-pointer" class="cursor-pointer"
@ -8,9 +13,4 @@
</div> </div>
</template> </template>
<script setup lang="ts">
import { useAppStore } from '@/store';
const appStore = useAppStore();
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,14 @@
<script setup lang="ts">
import { useAppRouter } from '@/hooks'
type TipType = '403' | '404' | '500'
defineProps<{
/** 异常类型 403 404 500 */
type: TipType
}>()
const { toRoot } = useAppRouter()
</script>
<template> <template>
<div class="flex-col-center h-full"> <div class="flex-col-center h-full">
<img <img
@ -27,15 +38,4 @@
</div> </div>
</template> </template>
<script setup lang="ts">
import { useAppRouter } from '@/hooks';
type TipType = '403' | '404' | '500';
defineProps<{
/** 异常类型 403 404 500 */
type: TipType;
}>();
const { toRoot } = useAppRouter();
</script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -1,37 +1,37 @@
<script setup lang="ts">
import { useDialog, useLoadingBar, useMessage, useNotification } from 'naive-ui'
import { defineComponent, h } from 'vue'
// naivewindow, 便
function registerNaiveTools() {
window.$loadingBar = useLoadingBar()
window.$dialog = useDialog()
window.$message = useMessage()
window.$notification = useNotification()
}
const NaiveProviderContent = defineComponent({
name: 'NaiveProviderContent',
setup() {
registerNaiveTools()
},
render() {
return h('div')
},
})
</script>
<template> <template>
<n-loading-bar-provider> <n-loading-bar-provider>
<n-dialog-provider> <n-dialog-provider>
<n-notification-provider> <n-notification-provider>
<n-message-provider> <n-message-provider>
<slot></slot> <slot />
<naive-provider-content /> <NaiveProviderContent />
</n-message-provider> </n-message-provider>
</n-notification-provider> </n-notification-provider>
</n-dialog-provider> </n-dialog-provider>
</n-loading-bar-provider> </n-loading-bar-provider>
</template> </template>
<script setup lang="ts">
import { useLoadingBar, useDialog, useMessage, useNotification } from 'naive-ui';
import { defineComponent, h } from 'vue';
// naivewindow, 便
function registerNaiveTools() {
window.$loadingBar = useLoadingBar();
window.$dialog = useDialog();
window.$message = useMessage();
window.$notification = useNotification();
}
const NaiveProviderContent = defineComponent({
name: 'NaiveProviderContent',
setup() {
registerNaiveTools();
},
render() {
return h('div');
},
});
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,8 @@
<script setup lang="ts">
import { useAppInfo } from '@/hooks'
const { title } = useAppInfo()
</script>
<template> <template>
<div id="loading-container"> <div id="loading-container">
<div class="boxes"> <div class="boxes">
@ -32,209 +37,204 @@
</div> </div>
</template> </template>
<script setup lang="ts">
import { useAppInfo } from '@/hooks';
const { title } = useAppInfo();
</script>
<style scoped> <style scoped>
#loading-container { #loading-container {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
gap: 15vh; gap: 15vh;
position: fixed; position: fixed;
background-color: aliceblue; background-color: aliceblue;
z-index: 1; z-index: 1;
} }
.boxes { .boxes {
--size: 48px; --size: 48px;
--duration: 800ms; --duration: 800ms;
height: calc(var(--size) * 2); height: calc(var(--size) * 2);
width: calc(var(--size) * 3); width: calc(var(--size) * 3);
position: relative; position: relative;
transform-style: preserve-3d; transform-style: preserve-3d;
transform-origin: 50% 50%; transform-origin: 50% 50%;
margin-top: calc(var(--size) * 1.5 * -1); margin-top: calc(var(--size) * 1.5 * -1);
transform: rotateX(60deg) rotateZ(45deg) rotateY(0deg) translateZ(0px); transform: rotateX(60deg) rotateZ(45deg) rotateY(0deg) translateZ(0px);
} }
.boxes .box { .boxes .box {
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
top: 0; top: 0;
left: 0; left: 0;
position: absolute; position: absolute;
transform-style: preserve-3d; transform-style: preserve-3d;
} }
.boxes .box:nth-child(1) { .boxes .box:nth-child(1) {
transform: translate(100%, 0); transform: translate(100%, 0);
-webkit-animation: box1 var(--duration) linear infinite; -webkit-animation: box1 var(--duration) linear infinite;
animation: box1 var(--duration) linear infinite; animation: box1 var(--duration) linear infinite;
} }
.boxes .box:nth-child(2) { .boxes .box:nth-child(2) {
transform: translate(0, 100%); transform: translate(0, 100%);
-webkit-animation: box2 var(--duration) linear infinite; -webkit-animation: box2 var(--duration) linear infinite;
animation: box2 var(--duration) linear infinite; animation: box2 var(--duration) linear infinite;
} }
.boxes .box:nth-child(3) { .boxes .box:nth-child(3) {
transform: translate(100%, 100%); transform: translate(100%, 100%);
-webkit-animation: box3 var(--duration) linear infinite; -webkit-animation: box3 var(--duration) linear infinite;
animation: box3 var(--duration) linear infinite; animation: box3 var(--duration) linear infinite;
} }
.boxes .box:nth-child(4) { .boxes .box:nth-child(4) {
transform: translate(200%, 0); transform: translate(200%, 0);
-webkit-animation: box4 var(--duration) linear infinite; -webkit-animation: box4 var(--duration) linear infinite;
animation: box4 var(--duration) linear infinite; animation: box4 var(--duration) linear infinite;
} }
.boxes .box > div { .boxes .box > div {
--background: #5c8df6; --background: #5c8df6;
--top: auto; --top: auto;
--right: auto; --right: auto;
--bottom: auto; --bottom: auto;
--left: auto; --left: auto;
--translateZ: calc(var(--size) / 2); --translateZ: calc(var(--size) / 2);
--rotateY: 0deg; --rotateY: 0deg;
--rotateX: 0deg; --rotateX: 0deg;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: var(--background); background: var(--background);
top: var(--top); top: var(--top);
right: var(--right); right: var(--right);
bottom: var(--bottom); bottom: var(--bottom);
left: var(--left); left: var(--left);
transform: rotateY(var(--rotateY)) rotateX(var(--rotateX)) translateZ(var(--translateZ)); transform: rotateY(var(--rotateY)) rotateX(var(--rotateX)) translateZ(var(--translateZ));
} }
.boxes .box > div:nth-child(1) { .boxes .box > div:nth-child(1) {
--top: 0; --top: 0;
--left: 0; --left: 0;
} }
.boxes .box > div:nth-child(2) { .boxes .box > div:nth-child(2) {
--background: #145af2; --background: #145af2;
--right: 0; --right: 0;
--rotateY: 90deg; --rotateY: 90deg;
} }
.boxes .box > div:nth-child(3) { .boxes .box > div:nth-child(3) {
--background: #447cf5; --background: #447cf5;
--rotateX: -90deg; --rotateX: -90deg;
} }
.boxes .box > div:nth-child(4) { .boxes .box > div:nth-child(4) {
--background: #dbe3f4; --background: #dbe3f4;
--top: 0; --top: 0;
--left: 0; --left: 0;
--translateZ: calc(var(--size) * 3 * -1); --translateZ: calc(var(--size) * 3 * -1);
} }
@-webkit-keyframes box1 { @-webkit-keyframes box1 {
0%, 0%,
50% { 50% {
transform: translate(100%, 0); transform: translate(100%, 0);
} }
100% { 100% {
transform: translate(200%, 0); transform: translate(200%, 0);
} }
} }
@keyframes box1 { @keyframes box1 {
0%, 0%,
50% { 50% {
transform: translate(100%, 0); transform: translate(100%, 0);
} }
100% { 100% {
transform: translate(200%, 0); transform: translate(200%, 0);
} }
} }
@-webkit-keyframes box2 { @-webkit-keyframes box2 {
0% { 0% {
transform: translate(0, 100%); transform: translate(0, 100%);
} }
50% { 50% {
transform: translate(0, 0); transform: translate(0, 0);
} }
100% { 100% {
transform: translate(100%, 0); transform: translate(100%, 0);
} }
} }
@keyframes box2 { @keyframes box2 {
0% { 0% {
transform: translate(0, 100%); transform: translate(0, 100%);
} }
50% { 50% {
transform: translate(0, 0); transform: translate(0, 0);
} }
100% { 100% {
transform: translate(100%, 0); transform: translate(100%, 0);
} }
} }
@-webkit-keyframes box3 { @-webkit-keyframes box3 {
0%, 0%,
50% { 50% {
transform: translate(100%, 100%); transform: translate(100%, 100%);
} }
100% { 100% {
transform: translate(0, 100%); transform: translate(0, 100%);
} }
} }
@keyframes box3 { @keyframes box3 {
0%, 0%,
50% { 50% {
transform: translate(100%, 100%); transform: translate(100%, 100%);
} }
100% { 100% {
transform: translate(0, 100%); transform: translate(0, 100%);
} }
} }
@-webkit-keyframes box4 { @-webkit-keyframes box4 {
0% { 0% {
transform: translate(200%, 0); transform: translate(200%, 0);
} }
50% { 50% {
transform: translate(200%, 100%); transform: translate(200%, 100%);
} }
100% { 100% {
transform: translate(100%, 100%); transform: translate(100%, 100%);
} }
} }
@keyframes box4 { @keyframes box4 {
0% { 0% {
transform: translate(200%, 0); transform: translate(200%, 0);
} }
50% { 50% {
transform: translate(200%, 100%); transform: translate(200%, 100%);
} }
100% { 100% {
transform: translate(100%, 100%); transform: translate(100%, 100%);
} }
} }
</style> </style>

View File

@ -1,3 +1,18 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
interface iconPorps {
icon?: string
color?: string
size?: number
depth?: 1 | 2 | 3 | 4 | 5
}
const props = withDefaults(defineProps<iconPorps>(), {
size: 18,
icon: 'icon-park-outline:baby-feet',
})
</script>
<template> <template>
<n-icon <n-icon
:size="props.size" :size="props.size"
@ -8,19 +23,4 @@
</n-icon> </n-icon>
</template> </template>
<script setup lang="ts">
import { Icon } from '@iconify/vue';
interface iconPorps {
icon?: string;
color?: string;
size?: number;
depth?: 1 | 2 | 3 | 4 | 5;
}
const props = withDefaults(defineProps<iconPorps>(), {
size: 18,
icon: 'icon-park-outline:baby-feet',
});
</script>
<style scoped></style> <style scoped></style>

View File

@ -32,4 +32,4 @@ export const icons: string[] = [
'ic:baseline-filter-8', 'ic:baseline-filter-8',
'ic:baseline-filter-9', 'ic:baseline-filter-9',
'ic:baseline-filter-9-plus', 'ic:baseline-filter-9-plus',
]; ]

View File

@ -1,8 +1,26 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { icons } from './icons'
const currentIcon = ref('')
const searchValue = ref('')
const showPopover = ref(false)
const iconList = computed(() => icons.filter(item => item.includes(searchValue.value)))
function handleSelectIcon(icon: string) {
currentIcon.value = icon
showPopover.value = false
}
</script>
<template> <template>
<n-popover v-model:show="showPopover" placement="bottom" trigger="click"> <n-popover v-model:show="showPopover" placement="bottom" trigger="click">
<template #trigger> <template #trigger>
<n-input v-model:value="currentIcon" readonly placeholder="选择目标图标"> <n-input v-model:value="currentIcon" readonly placeholder="选择目标图标">
<template #suffix><e-icon :icon="currentIcon || 'icon-park-outline:all-application'" /></template> <template #suffix>
<e-icon :icon="currentIcon || 'icon-park-outline:all-application'" />
</template>
</n-input> </n-input>
</template> </template>
<template #header> <template #header>
@ -24,20 +42,4 @@
</n-popover> </n-popover>
</template> </template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { icons } from './icons';
const currentIcon = ref('');
const searchValue = ref('');
const showPopover = ref(false);
const iconList = computed(() => icons.filter((item) => item.includes(searchValue.value)));
function handleSelectIcon(icon: string) {
currentIcon.value = icon;
showPopover.value = false;
}
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,29 +1,15 @@
<template>
<n-pagination
v-if="props.count > 0"
v-model:page="page"
v-model:page-size="pageSize"
:item-count="props.count"
:display-order="displayOrder"
show-size-picker
:page-sizes="pageSizes"
@update-page="changePage"
@update-page-size="changePage"
/>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue'
const emit = defineEmits(['change']);
const props = defineProps({ const props = defineProps({
count: { count: {
type: Number, type: Number,
default: 0, default: 0,
}, },
}); })
const page = ref(1); const emit = defineEmits(['change'])
const pageSize = ref(10); const page = ref(1)
const displayOrder: Array<'pages' | 'size-picker' | 'quick-jumper'> = ['size-picker', 'pages']; const pageSize = ref(10)
const displayOrder: Array<'pages' | 'size-picker' | 'quick-jumper'> = ['size-picker', 'pages']
const pageSizes = [ const pageSizes = [
{ {
label: '10 每页', label: '10 每页',
@ -41,11 +27,25 @@ const pageSizes = [
label: '50 每页', label: '50 每页',
value: 50, value: 50,
}, },
]; ]
function changePage() { function changePage() {
emit('change', page.value, pageSize.value); emit('change', page.value, pageSize.value)
} }
</script> </script>
<template>
<n-pagination
v-if="props.count > 0"
v-model:page="page"
v-model:page-size="pageSize"
:item-count="props.count"
:display-order="displayOrder"
show-size-picker
:page-sizes="pageSizes"
@update-page="changePage"
@update-page-size="changePage"
/>
</template>
<style scoped></style> <style scoped></style>

View File

@ -1,21 +1,21 @@
<template>
<div>
<vue-qr v-if="props.text" :text="props.text" qid="testid" :size="props.size" :correct-level="1"></vue-qr>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import vueQr from 'vue-qr/src/packages/vue-qr.vue'; //https://www.npmjs.com/package/vue-qr import vueQr from 'vue-qr/src/packages/vue-qr.vue' // https://www.npmjs.com/package/vue-qr
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
text?: string; text?: string
size?: number; size?: number
}>(), }>(),
{ {
text: '', text: '',
size: 300, size: 300,
}, },
); )
</script> </script>
<template>
<div>
<vue-qr v-if="props.text" :text="props.text" qid="testid" :size="props.size" :correct-level="1" />
</div>
</template>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,22 @@
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps({
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: Number,
default: 18,
},
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>
<template> <template>
<svg <svg
aria-hidden="true" aria-hidden="true"
@ -11,22 +30,3 @@
/> />
</svg> </svg>
</template> </template>
<script lang="ts" setup>
import { computed } from 'vue';
const props = defineProps({
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: Number,
default: 18,
},
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>

View File

@ -1,21 +1,21 @@
/** 不同请求服务的环境配置 */ /** 不同请求服务的环境配置 */
export const proxyConfig: Record<ServiceEnvType, ServiceEnvConfig> = { export const proxyConfig: Record<ServiceEnvType, ServiceEnvConfig> = {
development: { development: {
url: 'https://mock.mengxuegu.com/mock/61e4df7c17249f68847fc191/api', url: 'https://mock.mengxuegu.com/mock/61e4df7c17249f68847fc191/api',
urlPattern: '/url-pattern', urlPattern: '/url-pattern',
secondUrl: 'http://localhost:8081', secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern', secondUrlPattern: '/second-url-pattern',
}, },
test: { test: {
url: 'http://localhost:8080', url: 'http://localhost:8080',
urlPattern: '/url-pattern', urlPattern: '/url-pattern',
secondUrl: 'http://localhost:8081', secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern', secondUrlPattern: '/second-url-pattern',
}, },
production: { production: {
url: 'http://localhost:8080', url: 'http://localhost:8080',
urlPattern: '/url-pattern', urlPattern: '/url-pattern',
secondUrl: 'http://localhost:8081', secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern', secondUrlPattern: '/second-url-pattern',
}, },
}; }

View File

@ -1,4 +1,4 @@
export * from './sdk'; export * from './sdk'
export * from './service'; export * from './service'
export * from './env'; export * from './env'
export * from './system'; export * from './system'

View File

@ -1,5 +1,5 @@
/* 高德地图开发SDk */ /* 高德地图开发SDk */
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=85e62187c6f8e51c797c87b1f36f787a'; export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=85e62187c6f8e51c797c87b1f36f787a'
/* 百度地图开发SDk */ /* 百度地图开发SDk */
export const BAIDU_MAP_SDK_URL = export const BAIDU_MAP_SDK_URL
'https://api.map.baidu.com/getscript?v=3.0&ak=MwqQwPxa5ipusyNmH1WT62y5DKhYxIgb&services=&t=20220816154130'; = 'https://api.map.baidu.com/getscript?v=3.0&ak=MwqQwPxa5ipusyNmH1WT62y5DKhYxIgb&services=&t=20220816154130'

View File

@ -1,58 +1,58 @@
/** 默认实例的Aixos配置 */ /** 默认实例的Aixos配置 */
import type { AxiosRequestConfig } from 'axios'; import type { AxiosRequestConfig } from 'axios'
export const DEFAULT_AXIOS_OPTIONS: AxiosRequestConfig = { export const DEFAULT_AXIOS_OPTIONS: AxiosRequestConfig = {
// 请求超时时间,默认15秒 // 请求超时时间,默认15秒
timeout: 15 * 1000, timeout: 15 * 1000,
}; }
/** 默认实例的后端字段配置 */ /** 默认实例的后端字段配置 */
export const DEFAULT_BACKEND_OPTIONS = { export const DEFAULT_BACKEND_OPTIONS = {
codeKey: 'code', codeKey: 'code',
dataKey: 'data', dataKey: 'data',
msgKey: 'msg', msgKey: 'msg',
successCode: 200, successCode: 200,
}; }
/** 错误信息的显示时间 */ /** 错误信息的显示时间 */
export const ERROR_MSG_DURATION = 3 * 1000; export const ERROR_MSG_DURATION = 3 * 1000
/** 默认的请求错误code */ /** 默认的请求错误code */
export const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT'; export const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT'
/** 默认的请求错误文本 */ /** 默认的请求错误文本 */
export const DEFAULT_REQUEST_ERROR_MSG = '请求错误~'; export const DEFAULT_REQUEST_ERROR_MSG = '请求错误~'
/** 请求超时的错误code */ /** 请求超时的错误code */
export const REQUEST_TIMEOUT_CODE = 'TIME_OUT'; export const REQUEST_TIMEOUT_CODE = 'TIME_OUT'
/** 请求超时的错误文本 */ /** 请求超时的错误文本 */
export const REQUEST_TIMEOUT_MSG = '请求超时~'; export const REQUEST_TIMEOUT_MSG = '请求超时~'
/** 默认的请求错误code */ /** 默认的请求错误code */
export const NETWORK_ERROR_CODE = 'NETWORK_ERROR'; export const NETWORK_ERROR_CODE = 'NETWORK_ERROR'
/** 默认的请求错误文本 */ /** 默认的请求错误文本 */
export const NETWORK_ERROR_MSG = '网络错误'; export const NETWORK_ERROR_MSG = '网络错误'
/** 请求不成功各种状态的错误 */ /** 请求不成功各种状态的错误 */
export const ERROR_STATUS = { export const ERROR_STATUS = {
400: '400: 请求出现语法错误~', 400: '400: 请求出现语法错误~',
401: '401: 用户未授权~', 401: '401: 用户未授权~',
403: '403: 服务器拒绝访问~', 403: '403: 服务器拒绝访问~',
404: '404: 请求的资源不存在~', 404: '404: 请求的资源不存在~',
405: '405: 请求方法未允许~', 405: '405: 请求方法未允许~',
408: '408: 网络请求超时~', 408: '408: 网络请求超时~',
500: '500: 服务器内部错误~', 500: '500: 服务器内部错误~',
501: '501: 服务器未实现请求功能~', 501: '501: 服务器未实现请求功能~',
502: '502: 错误网关~', 502: '502: 错误网关~',
503: '503: 服务不可用~', 503: '503: 服务不可用~',
504: '504: 网关超时~', 504: '504: 网关超时~',
505: '505: http版本不支持该请求~', 505: '505: http版本不支持该请求~',
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG, [DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG,
}; }
/** token刷新的code */ /** token刷新的code */
export const REFRESH_TOKEN_CODE = [888, 999]; export const REFRESH_TOKEN_CODE = [888, 999]
/** 没有错误提示的code */ /** 没有错误提示的code */
export const ERROR_NO_TIP_STATUS = [10000]; export const ERROR_NO_TIP_STATUS = [10000]

View File

@ -1,8 +1,8 @@
/** 本地存储前缀 */ /** 本地存储前缀 */
export const STORAGE_PREFIX = ''; export const STORAGE_PREFIX = ''
/** 本地存储加密密钥 */ /** 本地存储加密密钥 */
export const STORAGE_ENCRYPT_SECRET = '__CryptoJS_Secret__'; export const STORAGE_ENCRYPT_SECRET = '__CryptoJS_Secret__'
/** 本地存储缓存时长 */ /** 本地存储缓存时长 */
export const STORAGE_DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; export const STORAGE_DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7

View File

@ -1,5 +1,5 @@
/** 用户性别 */ /** 用户性别 */
export const genderLabels: Record<NonNullable<CommonList.GenderType>, string> = { export const genderLabels: Record<NonNullable<CommonList.GenderType>, string> = {
0: '女', 0: '女',
1: '男' 1: '男',
}; }

View File

@ -1 +1 @@
export * from './business' export * from './business'

View File

@ -1,6 +1,6 @@
import type { App } from 'vue'; import type { App } from 'vue'
import { setupPermission } from './permission' import { setupPermission } from './permission'
export function setupDirectives(app: App) { export function setupDirectives(app: App) {
setupPermission(app); setupPermission(app)
} }

View File

@ -1,17 +1,15 @@
import type { App, Directive } from 'vue'; import type { App, Directive } from 'vue'
import { usePermission } from '@/hooks'; import { usePermission } from '@/hooks'
export function setupPermission(app: App) { export function setupPermission(app: App) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission()
function updatapermission(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) { function updatapermission(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) {
if (!permission) { if (!permission)
throw new Error(`v-permissson Directive with no explicit role attached`); throw new Error('v-permissson Directive with no explicit role attached')
}
if (!hasPermission(permission)) { if (!hasPermission(permission))
el.parentElement?.removeChild(el); el.parentElement?.removeChild(el)
}
} }
const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = { const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = {
@ -20,7 +18,7 @@ export function setupPermission(app: App) {
}, },
updated(el, binding) { updated(el, binding) {
updatapermission(el, binding.value) updatapermission(el, binding.value)
} },
} }
app.directive('permission', permissionDirective) app.directive('permission', permissionDirective)
} }

View File

@ -1,6 +1,6 @@
export * from './useAppRouter'; export * from './useAppRouter'
export * from './useBoolean'; export * from './useBoolean'
export * from './useLoading'; export * from './useLoading'
export * from './useEcharts'; export * from './useEcharts'
export * from './useClipBoard'; export * from './useClipBoard'
export * from './useSystem'; export * from './useSystem'

View File

@ -1,5 +1,6 @@
import { useRouter, RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router'
import { router as gobalRouter } from '@/router'; import { useRouter } from 'vue-router'
import { router as gobalRouter } from '@/router'
/** /**
* @description: vue-router自带的useRouter * @description: vue-router自带的useRouter
@ -7,45 +8,44 @@ import { router as gobalRouter } from '@/router';
* @return {*} * @return {*}
*/ */
export function useAppRouter(isSetup = true) { export function useAppRouter(isSetup = true) {
const router = isSetup ? useRouter() : gobalRouter; const router = isSetup ? useRouter() : gobalRouter
const route = router.currentRoute; const route = router.currentRoute
/* 路由跳转方法 */ /* 路由跳转方法 */
function routerPush(to: RouteLocationRaw) { function routerPush(to: RouteLocationRaw) {
router.push(to); router.push(to)
} }
/* 路由跳转方法 */ /* 路由跳转方法 */
function routerReplace(to: RouteLocationRaw) { function routerReplace(to: RouteLocationRaw) {
router.replace(to); router.replace(to)
} }
/* 前进后退方法 */ /* 前进后退方法 */
function routerGo(delta: number) { function routerGo(delta: number) {
router.go(delta); router.go(delta)
} }
/* 跳转根页方法 */ /* 跳转根页方法 */
function toRoot() { function toRoot() {
routerPush({ path: '/appRoot' }); routerPush({ path: '/appRoot' })
} }
/* 跳转至登录页 */ /* 跳转至登录页 */
function toLogin(redirectUrl?: string) { function toLogin(redirectUrl?: string) {
const redirect = redirectUrl || route.value.fullPath; const redirect = redirectUrl || route.value.fullPath
const targetUrl = { const targetUrl = {
name: 'login', name: 'login',
query: { redirect }, query: { redirect },
}; }
routerPush(targetUrl); routerPush(targetUrl)
} }
/* 跳转重定向方法 */ /* 跳转重定向方法 */
function toLoginRedirect() { function toLoginRedirect() {
const { query } = route.value; const { query } = route.value
if (query?.redirect) { if (query?.redirect)
routerPush(query.redirect as string); routerPush(query.redirect as string)
} else { else
toRoot(); toRoot()
}
} }
return { return {
@ -55,5 +55,5 @@ export function useAppRouter(isSetup = true) {
toRoot, toRoot,
toLogin, toLogin,
toLoginRedirect, toLoginRedirect,
}; }
} }

View File

@ -1,23 +1,23 @@
import { ref } from 'vue'; import { ref } from 'vue'
/** /**
* boolean组合式函数 * boolean组合式函数
* @param initValue * @param initValue
*/ */
export function useBoolean(initValue = false) { export function useBoolean(initValue = false) {
const bool = ref(initValue); const bool = ref(initValue)
function setBool(value: boolean) { function setBool(value: boolean) {
bool.value = value; bool.value = value
} }
function setTrue() { function setTrue() {
setBool(true); setBool(true)
} }
function setFalse() { function setFalse() {
setBool(false); setBool(false)
} }
function toggle() { function toggle() {
setBool(!bool.value); setBool(!bool.value)
} }
return { return {
@ -26,5 +26,5 @@ export function useBoolean(initValue = false) {
setTrue, setTrue,
setFalse, setFalse,
toggle, toggle,
}; }
} }

View File

@ -1,26 +1,26 @@
export function useClipBoard() { export function useClipBoard() {
function isSupport() { function isSupport() {
return !navigator.clipboard; return !navigator.clipboard
} }
async function copy(text: string) { async function copy(text: string) {
if (isSupport()) { if (isSupport())
return window.$message?.error('当前浏览器不支持复制!'); return window.$message?.error('当前浏览器不支持复制!')
}
if (!text) { if (!text) {
window.$message?.error('请输入要复制的内容'); window.$message?.error('请输入要复制的内容')
return; return
} }
navigator.clipboard.writeText(text).then( navigator.clipboard.writeText(text).then(
() => { () => {
window.$message?.success(`复制成功:${text}`); window.$message?.success(`复制成功:${text}`)
}, },
() => { () => {
window.$message?.error('复制失败'); window.$message?.error('复制失败')
}, },
); )
} }
return { return {
copy, copy,
}; }
} }

View File

@ -1,71 +1,71 @@
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core'
import { nextTick, ref, onUnmounted, watch } from 'vue'; import { nextTick, onUnmounted, ref, watch } from 'vue'
import type { Ref } from 'vue'; import type { Ref } from 'vue'
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts'; import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts'
// 系列类型的定义后缀都为 SeriesOption // 系列类型的定义后缀都为 SeriesOption
import type { import type {
BarSeriesOption, BarSeriesOption,
LineSeriesOption, LineSeriesOption,
PieSeriesOption, PieSeriesOption,
RadarSeriesOption, RadarSeriesOption,
} from 'echarts/charts'; } from 'echarts/charts'
// 组件类型的定义后缀都为 ComponentOption // 组件类型的定义后缀都为 ComponentOption
import type { import type {
TitleComponentOption, DatasetComponentOption,
TooltipComponentOption, GridComponentOption,
GridComponentOption, LegendComponentOption,
LegendComponentOption, TitleComponentOption,
DatasetComponentOption, ToolboxComponentOption,
ToolboxComponentOption, TooltipComponentOption,
} from 'echarts/components'; } from 'echarts/components'
import { import {
TitleComponent, DatasetComponent, // 数据集组件
TooltipComponent, GridComponent,
GridComponent, LegendComponent,
LegendComponent, TitleComponent,
DatasetComponent, // 数据集组件 ToolboxComponent,
TransformComponent, // 内置数据转换器组件 (filter, sort) TooltipComponent,
ToolboxComponent, TransformComponent, // 内置数据转换器组件 (filter, sort)
} from 'echarts/components'; } from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'; import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers'
import { useAppStore } from '@/store'; import { useElementSize } from '@vueuse/core'
import { useElementSize } from '@vueuse/core'; import { useAppStore } from '@/store'
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型 // 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = echarts.ComposeOption< export type ECOption = echarts.ComposeOption<
| BarSeriesOption | BarSeriesOption
| PieSeriesOption | PieSeriesOption
| LineSeriesOption | LineSeriesOption
| TitleComponentOption | TitleComponentOption
| TooltipComponentOption | TooltipComponentOption
| GridComponentOption | GridComponentOption
| LegendComponentOption | LegendComponentOption
| DatasetComponentOption | DatasetComponentOption
| ToolboxComponentOption | ToolboxComponentOption
| RadarSeriesOption | RadarSeriesOption
>; >
// 注册必须的组件 // 注册必须的组件
echarts.use([ echarts.use([
TitleComponent, TitleComponent,
TooltipComponent, TooltipComponent,
GridComponent, GridComponent,
LegendComponent, LegendComponent,
DatasetComponent, DatasetComponent,
TransformComponent, TransformComponent,
BarChart, BarChart,
PieChart, PieChart,
LineChart, LineChart,
LabelLayout, LabelLayout,
UniversalTransition, UniversalTransition,
CanvasRenderer, CanvasRenderer,
ToolboxComponent, ToolboxComponent,
RadarChart, RadarChart,
]); ])
/** /**
* Echarts hooks函数 * Echarts hooks函数
@ -73,72 +73,69 @@ echarts.use([
* @description * @description
*/ */
export function useEcharts(options: Ref<ECOption>) { export function useEcharts(options: Ref<ECOption>) {
const appStore = useAppStore(); const appStore = useAppStore()
const domRef = ref<HTMLElement>(); const domRef = ref<HTMLElement>()
let chart: echarts.ECharts | null = null; let chart: echarts.ECharts | null = null
const initialSize = { width: 0, height: 0 }; const initialSize = { width: 0, height: 0 }
const { width, height } = useElementSize(domRef, initialSize); const { width, height } = useElementSize(domRef, initialSize)
function canRender() { function canRender() {
return initialSize.width > 0 && initialSize.height > 0; return initialSize.width > 0 && initialSize.height > 0
} }
function isRendered() { function isRendered() {
return Boolean(domRef.value && chart); return Boolean(domRef.value && chart)
} }
async function render() { async function render() {
const chartTheme = appStore.darkMode ? 'dark' : 'light'; const chartTheme = appStore.darkMode ? 'dark' : 'light'
await nextTick(); await nextTick()
if (domRef.value) { if (domRef.value) {
chart = echarts.init(domRef.value, chartTheme); chart = echarts.init(domRef.value, chartTheme)
update(options.value); update(options.value)
} }
} }
function update(updateOptions: ECOption) { function update(updateOptions: ECOption) {
if (isRendered()) { if (isRendered())
chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' }); chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' })
} }
}
function resize() { function resize() {
chart?.resize(); chart?.resize()
} }
function destroy() { function destroy() {
chart?.dispose(); chart?.dispose()
chart = null; chart = null
} }
const sizeWatch = watch([width, height], ([newWidth, newHeight]) => { const sizeWatch = watch([width, height], ([newWidth, newHeight]) => {
initialSize.width = newWidth; initialSize.width = newWidth
initialSize.height = newHeight; initialSize.height = newHeight
if (newWidth === 0 && newHeight === 0) { if (newWidth === 0 && newHeight === 0) {
// 节点被删除 将chart置为空 // 节点被删除 将chart置为空
chart = null; chart = null
} }
if (!canRender()) return; if (!canRender())
if (isRendered()) { return
resize(); if (isRendered())
} else { resize()
render(); else render()
} })
});
const OptionWatch = watch(options, (newValue) => { const OptionWatch = watch(options, (newValue) => {
console.log(newValue); update(newValue)
update(newValue); })
});
onUnmounted(() => { onUnmounted(() => {
sizeWatch(); sizeWatch()
OptionWatch(); OptionWatch()
destroy(); destroy()
}); })
return { return {
domRef, domRef,
}; }
} }

View File

@ -1,11 +1,15 @@
import { useBoolean } from './useBoolean'; import { useBoolean } from './useBoolean'
export function useLoading(initValue = false) { export function useLoading(initValue = false) {
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(); const {
bool: loading,
setTrue: startLoading,
setFalse: endLoading,
} = useBoolean(initValue)
return { return {
loading, loading,
startLoading, startLoading,
endLoading, endLoading,
}; }
} }

View File

@ -1,56 +1,54 @@
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store'
import { isArray, isString } from '@/utils'; import { isArray, isString } from '@/utils'
interface AppInfo { interface AppInfo {
/** 项目名称 */ /** 项目名称 */
name: string; name: string
/** 项目标题 */ /** 项目标题 */
title: string; title: string
/** 项目描述 */ /** 项目描述 */
desc: string; desc: string
} }
/** 项目信息 */ /** 项目信息 */
export function useAppInfo(): AppInfo { export function useAppInfo(): AppInfo {
const { const {
VITE_APP_NAME: name, VITE_APP_NAME: name,
VITE_APP_TITLE: title, VITE_APP_TITLE: title,
VITE_APP_DESC: desc, VITE_APP_DESC: desc,
} = import.meta.env; } = import.meta.env
return { return {
name, name,
title, title,
desc, desc,
}; }
} }
/** 权限判断 */ /** 权限判断 */
export function usePermission() { export function usePermission() {
const authStore = useAuthStore() const authStore = useAuthStore()
function hasPermission(permission: Auth.RoleType | Auth.RoleType[] | undefined) { function hasPermission(permission: Auth.RoleType | Auth.RoleType[] | undefined) {
if (!permission)
return true
if (!permission) return true if (!authStore.userInfo)
return false
const { role } = authStore.userInfo
if (!authStore.userInfo) return false let has = role === 'super'
const { role } = authStore.userInfo if (!has) {
if (isArray(permission))
has = (permission as Auth.RoleType[]).includes(role)
let has = role === 'super'; if (isString(permission))
if (!has) { has = (permission as Auth.RoleType) === role
if (isArray(permission)) { }
has = (permission as Auth.RoleType[]).includes(role); return has
} }
if (isString(permission)) {
has = (permission as Auth.RoleType) === role;
}
}
return has;
}
return { return {
hasPermission hasPermission,
}; }
} }

View File

@ -1,3 +1,27 @@
<script lang="ts" setup>
import {
BackTop,
Breadcrumb,
CollapaseButton,
DarkMode,
FullScreen,
Github,
Logo,
Menu,
Notices,
Reload,
Search,
Setting,
TabBar,
UserCenter,
Watermark,
} from '../components'
import { useAppStore, useRouteStore } from '@/store'
const routeStore = useRouteStore()
const appStore = useAppStore()
</script>
<template> <template>
<n-layout <n-layout
has-sider has-sider
@ -83,35 +107,11 @@
</n-layout> </n-layout>
</template> </template>
<script lang="ts" setup>
import {
Breadcrumb,
CollapaseButton,
Menu,
Logo,
FullScreen,
DarkMode,
Setting,
Github,
Notices,
UserCenter,
Search,
Reload,
TabBar,
BackTop,
Watermark,
} from '../components';
import { useAppStore, useRouteStore } from '@/store';
const routeStore = useRouteStore();
const appStore = useAppStore();
</script>
<style scoped> <style scoped>
.n-layout-sider { .n-layout-sider {
box-shadow: 2px 0 8px #1d23290d; box-shadow: 2px 0 8px #1d23290d;
} }
.n-layout-header { .n-layout-header {
box-shadow: 0 1px 2px #00152914; box-shadow: 0 1px 2px #00152914;
} }
</style> </style>

View File

@ -1,3 +1,5 @@
<script setup lang="ts"></script>
<template> <template>
<n-back-top :bottom="80" :visibility-height="300"> <n-back-top :bottom="80" :visibility-height="300">
<n-tooltip placement="left" trigger="hover"> <n-tooltip placement="left" trigger="hover">
@ -11,6 +13,4 @@
</n-back-top> </n-back-top>
</template> </template>
<script setup lang="ts"></script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,5 @@
<script setup lang="ts"></script>
<template> <template>
<n-el <n-el
tag="div" tag="div"
@ -12,16 +14,14 @@
</n-el> </n-el>
</template> </template>
<script setup lang="ts"></script>
<style scoped> <style scoped>
.el { .el {
color: var(--n-text-color); color: var(--n-text-color);
background-color: var(--n-color); background-color: var(--n-color);
transition: 0.3s var(--cubic-bezier-ease-in-out); transition: 0.3s var(--cubic-bezier-ease-in-out);
} }
.el:hover { .el:hover {
background-color: var(--button-color-2-hover); background-color: var(--button-color-2-hover);
color: var(--n-text-color-hover); color: var(--n-text-color-hover);
} }
</style> </style>

View File

@ -1,3 +1,18 @@
<script setup lang="ts">
interface Props {
list?: Message.List[]
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
interface Emits {
(e: 'read', val: number): void
}
function handleRead(index: number) {
emit('read', index)
}
</script>
<template> <template>
<n-scrollbar style="height: 400px"> <n-scrollbar style="height: 400px">
<n-list hoverable clickable> <n-list hoverable clickable>
@ -12,34 +27,22 @@
<e-icon :icon="item.icon" :size="30" class="c-primary" /> <e-icon :icon="item.icon" :size="30" class="c-primary" />
</template> </template>
<template v-if="item.tagTitle" #header-extra> <template v-if="item.tagTitle" #header-extra>
<n-tag :bordered="false" :type="item.tagType" size="small">{{ item.tagTitle }}</n-tag> <n-tag :bordered="false" :type="item.tagType" size="small">
{{ item.tagTitle }}
</n-tag>
</template> </template>
<template v-if="item.description" #description> <template v-if="item.description" #description>
<n-ellipsis :line-clamp="2"> <n-ellipsis :line-clamp="2">
{{ item.description }} {{ item.description }}
</n-ellipsis> </n-ellipsis>
</template> </template>
<template #footer>{{ item.date }}</template> <template #footer>
{{ item.date }}
</template>
</n-thing> </n-thing>
</n-list-item> </n-list-item>
</n-list> </n-list>
</n-scrollbar> </n-scrollbar>
</template> </template>
<script setup lang="ts">
interface Props {
list?: Message.List[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'read', val: number): void;
}
const emit = defineEmits<Emits>();
function handleRead(index: number) {
emit('read', index);
}
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,12 @@
<script setup lang="ts">
interface Props {
showWatermark: boolean
}
const props = withDefaults(defineProps<Props>(), {
showWatermark: false,
})
</script>
<template> <template>
<n-watermark <n-watermark
v-if="props.showWatermark" v-if="props.showWatermark"
@ -13,12 +22,3 @@
:rotate="-15" :rotate="-15"
/> />
</template> </template>
<script setup lang="ts">
interface Props {
showWatermark: boolean;
}
const props = withDefaults(defineProps<Props>(), {
showWatermark: false,
});
</script>

View File

@ -1,3 +1,17 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useRouteStore } from '@/store'
import { useAppRouter } from '@/hooks'
const router = useRouter()
const routeStore = useRouteStore()
const { routerPush } = useAppRouter()
const routes = computed(() => {
return routeStore.createBreadcrumbFromRoutes(router.currentRoute.value.name as string)
})
</script>
<template> <template>
<n-breadcrumb class="px-4"> <n-breadcrumb class="px-4">
<n-breadcrumb-item <n-breadcrumb-item
@ -11,18 +25,4 @@
</n-breadcrumb> </n-breadcrumb>
</template> </template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useRouteStore } from '@/store';
import { useAppRouter } from '@/hooks';
const router = useRouter();
const routeStore = useRouteStore();
const { routerPush } = useAppRouter();
const routes = computed(() => {
return routeStore.createBreadcrumbFromRoutes(router.currentRoute.value.name as string);
});
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,10 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
import { useAppStore } from '@/store'
const appStore = useAppStore()
</script>
<template> <template>
<n-tooltip placement="bottom" trigger="hover"> <n-tooltip placement="bottom" trigger="hover">
<template #trigger> <template #trigger>
@ -10,11 +17,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import { useAppStore } from '@/store';
import HeaderButton from '../common/HeaderButton.vue';
const appStore = useAppStore();
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,7 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
</script>
<template> <template>
<n-tooltip <n-tooltip
placement="bottom" placement="bottom"
@ -12,8 +16,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,9 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
import { useAppStore } from '@/store'
const appStore = useAppStore()
</script>
<template> <template>
<n-tooltip placement="bottom" trigger="hover"> <n-tooltip placement="bottom" trigger="hover">
<template #trigger> <template #trigger>
@ -10,10 +16,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
import { useAppStore } from '@/store';
const appStore = useAppStore();
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,10 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
const toMyGithub = () => {
window.open('https://github.com/iam-see')
}
</script>
<template> <template>
<n-tooltip placement="bottom" trigger="hover"> <n-tooltip placement="bottom" trigger="hover">
<template #trigger> <template #trigger>
@ -9,11 +16,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
const toMyGithub = () => {
window.open('https://github.com/iam-see');
};
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,35 +1,7 @@
<template>
<n-popover placement="bottom" trigger="click" arrow-point-to-center class="!p-0">
<template #trigger>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<HeaderButton>
<n-badge :value="massageCount" :max="99" style="color: unset">
<i-icon-park-outline-remind />
</n-badge>
</HeaderButton>
</template>
<span>消息通知</span>
</n-tooltip>
</template>
<n-tabs v-model:value="currentTab" type="line" animated justify-content="space-evenly" class="w-390px">
<n-tab-pane v-for="item in MassageData" :key="item.key" :name="item.key">
<template #tab>
<n-space class="w-130px" justify="center">
{{ item.name }}
<n-badge v-bind="item.badgeProps" :value="item.list.filter((item) => !item.isRead).length" :max="99" />
</n-space>
</template>
<NoticeList :list="item.list" @read="handleRead" />
</n-tab-pane>
</n-tabs>
</n-popover>
</template>
<script setup lang="ts"> <script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'; import { computed, ref } from 'vue'
import NoticeList from '../common/NoticeList.vue'; import HeaderButton from '../common/HeaderButton.vue'
import { ref, computed } from 'vue'; import NoticeList from '../common/NoticeList.vue'
const MassageData = ref<Message.Tab[]>([ const MassageData = ref<Message.Tab[]>([
{ {
@ -120,16 +92,44 @@ const MassageData = ref<Message.Tab[]>([
}, },
], ],
}, },
]); ])
const currentTab = ref(0); const currentTab = ref(0)
function handleRead(index: number) { function handleRead(index: number) {
MassageData.value[currentTab.value].list[index].isRead = true; MassageData.value[currentTab.value].list[index].isRead = true
} }
const massageCount = computed(() => { const massageCount = computed(() => {
return MassageData.value.reduce((pre, cur) => { return MassageData.value.reduce((pre, cur) => {
return pre + cur.list.filter((item) => !item.isRead).length; return pre + cur.list.filter(item => !item.isRead).length
}, 0); }, 0)
}); })
</script> </script>
<template>
<n-popover placement="bottom" trigger="click" arrow-point-to-center class="!p-0">
<template #trigger>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<HeaderButton>
<n-badge :value="massageCount" :max="99" style="color: unset">
<i-icon-park-outline-remind />
</n-badge>
</HeaderButton>
</template>
<span>消息通知</span>
</n-tooltip>
</template>
<n-tabs v-model:value="currentTab" type="line" animated justify-content="space-evenly" class="w-390px">
<n-tab-pane v-for="item in MassageData" :key="item.key" :name="item.key">
<template #tab>
<n-space class="w-130px" justify="center">
{{ item.name }}
<n-badge v-bind="item.badgeProps" :value="item.list.filter((item) => !item.isRead).length" :max="99" />
</n-space>
</template>
<NoticeList :list="item.list" @read="handleRead" />
</n-tab-pane>
</n-tabs>
</n-popover>
</template>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,20 @@
<script setup lang="ts">
import { ref } from 'vue'
import HeaderButton from '../common/HeaderButton.vue'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const loading = ref(false)
const handleReload = () => {
loading.value = true
appStore.reloadPage()
setTimeout(() => {
loading.value = false
}, 800)
}
</script>
<template> <template>
<n-tooltip placement="bottom" trigger="hover"> <n-tooltip placement="bottom" trigger="hover">
<template #trigger> <template #trigger>
@ -9,21 +26,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
import { useAppStore } from '@/store';
import { ref } from 'vue';
const appStore = useAppStore();
const loading = ref(false);
const handleReload = () => {
loading.value = true;
appStore.reloadPage();
setTimeout(() => {
loading.value = false;
}, 800);
};
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,10 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
const handleSearch = () => {
window.$message.success('施工中...')
}
</script>
<template> <template>
<n-tooltip <n-tooltip
placement="bottom" placement="bottom"
@ -12,11 +19,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
const handleSearch = () => {
window.$message.success('施工中...')
};
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,15 @@
<script setup lang="ts">
import { ref } from 'vue'
import HeaderButton from '../common/HeaderButton.vue'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const drawerActive = ref(false)
const openSetting = () => {
drawerActive.value = !drawerActive.value
}
</script>
<template> <template>
<n-tooltip <n-tooltip
placement="bottom" placement="bottom"
@ -97,16 +109,4 @@
</n-tooltip> </n-tooltip>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
import { ref } from 'vue';
import { useAppStore } from '@/store';
const appStore = useAppStore();
const drawerActive = ref(false);
const openSetting = () => {
drawerActive.value = !drawerActive.value;
};
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,45 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import HeaderButton from '../common/HeaderButton.vue'
import { renderIcon } from '@/utils/icon'
import { useAuthStore } from '@/store'
const { userInfo, resetAuthStore } = useAuthStore()
const router = useRouter()
const options = [
{
label: '个人中心',
key: 'userCenter',
icon: renderIcon('carbon:user-avatar-filled-alt'),
},
{
type: 'divider',
key: 'd1',
},
{
label: '退出登录',
key: 'loginOut',
icon: renderIcon('icon-park-outline:logout'),
},
]
const handleSelect = (key: string | number) => {
if (key === 'loginOut') {
window.$dialog.info({
title: '退出登录',
content: '确认退出当前账号?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
resetAuthStore()
},
})
}
if (key === 'userCenter')
router.push('/userCenter')
}
</script>
<template> <template>
<n-dropdown <n-dropdown
trigger="click" trigger="click"
@ -15,47 +57,4 @@
</n-dropdown> </n-dropdown>
</template> </template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
import { renderIcon } from '@/utils/icon';
import { useAuthStore } from '@/store';
import { useRouter } from 'vue-router';
const { userInfo, resetAuthStore } = useAuthStore();
const router = useRouter()
const options = [
{
label: '个人中心',
key: 'userCenter',
icon: renderIcon('carbon:user-avatar-filled-alt'),
},
{
type: 'divider',
key: 'd1',
},
{
label: '退出登录',
key: 'loginOut',
icon: renderIcon('icon-park-outline:logout'),
},
];
const handleSelect = (key: string | number) => {
if (key === 'loginOut') {
window.$dialog.info({
title: '退出登录',
content: '确认退出当前账号?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
resetAuthStore();
},
})
}
if (key === 'userCenter') {
router.push('/userCenter')
}
};
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,41 +1,41 @@
/* 侧边栏组件 */ /* 侧边栏组件 */
import Logo from './sider/Logo.vue'; import Logo from './sider/Logo.vue'
import Menu from './sider/Menu.vue'; import Menu from './sider/Menu.vue'
/* 头部栏组件 */ /* 头部栏组件 */
import Breadcrumb from './header/Breadcrumb.vue'; import Breadcrumb from './header/Breadcrumb.vue'
import CollapaseButton from './header/CollapaseButton.vue'; import CollapaseButton from './header/CollapaseButton.vue'
import FullScreen from './header/FullScreen.vue'; import FullScreen from './header/FullScreen.vue'
import DarkMode from './header/DarkMode.vue'; import DarkMode from './header/DarkMode.vue'
import Setting from './header/Setting.vue'; import Setting from './header/Setting.vue'
import Github from './header/Github.vue'; import Github from './header/Github.vue'
import Notices from './header/Notices.vue'; import Notices from './header/Notices.vue'
import UserCenter from './header/UserCenter.vue'; import UserCenter from './header/UserCenter.vue'
import Search from './header/Search.vue'; import Search from './header/Search.vue'
import Reload from './header/Reload.vue'; import Reload from './header/Reload.vue'
/* 标签栏组件 */ /* 标签栏组件 */
import TabBar from './tab/TabBar.vue'; import TabBar from './tab/TabBar.vue'
/* 其他组件 */ /* 其他组件 */
// 返回顶部 // 返回顶部
import BackTop from './common/BackTop.vue'; import BackTop from './common/BackTop.vue'
import Watermark from './common/Watermark.vue'; import Watermark from './common/Watermark.vue'
export { export {
Breadcrumb, Breadcrumb,
CollapaseButton, CollapaseButton,
Menu, Menu,
Logo, Logo,
FullScreen, FullScreen,
DarkMode, DarkMode,
Setting, Setting,
Github, Github,
Notices, Notices,
UserCenter, UserCenter,
Search, Search,
Reload, Reload,
TabBar, TabBar,
BackTop, BackTop,
Watermark, Watermark,
}; }

View File

@ -1,3 +1,11 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
import { useAppInfo, useAppRouter } from '@/hooks'
const { name } = useAppInfo()
const { toRoot } = useAppRouter()
const appStore = useAppStore()
</script>
<template> <template>
<div <div
class="h-60px text-2xl flex-center overflow-hidden cursor-pointer" class="h-60px text-2xl flex-center overflow-hidden cursor-pointer"
@ -14,13 +22,4 @@
</div> </div>
</template> </template>
<script setup lang="ts">
import { useAppStore } from '@/store';
import { useAppRouter } from '@/hooks';
import { useAppInfo } from '@/hooks';
const { name } = useAppInfo();
const { toRoot } = useAppRouter();
const appStore = useAppStore();
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,11 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
import { useRouteStore } from '~/src/store/modules/route'
const appStore = useAppStore()
const routesStore = useRouteStore()
</script>
<template> <template>
<n-menu <n-menu
:collapsed="appStore.collapsed" :collapsed="appStore.collapsed"
@ -9,13 +17,4 @@
/> />
</template> </template>
<script setup lang="ts">
import { useAppStore } from '@/store';
import { useRouteStore } from '~/src/store/modules/route';
const appStore = useAppStore();
const routesStore = useRouteStore();
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,100 @@
<script setup lang="ts">
import type { RouteLocationNormalized } from 'vue-router'
import { nextTick, ref } from 'vue'
import { useAppStore, useTabStore } from '@/store'
import { useAppRouter } from '@/hooks'
import { renderIcon } from '@/utils'
const tabStore = useTabStore()
const appStore = useAppStore()
const { routerPush, toRoot } = useAppRouter()
function handleTab(route: RouteLocationNormalized) {
routerPush(route.path)
}
function handleClose(name: string) {
tabStore.closeTab(name)
}
const options = [
{
label: '刷新',
key: 'reload',
icon: renderIcon('icon-park-outline:redo'),
},
{
label: '关闭',
key: 'closeCurrent',
icon: renderIcon('icon-park-outline:close'),
},
{
label: '关闭其他',
key: 'closeOther',
icon: renderIcon('icon-park-outline:delete-four'),
},
{
label: '关闭左侧',
key: 'closeLeft',
icon: renderIcon('icon-park-outline:to-left'),
},
{
label: '关闭右侧',
key: 'closeRight',
icon: renderIcon('icon-park-outline:to-right'),
},
{
label: '全部关闭',
key: 'closeAll',
icon: renderIcon('icon-park-outline:fullwidth'),
},
]
const showDropdown = ref(false)
const x = ref(0)
const y = ref(0)
const currentRoute = ref()
function handleSelect(key: string) {
showDropdown.value = false
interface HandleFn {
[key: string]: any
}
const handleFn: HandleFn = {
reload() {
appStore.reloadPage()
},
closeCurrent() {
tabStore.closeTab(currentRoute.value.name)
},
closeOther() {
tabStore.closeOtherTabs(currentRoute.value.name)
},
closeLeft() {
tabStore.closeLeftTabs(currentRoute.value.name)
},
closeRight() {
tabStore.closeRightTabs(currentRoute.value.name)
},
closeAll() {
tabStore.closeAllTabs()
},
}
handleFn[key]()
}
function handleContextMenu(e: MouseEvent, route: RouteLocationNormalized) {
e.preventDefault()
currentRoute.value = route
showDropdown.value = false
nextTick().then(() => {
showDropdown.value = true
x.value = e.clientX
y.value = e.clientY
})
}
function onClickoutside() {
showDropdown.value = false
}
</script>
<template> <template>
<div class="wh-full flex items-end"> <div class="wh-full flex items-end">
<n-tabs <n-tabs
@ -41,101 +138,4 @@
</div> </div>
</template> </template>
<script setup lang="ts">
import { useTabStore, useAppStore } from '@/store';
import { useAppRouter } from '@/hooks';
import { RouteLocationNormalized } from 'vue-router';
import { ref, nextTick } from 'vue';
import { renderIcon } from '@/utils';
const tabStore = useTabStore();
const appStore = useAppStore();
const { routerPush, toRoot } = useAppRouter();
function handleTab(route: RouteLocationNormalized) {
routerPush(route.path);
}
function handleClose(name: string) {
tabStore.closeTab(name);
}
const options = [
{
label: '刷新',
key: 'reload',
icon: renderIcon('icon-park-outline:redo'),
},
{
label: '关闭',
key: 'closeCurrent',
icon: renderIcon('icon-park-outline:close'),
},
{
label: '关闭其他',
key: 'closeOther',
icon: renderIcon('icon-park-outline:delete-four'),
},
{
label: '关闭左侧',
key: 'closeLeft',
icon: renderIcon('icon-park-outline:to-left'),
},
{
label: '关闭右侧',
key: 'closeRight',
icon: renderIcon('icon-park-outline:to-right'),
},
{
label: '全部关闭',
key: 'closeAll',
icon: renderIcon('icon-park-outline:fullwidth'),
},
];
const showDropdown = ref(false);
const x = ref(0);
const y = ref(0);
const currentRoute = ref();
function handleSelect(key: string) {
showDropdown.value = false;
type HandleFn = {
[key: string]: any;
};
const handleFn: HandleFn = {
reload() {
appStore.reloadPage();
},
closeCurrent() {
tabStore.closeTab(currentRoute.value.name);
},
closeOther() {
tabStore.closeOtherTabs(currentRoute.value.name);
},
closeLeft() {
tabStore.closeLeftTabs(currentRoute.value.name);
},
closeRight() {
tabStore.closeRightTabs(currentRoute.value.name);
},
closeAll() {
tabStore.closeAllTabs();
},
};
handleFn[key]();
}
function handleContextMenu(e: MouseEvent, route: RouteLocationNormalized) {
e.preventDefault();
currentRoute.value = route;
showDropdown.value = false;
nextTick().then(() => {
showDropdown.value = true;
x.value = e.clientX;
y.value = e.clientY;
});
}
function onClickoutside() {
showDropdown.value = false;
}
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,3 @@
const BasicLayout = () => import('./BasicLayout/index.vue'); const BasicLayout = () => import('./BasicLayout/index.vue')
export { BasicLayout }; export { BasicLayout }

View File

@ -1,29 +1,29 @@
import { createApp } from 'vue'; import { createApp } from 'vue'
import App from './App.vue'; import App from './App.vue'
import AppLoading from './components/common/appLoading.vue'; import AppLoading from './components/common/appLoading.vue'
import { setupRouter } from './router'; import { setupRouter } from './router'
import { setupAssets } from './plugins'; import { setupAssets } from './plugins'
import { setupStore } from './store'; import { setupStore } from './store'
import { setupDirectives } from './directive' import { setupDirectives } from './directive'
async function setupApp() {
// 引入静态资源
setupAssets();
// 载入全局loading加载状态
const appLoading = createApp(AppLoading);
appLoading.mount('#appLoading');
// 创建vue实例 async function setupApp() {
const app = createApp(App); // 引入静态资源
// 安装pinia全局状态库 setupAssets()
setupStore(app); // 载入全局loading加载状态
// 安装自定义指令 const appLoading = createApp(AppLoading)
setupDirectives(app) appLoading.mount('#appLoading')
// 安装router
await setupRouter(app); // 创建vue实例
// 挂载 const app = createApp(App)
await app.mount('#app'); // 安装pinia全局状态库
// 卸载载入动画 setupStore(app)
appLoading.unmount(); // 安装自定义指令
setupDirectives(app)
// 安装router
await setupRouter(app)
// 挂载
await app.mount('#app')
// 卸载载入动画
appLoading.unmount()
} }
setupApp(); setupApp()

View File

@ -1,6 +1,6 @@
// 全局引入的静态资源 // 全局引入的静态资源
import 'uno.css'; import 'uno.css'
import '@/styles/css/index.css'; import '@/styles/css/index.css'
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register'
export default function setupAssets() {} export default function setupAssets() {}

View File

@ -1,3 +1,3 @@
import setupAssets from './assets'; import setupAssets from './assets'
export { setupAssets }; export { setupAssets }

View File

@ -1,86 +1,87 @@
import { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
import { BasicLayout } from '@/layouts/index'; import { BasicLayout } from '@/layouts/index'
import { useRouteStore } from '@/store'; import { useRouteStore } from '@/store'
import { usePermission } from '@/hooks'; import { usePermission } from '@/hooks'
// 引入所有页面 // 引入所有页面
const modules = import.meta.glob('../../views/**/*.vue'); const modules = import.meta.glob('../../views/**/*.vue')
/* 含有子级的路由重定向到第一个子级 */ /* 含有子级的路由重定向到第一个子级 */
function setRedirect(routes: AppRoute.Route[]) { function setRedirect(routes: AppRoute.Route[]) {
routes.forEach((route) => { routes.forEach((route) => {
if (route.children) { if (route.children) {
const nonHiddenChild = route.children.find((child) => !child.meta || !child.meta.hide); const nonHiddenChild = route.children.find(child => !child.meta || !child.meta.hide)
if (nonHiddenChild) { if (nonHiddenChild)
route.redirect = nonHiddenChild.path; route.redirect = nonHiddenChild.path
}
setRedirect(route.children); setRedirect(route.children)
} }
}); })
} }
/* 路由树转换成一维数组 */ /* 路由树转换成一维数组 */
function FlatAuthRoutes(routes: AppRoute.Route[]) { function FlatAuthRoutes(routes: AppRoute.Route[]) {
let result: AppRoute.Route[] = []; let result: AppRoute.Route[] = []
routes.forEach((item: AppRoute.Route) => { routes.forEach((item: AppRoute.Route) => {
if (item.children) { if (item.children) {
const temp = item.children || []; const temp = item.children || []
delete item.children; delete item.children
result.push(item); result.push(item)
result = [...result, ...FlatAuthRoutes(temp)]; result = [...result, ...FlatAuthRoutes(temp)]
} else { }
result.push(item); else {
} result.push(item)
}); }
return result; })
return result
} }
/* 路由无权限过滤 */ /* 路由无权限过滤 */
function filterPermissionRoutes(routes: AppRoute.Route[]) { function filterPermissionRoutes(routes: AppRoute.Route[]) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission()
return routes.filter((route) => { return routes.filter((route) => {
return hasPermission(route.meta.roles); return hasPermission(route.meta.roles)
}); })
} }
function createCatheRoutes(routes: AppRoute.Route[]) { function createCatheRoutes(routes: AppRoute.Route[]) {
return routes return routes
.filter((item) => { .filter((item) => {
return item.meta.keepAlive; return item.meta.keepAlive
}) })
.map((item) => item.name); .map(item => item.name)
} }
export async function createDynamicRoutes(routes: AppRoute.Route[]) { export async function createDynamicRoutes(routes: AppRoute.Route[]) {
/* 复制一层 */ /* 复制一层 */
let resultRouter = JSON.parse(JSON.stringify(routes)); let resultRouter = JSON.parse(JSON.stringify(routes))
/* 设置路由重定向到子级第一个 */ /* 设置路由重定向到子级第一个 */
setRedirect(resultRouter); setRedirect(resultRouter)
// 数组降维成一维数组,然后删除所有的childen // 数组降维成一维数组,然后删除所有的childen
resultRouter = FlatAuthRoutes(resultRouter); resultRouter = FlatAuthRoutes(resultRouter)
/* 路由权限过滤 */ /* 路由权限过滤 */
resultRouter = filterPermissionRoutes(resultRouter); resultRouter = filterPermissionRoutes(resultRouter)
// 过滤需要缓存的路由name数组 // 过滤需要缓存的路由name数组
const routeStore = useRouteStore(); const routeStore = useRouteStore()
routeStore.cacheRoutes = createCatheRoutes(resultRouter); routeStore.cacheRoutes = createCatheRoutes(resultRouter)
// 生成路由有redirect的不需要引入文件 // 生成路由有redirect的不需要引入文件
resultRouter = resultRouter.map((item: any) => { resultRouter = resultRouter.map((item: any) => {
if (!item.redirect) { if (!item.redirect) {
// 动态加载对应页面 // 动态加载对应页面
item['component'] = modules[`../../views${item.path}/index.vue`]; item.component = modules[`../../views${item.path}/index.vue`]
} }
return item; return item
}); })
const appRootRoute: RouteRecordRaw = { const appRootRoute: RouteRecordRaw = {
path: '/appRoot', path: '/appRoot',
name: 'appRoot', name: 'appRoot',
redirect: '/dashboard/workbench', redirect: '/dashboard/workbench',
component: BasicLayout, component: BasicLayout,
meta: { meta: {
title: '首页', title: '首页',
icon: 'icon-park-outline:home', icon: 'icon-park-outline:home',
}, },
children: [], children: [],
}; }
// 根据角色过滤后的插入根路由中 // 根据角色过滤后的插入根路由中
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]; appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
return appRootRoute; return appRootRoute
} }

View File

@ -1,39 +1,38 @@
import type { Router } from 'vue-router'; import type { Router } from 'vue-router'
import { createPermissionGuard } from './permission'; import { createPermissionGuard } from './permission'
import { useAppInfo } from '@/hooks'; import { useAppInfo } from '@/hooks'
import { useRouteStore, useTabStore } from '@/store'; import { useRouteStore, useTabStore } from '@/store'
const { title } = useAppInfo();
const { title } = useAppInfo()
export function setupRouterGuard(router: Router) { export function setupRouterGuard(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
// 判断是否是外链,如果是直接打开网页并拦截跳转 // 判断是否是外链,如果是直接打开网页并拦截跳转
if (to.meta.herf) { if (to.meta.herf) {
window.open(to.meta.herf); window.open(to.meta.herf)
return false; return false
} }
// 开始 loadingBar // 开始 loadingBar
window.$loadingBar?.start(); window.$loadingBar?.start()
// 权限操作 // 权限操作
await createPermissionGuard(to, from, next); await createPermissionGuard(to, from, next)
}); })
router.beforeResolve(async (to) => { router.beforeResolve(async (to) => {
const routeStore = useRouteStore(); const routeStore = useRouteStore()
const tabStore = useTabStore(); const tabStore = useTabStore()
// 设置菜单高亮 // 设置菜单高亮
routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath); routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath)
// 添加tabs // 添加tabs
tabStore.addTab(to); tabStore.addTab(to)
// 设置高亮标签; // 设置高亮标签;
tabStore.setCurrentTab(to.name as string); tabStore.setCurrentTab(to.name as string)
}) })
router.afterEach((to) => { router.afterEach((to) => {
// 修改网页标题 // 修改网页标题
document.title = `${to.meta.title} - ${title}`; document.title = `${to.meta.title} - ${title}`
// 结束 loadingBar // 结束 loadingBar
window.$loadingBar?.finish(); window.$loadingBar?.finish()
}); })
} }

View File

@ -1,36 +1,36 @@
import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router'; import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { local } from '@/utils'; import { local } from '@/utils'
import { useRouteStore } from '@/store'; import { useRouteStore } from '@/store'
export async function createPermissionGuard( export async function createPermissionGuard(
to: RouteLocationNormalized, to: RouteLocationNormalized,
from: RouteLocationNormalized, from: RouteLocationNormalized,
next: NavigationGuardNext next: NavigationGuardNext,
) { ) {
const routeStore = useRouteStore(); const routeStore = useRouteStore()
// 判断有无TOKEN,登录鉴权 // 判断有无TOKEN,登录鉴权
const isLogin = Boolean(local.get('token')); const isLogin = Boolean(local.get('token'))
if (!isLogin) { if (!isLogin) {
if (to.name == 'login') { if (to.name === 'login')
next() next()
}
if (to.name !== 'login') { if (to.name !== 'login') {
const redirect = to.name === '404' ? undefined : to.fullPath; const redirect = to.name === '404' ? undefined : to.fullPath
next({ path: '/login', query: { redirect } }); next({ path: '/login', query: { redirect } })
} }
return false return false
} }
// 判断路由有无进行初始化 // 判断路由有无进行初始化
if (!routeStore.isInitAuthRoute) { if (!routeStore.isInitAuthRoute) {
await routeStore.initAuthRoute(); await routeStore.initAuthRoute()
// 动态路由加载完回到根路由 // 动态路由加载完回到根路由
if (to.name === '404' && to.redirectedFrom) { if (to.name === '404' && to.redirectedFrom) {
// 等待权限路由加载好了,回到之前的路由,否则404 // 等待权限路由加载好了,回到之前的路由,否则404
const path = to.redirectedFrom?.fullPath; const path = to.redirectedFrom?.fullPath
next({ path, replace: true, query: to.query, hash: to.hash }); next({ path, replace: true, query: to.query, hash: to.hash })
return false; return false
} }
} }
@ -43,7 +43,7 @@ export async function createPermissionGuard(
// 判断当前页是否在login,则定位去首页 // 判断当前页是否在login,则定位去首页
if (to.name === 'login') { if (to.name === 'login') {
next({ path: '/appRoot' }) next({ path: '/appRoot' })
return false; return false
} }
next() next()

View File

@ -1,17 +1,17 @@
import type { App } from 'vue'; import type { App } from 'vue'
import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import { setupRouterGuard } from './guard'; import { setupRouterGuard } from './guard'
import { routes } from './routes'; import { routes } from './routes'
const { VITE_ROUTE_MODE = 'hash', VITE_BASE_URL } = import.meta.env; const { VITE_ROUTE_MODE = 'hash', VITE_BASE_URL } = import.meta.env
export const router = createRouter({ export const router = createRouter({
history: VITE_ROUTE_MODE === 'hash' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL), history: VITE_ROUTE_MODE === 'hash' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
routes, routes,
}); })
// 安装vue路由 // 安装vue路由
export async function setupRouter(app: App) { export async function setupRouter(app: App) {
// 添加路由守卫 // 添加路由守卫
setupRouterGuard(router); setupRouterGuard(router)
app.use(router); app.use(router)
await router.isReady(); //https://router.vuejs.org/zh/api/index.html#isready await router.isReady() // https://router.vuejs.org/zh/api/index.html#isready
} }

View File

@ -27,4 +27,4 @@ export const dashboard = {
}, },
}, },
], ],
}; }

View File

@ -1,4 +1,4 @@
import { dashboard } from './dashboard'; import { dashboard } from './dashboard'
import { test } from './test'; import { test } from './test'
export const staticRoutes = [dashboard, test]; export const staticRoutes = [dashboard, test]

View File

@ -60,4 +60,4 @@ export const test = {
], ],
}, },
], ],
}; }

View File

@ -1,5 +1,5 @@
import { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
import { BasicLayout } from '@/layouts/index'; import { BasicLayout } from '@/layouts/index'
/* 页面中的一些固定路由,错误页等 */ /* 页面中的一些固定路由,错误页等 */
export const routes: RouteRecordRaw[] = [ export const routes: RouteRecordRaw[] = [
@ -50,5 +50,5 @@ export const routes: RouteRecordRaw[] = [
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
redirect: '/404', redirect: '/404',
}, },
]; ]

View File

@ -1,19 +1,19 @@
import { mockRequest } from '../http'; import { mockRequest } from '../http'
interface Ilogin { interface Ilogin {
userName: string; userName: string
password: string; password: string
} }
export function fetchLogin(params: Ilogin) { export function fetchLogin(params: Ilogin) {
return mockRequest.post<any>('/login', params); return mockRequest.post<any>('/login', params)
} }
export function fetchUpdateToken(params: any) { export function fetchUpdateToken(params: any) {
return mockRequest.post<ApiAuth.loginToken>('/updateToken', params); return mockRequest.post<ApiAuth.loginToken>('/updateToken', params)
} }
export function fetchUserInfo(params:any) { export function fetchUserInfo(params: any) {
return mockRequest.get<Auth.UserInfo>('/getUserInfo',{params}); return mockRequest.get<Auth.UserInfo>('/getUserInfo', { params })
} }
export function fetchUserRoutes(params: { userId: number }) { export function fetchUserRoutes(params: { userId: number }) {
return mockRequest.post<any>('/getUserRoutes', params); return mockRequest.post<any>('/getUserRoutes', params)
} }

View File

@ -1,5 +1,5 @@
import { mockRequest } from '../http'; import { mockRequest } from '../http'
export function fetchUserList() { export function fetchUserList() {
return mockRequest.get('/userList'); return mockRequest.get('/userList')
} }

View File

@ -1,46 +1,45 @@
import { request } from '../http'; import { mockRequest, request } from '../http'
import { mockRequest } from '../http';
/* get方法测试 */ /* get方法测试 */
export function fetachGet(params?:any) { export function fetachGet(params?: any) {
return request.get('/getAPI', { params }); return request.get('/getAPI', { params })
} }
/* post方法测试 */ /* post方法测试 */
export function fetachPost(data: any) { export function fetachPost(data: any) {
return request.post('/postAPI', data); return request.post('/postAPI', data)
} }
/* formPost方法测试 */ /* formPost方法测试 */
export function fetachFormPost(data: any) { export function fetachFormPost(data: any) {
return request.formPost('/postAPI', data); return request.formPost('/postAPI', data)
} }
/* delete方法测试 */ /* delete方法测试 */
export function fetachDelete() { export function fetachDelete() {
return request.delete('/deleteAPI'); return request.delete('/deleteAPI')
} }
/* put方法测试 */ /* put方法测试 */
export function fetachPut(data: any) { export function fetachPut(data: any) {
return request.put('/putAPI', data); return request.put('/putAPI', data)
} }
/* 测试状态码500失败 */ /* 测试状态码500失败 */
export function testFailedRequest() { export function testFailedRequest() {
return request.get('/filedRequest'); return request.get('/filedRequest')
} }
/* 测试业务码500失败 */ /* 测试业务码500失败 */
export function testFailedResponse() { export function testFailedResponse() {
return request.get('/filedResponse'); return request.get('/filedResponse')
} }
/* 测试token刷新接口 */ /* 测试token刷新接口 */
export function testUpdataToken() { export function testUpdataToken() {
return request.get('/updataToken'); return request.get('/updataToken')
} }
/* 测试token刷新接口 */ /* 测试token刷新接口 */
export function testFailedResponse_NT() { export function testFailedResponse_NT() {
return request.get('/failedResponse_NT'); return request.get('/failedResponse_NT')
} }
/* mock方法测试 */ /* mock方法测试 */
export function fetchMock() { export function fetchMock() {
return mockRequest.post('/login'); return mockRequest.post('/login')
} }

View File

@ -1,19 +1,19 @@
import type { AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios'; import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { showError } from './utils'
import { import {
DEFAULT_REQUEST_ERROR_CODE, DEFAULT_REQUEST_ERROR_CODE,
DEFAULT_REQUEST_ERROR_MSG, DEFAULT_REQUEST_ERROR_MSG,
NETWORK_ERROR_CODE, ERROR_STATUS,
NETWORK_ERROR_MSG, NETWORK_ERROR_CODE,
REQUEST_TIMEOUT_CODE, NETWORK_ERROR_MSG,
REQUEST_TIMEOUT_MSG, REQUEST_TIMEOUT_CODE,
ERROR_STATUS, REQUEST_TIMEOUT_MSG,
} from '@/config'; } from '@/config'
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store'
import { fetchUpdateToken } from '@/service'; import { fetchUpdateToken } from '@/service'
import { local } from '@/utils'; import { local } from '@/utils'
import { showError } from './utils';
type ErrorStatus = keyof typeof ERROR_STATUS; type ErrorStatus = keyof typeof ERROR_STATUS
/** /**
* @description: axios或http错误 * @description: axios或http错误
@ -21,32 +21,32 @@ type ErrorStatus = keyof typeof ERROR_STATUS;
* @return {*} * @return {*}
*/ */
export function handleAxiosError(err: AxiosError) { export function handleAxiosError(err: AxiosError) {
const error: Service.RequestError = { const error: Service.RequestError = {
type: 'Axios', type: 'Axios',
code: DEFAULT_REQUEST_ERROR_CODE, code: DEFAULT_REQUEST_ERROR_CODE,
msg: DEFAULT_REQUEST_ERROR_MSG, msg: DEFAULT_REQUEST_ERROR_MSG,
}; }
// 网络错误 // 网络错误
if (!window.navigator.onLine || err.message === 'Network Error') { if (!window.navigator.onLine || err.message === 'Network Error')
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG }); Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG })
}
// 超时错误
if (err.code === REQUEST_TIMEOUT_CODE && err.message.includes('timeout')) {
Object.assign(error, {
code: REQUEST_TIMEOUT_CODE,
msg: REQUEST_TIMEOUT_MSG,
});
}
// 请求错误
if (err.response) {
const errorCode: ErrorStatus = (err.response?.status as ErrorStatus) || 'DEFAULT';
const msg = ERROR_STATUS[errorCode];
Object.assign(error, { code: errorCode, msg });
}
showError(error); // 超时错误
if (err.code === REQUEST_TIMEOUT_CODE && err.message.includes('timeout')) {
Object.assign(error, {
code: REQUEST_TIMEOUT_CODE,
msg: REQUEST_TIMEOUT_MSG,
})
}
// 请求错误
if (err.response) {
const errorCode: ErrorStatus = (err.response?.status as ErrorStatus) || 'DEFAULT'
const msg = ERROR_STATUS[errorCode]
Object.assign(error, { code: errorCode, msg })
}
return error; showError(error)
return error
} }
/** /**
@ -55,25 +55,26 @@ export function handleAxiosError(err: AxiosError) {
* @return {*} * @return {*}
*/ */
export function handleResponseError(response: AxiosResponse) { export function handleResponseError(response: AxiosResponse) {
const error: Service.RequestError = { const error: Service.RequestError = {
type: 'Axios', type: 'Axios',
code: DEFAULT_REQUEST_ERROR_CODE, code: DEFAULT_REQUEST_ERROR_CODE,
msg: DEFAULT_REQUEST_ERROR_MSG, msg: DEFAULT_REQUEST_ERROR_MSG,
}; }
if (!window.navigator.onLine) { if (!window.navigator.onLine) {
// 网路错误 // 网路错误
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG }); Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG })
} else { }
// 请求成功的状态码非200的错误 else {
const errorCode: ErrorStatus = response.status as ErrorStatus; // 请求成功的状态码非200的错误
const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG; const errorCode: ErrorStatus = response.status as ErrorStatus
Object.assign(error, { type: 'Response', code: errorCode, msg }); const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG
} Object.assign(error, { type: 'Response', code: errorCode, msg })
}
showError(error); showError(error)
return error; return error
} }
/** /**
@ -83,16 +84,16 @@ export function handleResponseError(response: AxiosResponse) {
* @return {*} * @return {*}
*/ */
export function handleBusinessError(data: Record<string, any>, config: Service.BackendResultConfig) { export function handleBusinessError(data: Record<string, any>, config: Service.BackendResultConfig) {
const { codeKey, msgKey } = config; const { codeKey, msgKey } = config
const error: Service.RequestError = { const error: Service.RequestError = {
type: 'Business', type: 'Business',
code: data[codeKey], code: data[codeKey],
msg: data[msgKey], msg: data[msgKey],
}; }
showError(error); showError(error)
return error; return error
} }
/** /**
@ -102,18 +103,18 @@ export function handleBusinessError(data: Record<string, any>, config: Service.B
* @return {*} result * @return {*} result
*/ */
export async function handleServiceResult<T = any>(data: any, error: Service.RequestError | null) { export async function handleServiceResult<T = any>(data: any, error: Service.RequestError | null) {
if (error) { if (error) {
const fail: Service.FailedResult = { const fail: Service.FailedResult = {
error, error,
data: null, data: null,
}; }
return fail; return fail
} }
const success: Service.SuccessResult<T> = { const success: Service.SuccessResult<T> = {
error: null, error: null,
data, data,
}; }
return success; return success
} }
/** /**
@ -122,20 +123,19 @@ export async function handleServiceResult<T = any>(data: any, error: Service.Req
* @return {*} * @return {*}
*/ */
export async function handleRefreshToken(config: AxiosRequestConfig) { export async function handleRefreshToken(config: AxiosRequestConfig) {
const { resetAuthStore } = useAuthStore(); const { resetAuthStore } = useAuthStore()
const refreshToken = local.get('refreshToken'); const refreshToken = local.get('refreshToken')
const { data } = await fetchUpdateToken(refreshToken); const { data } = await fetchUpdateToken(refreshToken)
if (data) { if (data) {
local.set('refreshToken', data.token, ) local.set('refreshToken', data.token)
local.set('token', data.refreshToken) local.set('token', data.refreshToken)
// 设置token // 设置token
if (config.headers) { if (config.headers)
typeof config.headers.set === 'function' && config.headers.set('Authorization', `Bearer ${data.token || ''}`); typeof config.headers.set === 'function' && config.headers.set('Authorization', `Bearer ${data.token || ''}`)
}
return config; return config
} }
resetAuthStore(); resetAuthStore()
return null; return null
} }

View File

@ -1,11 +1,11 @@
import { proxyConfig } from '@/config'; import { createRequest } from './request'
import { createRequest } from './request'; import { proxyConfig } from '@/config'
const { url, urlPattern } = proxyConfig[import.meta.env.MODE]; const { url, urlPattern } = proxyConfig[import.meta.env.MODE]
const isHttpProxy = import.meta.env.VITE_HTTP_PROXY || false; const isHttpProxy = import.meta.env.VITE_HTTP_PROXY || false
export const request = createRequest({ baseURL: isHttpProxy ? urlPattern : url }); export const request = createRequest({ baseURL: isHttpProxy ? urlPattern : url })
// export const secondRequest = createRequest({ baseURL: isHttpProxy ? secondUrlPattern : secondUrl }); // export const secondRequest = createRequest({ baseURL: isHttpProxy ? secondUrlPattern : secondUrl });
export const mockRequest = createRequest({ baseURL: '/mock' }); export const mockRequest = createRequest({ baseURL: '/mock' })

View File

@ -1,89 +1,87 @@
import axios from 'axios'; import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; import type { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'
import { local } from '@/utils';
import { REFRESH_TOKEN_CODE } from '@/config';
import { import {
handleAxiosError, handleAxiosError,
handleResponseError, handleBusinessError,
handleBusinessError, handleRefreshToken,
handleServiceResult, handleResponseError,
handleRefreshToken, handleServiceResult,
} from './handle'; } from './handle'
import { transformRequestData, clearInvalidParameters } from './utils'; import { clearInvalidParameters, transformRequestData } from './utils'
import { local } from '@/utils'
import { DEFAULT_AXIOS_OPTIONS, DEFAULT_BACKEND_OPTIONS } from '@/config'; import { DEFAULT_AXIOS_OPTIONS, DEFAULT_BACKEND_OPTIONS, REFRESH_TOKEN_CODE } from '@/config'
/** /**
* @description: axios请求类 * @description: axios请求类
*/ */
export default class createAxiosInstance { export default class CreateAxiosInstance {
// axios 实例 // axios 实例
instance: AxiosInstance; instance: AxiosInstance
// 后台字段配置 // 后台字段配置
backendConfig: Service.BackendResultConfig; backendConfig: Service.BackendResultConfig
// 基础配置 // 基础配置
axiosConfig: AxiosRequestConfig = {}; axiosConfig: AxiosRequestConfig = {}
constructor(axiosConfig: AxiosRequestConfig, backendConfig: Service.BackendResultConfig = DEFAULT_BACKEND_OPTIONS) { constructor(axiosConfig: AxiosRequestConfig, backendConfig: Service.BackendResultConfig = DEFAULT_BACKEND_OPTIONS) {
// 设置了axios实例上的一些默认配置,新配置会覆盖默认配置 // 设置了axios实例上的一些默认配置,新配置会覆盖默认配置
this.backendConfig = { ...DEFAULT_BACKEND_OPTIONS, ...backendConfig }; this.backendConfig = { ...DEFAULT_BACKEND_OPTIONS, ...backendConfig }
this.instance = axios.create({ ...DEFAULT_AXIOS_OPTIONS, ...axiosConfig }); this.instance = axios.create({ ...DEFAULT_AXIOS_OPTIONS, ...axiosConfig })
this.setInterceptor(); this.setInterceptor()
} }
// 设置类拦截器的函数
setInterceptor() { // 设置类拦截器的函数
this.instance.interceptors.request.use( setInterceptor() {
async (config) => { this.instance.interceptors.request.use(
const handleConfig = { ...config }; async (config) => {
if (handleConfig.headers) { const handleConfig = { ...config }
// 清除无效字段 if (handleConfig.headers) {
handleConfig.data = clearInvalidParameters(handleConfig.data) // 清除无效字段
// 数据格式转换 handleConfig.data = clearInvalidParameters(handleConfig.data)
const contentType = handleConfig.headers.getContentType() as unknown as UnionKey.ContentType // 数据格式转换
if (contentType) { const contentType = handleConfig.headers.getContentType() as unknown as UnionKey.ContentType
handleConfig.data = await transformRequestData(handleConfig.data, contentType); if (contentType)
} handleConfig.data = await transformRequestData(handleConfig.data, contentType)
// 设置token
handleConfig.headers.setAuthorization(`Bearer ${local.get('token') || ''}`) // 设置token
} handleConfig.headers.setAuthorization(`Bearer ${local.get('token') || ''}`)
return handleConfig; }
}, return handleConfig
(error: AxiosError) => { },
const errorResult = handleAxiosError(error); (error: AxiosError) => {
return handleServiceResult(null, errorResult); const errorResult = handleAxiosError(error)
} return handleServiceResult(null, errorResult)
); },
this.instance.interceptors.response.use( )
async (response): Promise<any> => { this.instance.interceptors.response.use(
const { status } = response; async (response): Promise<any> => {
if (status === 200) { const { status } = response
// 获取返回的数据 if (status === 200) {
const apiData = response.data; // 获取返回的数据
const { codeKey, successCode, dataKey } = this.backendConfig; const apiData = response.data
// 请求成功 const { codeKey, successCode, dataKey } = this.backendConfig
if (apiData[codeKey] == successCode) { // 请求成功
return handleServiceResult(apiData[dataKey], null); if (apiData[codeKey] === successCode)
} return handleServiceResult(apiData[dataKey], null)
// token失效, 刷新token
if (REFRESH_TOKEN_CODE.includes(apiData[codeKey])) { // token失效, 刷新token
const config = await handleRefreshToken(response.config); if (REFRESH_TOKEN_CODE.includes(apiData[codeKey])) {
if (config) { const config = await handleRefreshToken(response.config)
return this.instance.request(config); if (config)
} return this.instance.request(config)
} }
// 业务请求失败 // 业务请求失败
const errorResult = handleBusinessError(apiData, this.backendConfig); const errorResult = handleBusinessError(apiData, this.backendConfig)
return handleServiceResult(null, errorResult); return handleServiceResult(null, errorResult)
} }
// 接口请求失败 // 接口请求失败
const errorResult = handleResponseError(response); const errorResult = handleResponseError(response)
return handleServiceResult(null, errorResult); return handleServiceResult(null, errorResult)
}, },
(error: AxiosError) => { (error: AxiosError) => {
// 处理http常见错误进行全局提示等 // 处理http常见错误进行全局提示等
const errorResult = handleAxiosError(error); const errorResult = handleAxiosError(error)
return handleServiceResult(null, errorResult); return handleServiceResult(null, errorResult)
} },
); )
} }
} }

View File

@ -1,30 +1,29 @@
import type { AxiosRequestConfig, AxiosInstance } from 'axios'; import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import createAxiosInstance from './instance'; import CreateAxiosInstance from './instance'
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'; type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'
interface RequestParam { interface RequestParam {
url: string; url: string
method?: RequestMethod; method?: RequestMethod
data?: any; data?: any
config?: AxiosRequestConfig; config?: AxiosRequestConfig
} }
async function getRequestResponse(options: { async function getRequestResponse(options: {
instance: AxiosInstance; instance: AxiosInstance
method: RequestMethod; method: RequestMethod
url: string; url: string
data?: any; data?: any
config?: AxiosRequestConfig; config?: AxiosRequestConfig
}) { }) {
const { instance, method, url, data, config } = options; const { instance, method, url, data, config } = options
let res: any; let res: any
if (method === 'get' || method === 'delete') { if (method === 'get' || method === 'delete')
res = await instance[method](url, config); res = await instance[method](url, config)
} else { else res = await instance[method](url, data, config)
res = await instance[method](url, data, config);
} return res
return res;
} }
/** /**
@ -33,82 +32,95 @@ async function getRequestResponse(options: {
* @param {Service} backendConfig - * @param {Service} backendConfig -
* @return {*} * @return {*}
*/ */
export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) { export function createRequest(
const axiosInstance = new createAxiosInstance(axiosConfig, backendConfig); axiosConfig: AxiosRequestConfig,
/** backendConfig?: Service.BackendResultConfig,
* promise请求 ) {
* @param param - const axiosInstance = new CreateAxiosInstance(axiosConfig, backendConfig)
* - url: 请求地址 /**
* - method: 请求方法(get) * promise请求
* - data: 请求的body的data * @param param -
* - config: axios配置 * - url: 请求地址
*/ * - method: 请求方法(get)
async function asyncRequest<T>(param: RequestParam): Promise<Service.RequestResult<T>> { * - data: 请求的body的data
const { url, method = 'get', data, config } = param; * - config: axios配置
const { instance } = axiosInstance; */
const res = (await getRequestResponse({ async function asyncRequest<T>(
instance, param: RequestParam,
method, ): Promise<Service.RequestResult<T>> {
url, const { url, method = 'get', data, config } = param
data, const { instance } = axiosInstance
config, const res = (await getRequestResponse({
})) as Service.RequestResult<T>; instance,
return res; method,
} url,
/** data,
* get请求 config,
* @param url - })) as Service.RequestResult<T>
* @param config - axios配置 return res
*/ }
function get<T>(url: string, config?: AxiosRequestConfig) { /**
return asyncRequest<T>({ url, config: config }); * get请求
} * @param url -
* @param config - axios配置
*/
function get<T>(url: string, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, config })
}
/** /**
* post请求 * post请求
* @param url - * @param url -
* @param data - body的data * @param data - body的data
* @param config - axios配置 * @param config - axios配置
*/ */
function post<T>(url: string, data?: any, config?: AxiosRequestConfig) { function post<T>(url: string, data?: any, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'post', data, config: config }); return asyncRequest<T>({ url, method: 'post', data, config })
} }
/** /**
* post请求-form参数形式 * post请求-form参数形式
* @param url - * @param url -
* @param data - body的data * @param data - body的data
* @param config - axios配置 * @param config - axios配置
*/ */
function formPost<T>(url: string, data?: any, config: AxiosRequestConfig = {}) { function formPost<T>(
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' } url: string,
return asyncRequest<T>({ url, method: 'post', data, config: config }); data?: any,
} config: AxiosRequestConfig = {},
) {
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
return asyncRequest<T>({ url, method: 'post', data, config })
}
/** /**
* delete请求 * delete请求
* @param url - * @param url -
* @param config - axios配置 * @param config - axios配置
*/ */
function handleDelete<T>(url: string, params?: any, config?: AxiosRequestConfig) { function handleDelete<T>(
return asyncRequest<T>({ url, method: 'delete', config: config }); url: string,
} params?: any,
config?: AxiosRequestConfig,
) {
return asyncRequest<T>({ url, method: 'delete', config })
}
/** /**
* put请求 * put请求
* @param url - * @param url -
* @param data - body的data * @param data - body的data
* @param config - axios配置 * @param config - axios配置
*/ */
function put<T>(url: string, data?: any, config?: AxiosRequestConfig) { function put<T>(url: string, data?: any, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'put', data, config: config }); return asyncRequest<T>({ url, method: 'put', data, config })
} }
return { return {
get, get,
post, post,
formPost, formPost,
put, put,
delete: handleDelete, delete: handleDelete,
}; }
} }

View File

@ -1,52 +1,53 @@
import { ERROR_MSG_DURATION, ERROR_NO_TIP_STATUS } from '@/config'; import qs from 'qs'
import { isArray, isFile, isEmpty, isNullOrUnDef } from '@/utils'; import { ERROR_MSG_DURATION, ERROR_NO_TIP_STATUS } from '@/config'
import qs from 'qs'; import { isArray, isEmpty, isFile, isNullOrUnDef } from '@/utils'
export function showError(error: Service.RequestError) { export function showError(error: Service.RequestError) {
// 如果error不需要提示,则跳过 // 如果error不需要提示,则跳过
const code = Number(error.code); const code = Number(error.code)
if (ERROR_NO_TIP_STATUS.includes(code)) return; if (ERROR_NO_TIP_STATUS.includes(code))
return
window.console.warn(error.code, error.msg); window.console.warn(error.code, error.msg)
window.$message?.error(error.msg, { duration: ERROR_MSG_DURATION }); window.$message?.error(error.msg, { duration: ERROR_MSG_DURATION })
} }
/** /**
* *
* @param requestData - * @param requestData -
* @param contentType - Content-Type * @param contentType - Content-Type
*/ */
export async function transformRequestData(requestData: any, contentType?: UnionKey.ContentType) { export async function transformRequestData(
// application/json类型不处理,清除发送参数的无效字段 requestData: any,
let data: any = clearInvalidParameters(requestData); contentType?: UnionKey.ContentType,
) {
// application/json类型不处理,清除发送参数的无效字段
let data: any = clearInvalidParameters(requestData)
// form类型转换 // form类型转换
if (contentType === 'application/x-www-form-urlencoded') { if (contentType === 'application/x-www-form-urlencoded')
data = qs.stringify(data); data = qs.stringify(data)
}
// form-data类型转换
if (contentType === 'multipart/form-data') {
data = await handleFormData(data);
}
return data; // form-data类型转换
if (contentType === 'multipart/form-data')
data = await handleFormData(data)
return data
} }
async function handleFormData(data: Record<string, any>) { async function handleFormData(data: Record<string, any>) {
const formData = new FormData(); const formData = new FormData()
const entries = Object.entries(data); const entries = Object.entries(data)
entries.forEach(async ([key, value]) => { entries.forEach(async ([key, value]) => {
const isFileType = isFile(value) || (isArray(value) && value.length && isFile(value[0])); const isFileType
= isFile(value) || (isArray(value) && value.length && isFile(value[0]))
if (isFileType && isArray(value)) { if (isFileType && isArray(value))
value.forEach((item) => formData.append(key, item)) value.forEach(item => formData.append(key, item))
else formData.append(key, value)
})
} else { return formData
formData.append(key, value);
}
});
return formData;
} }
/** /**
@ -54,10 +55,11 @@ async function handleFormData(data: Record<string, any>) {
* @param requestData - * @param requestData -
*/ */
export function clearInvalidParameters(requestData: Record<string, any>) { export function clearInvalidParameters(requestData: Record<string, any>) {
const result: Record<string, any> = {}; const result: Record<string, any> = {}
for (const key in requestData) { for (const key in requestData) {
if (isEmpty(requestData[key]) || isNullOrUnDef(requestData[key])) continue; if (isEmpty(requestData[key]) || isNullOrUnDef(requestData[key]))
result[key] = requestData[key]; continue
} result[key] = requestData[key]
return result; }
return result
} }

View File

@ -1,3 +1,3 @@
export * from './api/test'; export * from './api/test'
export * from './api/login'; export * from './api/login'
export * from './api/mock'; export * from './api/mock'

View File

@ -1,10 +1,10 @@
import { createPinia } from 'pinia'; import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'; import piniaPluginPersist from 'pinia-plugin-persist'
import type { App } from 'vue'; import type { App } from 'vue'
export function setupStore(app: App) { export function setupStore(app: App) {
const store = createPinia(); const store = createPinia()
store.use(piniaPluginPersist); store.use(piniaPluginPersist)
app.use(store); app.use(store)
} }
export * from './modules'; export * from './modules'

View File

@ -1,134 +1,136 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { nextTick } from 'vue'; import { nextTick } from 'vue'
import { darkTheme, GlobalTheme } from 'naive-ui'; import type { GlobalTheme } from 'naive-ui'
import { darkTheme } from 'naive-ui'
interface AppStatus { interface AppStatus {
readonly footerText: string; readonly footerText: string
collapsed: boolean; collapsed: boolean
fullScreen: boolean; fullScreen: boolean
darkMode: boolean; darkMode: boolean
grayMode: boolean; grayMode: boolean
colorWeak: boolean; colorWeak: boolean
darkTheme: GlobalTheme | null; darkTheme: GlobalTheme | null
loadFlag: boolean; loadFlag: boolean
showLogo: boolean; showLogo: boolean
showTabs: boolean; showTabs: boolean
showBreadcrumb: boolean; showBreadcrumb: boolean
fixedHeader: boolean; fixedHeader: boolean
invertedSider: boolean; invertedSider: boolean
invertedHeader: boolean; invertedHeader: boolean
showWatermark: boolean; showWatermark: boolean
} }
const docEle = document.documentElement; const docEle = document.documentElement
export const useAppStore = defineStore('app-store', { export const useAppStore = defineStore('app-store', {
state: (): AppStatus => { state: (): AppStatus => {
return { return {
footerText: 'Copyright ©2023 Ench Admin', footerText: 'Copyright ©2023 Ench Admin',
collapsed: false, collapsed: false,
fullScreen: false, fullScreen: false,
darkMode: false, darkMode: false,
grayMode: false, grayMode: false,
colorWeak: false, colorWeak: false,
darkTheme: null, darkTheme: null,
loadFlag: true, loadFlag: true,
showLogo: true, showLogo: true,
showTabs: true, showTabs: true,
showBreadcrumb: true, showBreadcrumb: true,
fixedHeader: false, fixedHeader: false,
invertedSider: false, invertedSider: false,
invertedHeader: false, invertedHeader: false,
showWatermark: false, showWatermark: false,
}; }
}, },
actions: { actions: {
/* 切换侧边栏收缩 */ /* 切换侧边栏收缩 */
toggleCollapse() { toggleCollapse() {
this.collapsed = !this.collapsed; this.collapsed = !this.collapsed
}, },
/* 切换全屏 */ /* 切换全屏 */
toggleFullScreen() { toggleFullScreen() {
if (!document.fullscreenElement) { if (!document.fullscreenElement) {
this.fullScreen = true; this.fullScreen = true
document.documentElement.requestFullscreen(); document.documentElement.requestFullscreen()
} else if (document.exitFullscreen) { }
this.fullScreen = false; else if (document.exitFullscreen) {
document.exitFullscreen(); this.fullScreen = false
} document.exitFullscreen()
}, }
/* 切换主题 亮/深色 */ },
toggleDarkMode() { /* 切换主题 亮/深色 */
this.darkMode = !this.darkMode; toggleDarkMode() {
if (this.darkMode) { this.darkMode = !this.darkMode
this.darkTheme = darkTheme; if (this.darkMode)
} else { this.darkTheme = darkTheme
this.darkTheme = null; else this.darkTheme = null
} },
}, /* 设置主题深色 */
/* 设置主题深色 */ setDarkMode(mode: boolean) {
setDarkMode(mode: boolean) { if (mode) {
if (mode) { this.darkMode = true
this.darkMode = true; this.darkTheme = darkTheme
this.darkTheme = darkTheme; }
} else { else {
this.darkMode = false; this.darkMode = false
this.darkTheme = null; this.darkTheme = null
} }
}, },
/** /**
* @description: * @description:
* @param {number} delay - * @param {number} delay -
* @return {*} * @return {*}
*/ */
async reloadPage(delay = 600) { async reloadPage(delay = 600) {
this.loadFlag = false; this.loadFlag = false
await nextTick(); await nextTick()
if (delay) { if (delay) {
setTimeout(() => { setTimeout(() => {
this.loadFlag = true; this.loadFlag = true
}, delay); }, delay)
} else { }
this.loadFlag = true; else {
} this.loadFlag = true
}, }
/* 切换色弱模式 */ },
toggleColorWeak() { /* 切换色弱模式 */
docEle.classList.toggle('color-weak'); toggleColorWeak() {
this.colorWeak = docEle.classList.contains('color-weak'); docEle.classList.toggle('color-weak')
}, this.colorWeak = docEle.classList.contains('color-weak')
/* 切换灰色模式 */ },
toggleGrayMode() { /* 切换灰色模式 */
docEle.classList.toggle('gray-mode'); toggleGrayMode() {
this.grayMode = docEle.classList.contains('gray-mode'); docEle.classList.toggle('gray-mode')
}, this.grayMode = docEle.classList.contains('gray-mode')
/* 切换显示logo */ },
toggleShowLogo() { /* 切换显示logo */
this.showLogo = !this.showLogo; toggleShowLogo() {
}, this.showLogo = !this.showLogo
/* 切换显示多页签 */ },
toggleShowTabs() { /* 切换显示多页签 */
this.showTabs = !this.showTabs; toggleShowTabs() {
}, this.showTabs = !this.showTabs
/* 切换显示多页签 */ },
toggleShowBreadcrumb() { /* 切换显示多页签 */
this.showBreadcrumb = !this.showBreadcrumb; toggleShowBreadcrumb() {
}, this.showBreadcrumb = !this.showBreadcrumb
/* 切换固定头部和标签页 */ },
toggleFixedHeader() { /* 切换固定头部和标签页 */
this.fixedHeader = !this.fixedHeader; toggleFixedHeader() {
}, this.fixedHeader = !this.fixedHeader
/* 切换固定底部 */ },
toggleInvertedSider() { /* 切换固定底部 */
this.invertedSider = !this.invertedSider; toggleInvertedSider() {
}, this.invertedSider = !this.invertedSider
/* 切换固定底部 */ },
toggleInvertedHeader() { /* 切换固定底部 */
this.invertedHeader = !this.invertedHeader; toggleInvertedHeader() {
}, this.invertedHeader = !this.invertedHeader
/* 切换固定底部 */ },
toggleShowWatermark() { /* 切换固定底部 */
this.showWatermark = !this.showWatermark; toggleShowWatermark() {
}, this.showWatermark = !this.showWatermark
}, },
}); },
})

View File

@ -1,117 +1,116 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { fetchLogin, fetchUserInfo } from '@/service'; import { unref } from 'vue'
import { router } from '@/router'; import { useRouteStore } from './route'
import { useAppRouter } from '@/hooks'; import { fetchLogin, fetchUserInfo } from '@/service'
import { unref } from 'vue'; import { router } from '@/router'
import { useRouteStore } from './route'; import { useAppRouter } from '@/hooks'
import { local } from '@/utils'; import { local } from '@/utils'
const emptyInfo: Auth.UserInfo = { const emptyInfo: Auth.UserInfo = {
userId: 0, userId: 0,
userName: '', userName: '',
nickName: '', nickName: '',
avatar: '', avatar: '',
role: 'user', role: 'user',
}; }
export const useAuthStore = defineStore('auth-store', { export const useAuthStore = defineStore('auth-store', {
state: () => { state: () => {
return { return {
userInfo: local.get('userInfo') || emptyInfo, userInfo: local.get('userInfo') || emptyInfo,
token: local.get('token') || '', token: local.get('token') || '',
refreshToken: local.get('refreshToken') || '', refreshToken: local.get('refreshToken') || '',
loginLoading: false, loginLoading: false,
}; }
}, },
getters: { getters: {
/** 是否登录 */ /** 是否登录 */
isLogin(state) { isLogin(state) {
return Boolean(state.token); return Boolean(state.token)
}, },
}, },
actions: { actions: {
/* 登录退出,重置用户信息等 */ /* 登录退出,重置用户信息等 */
resetAuthStore() { resetAuthStore() {
const route = unref(router.currentRoute); const route = unref(router.currentRoute)
const { toLogin } = useAppRouter(false); const { toLogin } = useAppRouter(false)
const { resetRouteStore } = useRouteStore(); const { resetRouteStore } = useRouteStore()
// 清除本地缓存 // 清除本地缓存
this.clearAuthStorage(); this.clearAuthStorage()
// 清空路由、菜单等数据 // 清空路由、菜单等数据
resetRouteStore(); resetRouteStore()
this.$reset(); this.$reset()
if (route.meta.requiresAuth) { if (route.meta.requiresAuth)
toLogin(); toLogin()
} },
}, clearAuthStorage() {
clearAuthStorage() { local.remove('token')
local.remove('token'); local.remove('refreshToken')
local.remove('refreshToken'); local.remove('userInfo')
local.remove('userInfo'); },
},
/* 用户登录 */ /* 用户登录 */
async login(userName: string, password: string) { async login(userName: string, password: string) {
this.loginLoading = true; this.loginLoading = true
const { error, data } = await fetchLogin({ userName, password }); const { error, data } = await fetchLogin({ userName, password })
if (error) { if (error) {
this.loginLoading = false; this.loginLoading = false
return; return
} }
// 处理登录信息 // 处理登录信息
await this.handleAfterLogin(data); await this.handleAfterLogin(data)
this.loginLoading = false; this.loginLoading = false
}, },
/* 登录后的处理函数 */ /* 登录后的处理函数 */
async handleAfterLogin(data: ApiAuth.loginToken) { async handleAfterLogin(data: ApiAuth.loginToken) {
// 将token和userInfo保存下来 // 将token和userInfo保存下来
const catchSuccess = await this.catchUserInfo(data); const catchSuccess = await this.catchUserInfo(data)
// 添加路由和菜单 // 添加路由和菜单
const { initAuthRoute } = useRouteStore(); const { initAuthRoute } = useRouteStore()
await initAuthRoute(); await initAuthRoute()
// 登录写入信息成功 // 登录写入信息成功
if (catchSuccess) { if (catchSuccess) {
// 进行重定向跳转 // 进行重定向跳转
const { toLoginRedirect } = useAppRouter(false); const { toLoginRedirect } = useAppRouter(false)
toLoginRedirect(); toLoginRedirect()
// 触发用户提示 // 触发用户提示
window.$notification?.success({ window.$notification?.success({
title: '登录成功!', title: '登录成功!',
content: `欢迎回来😊,${this.userInfo.nickName}!`, content: `欢迎回来😊,${this.userInfo.nickName}!`,
duration: 3000, duration: 3000,
}); })
return; return
} }
// 如果不成功则重置存储 // 如果不成功则重置存储
this.resetAuthStore(); this.resetAuthStore()
}, },
/* 缓存用户信息 */ /* 缓存用户信息 */
async catchUserInfo(userToken: ApiAuth.loginToken) { async catchUserInfo(userToken: ApiAuth.loginToken) {
let catchSuccess = false; let catchSuccess = false
const { token, refreshToken, userId } = userToken; const { token, refreshToken, userId } = userToken
const { error, data } = await fetchUserInfo({ userId }); const { error, data } = await fetchUserInfo({ userId })
if (error) { if (error)
return catchSuccess; return catchSuccess
}
// 先存储token
local.set('token', token);
local.set('refreshToken', refreshToken);
this.token = token;
this.refreshToken = refreshToken;
// 请求/存储用户信息
local.set('userInfo', data);
this.userInfo = data;
catchSuccess = true;
return catchSuccess; // 先存储token
}, local.set('token', token)
toggleUserRole(role: Auth.RoleType) { local.set('refreshToken', refreshToken)
this.login(role, '123456'); this.token = token
}, this.refreshToken = refreshToken
}, // 请求/存储用户信息
}); local.set('userInfo', data)
this.userInfo = data
catchSuccess = true
return catchSuccess
},
toggleUserRole(role: Auth.RoleType) {
this.login(role, '123456')
},
},
})

View File

@ -1,4 +1,4 @@
export * from './app'; export * from './app'
export * from './auth'; export * from './auth'
export * from './route'; export * from './route'
export * from './tab'; export * from './tab'

View File

@ -1,188 +1,186 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { renderIcon, local } from '@/utils'; import type { MenuOption } from 'naive-ui'
import { MenuOption } from 'naive-ui'; import { RouterLink } from 'vue-router'
import { createDynamicRoutes } from '@/router/guard/dynamic'; import { h } from 'vue'
import { router } from '@/router'; import { local, renderIcon } from '@/utils'
import { fetchUserRoutes } from '@/service'; import { createDynamicRoutes } from '@/router/guard/dynamic'
import { staticRoutes } from '@/router/modules'; import { router } from '@/router'
import { RouterLink } from 'vue-router'; import { fetchUserRoutes } from '@/service'
import { usePermission } from '@/hooks'; import { staticRoutes } from '@/router/modules'
import { h } from 'vue'; import { usePermission } from '@/hooks'
interface RoutesStatus { interface RoutesStatus {
isInitAuthRoute: boolean; isInitAuthRoute: boolean
menus: any; menus: any
userRoutes: AppRoute.Route[]; userRoutes: AppRoute.Route[]
activeMenu: string | null; activeMenu: string | null
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE']; authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE']
cacheRoutes: string[]; cacheRoutes: string[]
} }
export const useRouteStore = defineStore('route-store', { export const useRouteStore = defineStore('route-store', {
state: (): RoutesStatus => { state: (): RoutesStatus => {
return { return {
userRoutes: [], userRoutes: [],
isInitAuthRoute: false, isInitAuthRoute: false,
menus: [], menus: [],
activeMenu: null, activeMenu: null,
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE, authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
cacheRoutes: [], cacheRoutes: [],
}; }
}, },
actions: { actions: {
resetRouteStore() { resetRouteStore() {
this.resetRoutes(); this.resetRoutes()
this.$reset(); this.$reset()
}, },
resetRoutes() { resetRoutes() {
/* 删除后面添加的路由 */ /* 删除后面添加的路由 */
router.removeRoute('appRoot'); router.removeRoute('appRoot')
}, },
/* 根据当前路由的name生成面包屑数据 */ /* 根据当前路由的name生成面包屑数据 */
createBreadcrumbFromRoutes(routeName = '/') { createBreadcrumbFromRoutes(routeName = '/') {
const path: AppRoute.Route[] = []; const path: AppRoute.Route[] = []
// 筛选所有包含目标的各级路由组合成一维数组 // 筛选所有包含目标的各级路由组合成一维数组
const getPathfromRoutes = (routeName: string, userRoutes: AppRoute.Route[]) => { const getPathfromRoutes = (
userRoutes.forEach((item) => { routeName: string,
if (this.hasPathinAllPath(routeName, item)) { userRoutes: AppRoute.Route[],
path.push(item); ) => {
if (item.children && item.children.length !== 0) { userRoutes.forEach((item) => {
getPathfromRoutes(routeName, item.children); if (this.hasPathinAllPath(routeName, item)) {
} path.push(item)
} if (item.children && item.children.length !== 0)
}); getPathfromRoutes(routeName, item.children)
}; }
getPathfromRoutes(routeName, this.userRoutes); })
return path; }
}, getPathfromRoutes(routeName, this.userRoutes)
/* 判断当前路由和子路由中是否存在为routeName的路由 */ return path
hasPathinAllPath(routeName: string, userRoutes: AppRoute.Route) { },
if (userRoutes.name === routeName) { /* 判断当前路由和子路由中是否存在为routeName的路由 */
return true; hasPathinAllPath(routeName: string, userRoutes: AppRoute.Route) {
} if (userRoutes.name === routeName)
if (userRoutes.children && userRoutes.children.length !== 0) { return true
const arr: boolean[] = [];
userRoutes.children.forEach((item) => {
arr.push(this.hasPathinAllPath(routeName, item));
});
return arr.some((item) => {
return item;
});
}
return false;
},
/* 设置当前高亮的菜单key */
setActiveMenu(key: string) {
this.activeMenu = key;
},
/* 生成侧边菜单的数据 */
createMenus(userRoutes: AppRoute.Route[]) {
this.userRoutes = userRoutes;
let resultMenus = JSON.parse(JSON.stringify(userRoutes)); if (userRoutes.children && userRoutes.children.length !== 0) {
resultMenus = this.removeHiddenRoutes(resultMenus); const arr: boolean[] = []
this.menus = this.transformAuthRoutesToMenus(resultMenus); userRoutes.children.forEach((item) => {
}, arr.push(this.hasPathinAllPath(routeName, item))
/** 过滤不需要显示的菜单 */ })
removeHiddenRoutes(routes: AppRoute.Route[]) { return arr.some((item) => {
return routes.filter((route) => { return item
if (route.meta && route.meta.hide) { })
return false; }
} else if (route.children) { return false
route.children = this.removeHiddenRoutes(route.children); },
} /* 设置当前高亮的菜单key */
return true; setActiveMenu(key: string) {
}); this.activeMenu = key
}, },
/* 生成侧边菜单的数据 */
createMenus(userRoutes: AppRoute.Route[]) {
this.userRoutes = userRoutes
//* 将返回的路由表渲染成侧边栏 */ let resultMenus = JSON.parse(JSON.stringify(userRoutes))
transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] { resultMenus = this.removeHiddenRoutes(resultMenus)
return ( this.menus = this.transformAuthRoutesToMenus(resultMenus)
userRoutes },
/** 过滤没有权限的侧边菜单 */ /** 过滤不需要显示的菜单 */
.filter((item: AppRoute.Route) => { removeHiddenRoutes(routes: AppRoute.Route[]) {
const { hasPermission } = usePermission(); return routes.filter((route) => {
return hasPermission(item.meta.roles); if (route.meta && route.meta.hide)
}) return false
/** 根据order大小菜单排序 */ else if (route.children)
.sort((a, b) => { route.children = this.removeHiddenRoutes(route.children)
if (a.meta && a.meta.order && b.meta && b.meta.order) {
return a.meta.order - b.meta.order;
} else if (a.meta && a.meta.order) {
return -1;
} else if (b.meta && b.meta.order) {
return 1;
} else {
return 0;
}
})
/** 转换为侧边菜单数据结构 */
.map((item) => {
const target: MenuOption = {
label:
!item.children || item.children.length == 0
? () =>
h(
RouterLink,
{
to: {
path: item.path,
},
},
{ default: () => item.meta.title }
)
: item.meta.title,
key: item.path,
icon: renderIcon(item.meta.icon),
};
/** 判断子元素 */
if (item.children) {
const children = this.transformAuthRoutesToMenus(item.children);
// 只有子元素有且不为空时才添加
if (children.length !== 0) {
target.children = children;
} else {
target.children = undefined;
}
}
return target;
})
);
},
/* 初始化动态路由 */
async initDynamicRoute() {
// 根据用户id来获取用户的路由
const userInfo = local.get('userInfo');
if (!userInfo || !userInfo.userId) { return true
return; })
} },
const { data: routes } = await fetchUserRoutes({ userId: userInfo.userId }); //* 将返回的路由表渲染成侧边栏 */
// 根据用户返回的路由表来生成真实路由 transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
const appRoutes = await createDynamicRoutes(routes); return (
// 生成侧边菜单 userRoutes
this.createMenus(routes); /** 过滤没有权限的侧边菜单 */
// 插入路由表 .filter((item: AppRoute.Route) => {
router.addRoute(appRoutes); const { hasPermission } = usePermission()
}, return hasPermission(item.meta.roles)
/* 初始化静态路由 */ })
async initStaticRoute() { /** 根据order大小菜单排序 */
// 根据静态路由表来生成真实路由 .sort((a, b) => {
const appRoutes = await createDynamicRoutes(staticRoutes); if (a.meta && a.meta.order && b.meta && b.meta.order)
// 生成侧边菜单 return a.meta.order - b.meta.order
this.createMenus(staticRoutes); else if (a.meta && a.meta.order)
// 插入路由表 return -1
router.addRoute(appRoutes); else if (b.meta && b.meta.order)
}, return 1
else return 0
})
/** 转换为侧边菜单数据结构 */
.map((item) => {
const target: MenuOption = {
label:
(!item.children || item.children.length === 0)
? () =>
h(
RouterLink,
{
to: {
path: item.path,
},
},
{ default: () => item.meta.title },
)
: item.meta.title,
key: item.path,
icon: renderIcon(item.meta.icon),
}
/** 判断子元素 */
if (item.children) {
const children = this.transformAuthRoutesToMenus(item.children)
// 只有子元素有且不为空时才添加
if (children.length !== 0)
target.children = children
else target.children = undefined
}
return target
})
)
},
/* 初始化动态路由 */
async initDynamicRoute() {
// 根据用户id来获取用户的路由
const userInfo = local.get('userInfo')
async initAuthRoute() { if (!userInfo || !userInfo.userId)
this.isInitAuthRoute = false; return
if (this.authRouteMode === 'dynamic') {
await this.initDynamicRoute(); const { data: routes } = await fetchUserRoutes({
} else { userId: userInfo.userId,
await this.initStaticRoute(); })
} // 根据用户返回的路由表来生成真实路由
this.isInitAuthRoute = true; const appRoutes = await createDynamicRoutes(routes)
}, // 生成侧边菜单
}, this.createMenus(routes)
}); // 插入路由表
router.addRoute(appRoutes)
},
/* 初始化静态路由 */
async initStaticRoute() {
// 根据静态路由表来生成真实路由
const appRoutes = await createDynamicRoutes(staticRoutes)
// 生成侧边菜单
this.createMenus(staticRoutes)
// 插入路由表
router.addRoute(appRoutes)
},
async initAuthRoute() {
this.isInitAuthRoute = false
if (this.authRouteMode === 'dynamic')
await this.initDynamicRoute()
else await this.initStaticRoute()
this.isInitAuthRoute = true
},
},
})

View File

@ -1,16 +1,16 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { RouteLocationNormalized } from 'vue-router'; import type { RouteLocationNormalized } from 'vue-router'
import { useAppRouter } from '@/hooks'; import { useAppRouter } from '@/hooks'
interface TabState { interface TabState {
inherentTab: { inherentTab: {
name: string; name: string
title: string; title: string
path: string; path: string
}[]; }[]
tabs: RouteLocationNormalized[]; tabs: RouteLocationNormalized[]
tabWhiteList: string[]; tabWhiteList: string[]
currentTab: string; currentTab: string
} }
export const useTabStore = defineStore('tab-store', { export const useTabStore = defineStore('tab-store', {
state: (): TabState => { state: (): TabState => {
@ -25,92 +25,92 @@ export const useTabStore = defineStore('tab-store', {
tabs: [], tabs: [],
tabWhiteList: ['404', '403', '500', 'login'], tabWhiteList: ['404', '403', '500', 'login'],
currentTab: 'dashboard_workbench', currentTab: 'dashboard_workbench',
}; }
}, },
getters: { getters: {
inherentTabName(): string[] { inherentTabName(): string[] {
return this.inherentTab.map((item) => { return this.inherentTab.map((item) => {
return item.name; return item.name
}); })
}, },
}, },
actions: { actions: {
addTab(route: RouteLocationNormalized) { addTab(route: RouteLocationNormalized) {
// 如果已经在固有标签里则不添加 // 如果已经在固有标签里则不添加
if (this.inherentTabName.includes(route.name as string)) { if (this.inherentTabName.includes(route.name as string))
return; return
}
// 如果标签名称已存在则不添加 // 如果标签名称已存在则不添加
if (this.hasExistTab(route.name as string)) { if (this.hasExistTab(route.name as string))
return; return
}
// 如果在白名单内则不添加,错误页等 // 如果在白名单内则不添加,错误页等
if (this.tabWhiteList.includes(route.name as string)) { if (this.tabWhiteList.includes(route.name as string))
return; return
}
this.tabs.push(route); this.tabs.push(route)
}, },
closeTab(name: string) { closeTab(name: string) {
const { routerPush, toRoot } = useAppRouter(false); const { routerPush, toRoot } = useAppRouter(false)
const tabsLength = this.tabs.length; const tabsLength = this.tabs.length
// 如果动态标签大于一个,才会标签跳转 // 如果动态标签大于一个,才会标签跳转
if (this.tabs.length > 1) { if (this.tabs.length > 1) {
// 获取关闭的标签索引 // 获取关闭的标签索引
const index = this.getTabIndex(name); const index = this.getTabIndex(name)
const isLast = index + 1 === tabsLength; const isLast = index + 1 === tabsLength
// 如果是关闭的当前页面,路由跳转到原先标签的后一个标签 // 如果是关闭的当前页面,路由跳转到原先标签的后一个标签
if (this.currentTab === name && !isLast) { if (this.currentTab === name && !isLast) {
// 跳转到后一个标签 // 跳转到后一个标签
routerPush(this.tabs[index + 1].path); routerPush(this.tabs[index + 1].path)
} else if (this.currentTab === name && isLast) { }
else if (this.currentTab === name && isLast) {
// 已经是最后一个了,就跳转前一个 // 已经是最后一个了,就跳转前一个
routerPush(this.tabs[index - 1].path); routerPush(this.tabs[index - 1].path)
} }
} }
// 删除标签 // 删除标签
this.tabs = this.tabs.filter((item) => { this.tabs = this.tabs.filter((item) => {
return item.name !== name; return item.name !== name
}); })
// 删除后如果清空了,就跳转到默认首页 // 删除后如果清空了,就跳转到默认首页
if (tabsLength - 1 === 0) { if (tabsLength - 1 === 0)
toRoot(); toRoot()
}
}, },
closeOtherTabs(name: string) { closeOtherTabs(name: string) {
const index = this.getTabIndex(name); const index = this.getTabIndex(name)
this.tabs = this.tabs.filter((item, i) => i === index); this.tabs = this.tabs.filter((item, i) => i === index)
}, },
closeLeftTabs(name: string) { closeLeftTabs(name: string) {
const index = this.getTabIndex(name); const index = this.getTabIndex(name)
this.tabs = this.tabs.filter((item, i) => i >= index); this.tabs = this.tabs.filter((item, i) => i >= index)
}, },
closeRightTabs(name: string) { closeRightTabs(name: string) {
const index = this.getTabIndex(name); const index = this.getTabIndex(name)
this.tabs = this.tabs.filter((item, i) => i <= index); this.tabs = this.tabs.filter((item, i) => i <= index)
}, },
closeAllTabs() { closeAllTabs() {
const { toRoot } = useAppRouter(false); const { toRoot } = useAppRouter(false)
this.tabs.length = 0; this.tabs.length = 0
toRoot(); toRoot()
}, },
hasExistTab(name: string) { hasExistTab(name: string) {
return this.tabs.some((item) => { return this.tabs.some((item) => {
return item.name === name; return item.name === name
}); })
}, },
/* 设置当前激活的标签 */ /* 设置当前激活的标签 */
setCurrentTab(name: string) { setCurrentTab(name: string) {
this.currentTab = name; this.currentTab = name
}, },
getTabIndex(name: string) { getTabIndex(name: string) {
return this.tabs.findIndex((item) => { return this.tabs.findIndex((item) => {
return item.name === name; return item.name === name
}); })
}, },
}, },
persist: { persist: {
enabled: true, enabled: true,
}, },
}); })

View File

@ -1,25 +1,23 @@
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js'
import { isObject } from './is'; import { isObject } from './is'
import { STORAGE_ENCRYPT_SECRET } from '@/config'
const { VITE_STORAGE_ENCRYPT } = import.meta.env; const { VITE_STORAGE_ENCRYPT } = import.meta.env
import { STORAGE_ENCRYPT_SECRET } from '@/config';
/** /**
* *
* @param data - * @param data -
*/ */
export function encrypto(data: any) { export function encrypto(data: any) {
let newData = data; let newData = data
if (isObject(data)) { if (isObject(data))
newData = JSON.stringify(data); newData = JSON.stringify(data)
}
if (VITE_STORAGE_ENCRYPT) { if (VITE_STORAGE_ENCRYPT)
return newData; return newData
}
return CryptoJS.AES.encrypt(newData, STORAGE_ENCRYPT_SECRET).toString(); return CryptoJS.AES.encrypt(newData, STORAGE_ENCRYPT_SECRET).toString()
} }
/** /**
@ -27,16 +25,14 @@ export function encrypto(data: any) {
* @param cipherText - * @param cipherText -
*/ */
export function decrypto(cipherText: string) { export function decrypto(cipherText: string) {
if (!VITE_STORAGE_ENCRYPT) { if (!VITE_STORAGE_ENCRYPT)
return JSON.parse(cipherText); return JSON.parse(cipherText)
}
const bytes = CryptoJS.AES.decrypt(cipherText, STORAGE_ENCRYPT_SECRET); const bytes = CryptoJS.AES.decrypt(cipherText, STORAGE_ENCRYPT_SECRET)
const originalText = bytes.toString(CryptoJS.enc.Utf8); const originalText = bytes.toString(CryptoJS.enc.Utf8)
if (originalText) { if (originalText)
return JSON.parse(originalText); return JSON.parse(originalText)
}
return null; return null
} }

View File

@ -1,10 +1,10 @@
import { h } from 'vue'; import { h } from 'vue'
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue'
import { NIcon } from 'naive-ui'; import { NIcon } from 'naive-ui'
export function renderIcon(icon?: string) { export function renderIcon(icon?: string) {
if (!icon) { if (!icon)
return undefined return undefined
}
return () => h(NIcon, null, { default: () => h(Icon, { icon }) }); return () => h(NIcon, null, { default: () => h(Icon, { icon }) })
} }

View File

@ -1,3 +1,3 @@
export * from './icon'; export * from './icon'
export * from './is'; export * from './is'
export * from './storage'; export * from './storage'

View File

@ -1,99 +1,97 @@
/* eslint-disable */ const toString = Object.prototype.toString
const toString = Object.prototype.toString;
export function is(val: unknown, type: string) { export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`; return toString.call(val) === `[object ${type}]`
} }
export function isString(val: unknown): val is string { export function isString(val: unknown): val is string {
return is(val, 'String'); return is(val, 'String')
} }
export function isNumber(val: unknown): val is number { export function isNumber(val: unknown): val is number {
return is(val, 'Number'); return is(val, 'Number')
} }
export function isBoolean(val: unknown): val is boolean { export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean'); return is(val, 'Boolean')
} }
export function isNull(val: unknown): val is null { export function isNull(val: unknown): val is null {
return val === null; return val === null
} }
export function isUnDef<T = unknown>(val?: T): val is T { export function isUnDef<T = unknown>(val?: T): val is T {
return !isDef(val); return !isDef(val)
} }
export function isDef<T = unknown>(val?: T): val is T { export function isDef<T = unknown>(val?: T): val is T {
return typeof val !== 'undefined'; return typeof val !== 'undefined'
} }
export function isNullOrUnDef(val: unknown): val is null | undefined { export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val); return isUnDef(val) || isNull(val)
} }
export function isObject(val: any): val is Record<any, any> { export function isObject(val: any): val is Record<any, any> {
return val !== null && is(val, 'Object'); return val !== null && is(val, 'Object')
} }
export function isArray(val: any): val is Array<any> { export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val); return val && Array.isArray(val)
} }
export function isEmpty<T = unknown>(val: T): val is T { export function isEmpty<T = unknown>(val: T): val is T {
if (isArray(val) || isString(val)) { if (isArray(val) || isString(val))
return val.length === 0; return val.length === 0
}
if (val instanceof Map || val instanceof Set) { if (val instanceof Map || val instanceof Set)
return val.size === 0; return val.size === 0
}
if (isObject(val)) { if (isObject(val))
return Object.keys(val).length === 0; return Object.keys(val).length === 0
}
return false; return false
} }
export function isDate(val: unknown): val is Date { export function isDate(val: unknown): val is Date {
return is(val, 'Date'); return is(val, 'Date')
} }
export function isPromise<T = any>(val: unknown): val is Promise<T> { export function isPromise<T = any>(val: unknown): val is Promise<T> {
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch); return (
is(val, 'Promise')
&& isObject(val)
&& isFunction(val.then)
&& isFunction(val.catch)
)
} }
export function isFunction(val: unknown): val is Function { export function isFunction(val: unknown): val is Function {
return typeof val === 'function'; return typeof val === 'function'
} }
export function isFile<T extends File>(val: T | unknown): val is T { export function isFile<T extends File>(val: T | unknown): val is T {
return is(val, 'File'); return is(val, 'File')
} }
export function isRegExp(val: unknown): val is RegExp { export function isRegExp(val: unknown): val is RegExp {
return is(val, 'RegExp'); return is(val, 'RegExp')
} }
export function isWindow(val: any): val is Window { export function isWindow(val: any): val is Window {
return typeof window !== 'undefined' && is(val, 'Window'); return typeof window !== 'undefined' && is(val, 'Window')
} }
export function isElement(val: unknown): val is Element { export function isElement(val: unknown): val is Element {
return isObject(val) && !!val.tagName; return isObject(val) && !!val.tagName
} }
export const isServer = typeof window === 'undefined'; export const isServer = typeof window === 'undefined'
export const isClient = !isServer; export const isClient = !isServer
export function isUrl(path: string): boolean { export function isUrl(path: string): boolean {
const reg = const reg
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/
return reg.test(path); return reg.test(path)
} }

View File

@ -1,100 +1,103 @@
import { encrypto, decrypto } from './crypto'; import { decrypto, encrypto } from './crypto'
// 读取缓存前缀 // 读取缓存前缀
import { STORAGE_PREFIX, STORAGE_DEFAULT_CACHE_TIME } from '@/config'; import { STORAGE_DEFAULT_CACHE_TIME, STORAGE_PREFIX } from '@/config'
interface StorageData<T> { interface StorageData<T> {
value: T; value: T
expire: number | null; expire: number | null
} }
/** /**
* LocalStorage部分操作 * LocalStorage部分操作
*/ */
function createLocalStorage<T extends Storage.Local>() { function createLocalStorage<T extends Storage.Local>() {
// 默认缓存期限为7天 // 默认缓存期限为7天
function set<K extends keyof T>(key: K, value: T[K], expire: number = STORAGE_DEFAULT_CACHE_TIME) { function set<K extends keyof T>(key: K, value: T[K], expire: number = STORAGE_DEFAULT_CACHE_TIME) {
const storageData: StorageData<T[K]> = { const storageData: StorageData<T[K]> = {
value, value,
expire: new Date().getTime() + expire * 1000, expire: new Date().getTime() + expire * 1000,
}; }
const json = encrypto(storageData); const json = encrypto(storageData)
window.localStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json); window.localStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json)
} }
function get<K extends keyof T>(key: K) { function get<K extends keyof T>(key: K) {
const json = window.localStorage.getItem(`${STORAGE_PREFIX}${String(key)}`); const json = window.localStorage.getItem(`${STORAGE_PREFIX}${String(key)}`)
if (!json) return null; if (!json)
return null
let storageData: StorageData<T[K]> | null = null; let storageData: StorageData<T[K]> | null = null
try { try {
storageData = decrypto(json); storageData = decrypto(json)
} catch { }
// 防止解析失败 catch {
} // 防止解析失败
}
if (storageData) { if (storageData) {
const { value, expire } = storageData; const { value, expire } = storageData
if (expire === null || expire >= Date.now()) { if (expire === null || expire >= Date.now())
return value; return value
} }
} remove(key)
remove(key); return null
return null; }
}
function remove(key: keyof T) { function remove(key: keyof T) {
window.localStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`); window.localStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`)
} }
function clear() { function clear() {
window.localStorage.clear(); window.localStorage.clear()
} }
return { return {
set, set,
get, get,
remove, remove,
clear, clear,
}; }
} }
/** /**
* sessionStorage部分操作 * sessionStorage部分操作
*/ */
function createSessionStorage<T extends Storage.Session>() { function createSessionStorage<T extends Storage.Session>() {
function set<K extends keyof T>(key: K, value: T[K]) { function set<K extends keyof T>(key: K, value: T[K]) {
const json = encrypto(value); const json = encrypto(value)
window.sessionStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json); window.sessionStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json)
} }
function get<K extends keyof T>(key: K) { function get<K extends keyof T>(key: K) {
const json = sessionStorage.getItem(`${STORAGE_PREFIX}${String(key)}`); const json = sessionStorage.getItem(`${STORAGE_PREFIX}${String(key)}`)
if (!json) return null; if (!json)
return null
let storageData: T[K] | null = null; let storageData: T[K] | null = null
try { try {
storageData = decrypto(json); storageData = decrypto(json)
} catch { }
// 防止解析失败 catch {
} // 防止解析失败
}
if (storageData) { if (storageData)
return storageData; return storageData
}
return null;
}
function remove(key: keyof T) {
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`);
}
function clear() {
window.sessionStorage.clear();
}
return { return null
set, }
get, function remove(key: keyof T) {
remove, window.sessionStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`)
clear, }
}; function clear() {
window.sessionStorage.clear()
}
return {
set,
get,
remove,
clear,
}
} }
export const local = createLocalStorage(); export const local = createLocalStorage()
export const session = createSessionStorage(); export const session = createSessionStorage()

View File

@ -1,3 +1,7 @@
<script setup lang="ts">
import lib from '~/package.json'
</script>
<template> <template>
<n-space vertical> <n-space vertical>
<n-card title="关于"> <n-card title="关于">
@ -72,8 +76,4 @@
</n-space> </n-space>
</template> </template>
<script setup lang="ts">
import lib from '~/package.json';
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,32 @@
<script setup lang="ts">
const tableData = [
{
id: 0,
name: '商品名称1',
start: '2022-02-02',
end: '2022-02-02',
prograss: '100',
status: '已完成',
},
{
id: 0,
name: '商品名称2',
start: '2022-02-02',
end: '2022-02-02',
prograss: '50',
status: '交易中',
},
{
id: 0,
name: '商品名称3',
start: '2022-02-02',
end: '2022-02-02',
prograss: '100',
status: '已完成',
},
]
</script>
<template> <template>
<div> <div>
<n-grid <n-grid
@ -12,7 +41,6 @@
> >
<n-statistic label="访问量"> <n-statistic label="访问量">
<n-number-animation <n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="12039" :to="12039"
show-separator show-separator
@ -29,7 +57,6 @@
<n-space justify="space-between"> <n-space justify="space-between">
<span>累计访问数</span> <span>累计访问数</span>
<span><n-number-animation <span><n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="322039" :to="322039"
show-separator show-separator
@ -46,7 +73,6 @@
> >
<n-statistic label="下载量"> <n-statistic label="下载量">
<n-number-animation <n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="12039" :to="12039"
show-separator show-separator
@ -63,7 +89,6 @@
<n-space justify="space-between"> <n-space justify="space-between">
<span>累计下载量</span> <span>累计下载量</span>
<span><n-number-animation <span><n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="322039" :to="322039"
show-separator show-separator
@ -80,7 +105,6 @@
> >
<n-statistic label="浏览量"> <n-statistic label="浏览量">
<n-number-animation <n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="12039" :to="12039"
show-separator show-separator
@ -97,7 +121,6 @@
<n-space justify="space-between"> <n-space justify="space-between">
<span>累计浏览量</span> <span>累计浏览量</span>
<span><n-number-animation <span><n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="322039" :to="322039"
show-separator show-separator
@ -114,7 +137,6 @@
> >
<n-statistic label="注册量"> <n-statistic label="注册量">
<n-number-animation <n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="12039" :to="12039"
show-separator show-separator
@ -131,7 +153,6 @@
<n-space justify="space-between"> <n-space justify="space-between">
<span>累计注册量</span> <span>累计注册量</span>
<span><n-number-animation <span><n-number-animation
ref="numberAnimationInstRef"
:from="0" :from="0"
:to="322039" :to="322039"
show-separator show-separator
@ -221,33 +242,4 @@
</div> </div>
</template> </template>
<script setup lang="ts">
const tableData = [
{
id: 0,
name: '商品名称1',
start: '2022-02-02',
end: '2022-02-02',
prograss: '100',
status: '已完成',
},
{
id: 0,
name: '商品名称2',
start: '2022-02-02',
end: '2022-02-02',
prograss: '50',
status: '交易中',
},
{
id: 0,
name: '商品名称3',
start: '2022-02-02',
end: '2022-02-02',
prograss: '100',
status: '已完成',
},
];
</script>
<style scoped></style> <style scoped></style>

View File

@ -1,3 +1,9 @@
<script setup lang="ts">
import { useAuthStore } from '@/store'
const { userInfo } = useAuthStore()
</script>
<template> <template>
<n-grid <n-grid
:x-gap="16" :x-gap="16"
@ -364,10 +370,4 @@
</n-grid> </n-grid>
</template> </template>
<script setup lang="ts">
import { useAuthStore } from '@/store';
const { userInfo } = useAuthStore();
</script>
<style scoped></style> <style scoped></style>

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