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

View File

@ -6,5 +6,5 @@
"source.fixAll.eslint": true
},
"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,4 +1,4 @@
import type { ProxyOptions } from 'vite';
import type { ProxyOptions } from 'vite'
/**
* @description: vite代理字段
* @param {*} env -
@ -8,14 +8,14 @@ export function createViteProxy(envConfig: ServiceEnvConfig) {
[envConfig.urlPattern]: {
target: envConfig.url,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${envConfig.urlPattern}`), ''),
rewrite: path => path.replace(new RegExp(`^${envConfig.urlPattern}`), ''),
},
[envConfig.secondUrlPattern]: {
target: envConfig.secondUrl,
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 './plugins';
export * from './config'
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) => {
// 默认使用gzip压缩
const { VITE_COMPRESS_TYPE = 'gzip' } = env;
const { VITE_COMPRESS_TYPE = 'gzip' } = env
return viteCompression({
algorithm: VITE_COMPRESS_TYPE, // 压缩算法
});
};
})
}

View File

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

View File

@ -1,9 +1,9 @@
import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import Icons from 'unplugin-icons/vite'; // https://github.com/antfu/unplugin-icons
import IconsResolver from 'unplugin-icons/resolver';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; // https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md
import path from 'path';
import path from 'node:path'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import Icons from 'unplugin-icons/vite' // https://github.com/antfu/unplugin-icons
import IconsResolver from 'unplugin-icons/resolver'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' // https://github.com/vbenjs/vite-plugin-svg-icons/blob/main/README.zh_CN.md
export default [
Components({
@ -23,4 +23,4 @@ export default [
// inject: 'body-last',
// 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({
gzipSize: true,
brotliSize: true,
open: true,
});
})

View File

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

View File

@ -1,9 +1,9 @@
import Mock from 'mockjs';
import { resultSuccess, resultFailed } from '../utils';
import Mock from 'mockjs'
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 = [
{
@ -30,7 +30,7 @@ const userData = [
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
role: 'user',
},
];
]
const userRoutes = [
{
name: 'dashboard',
@ -433,20 +433,19 @@ const userRoutes = [
icon: 'icon-park-outline:info',
},
},
];
]
export default [
{
url: '/mock/login',
method: 'post',
response: (options: any) => {
const { userName = undefined, password = undefined } = options.body;
const { userName = undefined, password = undefined } = options.body
if (!userName || !password) {
return resultFailed(null, '账号密码不全');
}
if (!userName || !password)
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) {
return {
@ -457,38 +456,38 @@ export default [
token: token(),
refreshToken: token(),
},
};
}
return resultFailed(null, '账号密码错误');
}
return resultFailed(null, '账号密码错误')
},
},
{
url: '/mock/updateToken',
method: 'post',
response: () => {
return resultSuccess({ token: token(), refreshToken: token() });
return resultSuccess({ token: token(), refreshToken: token() })
},
},
{
url: '/mock/getUserInfo',
method: 'get',
response: (options: any) => {
const { userId = undefined } = options.query;
if (!userId) {
return resultFailed(null, '未传入用户id');
}
const userInfo = userData.find((item) => item.userId == userId);
if (userInfo) {
return resultSuccess(userInfo);
}
return resultFailed(null, '未找到用户信息,请检查提交参数');
const { userId = undefined } = options.query
if (!userId)
return resultFailed(null, '未传入用户id')
const userInfo = userData.find(item => item.userId === userId)
if (userInfo)
return resultSuccess(userInfo)
return resultFailed(null, '未找到用户信息,请检查提交参数')
},
},
{
url: '/mock/getUserRoutes',
method: 'post',
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) {
return Mock.mock({
code: 200,
data,
msg: msg || 'success',
});
})
}
export function resultFailed(data: any, msg?: string) {
return Mock.mock({
code: 500,
data,
msg: msg || 'failed',
});
})
}

View File

@ -1,7 +1,7 @@
{
"name": "ench-admin",
"private": true,
"version": "0.0.1",
"private": true,
"description": "",
"author": "iam-see <chen.dev@foxmail.com> (https://github.com/iam-see/)",
"license": "MIT",
@ -27,9 +27,6 @@
"path": "./node_modules/cz-customizable"
}
},
"lint-staged": {
"*.{vue,js,jsx,ts,tsx,json}": "eslint --fix"
},
"dependencies": {
"@vueuse/core": "^9.13.0",
"@wangeditor/editor": "^5.1.23",
@ -46,6 +43,7 @@
"vue-router": "^4.1.6"
},
"devDependencies": {
"@antfu/eslint-config": "^0.37.0",
"@commitlint/cli": "^17.4.1",
"@commitlint/config-conventional": "^17.4.0",
"@iconify-json/icon-park-outline": "^1.1.9",
@ -65,7 +63,7 @@
"commitizen": "^4.2.6",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^7.0.0",
"eslint": "^8.31.0",
"eslint": "^8.36.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-vue": "^9.9.0",
@ -82,6 +80,10 @@
"vite-plugin-compression": "^0.5.1",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-setup-extend": "^0.4.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>
<n-config-provider
class="wh-full"
@ -10,19 +26,4 @@
</n-config-provider>
</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>

View File

@ -1,3 +1,8 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
const appStore = useAppStore()
</script>
<template>
<div
class="cursor-pointer"
@ -8,9 +13,4 @@
</div>
</template>
<script setup lang="ts">
import { useAppStore } from '@/store';
const appStore = useAppStore();
</script>
<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>
<div class="flex-col-center h-full">
<img
@ -27,15 +38,4 @@
</div>
</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>

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>
<n-loading-bar-provider>
<n-dialog-provider>
<n-notification-provider>
<n-message-provider>
<slot></slot>
<naive-provider-content />
<slot />
<NaiveProviderContent />
</n-message-provider>
</n-notification-provider>
</n-dialog-provider>
</n-loading-bar-provider>
</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>

View File

@ -1,3 +1,8 @@
<script setup lang="ts">
import { useAppInfo } from '@/hooks'
const { title } = useAppInfo()
</script>
<template>
<div id="loading-container">
<div class="boxes">
@ -32,11 +37,6 @@
</div>
</template>
<script setup lang="ts">
import { useAppInfo } from '@/hooks';
const { title } = useAppInfo();
</script>
<style scoped>
#loading-container {
width: 100vw;

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>
<n-icon
:size="props.size"
@ -8,19 +23,4 @@
</n-icon>
</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>

View File

@ -32,4 +32,4 @@ export const icons: string[] = [
'ic:baseline-filter-8',
'ic:baseline-filter-9',
'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>
<n-popover v-model:show="showPopover" placement="bottom" trigger="click">
<template #trigger>
<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>
</template>
<template #header>
@ -24,20 +42,4 @@
</n-popover>
</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>

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">
import { ref } from 'vue';
const emit = defineEmits(['change']);
import { ref } from 'vue'
const props = defineProps({
count: {
type: Number,
default: 0,
},
});
const page = ref(1);
const pageSize = ref(10);
const displayOrder: Array<'pages' | 'size-picker' | 'quick-jumper'> = ['size-picker', 'pages'];
})
const emit = defineEmits(['change'])
const page = ref(1)
const pageSize = ref(10)
const displayOrder: Array<'pages' | 'size-picker' | 'quick-jumper'> = ['size-picker', 'pages']
const pageSizes = [
{
label: '10 每页',
@ -41,11 +27,25 @@ const pageSizes = [
label: '50 每页',
value: 50,
},
];
]
function changePage() {
emit('change', page.value, pageSize.value);
emit('change', page.value, pageSize.value)
}
</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>

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">
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(
defineProps<{
text?: string;
size?: number;
text?: string
size?: number
}>(),
{
text: '',
size: 300,
},
);
)
</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>

View File

@ -1,19 +1,5 @@
<template>
<svg
aria-hidden="true"
:width="`${props.size}px`"
:height="`${props.size}px`"
display="inline"
>
<use
:xlink:href="symbolId"
fill="currentColor"
/>
</svg>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed } from 'vue'
const props = defineProps({
prefix: {
type: String,
@ -27,6 +13,20 @@ const props = defineProps({
type: Number,
default: 18,
},
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>
<template>
<svg
aria-hidden="true"
:width="`${props.size}px`"
:height="`${props.size}px`"
display="inline"
>
<use
:xlink:href="symbolId"
fill="currentColor"
/>
</svg>
</template>

View File

@ -18,4 +18,4 @@ export const proxyConfig: Record<ServiceEnvType, ServiceEnvConfig> = {
secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern',
},
};
}

View File

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

View File

@ -1,5 +1,5 @@
/* 高德地图开发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 */
export const BAIDU_MAP_SDK_URL =
'https://api.map.baidu.com/getscript?v=3.0&ak=MwqQwPxa5ipusyNmH1WT62y5DKhYxIgb&services=&t=20220816154130';
export const BAIDU_MAP_SDK_URL
= 'https://api.map.baidu.com/getscript?v=3.0&ak=MwqQwPxa5ipusyNmH1WT62y5DKhYxIgb&services=&t=20220816154130'

View File

@ -1,9 +1,9 @@
/** 默认实例的Aixos配置 */
import type { AxiosRequestConfig } from 'axios';
import type { AxiosRequestConfig } from 'axios'
export const DEFAULT_AXIOS_OPTIONS: AxiosRequestConfig = {
// 请求超时时间,默认15秒
timeout: 15 * 1000,
};
}
/** 默认实例的后端字段配置 */
export const DEFAULT_BACKEND_OPTIONS = {
@ -11,28 +11,28 @@ export const DEFAULT_BACKEND_OPTIONS = {
dataKey: 'data',
msgKey: 'msg',
successCode: 200,
};
}
/** 错误信息的显示时间 */
export const ERROR_MSG_DURATION = 3 * 1000;
export const ERROR_MSG_DURATION = 3 * 1000
/** 默认的请求错误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 */
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 */
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 = {
@ -49,10 +49,10 @@ export const ERROR_STATUS = {
504: '504: 网关超时~',
505: '505: http版本不支持该请求~',
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG,
};
}
/** token刷新的code */
export const REFRESH_TOKEN_CODE = [888, 999];
export const REFRESH_TOKEN_CODE = [888, 999]
/** 没有错误提示的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> = {
0: '女',
1: '男'
};
1: '男',
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,13 @@
import { useAuthStore } from '@/store';
import { isArray, isString } from '@/utils';
import { useAuthStore } from '@/store'
import { isArray, isString } from '@/utils'
interface AppInfo {
/** 项目名称 */
name: string;
name: string
/** 项目标题 */
title: string;
title: string
/** 项目描述 */
desc: string;
desc: string
}
/** 项目信息 */
@ -17,13 +16,13 @@ export function useAppInfo(): AppInfo {
VITE_APP_NAME: name,
VITE_APP_TITLE: title,
VITE_APP_DESC: desc,
} = import.meta.env;
} = import.meta.env
return {
name,
title,
desc,
};
}
}
/** 权限判断 */
@ -31,26 +30,25 @@ export function usePermission() {
const authStore = useAuthStore()
function hasPermission(permission: Auth.RoleType | Auth.RoleType[] | undefined) {
if (!permission)
return true
if (!permission) return true
if (!authStore.userInfo) return false
if (!authStore.userInfo)
return false
const { role } = authStore.userInfo
let has = role === 'super';
let has = role === 'super'
if (!has) {
if (isArray(permission)) {
has = (permission as Auth.RoleType[]).includes(role);
if (isArray(permission))
has = (permission as Auth.RoleType[]).includes(role)
if (isString(permission))
has = (permission as Auth.RoleType) === role
}
if (isString(permission)) {
has = (permission as Auth.RoleType) === role;
}
}
return has;
return has
}
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>
<n-layout
has-sider
@ -83,30 +107,6 @@
</n-layout>
</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>
.n-layout-sider {
box-shadow: 2px 0 8px #1d23290d;

View File

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

View File

@ -1,3 +1,5 @@
<script setup lang="ts"></script>
<template>
<n-el
tag="div"
@ -12,8 +14,6 @@
</n-el>
</template>
<script setup lang="ts"></script>
<style scoped>
.el {
color: var(--n-text-color);

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>
<n-scrollbar style="height: 400px">
<n-list hoverable clickable>
@ -12,34 +27,22 @@
<e-icon :icon="item.icon" :size="30" class="c-primary" />
</template>
<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 v-if="item.description" #description>
<n-ellipsis :line-clamp="2">
{{ item.description }}
</n-ellipsis>
</template>
<template #footer>{{ item.date }}</template>
<template #footer>
{{ item.date }}
</template>
</n-thing>
</n-list-item>
</n-list>
</n-scrollbar>
</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>

View File

@ -1,3 +1,12 @@
<script setup lang="ts">
interface Props {
showWatermark: boolean
}
const props = withDefaults(defineProps<Props>(), {
showWatermark: false,
})
</script>
<template>
<n-watermark
v-if="props.showWatermark"
@ -13,12 +22,3 @@
:rotate="-15"
/>
</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>
<n-breadcrumb class="px-4">
<n-breadcrumb-item
@ -11,18 +25,4 @@
</n-breadcrumb>
</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>

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

View File

@ -1,3 +1,7 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
</script>
<template>
<n-tooltip
placement="bottom"
@ -12,8 +16,4 @@
</n-tooltip>
</template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
</script>
<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>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
@ -10,10 +16,4 @@
</n-tooltip>
</template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
import { useAppStore } from '@/store';
const appStore = useAppStore();
</script>
<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>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
@ -9,11 +16,4 @@
</n-tooltip>
</template>
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue';
const toMyGithub = () => {
window.open('https://github.com/iam-see');
};
</script>
<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">
import HeaderButton from '../common/HeaderButton.vue';
import NoticeList from '../common/NoticeList.vue';
import { ref, computed } from 'vue';
import { computed, ref } from 'vue'
import HeaderButton from '../common/HeaderButton.vue'
import NoticeList from '../common/NoticeList.vue'
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) {
MassageData.value[currentTab.value].list[index].isRead = true;
MassageData.value[currentTab.value].list[index].isRead = true
}
const massageCount = computed(() => {
return MassageData.value.reduce((pre, cur) => {
return pre + cur.list.filter((item) => !item.isRead).length;
}, 0);
});
return pre + cur.list.filter(item => !item.isRead).length
}, 0)
})
</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>

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>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
@ -9,21 +26,4 @@
</n-tooltip>
</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>

View File

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

View File

@ -1,27 +1,10 @@
<template>
<n-dropdown
trigger="click"
:options="options"
@select="handleSelect"
>
<HeaderButton>
<n-avatar
round
size="large"
:src="userInfo?.avatar"
/>
{{ userInfo?.nickName }}
</HeaderButton>
</n-dropdown>
</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';
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 { userInfo, resetAuthStore } = useAuthStore()
const router = useRouter()
const options = [
@ -39,7 +22,7 @@ const { userInfo, resetAuthStore } = useAuthStore();
key: 'loginOut',
icon: renderIcon('icon-park-outline:logout'),
},
];
]
const handleSelect = (key: string | number) => {
if (key === 'loginOut') {
window.$dialog.info({
@ -48,14 +31,30 @@ const { userInfo, resetAuthStore } = useAuthStore();
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
resetAuthStore();
resetAuthStore()
},
})
}
if (key === 'userCenter') {
if (key === 'userCenter')
router.push('/userCenter')
}
};
</script>
<template>
<n-dropdown
trigger="click"
:options="options"
@select="handleSelect"
>
<HeaderButton>
<n-avatar
round
size="large"
:src="userInfo?.avatar"
/>
{{ userInfo?.nickName }}
</HeaderButton>
</n-dropdown>
</template>
<style scoped></style>

View File

@ -1,26 +1,26 @@
/* 侧边栏组件 */
import Logo from './sider/Logo.vue';
import Menu from './sider/Menu.vue';
import Logo from './sider/Logo.vue'
import Menu from './sider/Menu.vue'
/* 头部栏组件 */
import Breadcrumb from './header/Breadcrumb.vue';
import CollapaseButton from './header/CollapaseButton.vue';
import FullScreen from './header/FullScreen.vue';
import DarkMode from './header/DarkMode.vue';
import Setting from './header/Setting.vue';
import Github from './header/Github.vue';
import Notices from './header/Notices.vue';
import UserCenter from './header/UserCenter.vue';
import Search from './header/Search.vue';
import Reload from './header/Reload.vue';
import Breadcrumb from './header/Breadcrumb.vue'
import CollapaseButton from './header/CollapaseButton.vue'
import FullScreen from './header/FullScreen.vue'
import DarkMode from './header/DarkMode.vue'
import Setting from './header/Setting.vue'
import Github from './header/Github.vue'
import Notices from './header/Notices.vue'
import UserCenter from './header/UserCenter.vue'
import Search from './header/Search.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 Watermark from './common/Watermark.vue';
import BackTop from './common/BackTop.vue'
import Watermark from './common/Watermark.vue'
export {
Breadcrumb,
@ -38,4 +38,4 @@ export {
TabBar,
BackTop,
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>
<div
class="h-60px text-2xl flex-center overflow-hidden cursor-pointer"
@ -14,13 +22,4 @@
</div>
</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>

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>
<n-menu
:collapsed="appStore.collapsed"
@ -9,13 +17,4 @@
/>
</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>

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>
<div class="wh-full flex items-end">
<n-tabs
@ -41,101 +138,4 @@
</div>
</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>

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

View File

@ -1,6 +1,6 @@
// 全局引入的静态资源
import 'uno.css';
import '@/styles/css/index.css';
import 'virtual:svg-icons-register';
import 'uno.css'
import '@/styles/css/index.css'
import 'virtual:svg-icons-register'
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,73 +1,74 @@
import { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts/index';
import { useRouteStore } from '@/store';
import { usePermission } from '@/hooks';
import type { RouteRecordRaw } from 'vue-router'
import { BasicLayout } from '@/layouts/index'
import { useRouteStore } from '@/store'
import { usePermission } from '@/hooks'
// 引入所有页面
const modules = import.meta.glob('../../views/**/*.vue');
const modules = import.meta.glob('../../views/**/*.vue')
/* 含有子级的路由重定向到第一个子级 */
function setRedirect(routes: AppRoute.Route[]) {
routes.forEach((route) => {
if (route.children) {
const nonHiddenChild = route.children.find((child) => !child.meta || !child.meta.hide);
if (nonHiddenChild) {
route.redirect = nonHiddenChild.path;
const nonHiddenChild = route.children.find(child => !child.meta || !child.meta.hide)
if (nonHiddenChild)
route.redirect = nonHiddenChild.path
setRedirect(route.children)
}
setRedirect(route.children);
}
});
})
}
/* 路由树转换成一维数组 */
function FlatAuthRoutes(routes: AppRoute.Route[]) {
let result: AppRoute.Route[] = [];
let result: AppRoute.Route[] = []
routes.forEach((item: AppRoute.Route) => {
if (item.children) {
const temp = item.children || [];
delete item.children;
result.push(item);
result = [...result, ...FlatAuthRoutes(temp)];
} else {
result.push(item);
const temp = item.children || []
delete item.children
result.push(item)
result = [...result, ...FlatAuthRoutes(temp)]
}
});
return result;
else {
result.push(item)
}
})
return result
}
/* 路由无权限过滤 */
function filterPermissionRoutes(routes: AppRoute.Route[]) {
const { hasPermission } = usePermission();
const { hasPermission } = usePermission()
return routes.filter((route) => {
return hasPermission(route.meta.roles);
});
return hasPermission(route.meta.roles)
})
}
function createCatheRoutes(routes: AppRoute.Route[]) {
return routes
.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[]) {
/* 复制一层 */
let resultRouter = JSON.parse(JSON.stringify(routes));
let resultRouter = JSON.parse(JSON.stringify(routes))
/* 设置路由重定向到子级第一个 */
setRedirect(resultRouter);
setRedirect(resultRouter)
// 数组降维成一维数组,然后删除所有的childen
resultRouter = FlatAuthRoutes(resultRouter);
resultRouter = FlatAuthRoutes(resultRouter)
/* 路由权限过滤 */
resultRouter = filterPermissionRoutes(resultRouter);
resultRouter = filterPermissionRoutes(resultRouter)
// 过滤需要缓存的路由name数组
const routeStore = useRouteStore();
routeStore.cacheRoutes = createCatheRoutes(resultRouter);
const routeStore = useRouteStore()
routeStore.cacheRoutes = createCatheRoutes(resultRouter)
// 生成路由有redirect的不需要引入文件
resultRouter = resultRouter.map((item: any) => {
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 = {
path: '/appRoot',
@ -79,8 +80,8 @@ export async function createDynamicRoutes(routes: AppRoute.Route[]) {
icon: 'icon-park-outline:home',
},
children: [],
};
// 根据角色过滤后的插入根路由中
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[];
return appRootRoute;
}
// 根据角色过滤后的插入根路由中
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
return appRootRoute
}

View File

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

View File

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

View File

@ -1,17 +1,17 @@
import type { App } from 'vue';
import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { setupRouterGuard } from './guard';
import { routes } from './routes';
import type { App } from 'vue'
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import { setupRouterGuard } from './guard'
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({
history: VITE_ROUTE_MODE === 'hash' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
routes,
});
})
// 安装vue路由
export async function setupRouter(app: App) {
// 添加路由守卫
setupRouterGuard(router);
app.use(router);
await router.isReady(); //https://router.vuejs.org/zh/api/index.html#isready
setupRouterGuard(router)
app.use(router)
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 { test } from './test';
import { dashboard } from './dashboard'
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 { BasicLayout } from '@/layouts/index';
import type { RouteRecordRaw } from 'vue-router'
import { BasicLayout } from '@/layouts/index'
/* 页面中的一些固定路由,错误页等 */
export const routes: RouteRecordRaw[] = [
@ -51,4 +51,4 @@ export const routes: RouteRecordRaw[] = [
redirect: '/404',
},
];
]

View File

@ -1,19 +1,19 @@
import { mockRequest } from '../http';
import { mockRequest } from '../http'
interface Ilogin {
userName: string;
password: string;
userName: string
password: string
}
export function fetchLogin(params: Ilogin) {
return mockRequest.post<any>('/login', params);
return mockRequest.post<any>('/login', params)
}
export function fetchUpdateToken(params: any) {
return mockRequest.post<ApiAuth.loginToken>('/updateToken', params);
return mockRequest.post<ApiAuth.loginToken>('/updateToken', params)
}
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 }) {
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() {
return mockRequest.get('/userList');
return mockRequest.get('/userList')
}

View File

@ -1,46 +1,45 @@
import { request } from '../http';
import { mockRequest } from '../http';
import { mockRequest, request } from '../http'
/* get方法测试 */
export function fetachGet(params?: any) {
return request.get('/getAPI', { params });
return request.get('/getAPI', { params })
}
/* post方法测试 */
export function fetachPost(data: any) {
return request.post('/postAPI', data);
return request.post('/postAPI', data)
}
/* formPost方法测试 */
export function fetachFormPost(data: any) {
return request.formPost('/postAPI', data);
return request.formPost('/postAPI', data)
}
/* delete方法测试 */
export function fetachDelete() {
return request.delete('/deleteAPI');
return request.delete('/deleteAPI')
}
/* put方法测试 */
export function fetachPut(data: any) {
return request.put('/putAPI', data);
return request.put('/putAPI', data)
}
/* 测试状态码500失败 */
export function testFailedRequest() {
return request.get('/filedRequest');
return request.get('/filedRequest')
}
/* 测试业务码500失败 */
export function testFailedResponse() {
return request.get('/filedResponse');
return request.get('/filedResponse')
}
/* 测试token刷新接口 */
export function testUpdataToken() {
return request.get('/updataToken');
return request.get('/updataToken')
}
/* 测试token刷新接口 */
export function testFailedResponse_NT() {
return request.get('/failedResponse_NT');
return request.get('/failedResponse_NT')
}
/* mock方法测试 */
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 {
DEFAULT_REQUEST_ERROR_CODE,
DEFAULT_REQUEST_ERROR_MSG,
ERROR_STATUS,
NETWORK_ERROR_CODE,
NETWORK_ERROR_MSG,
REQUEST_TIMEOUT_CODE,
REQUEST_TIMEOUT_MSG,
ERROR_STATUS,
} from '@/config';
import { useAuthStore } from '@/store';
import { fetchUpdateToken } from '@/service';
import { local } from '@/utils';
import { showError } from './utils';
} from '@/config'
import { useAuthStore } from '@/store'
import { fetchUpdateToken } from '@/service'
import { local } from '@/utils'
type ErrorStatus = keyof typeof ERROR_STATUS;
type ErrorStatus = keyof typeof ERROR_STATUS
/**
* @description: axios或http错误
@ -25,28 +25,28 @@ export function handleAxiosError(err: AxiosError) {
type: 'Axios',
code: DEFAULT_REQUEST_ERROR_CODE,
msg: DEFAULT_REQUEST_ERROR_MSG,
};
// 网络错误
if (!window.navigator.onLine || err.message === 'Network Error') {
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });
}
// 网络错误
if (!window.navigator.onLine || err.message === 'Network Error')
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 });
const errorCode: ErrorStatus = (err.response?.status as ErrorStatus) || 'DEFAULT'
const msg = ERROR_STATUS[errorCode]
Object.assign(error, { code: errorCode, msg })
}
showError(error);
showError(error)
return error;
return error
}
/**
@ -59,21 +59,22 @@ export function handleResponseError(response: AxiosResponse) {
type: 'Axios',
code: DEFAULT_REQUEST_ERROR_CODE,
msg: DEFAULT_REQUEST_ERROR_MSG,
};
}
if (!window.navigator.onLine) {
// 网路错误
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });
} else {
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG })
}
else {
// 请求成功的状态码非200的错误
const errorCode: ErrorStatus = response.status as ErrorStatus;
const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG;
Object.assign(error, { type: 'Response', code: errorCode, msg });
const errorCode: ErrorStatus = response.status as ErrorStatus
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 {*}
*/
export function handleBusinessError(data: Record<string, any>, config: Service.BackendResultConfig) {
const { codeKey, msgKey } = config;
const { codeKey, msgKey } = config
const error: Service.RequestError = {
type: 'Business',
code: data[codeKey],
msg: data[msgKey],
};
}
showError(error);
showError(error)
return error;
return error
}
/**
@ -106,14 +107,14 @@ export async function handleServiceResult<T = any>(data: any, error: Service.Req
const fail: Service.FailedResult = {
error,
data: null,
};
return fail;
}
return fail
}
const success: Service.SuccessResult<T> = {
error: null,
data,
};
return success;
}
return success
}
/**
@ -122,20 +123,19 @@ export async function handleServiceResult<T = any>(data: any, error: Service.Req
* @return {*}
*/
export async function handleRefreshToken(config: AxiosRequestConfig) {
const { resetAuthStore } = useAuthStore();
const refreshToken = local.get('refreshToken');
const { data } = await fetchUpdateToken(refreshToken);
const { resetAuthStore } = useAuthStore()
const refreshToken = local.get('refreshToken')
const { data } = await fetchUpdateToken(refreshToken)
if (data) {
local.set('refreshToken', data.token, )
local.set('refreshToken', data.token)
local.set('token', data.refreshToken)
// 设置token
if (config.headers) {
typeof config.headers.set === 'function' && config.headers.set('Authorization', `Bearer ${data.token || ''}`);
}
if (config.headers)
typeof config.headers.set === 'function' && config.headers.set('Authorization', `Bearer ${data.token || ''}`)
return config;
return config
}
resetAuthStore();
return null;
resetAuthStore()
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 mockRequest = createRequest({ baseURL: '/mock' });
export const mockRequest = createRequest({ baseURL: '/mock' })

View File

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

View File

@ -1,30 +1,29 @@
import type { AxiosRequestConfig, AxiosInstance } from 'axios';
import createAxiosInstance from './instance';
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import CreateAxiosInstance from './instance'
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'
interface RequestParam {
url: string;
method?: RequestMethod;
data?: any;
config?: AxiosRequestConfig;
url: string
method?: RequestMethod
data?: any
config?: AxiosRequestConfig
}
async function getRequestResponse(options: {
instance: AxiosInstance;
method: RequestMethod;
url: string;
data?: any;
config?: AxiosRequestConfig;
instance: AxiosInstance
method: RequestMethod
url: string
data?: any
config?: AxiosRequestConfig
}) {
const { instance, method, url, data, config } = options;
const { instance, method, url, data, config } = options
let res: any;
if (method === 'get' || method === 'delete') {
res = await instance[method](url, config);
} else {
res = await instance[method](url, data, config);
}
return res;
let res: any
if (method === 'get' || method === 'delete')
res = await instance[method](url, config)
else res = await instance[method](url, data, config)
return res
}
/**
@ -33,8 +32,11 @@ async function getRequestResponse(options: {
* @param {Service} backendConfig -
* @return {*}
*/
export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {
const axiosInstance = new createAxiosInstance(axiosConfig, backendConfig);
export function createRequest(
axiosConfig: AxiosRequestConfig,
backendConfig?: Service.BackendResultConfig,
) {
const axiosInstance = new CreateAxiosInstance(axiosConfig, backendConfig)
/**
* promise请求
* @param param -
@ -43,17 +45,19 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
* - data: 请求的body的data
* - config: axios配置
*/
async function asyncRequest<T>(param: RequestParam): Promise<Service.RequestResult<T>> {
const { url, method = 'get', data, config } = param;
const { instance } = axiosInstance;
async function asyncRequest<T>(
param: RequestParam,
): Promise<Service.RequestResult<T>> {
const { url, method = 'get', data, config } = param
const { instance } = axiosInstance
const res = (await getRequestResponse({
instance,
method,
url,
data,
config,
})) as Service.RequestResult<T>;
return res;
})) as Service.RequestResult<T>
return res
}
/**
* get请求
@ -61,7 +65,7 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
* @param config - axios配置
*/
function get<T>(url: string, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, config: config });
return asyncRequest<T>({ url, config })
}
/**
@ -71,7 +75,7 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
* @param config - axios配置
*/
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 })
}
/**
@ -80,9 +84,13 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
* @param data - body的data
* @param config - axios配置
*/
function formPost<T>(url: string, data?: any, config: AxiosRequestConfig = {}) {
function formPost<T>(
url: string,
data?: any,
config: AxiosRequestConfig = {},
) {
config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
return asyncRequest<T>({ url, method: 'post', data, config: config });
return asyncRequest<T>({ url, method: 'post', data, config })
}
/**
@ -90,8 +98,12 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
* @param url -
* @param config - axios配置
*/
function handleDelete<T>(url: string, params?: any, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'delete', config: config });
function handleDelete<T>(
url: string,
params?: any,
config?: AxiosRequestConfig,
) {
return asyncRequest<T>({ url, method: 'delete', config })
}
/**
@ -101,7 +113,7 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
* @param config - axios配置
*/
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 {
@ -110,5 +122,5 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
formPost,
put,
delete: handleDelete,
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +1,21 @@
import { defineStore } from 'pinia';
import { renderIcon, local } from '@/utils';
import { MenuOption } from 'naive-ui';
import { createDynamicRoutes } from '@/router/guard/dynamic';
import { router } from '@/router';
import { fetchUserRoutes } from '@/service';
import { staticRoutes } from '@/router/modules';
import { RouterLink } from 'vue-router';
import { usePermission } from '@/hooks';
import { h } from 'vue';
import { defineStore } from 'pinia'
import type { MenuOption } from 'naive-ui'
import { RouterLink } from 'vue-router'
import { h } from 'vue'
import { local, renderIcon } from '@/utils'
import { createDynamicRoutes } from '@/router/guard/dynamic'
import { router } from '@/router'
import { fetchUserRoutes } from '@/service'
import { staticRoutes } from '@/router/modules'
import { usePermission } from '@/hooks'
interface RoutesStatus {
isInitAuthRoute: boolean;
menus: any;
userRoutes: AppRoute.Route[];
activeMenu: string | null;
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];
cacheRoutes: string[];
isInitAuthRoute: boolean
menus: any
userRoutes: AppRoute.Route[]
activeMenu: string | null
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE']
cacheRoutes: string[]
}
export const useRouteStore = defineStore('route-store', {
state: (): RoutesStatus => {
@ -26,72 +26,74 @@ export const useRouteStore = defineStore('route-store', {
activeMenu: null,
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
cacheRoutes: [],
};
}
},
actions: {
resetRouteStore() {
this.resetRoutes();
this.$reset();
this.resetRoutes()
this.$reset()
},
resetRoutes() {
/* 删除后面添加的路由 */
router.removeRoute('appRoot');
router.removeRoute('appRoot')
},
/* 根据当前路由的name生成面包屑数据 */
createBreadcrumbFromRoutes(routeName = '/') {
const path: AppRoute.Route[] = [];
const path: AppRoute.Route[] = []
// 筛选所有包含目标的各级路由组合成一维数组
const getPathfromRoutes = (routeName: string, userRoutes: AppRoute.Route[]) => {
const getPathfromRoutes = (
routeName: string,
userRoutes: AppRoute.Route[],
) => {
userRoutes.forEach((item) => {
if (this.hasPathinAllPath(routeName, item)) {
path.push(item);
if (item.children && item.children.length !== 0) {
getPathfromRoutes(routeName, item.children);
path.push(item)
if (item.children && item.children.length !== 0)
getPathfromRoutes(routeName, item.children)
}
})
}
});
};
getPathfromRoutes(routeName, this.userRoutes);
return path;
getPathfromRoutes(routeName, this.userRoutes)
return path
},
/* 判断当前路由和子路由中是否存在为routeName的路由 */
hasPathinAllPath(routeName: string, userRoutes: AppRoute.Route) {
if (userRoutes.name === routeName) {
return true;
}
if (userRoutes.name === routeName)
return true
if (userRoutes.children && userRoutes.children.length !== 0) {
const arr: boolean[] = [];
const arr: boolean[] = []
userRoutes.children.forEach((item) => {
arr.push(this.hasPathinAllPath(routeName, item));
});
arr.push(this.hasPathinAllPath(routeName, item))
})
return arr.some((item) => {
return item;
});
return item
})
}
return false;
return false
},
/* 设置当前高亮的菜单key */
setActiveMenu(key: string) {
this.activeMenu = key;
this.activeMenu = key
},
/* 生成侧边菜单的数据 */
createMenus(userRoutes: AppRoute.Route[]) {
this.userRoutes = userRoutes;
this.userRoutes = userRoutes
let resultMenus = JSON.parse(JSON.stringify(userRoutes));
resultMenus = this.removeHiddenRoutes(resultMenus);
this.menus = this.transformAuthRoutesToMenus(resultMenus);
let resultMenus = JSON.parse(JSON.stringify(userRoutes))
resultMenus = this.removeHiddenRoutes(resultMenus)
this.menus = this.transformAuthRoutesToMenus(resultMenus)
},
/** 过滤不需要显示的菜单 */
removeHiddenRoutes(routes: AppRoute.Route[]) {
return routes.filter((route) => {
if (route.meta && route.meta.hide) {
return false;
} else if (route.children) {
route.children = this.removeHiddenRoutes(route.children);
}
return true;
});
if (route.meta && route.meta.hide)
return false
else if (route.children)
route.children = this.removeHiddenRoutes(route.children)
return true
})
},
//* 将返回的路由表渲染成侧边栏 */
@ -100,26 +102,24 @@ export const useRouteStore = defineStore('route-store', {
userRoutes
/** 过滤没有权限的侧边菜单 */
.filter((item: AppRoute.Route) => {
const { hasPermission } = usePermission();
return hasPermission(item.meta.roles);
const { hasPermission } = usePermission()
return hasPermission(item.meta.roles)
})
/** 根据order大小菜单排序 */
.sort((a, b) => {
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;
}
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
(!item.children || item.children.length === 0)
? () =>
h(
RouterLink,
@ -128,61 +128,59 @@ export const useRouteStore = defineStore('route-store', {
path: item.path,
},
},
{ default: () => item.meta.title }
{ default: () => item.meta.title },
)
: item.meta.title,
key: item.path,
icon: renderIcon(item.meta.icon),
};
}
/** 判断子元素 */
if (item.children) {
const children = this.transformAuthRoutesToMenus(item.children);
const children = this.transformAuthRoutesToMenus(item.children)
// 只有子元素有且不为空时才添加
if (children.length !== 0) {
target.children = children;
} else {
target.children = undefined;
if (children.length !== 0)
target.children = children
else target.children = undefined
}
}
return target;
return target
})
);
)
},
/* 初始化动态路由 */
async initDynamicRoute() {
// 根据用户id来获取用户的路由
const userInfo = local.get('userInfo');
const userInfo = local.get('userInfo')
if (!userInfo || !userInfo.userId) {
return;
}
if (!userInfo || !userInfo.userId)
return
const { data: routes } = await fetchUserRoutes({ userId: userInfo.userId });
const { data: routes } = await fetchUserRoutes({
userId: userInfo.userId,
})
// 根据用户返回的路由表来生成真实路由
const appRoutes = await createDynamicRoutes(routes);
const appRoutes = await createDynamicRoutes(routes)
// 生成侧边菜单
this.createMenus(routes);
this.createMenus(routes)
// 插入路由表
router.addRoute(appRoutes);
router.addRoute(appRoutes)
},
/* 初始化静态路由 */
async initStaticRoute() {
// 根据静态路由表来生成真实路由
const appRoutes = await createDynamicRoutes(staticRoutes);
const appRoutes = await createDynamicRoutes(staticRoutes)
// 生成侧边菜单
this.createMenus(staticRoutes);
this.createMenus(staticRoutes)
// 插入路由表
router.addRoute(appRoutes);
router.addRoute(appRoutes)
},
async initAuthRoute() {
this.isInitAuthRoute = false;
if (this.authRouteMode === 'dynamic') {
await this.initDynamicRoute();
} else {
await this.initStaticRoute();
}
this.isInitAuthRoute = true;
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 { RouteLocationNormalized } from 'vue-router';
import { useAppRouter } from '@/hooks';
import { defineStore } from 'pinia'
import type { RouteLocationNormalized } from 'vue-router'
import { useAppRouter } from '@/hooks'
interface TabState {
inherentTab: {
name: string;
title: string;
path: string;
}[];
tabs: RouteLocationNormalized[];
tabWhiteList: string[];
currentTab: string;
name: string
title: string
path: string
}[]
tabs: RouteLocationNormalized[]
tabWhiteList: string[]
currentTab: string
}
export const useTabStore = defineStore('tab-store', {
state: (): TabState => {
@ -25,92 +25,92 @@ export const useTabStore = defineStore('tab-store', {
tabs: [],
tabWhiteList: ['404', '403', '500', 'login'],
currentTab: 'dashboard_workbench',
};
}
},
getters: {
inherentTabName(): string[] {
return this.inherentTab.map((item) => {
return item.name;
});
return item.name
})
},
},
actions: {
addTab(route: RouteLocationNormalized) {
// 如果已经在固有标签里则不添加
if (this.inherentTabName.includes(route.name as string)) {
return;
}
if (this.inherentTabName.includes(route.name as string))
return
// 如果标签名称已存在则不添加
if (this.hasExistTab(route.name as string)) {
return;
}
if (this.hasExistTab(route.name as string))
return
// 如果在白名单内则不添加,错误页等
if (this.tabWhiteList.includes(route.name as string)) {
return;
}
this.tabs.push(route);
if (this.tabWhiteList.includes(route.name as string))
return
this.tabs.push(route)
},
closeTab(name: string) {
const { routerPush, toRoot } = useAppRouter(false);
const tabsLength = this.tabs.length;
const { routerPush, toRoot } = useAppRouter(false)
const tabsLength = this.tabs.length
// 如果动态标签大于一个,才会标签跳转
if (this.tabs.length > 1) {
// 获取关闭的标签索引
const index = this.getTabIndex(name);
const isLast = index + 1 === tabsLength;
const index = this.getTabIndex(name)
const isLast = index + 1 === tabsLength
// 如果是关闭的当前页面,路由跳转到原先标签的后一个标签
if (this.currentTab === name && !isLast) {
// 跳转到后一个标签
routerPush(this.tabs[index + 1].path);
} else if (this.currentTab === name && isLast) {
routerPush(this.tabs[index + 1].path)
}
else if (this.currentTab === name && isLast) {
// 已经是最后一个了,就跳转前一个
routerPush(this.tabs[index - 1].path);
routerPush(this.tabs[index - 1].path)
}
}
// 删除标签
this.tabs = this.tabs.filter((item) => {
return item.name !== name;
});
return item.name !== name
})
// 删除后如果清空了,就跳转到默认首页
if (tabsLength - 1 === 0) {
toRoot();
}
if (tabsLength - 1 === 0)
toRoot()
},
closeOtherTabs(name: string) {
const index = this.getTabIndex(name);
this.tabs = this.tabs.filter((item, i) => i === index);
const index = this.getTabIndex(name)
this.tabs = this.tabs.filter((item, i) => i === index)
},
closeLeftTabs(name: string) {
const index = this.getTabIndex(name);
this.tabs = this.tabs.filter((item, i) => i >= index);
const index = this.getTabIndex(name)
this.tabs = this.tabs.filter((item, i) => i >= index)
},
closeRightTabs(name: string) {
const index = this.getTabIndex(name);
this.tabs = this.tabs.filter((item, i) => i <= index);
const index = this.getTabIndex(name)
this.tabs = this.tabs.filter((item, i) => i <= index)
},
closeAllTabs() {
const { toRoot } = useAppRouter(false);
this.tabs.length = 0;
toRoot();
const { toRoot } = useAppRouter(false)
this.tabs.length = 0
toRoot()
},
hasExistTab(name: string) {
return this.tabs.some((item) => {
return item.name === name;
});
return item.name === name
})
},
/* 设置当前激活的标签 */
setCurrentTab(name: string) {
this.currentTab = name;
this.currentTab = name
},
getTabIndex(name: string) {
return this.tabs.findIndex((item) => {
return item.name === name;
});
return item.name === name
})
},
},
persist: {
enabled: true,
},
});
})

View File

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

View File

@ -1,10 +1,10 @@
import { h } from 'vue';
import { Icon } from '@iconify/vue';
import { NIcon } from 'naive-ui';
import { h } from 'vue'
import { Icon } from '@iconify/vue'
import { NIcon } from 'naive-ui'
export function renderIcon(icon?: string) {
if (!icon) {
if (!icon)
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 './is';
export * from './storage';
export * from './icon'
export * from './is'
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) {
return toString.call(val) === `[object ${type}]`;
return toString.call(val) === `[object ${type}]`
}
export function isString(val: unknown): val is string {
return is(val, 'String');
return is(val, 'String')
}
export function isNumber(val: unknown): val is number {
return is(val, 'Number');
return is(val, 'Number')
}
export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean');
return is(val, 'Boolean')
}
export function isNull(val: unknown): val is null {
return val === null;
return val === null
}
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 {
return typeof val !== 'undefined';
return typeof val !== '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> {
return val !== null && is(val, 'Object');
return val !== null && is(val, 'Object')
}
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 {
if (isArray(val) || isString(val)) {
return val.length === 0;
}
if (isArray(val) || isString(val))
return val.length === 0
if (val instanceof Map || val instanceof Set) {
return val.size === 0;
}
if (val instanceof Map || val instanceof Set)
return val.size === 0
if (isObject(val)) {
return Object.keys(val).length === 0;
}
if (isObject(val))
return Object.keys(val).length === 0
return false;
return false
}
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> {
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 {
return typeof val === 'function';
return typeof val === 'function'
}
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 {
return is(val, 'RegExp');
return is(val, 'RegExp')
}
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 {
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 {
const reg =
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
return reg.test(path);
const reg
= /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/
return reg.test(path)
}

View File

@ -1,10 +1,10 @@
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> {
value: T;
expire: number | null;
value: T
expire: number | null
}
/**
* LocalStorage部分操作
@ -16,45 +16,46 @@ function createLocalStorage<T extends Storage.Local>() {
const storageData: StorageData<T[K]> = {
value,
expire: new Date().getTime() + expire * 1000,
};
const json = encrypto(storageData);
window.localStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json);
}
const json = encrypto(storageData)
window.localStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json)
}
function get<K extends keyof T>(key: K) {
const json = window.localStorage.getItem(`${STORAGE_PREFIX}${String(key)}`);
if (!json) return null;
const json = window.localStorage.getItem(`${STORAGE_PREFIX}${String(key)}`)
if (!json)
return null
let storageData: StorageData<T[K]> | null = null;
let storageData: StorageData<T[K]> | null = null
try {
storageData = decrypto(json);
} catch {
storageData = decrypto(json)
}
catch {
// 防止解析失败
}
if (storageData) {
const { value, expire } = storageData;
if (expire === null || expire >= Date.now()) {
return value;
const { value, expire } = storageData
if (expire === null || expire >= Date.now())
return value
}
}
remove(key);
return null;
remove(key)
return null
}
function remove(key: keyof T) {
window.localStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`);
window.localStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`)
}
function clear() {
window.localStorage.clear();
window.localStorage.clear()
}
return {
set,
get,
remove,
clear,
};
}
}
/**
* sessionStorage部分操作
@ -62,30 +63,32 @@ function createLocalStorage<T extends Storage.Local>() {
function createSessionStorage<T extends Storage.Session>() {
function set<K extends keyof T>(key: K, value: T[K]) {
const json = encrypto(value);
window.sessionStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json);
const json = encrypto(value)
window.sessionStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json)
}
function get<K extends keyof T>(key: K) {
const json = sessionStorage.getItem(`${STORAGE_PREFIX}${String(key)}`);
if (!json) return null;
const json = sessionStorage.getItem(`${STORAGE_PREFIX}${String(key)}`)
if (!json)
return null
let storageData: T[K] | null = null;
let storageData: T[K] | null = null
try {
storageData = decrypto(json);
} catch {
storageData = decrypto(json)
}
catch {
// 防止解析失败
}
if (storageData) {
return storageData;
}
return null;
if (storageData)
return storageData
return null
}
function remove(key: keyof T) {
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`);
window.sessionStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`)
}
function clear() {
window.sessionStorage.clear();
window.sessionStorage.clear()
}
return {
@ -93,8 +96,8 @@ function createSessionStorage<T extends Storage.Session>() {
get,
remove,
clear,
};
}
}
export const local = createLocalStorage();
export const session = createSessionStorage();
export const local = createLocalStorage()
export const session = createSessionStorage()

View File

@ -1,3 +1,7 @@
<script setup lang="ts">
import lib from '~/package.json'
</script>
<template>
<n-space vertical>
<n-card title="关于">
@ -72,8 +76,4 @@
</n-space>
</template>
<script setup lang="ts">
import lib from '~/package.json';
</script>
<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>
<div>
<n-grid
@ -12,7 +41,6 @@
>
<n-statistic label="访问量">
<n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="12039"
show-separator
@ -29,7 +57,6 @@
<n-space justify="space-between">
<span>累计访问数</span>
<span><n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="322039"
show-separator
@ -46,7 +73,6 @@
>
<n-statistic label="下载量">
<n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="12039"
show-separator
@ -63,7 +89,6 @@
<n-space justify="space-between">
<span>累计下载量</span>
<span><n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="322039"
show-separator
@ -80,7 +105,6 @@
>
<n-statistic label="浏览量">
<n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="12039"
show-separator
@ -97,7 +121,6 @@
<n-space justify="space-between">
<span>累计浏览量</span>
<span><n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="322039"
show-separator
@ -114,7 +137,6 @@
>
<n-statistic label="注册量">
<n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="12039"
show-separator
@ -131,7 +153,6 @@
<n-space justify="space-between">
<span>累计注册量</span>
<span><n-number-animation
ref="numberAnimationInstRef"
:from="0"
:to="322039"
show-separator
@ -221,33 +242,4 @@
</div>
</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>

View File

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

View File

@ -1,9 +1,9 @@
<script setup lang="ts"></script>
<template>
<div class="h-full">
<iframe src="https://cn.vitejs.dev/guide/" frameborder="0" class="wh-full"></iframe>
<iframe src="https://cn.vitejs.dev/guide/" frameborder="0" class="wh-full" />
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

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