Compare commits

..

30 Commits

Author SHA1 Message Date
Soybean
889c859865 Merge pull request #82 from tclyjy/main
feat(layouts): 添加侧边栏/头部的反转模式来增加对比度
2022-04-27 16:46:09 +08:00
元家怿
3c8dd772f8 feat(layouts): 添加侧边栏/头部的反转模式来增加对比度 2022-04-27 16:39:20 +08:00
Soybean
20347b7d65 Merge pull request #79 from toolvcn/相思
feat(projects): HTML lang 修改为 zh-cmn-Hans
2022-04-22 13:31:11 +08:00
相思
dbeb595c0b feat(projects): HTML lang 修改为 zh-cmn-Hans 2022-04-22 13:00:50 +08:00
Soybean
5e276421ad refactor(projects): 代码优化 2022-04-22 09:07:53 +08:00
Soybean
219f87f467 fix(projects): 添加获取路由组件文件未找到时的错误提示 2022-04-21 09:04:18 +08:00
Soybean
b35ed8960d refactor(projects): 去除在pinia的getters的函数调用副作用,用watch代替 2022-04-21 00:46:03 +08:00
Soybean
24010d05fb feat(projects): 登录页背景图片位置适配移动端 2022-04-21 00:26:21 +08:00
Soybean
ec0776e268 feat(projects): 登录页面适配移动端 2022-04-21 00:15:34 +08:00
Soybean
cecce83bc3 build(deps): update deps 2022-04-18 23:47:38 +08:00
Soybean
4eb46ea3dd chore(deps): update deps 2022-04-14 00:02:19 +08:00
Soybean
e8b534b84e refactor(projects): 代码优化 2022-04-13 23:45:15 +08:00
Soybean
46e1ae7825 fix(projects): 修复获取vite环境变量的方式 2022-04-07 22:48:52 +08:00
Soybean
60a55a776e docs(projects): update README.md 2022-04-06 14:34:52 +08:00
Soybean
bed4292ed3 feat(projects): 添加请求适配器的请求示例 2022-04-04 19:13:15 +08:00
Soybean
6bed9ead38 feat(projects): 插件方式按需引入naiveUI 2022-04-04 17:41:55 +08:00
Soybean
3fb13ca9e7 fix(projects): 修复在新版vite下环境变量获取不到的问题 2022-04-04 17:30:26 +08:00
Soybean
2d6d179d66 fix(projects): 去除从环境文件引入端口号导致的错误 2022-04-03 01:07:25 +08:00
Soybean
eebb753884 Merge pull request #70 from yanbowe/main
fix(projects): 全局搜索弹窗弹出时动画闪屏问题
2022-04-02 14:13:39 +08:00
yanbowen
bb1bbf2724 fix(projects): 全局搜索弹窗弹出时动画闪屏问题 2022-04-02 10:35:21 +08:00
Soybean
df56abe18d style(projects): update prettier config 2022-04-01 14:47:57 +08:00
Soybean
ca2dfa6185 feat(projects): 新增静态路由 2022-03-30 01:30:12 +08:00
Soybean
bbfdcc8276 Merge pull request #67 from Southliu/main
perf: refresh-koken命名
2022-03-24 15:59:00 +08:00
“Southliu”
1715504789 perf: refresh-koken命名 2022-03-24 11:49:07 +08:00
Soybean
9a90f18e77 docs(projects): update README.md 2022-03-23 15:21:09 +08:00
Soybean
21645537d5 docs(projects): update README.md 2022-03-15 01:34:31 +08:00
Soybean
e6c26fcb4a fix(projects): 修复路由守卫的动态路由逻辑 2022-03-14 15:16:28 +08:00
Soybean
20911dd882 refactor(projects): lint命令修改 2022-03-13 19:42:01 +08:00
Soybean
cd7ca8f4c7 fix(projects): 修复vite alias 2022-03-13 18:37:44 +08:00
Soybean
ca707a456b build(projects): vite.config代码优化 2022-03-13 18:14:27 +08:00
175 changed files with 3857 additions and 5292 deletions

5
.env
View File

@@ -6,4 +6,7 @@ VITE_APP_TITLE=Soybean管理系统
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版 VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
VITE_HTTP_PROXY=true # 权限路由模式: static dynamic
VITE_AUTH_ROUTE_MODE=dynamic
VITE_VISUALIZER=false

View File

@@ -9,20 +9,20 @@ type ServiceEnv = Record<
} }
>; >;
/** 请求的环境 */ /** 环境配置 */
const serviceEnvConfig: ServiceEnv = { const serviceEnvConfig: ServiceEnv = {
dev: { dev: {
url: 'http://localhost:8080', url: 'http://localhost:8080',
proxy: '/api', proxy: '/api'
}, },
test: { test: {
url: 'http://localhost:8080', url: 'http://localhost:8080',
proxy: '/api', proxy: '/api'
}, },
prod: { prod: {
url: 'http://localhost:8080', url: 'http://localhost:8080',
proxy: '/api', proxy: '/api'
}, }
}; };
/** /**
@@ -32,7 +32,7 @@ const serviceEnvConfig: ServiceEnv = {
export function getEnvConfig(env: ImportMetaEnv) { export function getEnvConfig(env: ImportMetaEnv) {
const { VITE_ENV_TYPE = 'dev' } = env; const { VITE_ENV_TYPE = 'dev' } = env;
const envConfig = { const envConfig = {
http: serviceEnvConfig[VITE_ENV_TYPE], http: serviceEnvConfig[VITE_ENV_TYPE]
}; };
return envConfig; return envConfig;
} }

View File

@@ -1,2 +1 @@
# 是否开启打包文件大小结果分析 VITE_HTTP_PROXY=true
VITE_VISUALIZER=false

View File

@@ -0,0 +1 @@

View File

@@ -13,3 +13,4 @@ lib
.local .local
package.json package.json
!.env-config.ts !.env-config.ts
components.d.ts

View File

@@ -2,19 +2,19 @@ module.exports = {
env: { env: {
browser: true, browser: true,
es2021: true, es2021: true,
'vue/setup-compiler-macros': true, 'vue/setup-compiler-macros': true
}, },
globals: { globals: {
PROJECT_BUILD_TIME: 'readonly', PROJECT_BUILD_TIME: 'readonly',
AMap: 'readonly', AMap: 'readonly',
BMap: 'readonly', BMap: 'readonly',
TMap: 'readonly', TMap: 'readonly'
}, },
parser: 'vue-eslint-parser', parser: 'vue-eslint-parser',
parserOptions: { parserOptions: {
ecmaVersion: 12, ecmaVersion: 12,
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
sourceType: 'module', sourceType: 'module'
}, },
plugins: ['vue', '@typescript-eslint'], plugins: ['vue', '@typescript-eslint'],
extends: [ extends: [
@@ -24,7 +24,7 @@ module.exports = {
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'@vue/eslint-config-typescript/recommended', '@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier', '@vue/eslint-config-prettier',
'@vue/typescript/recommended', '@vue/typescript/recommended'
], ],
rules: { rules: {
'import/extensions': 'off', 'import/extensions': 'off',
@@ -38,22 +38,22 @@ module.exports = {
{ {
pattern: 'vue', pattern: 'vue',
group: 'external', group: 'external',
position: 'before', position: 'before'
}, },
{ {
pattern: 'vue-router', pattern: 'vue-router',
group: 'external', group: 'external',
position: 'before', position: 'before'
}, },
{ {
pattern: 'vuex', pattern: 'vuex',
group: 'external', group: 'external',
position: 'before', position: 'before'
}, },
{ {
pattern: 'pinia', pattern: 'pinia',
group: 'external', group: 'external',
position: 'before', position: 'before'
}, },
// ui framework, such as "naive-ui" // ui framework, such as "naive-ui"
// { // {
@@ -64,92 +64,92 @@ module.exports = {
{ {
pattern: '@/config', pattern: '@/config',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/settings', pattern: '@/settings',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/enum', pattern: '@/enum',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/plugins', pattern: '@/plugins',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/layouts', pattern: '@/layouts',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/views', pattern: '@/views',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/components', pattern: '@/components',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/router', pattern: '@/router',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/store', pattern: '@/store',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/composables', pattern: '@/composables',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/hooks', pattern: '@/hooks',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/service', pattern: '@/service',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/utils', pattern: '@/utils',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/assets', pattern: '@/assets',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/**', pattern: '@/**',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, },
{ {
pattern: '@/interface', pattern: '@/interface',
group: 'internal', group: 'internal',
position: 'before', position: 'before'
}, }
], ],
pathGroupsExcludedImportTypes: [ pathGroupsExcludedImportTypes: [
'vue', 'vue',
'vue-router', 'vue-router',
'vuex', 'vuex',
'pinia', 'pinia'
// 'naive-ui' // 'naive-ui'
], ]
}, }
], ],
'import/no-unresolved': 'off', 'import/no-unresolved': 'off',
'import/prefer-default-export': 'off', 'import/prefer-default-export': 'off',
@@ -158,8 +158,8 @@ module.exports = {
'error', 'error',
{ {
props: true, props: true,
ignorePropertyModificationsFor: ['state', 'acc', 'e'], ignorePropertyModificationsFor: ['state', 'acc', 'e']
}, }
], ],
'no-plusplus': 'off', 'no-plusplus': 'off',
'no-shadow': 'off', 'no-shadow': 'off',
@@ -168,44 +168,44 @@ module.exports = {
'vue/multi-word-component-names': [ 'vue/multi-word-component-names': [
'error', 'error',
{ {
ignores: ['index'], ignores: ['index']
}, }
], ],
'@typescript-eslint/ban-types': [ '@typescript-eslint/ban-types': [
'error', 'error',
{ {
types: { types: {
'{}': { '{}': {
message: 'Use object instead', message: 'Use object instead',
fixWith: 'object', fixWith: 'object'
}, }
}, }
}, }
], ],
'@typescript-eslint/no-empty-interface': [ '@typescript-eslint/no-empty-interface': [
'error', 'error',
{ {
allowSingleExtends: true, allowSingleExtends: true
}, }
], ],
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true, varsIgnorePattern: '^_' }], '@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true, varsIgnorePattern: '^_' }],
'@typescript-eslint/no-use-before-define': ['error', { classes: true, functions: false, typedefs: false }], '@typescript-eslint/no-use-before-define': ['error', { classes: true, functions: false, typedefs: false }]
}, },
overrides: [ overrides: [
{ {
files: ['*.vue'], files: ['*.vue'],
rules: { rules: {
'no-undef': 'off', 'no-undef': 'off'
}, }
}, },
{ {
files: ['*.html'], files: ['*.html'],
rules: { rules: {
'vue/comment-directive': 'off', 'vue/comment-directive': 'off'
}, }
}, }
], ]
}; };

View File

@@ -1,5 +1,5 @@
module.exports = { // https://prettier.io/docs/en/options.html module.exports = { // https://prettier.io/docs/en/options.html
arrowParens: 'always', arrowParens: 'avoid',
bracketSameLine: false, bracketSameLine: false,
bracketSpacing: true, bracketSpacing: true,
embeddedLanguageFormatting: 'auto', embeddedLanguageFormatting: 'auto',
@@ -13,7 +13,7 @@ module.exports = { // https://prettier.io/docs/en/options.html
semi: true, semi: true,
singleQuote: true, singleQuote: true,
tabWidth: 2, tabWidth: 2,
trailingComma: 'es5', trailingComma: 'none',
useTabs: false, useTabs: false,
vueIndentScriptAndStyle: false, vueIndentScriptAndStyle: false,
overrides: [ overrides: [

View File

@@ -69,6 +69,8 @@
"composables": "hook", "composables": "hook",
"directive": "tools", "directive": "tools",
"directives": "tools", "directives": "tools",
"business": "core" "business": "core",
"request": "api",
"adapter": "middleware"
} }
} }

View File

@@ -50,16 +50,23 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
## 开发计划 ## 开发计划
- [ ] 示例页面完善 - [x] 添加前端静态路由
- [ ] 集成unocss替换windicss(新分支unocss)
- [ ] 用户角色切换示例、按钮级别权限指令
- [ ] 最近功能的有关文档更新
- [ ] 引入ECharts替换AntV G2Plot
- [ ] 性能优化(优化递归函数)
- [ ] 精简版(新分支thin)
- [ ] 表单、表格示例 - [ ] 表单、表格示例
- [ ] 添加锁屏组件、全局Iframe组件 - [ ] 添加锁屏组件、全局Iframe组件
- [ ] 用户角色切换示例、按钮级别权限指令 - [ ] 示例页面完善
- [ ] 性能优化(优化递归函数)
- [ ] element-plus版本
- [ ] 其他UI版本 - [ ] 其他UI版本
- [ ] element-plus版本
- [ ] soybean-admin cli工具(选择不同UI) - [ ] soybean-admin cli工具(选择不同UI)
- [ ] 前端可视化创建路由页面 - [ ] 前端可视化创建路由页面
- [ ] soybean-admin 后台nodejs服务 - [ ] soybean-admin 后台服务java版: [soybean-admin-java](https://github.com/honghuangdc/soybean-admin-java)
- [ ] soybean-admin 后台服务go版: [soybean-admin-go](https://github.com/honghuangdc/soybean-admin-go)
- [ ] soybean-admin 后台服务nodejs版: [soybean-admin-nestjs](https://github.com/honghuangdc/soybean-admin-nestjs)
## 安装使用 ## 安装使用
@@ -87,6 +94,12 @@ pnpm dev
pnpm build pnpm build
``` ```
::: warning 注意
**本地环境需要安装 pnpm 6.x 、Node.js 14.x 和 Git**
:::
## 如何贡献 ## 如何贡献
非常欢迎您的加入![提一个 Issue](https://github.com/honghuangdc/soybean-admin/issues/new) 或者提交一个 Pull Request。 非常欢迎您的加入![提一个 Issue](https://github.com/honghuangdc/soybean-admin/issues/new) 或者提交一个 Pull Request。
@@ -119,10 +132,10 @@ pnpm i -g commitizen
`Soybean Admin` 是完全开源免费的项目在帮助开发者更方便地进行中大型管理系统开发同时也提供微信和QQ交流群使用问题欢迎在群内提问。 `Soybean Admin` 是完全开源免费的项目在帮助开发者更方便地进行中大型管理系统开发同时也提供微信和QQ交流群使用问题欢迎在群内提问。
- 本人微信号honghuangdc欢迎来技术交流业务咨询。
- 微信交流群: - 微信交流群:
<div style="text-align:left"> **微信群的人数已经满200个了无法扫码可以添加本人的微信再邀请进入**
<img src="https://s2.loli.net/2022/03/06/4wokvQ7R5B62Ei1.jpg" style="width:200px" />
</div>
- QQ交流群 `711301266` - QQ交流群 `711301266`
@@ -130,8 +143,6 @@ pnpm i -g commitizen
<img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" /> <img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" />
</div> </div>
- 本人微信号honghuangdc欢迎来技术交流业务咨询。
## License ## License
[MIT © Soybean-2021](./LICENSE) [MIT © Soybean-2021](./LICENSE)

View File

@@ -3,6 +3,6 @@ import dayjs from 'dayjs';
/** 项目构建时间 */ /** 项目构建时间 */
const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss')); const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'));
export const define = { export const viteDefine = {
PROJECT_BUILD_TIME, PROJECT_BUILD_TIME
}; };

3
build/config/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './path';
export * from './define';
export * from './proxy';

15
build/config/path.ts Normal file
View File

@@ -0,0 +1,15 @@
import { fileURLToPath } from 'url';
/**
* 解析路径
* @param basePath - 基础路径
*/
export function resolvePath(rootPath: string, basePath: string) {
const root = fileURLToPath(new URL(rootPath, basePath));
const src = `${root}src`;
return {
root,
src
};
}

23
build/config/proxy.ts Normal file
View File

@@ -0,0 +1,23 @@
import type { ProxyOptions } from 'vite';
import { getEnvConfig } from '../../.env-config';
/**
* 设置网络代理
* @param viteEnv - vite环境描述
*/
export function createViteProxy(viteEnv: ImportMetaEnv) {
const isOpenProxy = viteEnv.VITE_HTTP_PROXY === 'true';
if (!isOpenProxy) return undefined;
const { http } = getEnvConfig(viteEnv);
const proxy: Record<string, string | ProxyOptions> = {
[http.proxy]: {
target: http.url,
changeOrigin: true,
rewrite: path => path.replace(new RegExp(`^${http.proxy}`), '')
}
};
return proxy;
}

View File

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

View File

@@ -1,6 +1,7 @@
import Icons from 'unplugin-icons/vite'; import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver'; import IconsResolver from 'unplugin-icons/resolver';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import { FileSystemIconLoader } from 'unplugin-icons/loaders'; import { FileSystemIconLoader } from 'unplugin-icons/loaders';
export default (srcPath: string) => { export default (srcPath: string) => {
@@ -8,14 +9,14 @@ export default (srcPath: string) => {
Icons({ Icons({
compiler: 'vue3', compiler: 'vue3',
customCollections: { customCollections: {
custom: FileSystemIconLoader(`${srcPath}/assets/svg`), custom: FileSystemIconLoader(`${srcPath}/assets/svg`)
}, },
scale: 1, scale: 1,
defaultClass: 'inline-block', defaultClass: 'inline-block'
}), }),
Components({ Components({
dts: true, dts: true,
resolvers: [IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })], resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
}), })
]; ];
}; };

View File

@@ -1,17 +1,17 @@
import { loadEnv } from 'vite'; import { loadEnv } from 'vite';
import type { ConfigEnv, PluginOption } from 'vite'; import type { ConfigEnv, PluginOption } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html'; // html插件(使用变量、压缩) import { createHtmlPlugin } from 'vite-plugin-html';
export default (config: ConfigEnv): PluginOption[] => { export default (config: ConfigEnv): PluginOption[] => {
const viteEnv = loadEnv(config.mode, `.env.${config.mode}`); const viteEnv = loadEnv(config.mode, process.cwd());
return createHtmlPlugin({ return createHtmlPlugin({
minify: true, minify: true,
inject: { inject: {
data: { data: {
appName: viteEnv.VITE_APP_NAME, appName: viteEnv.VITE_APP_NAME,
appTitle: viteEnv.VITE_APP_TITLE, appTitle: viteEnv.VITE_APP_TITLE
}, }
}, }
}); });
}; };

View File

@@ -5,5 +5,5 @@ export default viteMockServe({
injectCode: ` injectCode: `
import { setupMockServer } from '../mock'; import { setupMockServer } from '../mock';
setupMockServer(); setupMockServer();
`, `
}); });

View File

@@ -2,5 +2,5 @@ import { visualizer } from 'rollup-plugin-visualizer';
export default visualizer({ export default visualizer({
gzipSize: true, gzipSize: true,
brotliSize: true, brotliSize: true
}); });

130
components.d.ts vendored
View File

@@ -4,45 +4,97 @@
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
BetterScroll: typeof import('./src/components/custom/BetterScroll.vue')['default']; BetterScroll: typeof import('./src/components/custom/BetterScroll.vue')['default']
CountTo: typeof import('./src/components/custom/CountTo.vue')['default']; CountTo: typeof import('./src/components/custom/CountTo.vue')['default']
DarkModeContainer: typeof import('./src/components/common/DarkModeContainer.vue')['default']; DarkModeContainer: typeof import('./src/components/common/DarkModeContainer.vue')['default']
DarkModeSwitch: typeof import('./src/components/common/DarkModeSwitch.vue')['default']; DarkModeSwitch: typeof import('./src/components/common/DarkModeSwitch.vue')['default']
GithubLink: typeof import('./src/components/custom/GithubLink.vue')['default']; GithubLink: typeof import('./src/components/custom/GithubLink.vue')['default']
HoverContainer: typeof import('./src/components/common/HoverContainer.vue')['default']; HoverContainer: typeof import('./src/components/common/HoverContainer.vue')['default']
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']; IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']; IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']; IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconCustomActivity: typeof import('~icons/custom/activity')['default']; IconCustomActivity: typeof import('~icons/custom/activity')['default']
IconCustomAvatar: typeof import('~icons/custom/avatar')['default']; IconCustomAvatar: typeof import('~icons/custom/avatar')['default']
IconCustomCast: typeof import('~icons/custom/cast')['default']; IconCustomBanner: typeof import('~icons/custom/banner')['default']
IconCustomLogo: typeof import('~icons/custom/logo')['default']; IconCustomCast: typeof import('~icons/custom/cast')['default']
IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default']; IconCustomEmptyData: typeof import('~icons/custom/empty-data')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']; IconCustomLogo: typeof import('~icons/custom/logo')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']; IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default']
IconIcOutlineCheck: typeof import('~icons/ic/outline-check')['default']; IconCustomNetworkError: typeof import('~icons/custom/network-error')['default']
IconLineMdMenuFoldLeft: typeof import('~icons/line-md/menu-fold-left')['default']; IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default']
IconLineMdMenuUnfoldLeft: typeof import('~icons/line-md/menu-unfold-left')['default']; IconCustomNotFound: typeof import('~icons/custom/not-found')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']; IconCustomServiceError: typeof import('~icons/custom/service-error')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']; IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']; IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconMdiGithub: typeof import('~icons/mdi/github')['default']; IconIcOutlineCheck: typeof import('~icons/ic/outline-check')['default']
IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default']; IconLineMdMenuFoldLeft: typeof import('~icons/line-md/menu-fold-left')['default']
IconMdiPin: typeof import('~icons/mdi/pin')['default']; IconLineMdMenuUnfoldLeft: typeof import('~icons/line-md/menu-unfold-left')['default']
IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default']; IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']; IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']; IconMdiClose: typeof import('~icons/mdi/close')['default']
IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default']; IconMdiGithub: typeof import('~icons/mdi/github')['default']
IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default']; IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default']
IconSelect: typeof import('./src/components/custom/IconSelect.vue')['default']; IconMdiPin: typeof import('~icons/mdi/pin')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']; IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default']
ImageVerify: typeof import('./src/components/custom/ImageVerify.vue')['default']; IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
LoadingEmptyWrapper: typeof import('./src/components/business/LoadingEmptyWrapper.vue')['default']; IconMdiWechat: typeof import('~icons/mdi/wechat')['default']
LoginAgreement: typeof import('./src/components/business/LoginAgreement.vue')['default']; IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']
NaiveProvider: typeof import('./src/components/common/NaiveProvider.vue')['default']; IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default']
SystemLogo: typeof import('./src/components/common/SystemLogo.vue')['default']; IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default']
WebSiteLink: typeof import('./src/components/custom/WebSiteLink.vue')['default']; IconSelect: typeof import('./src/components/custom/IconSelect.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
ImageVerify: typeof import('./src/components/custom/ImageVerify.vue')['default']
LoadingEmptyWrapper: typeof import('./src/components/business/LoadingEmptyWrapper.vue')['default']
LoginAgreement: typeof import('./src/components/business/LoginAgreement.vue')['default']
NaiveProvider: typeof import('./src/components/common/NaiveProvider.vue')['default']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGradientText: typeof import('naive-ui')['NGradientText']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NList: typeof import('naive-ui')['NList']
NListItem: typeof import('naive-ui')['NListItem']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTimeline: typeof import('naive-ui')['NTimeline']
NTimelineItem: typeof import('naive-ui')['NTimelineItem']
NTooltip: typeof import('naive-ui')['NTooltip']
SystemLogo: typeof import('./src/components/common/SystemLogo.vue')['default']
WebSiteLink: typeof import('./src/components/custom/WebSiteLink.vue')['default']
} }
} }
export {}; export { }

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="zh-cmn-Hans">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />

View File

@@ -2,7 +2,7 @@ import type { MockMethod } from 'vite-plugin-mock';
const token: ApiAuth.Token = { const token: ApiAuth.Token = {
token: '__TEMP_TOKEN__', token: '__TEMP_TOKEN__',
refreshToken: '__TEMP_REFRESH_TOKEN__', refreshToken: '__TEMP_REFRESH_TOKEN__'
}; };
const apis: MockMethod[] = [ const apis: MockMethod[] = [
@@ -14,9 +14,9 @@ const apis: MockMethod[] = [
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: true, data: true
}; };
}, }
}, },
// 密码登录 // 密码登录
{ {
@@ -26,9 +26,9 @@ const apis: MockMethod[] = [
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: token, data: token
}; };
}, }
}, },
// 验证码登录 // 验证码登录
{ {
@@ -38,9 +38,9 @@ const apis: MockMethod[] = [
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: token, data: token
}; };
}, }
}, },
// 获取用户信息(请求头携带token) // 获取用户信息(请求头携带token)
{ {
@@ -54,10 +54,10 @@ const apis: MockMethod[] = [
userId: '0', userId: '0',
userName: 'Soybean', userName: 'Soybean',
userPhone: '15170283876', userPhone: '15170283876',
userRole: 'super', userRole: 'super'
}, }
}; };
}, }
}, },
{ {
url: '/mock/testToken', url: '/mock/testToken',
@@ -67,15 +67,15 @@ const apis: MockMethod[] = [
return { return {
code: 66666, code: 66666,
message: 'token 失效', message: 'token 失效',
data: null, data: null
}; };
} }
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: true, data: true
}; };
}, }
}, },
{ {
url: '/mock/updateToken', url: '/mock/updateToken',
@@ -84,10 +84,10 @@ const apis: MockMethod[] = [
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: token, data: token
}; };
}, }
}, }
]; ];
export default apis; export default apis;

20
mock/api/demo.ts Normal file
View File

@@ -0,0 +1,20 @@
import type { MockMethod } from 'vite-plugin-mock';
const apis: MockMethod[] = [
{
url: '/mock/apiDemoWithAdapter',
method: 'post',
response: (): Service.MockServiceResult<ApiDemo.DataWithAdapter> => {
return {
code: 200,
message: 'ok',
data: {
dataId: '123',
dataName: 'demoName'
}
};
}
}
];
export default apis;

View File

@@ -13,8 +13,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '分析页', title: '分析页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:analysis', icon: 'icon-park-outline:analysis'
}, }
}, },
{ {
name: 'dashboard_workbench', name: 'dashboard_workbench',
@@ -24,15 +24,15 @@ const routes: AuthRoute.Route[] = [
title: '工作台', title: '工作台',
requiresAuth: true, requiresAuth: true,
permissions: ['super', 'admin'], permissions: ['super', 'admin'],
icon: 'icon-park-outline:workbench', icon: 'icon-park-outline:workbench'
}, }
}, }
], ],
meta: { meta: {
title: '仪表盘', title: '仪表盘',
icon: 'carbon:dashboard', icon: 'carbon:dashboard',
order: 1, order: 1
}, }
}, },
{ {
name: 'document', name: 'document',
@@ -46,8 +46,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: 'vue文档', title: 'vue文档',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:vuejs', icon: 'mdi:vuejs'
}, }
}, },
{ {
name: 'document_vue-new', name: 'document_vue-new',
@@ -56,8 +56,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: 'vue文档(新版)', title: 'vue文档(新版)',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:vuejs', icon: 'mdi:vuejs'
}, }
}, },
{ {
name: 'document_vite', name: 'document_vite',
@@ -66,18 +66,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: 'vite文档', title: 'vite文档',
requiresAuth: true, requiresAuth: true,
icon: 'simple-icons:vite', icon: 'simple-icons:vite'
}, }
},
{
name: 'document_naive',
path: '/document/naive',
component: 'self',
meta: {
title: 'naive文档',
requiresAuth: true,
icon: 'mdi:alpha-n-box-outline',
},
}, },
{ {
name: 'document_project', name: 'document_project',
@@ -86,15 +76,15 @@ const routes: AuthRoute.Route[] = [
title: '项目文档(外链)', title: '项目文档(外链)',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:file-link-outline', icon: 'mdi:file-link-outline',
href: 'https://docs.soybean.pro/', href: 'https://docs.soybean.pro/'
}, }
}, }
], ],
meta: { meta: {
title: '文档', title: '文档',
icon: 'carbon:document', icon: 'carbon:document',
order: 2, order: 2
}, }
}, },
{ {
name: 'component', name: 'component',
@@ -108,8 +98,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '按钮', title: '按钮',
requiresAuth: true, requiresAuth: true,
icon: 'ic:baseline-radio-button-checked', icon: 'ic:baseline-radio-button-checked'
}, }
}, },
{ {
name: 'component_card', name: 'component_card',
@@ -118,8 +108,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '卡片', title: '卡片',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:card-outline', icon: 'mdi:card-outline'
}, }
}, },
{ {
name: 'component_table', name: 'component_table',
@@ -128,15 +118,15 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '表格', title: '表格',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:table-large', icon: 'mdi:table-large'
}, }
}, }
], ],
meta: { meta: {
title: '组件示例', title: '组件示例',
icon: 'fluent:app-store-24-regular', icon: 'fluent:app-store-24-regular',
order: 3, order: 3
}, }
}, },
{ {
name: 'plugin', name: 'plugin',
@@ -150,8 +140,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '地图', title: '地图',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:map', icon: 'mdi:map'
}, }
}, },
{ {
name: 'plugin_video', name: 'plugin_video',
@@ -160,8 +150,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '视频', title: '视频',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:video', icon: 'mdi:video'
}, }
}, },
{ {
name: 'plugin_editor', name: 'plugin_editor',
@@ -175,8 +165,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '富文本编辑器', title: '富文本编辑器',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:file-document-edit-outline', icon: 'mdi:file-document-edit-outline'
}, }
}, },
{ {
name: 'plugin_editor_markdown', name: 'plugin_editor_markdown',
@@ -185,14 +175,14 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: 'markdown编辑器', title: 'markdown编辑器',
requiresAuth: true, requiresAuth: true,
icon: 'ri:markdown-line', icon: 'ri:markdown-line'
}, }
}, }
], ],
meta: { meta: {
title: '编辑器', title: '编辑器',
icon: 'icon-park-outline:editor', icon: 'icon-park-outline:editor'
}, }
}, },
{ {
name: 'plugin_swiper', name: 'plugin_swiper',
@@ -201,8 +191,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: 'Swiper插件', title: 'Swiper插件',
requiresAuth: true, requiresAuth: true,
icon: 'simple-icons:swiper', icon: 'simple-icons:swiper'
}, }
}, },
{ {
name: 'plugin_copy', name: 'plugin_copy',
@@ -211,8 +201,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '剪贴板', title: '剪贴板',
requiresAuth: true, requiresAuth: true,
icon: 'mdi:clipboard-outline', icon: 'mdi:clipboard-outline'
}, }
}, },
{ {
name: 'plugin_icon', name: 'plugin_icon',
@@ -221,8 +211,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '图标', title: '图标',
requiresAuth: true, requiresAuth: true,
icon: 'ic:baseline-insert-emoticon', icon: 'ic:baseline-insert-emoticon'
}, }
}, },
{ {
name: 'plugin_print', name: 'plugin_print',
@@ -231,15 +221,15 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '打印', title: '打印',
requiresAuth: true, requiresAuth: true,
icon: 'ic:baseline-local-printshop', icon: 'ic:baseline-local-printshop'
}, }
}, }
], ],
meta: { meta: {
title: '插件示例', title: '插件示例',
icon: 'clarity:plugin-line', icon: 'clarity:plugin-line',
order: 4, order: 4
}, }
}, },
{ {
name: 'exception', name: 'exception',
@@ -253,8 +243,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '异常页403', title: '异常页403',
requiresAuth: true, requiresAuth: true,
icon: 'ic:baseline-block', icon: 'ic:baseline-block'
}, }
}, },
{ {
name: 'exception_404', name: 'exception_404',
@@ -263,8 +253,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '异常页404', title: '异常页404',
requiresAuth: true, requiresAuth: true,
icon: 'ic:baseline-web-asset-off', icon: 'ic:baseline-web-asset-off'
}, }
}, },
{ {
name: 'exception_500', name: 'exception_500',
@@ -273,15 +263,15 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '异常页500', title: '异常页500',
requiresAuth: true, requiresAuth: true,
icon: 'ic:baseline-wifi-off', icon: 'ic:baseline-wifi-off'
}, }
}, }
], ],
meta: { meta: {
title: '异常页', title: '异常页',
icon: 'ant-design:exception-outlined', icon: 'ant-design:exception-outlined',
order: 5, order: 5
}, }
}, },
{ {
name: 'multi-menu', name: 'multi-menu',
@@ -300,8 +290,8 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '二级菜单', title: '二级菜单',
requiresAuth: true, requiresAuth: true,
icon: 'ic:outline-menu', icon: 'ic:outline-menu'
}, }
}, },
{ {
name: 'multi-menu_first_second-new', name: 'multi-menu_first_second-new',
@@ -315,27 +305,27 @@ const routes: AuthRoute.Route[] = [
meta: { meta: {
title: '三级菜单', title: '三级菜单',
requiresAuth: true, requiresAuth: true,
icon: 'ic:outline-menu', icon: 'ic:outline-menu'
}, }
}, }
], ],
meta: { meta: {
title: '二级菜单(有子菜单)', title: '二级菜单(有子菜单)',
icon: 'ic:outline-menu', icon: 'ic:outline-menu'
}, }
}, }
], ],
meta: { meta: {
title: '一级菜单', title: '一级菜单',
icon: 'ic:outline-menu', icon: 'ic:outline-menu'
}, }
}, }
], ],
meta: { meta: {
title: '多级菜单', title: '多级菜单',
icon: 'carbon:menu', icon: 'carbon:menu',
order: 6, order: 6
}, }
}, },
{ {
name: 'about', name: 'about',
@@ -347,9 +337,9 @@ const routes: AuthRoute.Route[] = [
singleLayout: 'basic', singleLayout: 'basic',
permissions: ['super', 'admin', 'test'], permissions: ['super', 'admin', 'test'],
icon: 'fluent:book-information-24-regular', icon: 'fluent:book-information-24-regular',
order: 7, order: 7
}, }
}, }
]; ];
function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route { function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
@@ -361,7 +351,7 @@ function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
return { return {
routes: sortRoutes(data), routes: sortRoutes(data),
home: routeHomeName, home: routeHomeName
}; };
} }
@@ -373,10 +363,10 @@ const apis: MockMethod[] = [
return { return {
code: 200, code: 200,
message: 'ok', message: 'ok',
data: dataMiddleware(routes), data: dataMiddleware(routes)
}; };
}, }
}, }
]; ];
export default apis; export default apis;

View File

@@ -11,7 +11,7 @@
"build:vercel": "cross-env VITE_HASH_ROUTE=true vite build", "build:vercel": "cross-env VITE_HASH_ROUTE=true vite build",
"preview": "vite preview --port 5050", "preview": "vite preview --port 5050",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"prepare": "husky install", "prepare": "husky install",
"postinstall": "patch-package", "postinstall": "patch-package",
"release": "standard-version", "release": "standard-version",
@@ -26,44 +26,44 @@
} }
}, },
"dependencies": { "dependencies": {
"@antv/g2plot": "^2.4.10", "@antv/g2plot": "^2.4.15",
"@better-scroll/core": "^2.4.2", "@better-scroll/core": "^2.4.2",
"@vueuse/core": "^8.0.0", "@vueuse/core": "^8.3.1",
"axios": "^0.26.1", "axios": "^0.26.1",
"clipboard": "^2.0.10", "clipboard": "^2.0.10",
"colord": "^2.9.2", "colord": "^2.9.2",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dayjs": "^1.10.8", "dayjs": "^1.11.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"naive-ui": "^2.26.4", "naive-ui": "^2.28.2",
"pinia": "^2.0.11", "pinia": "^2.0.13",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qs": "^6.10.3", "qs": "^6.10.3",
"soybean-admin-layout": "^1.0.4", "soybean-admin-layout": "^1.0.4",
"soybean-admin-tab": "^1.2.3", "soybean-admin-tab": "^1.2.3",
"swiper": "^8.0.7", "swiper": "^8.1.3",
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
"vditor": "^3.8.12", "vditor": "^3.8.13",
"vue": "^3.2.31", "vue": "3.2.33",
"vue-router": "^4.0.14", "vue-router": "^4.0.14",
"wangeditor": "^4.7.12", "wangeditor": "^4.7.15",
"xgplayer": "^2.31.4" "xgplayer": "^2.31.6"
}, },
"devDependencies": { "devDependencies": {
"@amap/amap-jsapi-types": "^0.0.8", "@amap/amap-jsapi-types": "^0.0.8",
"@commitlint/cli": "^16.2.1", "@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "^16.2.1",
"@iconify/json": "^2.1.14", "@iconify/json": "^2.1.30",
"@iconify/vue": "^3.1.4", "@iconify/vue": "^3.2.1",
"@types/bmapgl": "^0.0.5", "@types/bmapgl": "^0.0.5",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/node": "^17.0.21", "@types/node": "^17.0.25",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@typescript-eslint/eslint-plugin": "^5.14.0", "@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.14.0", "@typescript-eslint/parser": "^5.20.0",
"@vitejs/plugin-vue": "^2.2.4", "@vitejs/plugin-vue": "^2.3.1",
"@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
@@ -71,29 +71,30 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^6.3.0", "cz-customizable": "^6.3.0",
"eslint": "^8.11.0", "eslint": "^8.13.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.25.4", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.5.0", "eslint-plugin-vue": "^8.6.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.3.5", "lint-staged": "^12.4.0",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"patch-package": "^6.4.7", "patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.5.1", "prettier": "^2.6.2",
"rollup-plugin-visualizer": "^5.6.0", "rollup-plugin-visualizer": "^5.6.0",
"sass": "^1.49.9", "sass": "^1.50.1",
"typescript": "~4.6.2", "typescript": "^4.6.3",
"unplugin-icons": "^0.13.3", "unplugin-icons": "^0.14.1",
"unplugin-vue-components": "^0.18.0", "unplugin-vue-components": "0.18.5",
"vite": "2.8.6", "vite": "^2.9.5",
"vite-plugin-html": "^3.1.0", "vite-plugin-html": "^3.2.0",
"vite-plugin-html-template": "^1.1.2",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-windicss": "^1.8.3", "vite-plugin-windicss": "^1.8.4",
"vue-tsc": "^0.32.1", "vue-tsc": "^0.34.9",
"vueuc": "^0.4.27", "vueuc": "^0.4.32",
"windicss": "^3.5.1" "windicss": "^3.5.1"
} }
} }

6506
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/** /**
* 初始化加载效果的svg格式logo * 初始化加载效果的svg格式logo
* @param { string }id - 元素id * @param {string} id - 元素id
*/ */
function initSvgLogo(id) { function initSvgLogo(id) {
const svgStr = `<svg width="128px" height="128px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" const svgStr = `<svg width="128px" height="128px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
@@ -22,8 +22,7 @@ function initSvgLogo(id) {
<path style="fill:currentColor" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6 <path style="fill:currentColor" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6
c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1 c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1
C73.3,117.7,77.9,121.3,77.9,126.6z" /> C73.3,117.7,77.9,121.3,77.9,126.6z" />
</svg> </svg>`;
`;
const appEl = document.querySelector(id); const appEl = document.querySelector(id);
const div = document.createElement('div'); const div = document.createElement('div');
div.innerHTML = svgStr; div.innerHTML = svgStr;
@@ -34,11 +33,12 @@ function initSvgLogo(id) {
function addThemeColorCssVars() { function addThemeColorCssVars() {
const key = '__THEME_COLOR__'; const key = '__THEME_COLOR__';
const themeColor = window.localStorage.getItem(key) || '#1890ff'; const defaultColor = '#1890ff';
const themeColor = window.localStorage.getItem(key) || defaultColor;
const cssVars = `--primary-color: ${themeColor}`; const cssVars = `--primary-color: ${themeColor}`;
document.documentElement.style.cssText = cssVars; document.documentElement.style.cssText = cssVars;
} }
initSvgLogo('#loadingLogo');
addThemeColorCssVars(); addThemeColorCssVars();
initSvgLogo('#loadingLogo');

View File

@@ -13,6 +13,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { RouterView } from 'vue-router';
import { zhCN, dateZhCN } from 'naive-ui'; import { zhCN, dateZhCN } from 'naive-ui';
import { useThemeStore, subscribeStore } from '@/store'; import { useThemeStore, subscribeStore } from '@/store';

View File

@@ -53,7 +53,7 @@ const props = withDefaults(defineProps<Props>(), {
emptyDesc: '暂无数据', emptyDesc: '暂无数据',
iconClass: 'text-320px text-primary', iconClass: 'text-320px text-primary',
descClass: 'text-16px text-[#666]', descClass: 'text-16px text-[#666]',
showNetworkReload: false, showNetworkReload: false
}); });
// 网络状态 // 网络状态
@@ -79,7 +79,7 @@ function handleReload() {
const stopHandle = watch( const stopHandle = watch(
() => props.loading, () => props.loading,
(newValue) => { newValue => {
// 结束加载判断一下网络状态 // 结束加载判断一下网络状态
if (!newValue) { if (!newValue) {
setNetwork(window.navigator.onLine); setNetwork(window.navigator.onLine);

View File

@@ -23,7 +23,7 @@ interface Emits {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
value: true, value: true
}); });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
@@ -34,7 +34,7 @@ const checked = computed({
}, },
set(newValue: boolean) { set(newValue: boolean) {
emit('update:value', newValue); emit('update:value', newValue);
}, }
}); });
function handleClickProtocol() { function handleClickProtocol() {

View File

@@ -1,10 +1,18 @@
<template> <template>
<div <div
class="bg-white text-[#333639] dark:(bg-[#18181c] text-white text-opacity-82) transition-all duration-300 ease-in-out" class="dark:(bg-[#18181c] text-white text-opacity-82) transition-all duration-300 ease-in-out"
:class="inverted ? 'bg-[#001428] text-white' : 'bg-white text-[#333639]'"
> >
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
interface Props {
inverted?: boolean;
}
withDefaults(defineProps<Props>(), {
inverted: false
});
</script>
<style scoped></style> <style scoped></style>

View File

@@ -18,7 +18,7 @@ interface Emits {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
dark: false, dark: false
}); });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
@@ -29,7 +29,7 @@ const darkMode = computed({
}, },
set(newValue: boolean) { set(newValue: boolean) {
emit('update:dark', newValue); emit('update:dark', newValue);
}, }
}); });
function handleSwitch() { function handleSwitch() {

View File

@@ -29,7 +29,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
tooltipContent: '', tooltipContent: '',
placement: 'bottom', placement: 'bottom',
contentClass: '', contentClass: ''
}); });
const showTooltip = computed(() => Boolean(props.tooltipContent)); const showTooltip = computed(() => Boolean(props.tooltipContent));

View File

@@ -29,7 +29,7 @@ const NaiveProviderContent = defineComponent({
}, },
render() { render() {
return h('div'); return h('div');
}, }
}); });
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -10,7 +10,7 @@ interface Props {
} }
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
fill: false, fill: false
}); });
</script> </script>
<style scoped></style> <style scoped></style>

View File

@@ -42,7 +42,7 @@ const props = withDefaults(defineProps<Props>(), {
separator: ',', separator: ',',
decimal: '.', decimal: '.',
useEasing: true, useEasing: true,
transition: 'linear', transition: 'linear'
}); });
const emit = defineEmits<{ const emit = defineEmits<{
@@ -61,7 +61,7 @@ function run() {
duration: props.duration, duration: props.duration,
onStarted: () => emit('on-started'), onStarted: () => emit('on-started'),
onFinished: () => emit('on-finished'), onFinished: () => emit('on-finished'),
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}), ...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {})
}); });
} }

View File

@@ -43,7 +43,7 @@ interface Emits {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
emptyIcon: 'mdi:apps', emptyIcon: 'mdi:apps'
}); });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
@@ -51,7 +51,7 @@ const emit = defineEmits<Emits>();
const theme = useThemeStore(); const theme = useThemeStore();
const searchValue = ref(''); const searchValue = ref('');
const iconsList = computed(() => props.icons.filter((v) => v.includes(searchValue.value))); const iconsList = computed(() => props.icons.filter(v => v.includes(searchValue.value)));
const modelValue = computed({ const modelValue = computed({
get() { get() {
@@ -59,7 +59,7 @@ const modelValue = computed({
}, },
set(val: string) { set(val: string) {
emit('update:value', val); emit('update:value', val);
}, }
}); });
function handleChange(iconItem: string) { function handleChange(iconItem: string) {

View File

@@ -17,7 +17,7 @@ interface Emits {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
code: '', code: ''
}); });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
@@ -26,11 +26,11 @@ const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify();
watch( watch(
() => props.code, () => props.code,
(newValue) => { newValue => {
setImgCode(newValue); setImgCode(newValue);
} }
); );
watch(imgCode, (newValue) => { watch(imgCode, newValue => {
emit('update:code', newValue); emit('update:code', newValue);
}); });

View File

@@ -18,23 +18,23 @@ export function useBasicLayout() {
vertical: { vertical: {
showLogo: false, showLogo: false,
showHeaderMenu: false, showHeaderMenu: false,
showMenuCollape: true, showMenuCollapse: true
}, },
'vertical-mix': { 'vertical-mix': {
showLogo: false, showLogo: false,
showHeaderMenu: false, showHeaderMenu: false,
showMenuCollape: false, showMenuCollapse: false
}, },
horizontal: { horizontal: {
showLogo: true, showLogo: true,
showHeaderMenu: true, showHeaderMenu: true,
showMenuCollape: false, showMenuCollapse: false
}, },
'horizontal-mix': { 'horizontal-mix': {
showLogo: true, showLogo: true,
showHeaderMenu: false, showHeaderMenu: false,
showMenuCollape: true, showMenuCollapse: true
}, }
}; };
const headerProps = computed(() => layoutHeaderProps[theme.layout.mode]); const headerProps = computed(() => layoutHeaderProps[theme.layout.mode]);
@@ -64,6 +64,6 @@ export function useBasicLayout() {
headerProps, headerProps,
siderVisible, siderVisible,
siderWidth, siderWidth,
siderCollapsedWidth, siderCollapsedWidth
}; };
} }

View File

@@ -46,7 +46,7 @@ export function useRouterPush(inSetup = true) {
const module: EnumType.LoginModuleKey = loginModule || 'pwd-login'; const module: EnumType.LoginModuleKey = loginModule || 'pwd-login';
const routeLocation: RouteLocationRaw = { const routeLocation: RouteLocationRaw = {
name: routeName('login'), name: routeName('login'),
params: { module }, params: { module }
}; };
const redirect = redirectUrl || route.value.fullPath; const redirect = redirectUrl || route.value.fullPath;
Object.assign(routeLocation, { query: { redirect } }); Object.assign(routeLocation, { query: { redirect } });
@@ -80,6 +80,6 @@ export function useRouterPush(inSetup = true) {
toHome, toHome,
toLogin, toLogin,
toLoginModule, toLoginModule,
toLoginRedirect, toLoginRedirect
}; };
} }

View File

@@ -16,7 +16,7 @@ export function useAppInfo(): AppInfo {
return { return {
name, name,
title, title,
desc, desc
}; };
} }

View File

@@ -36,7 +36,7 @@ export const ERROR_STATUS = {
503: '503: 服务不可用~', 503: '503: 服务不可用~',
504: '504: 网关超时~', 504: '504: 网关超时~',
505: '505: http版本不支持该请求~', 505: '505: http版本不支持该请求~',
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG, [DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG
}; };
/** 不弹出错误信息的code */ /** 不弹出错误信息的code */

View File

@@ -2,6 +2,7 @@ import type { App } from 'vue';
import setupNetworkDirective from './network'; import setupNetworkDirective from './network';
import setupLoginDirective from './login'; import setupLoginDirective from './login';
/** setup custom vue directives. - [安装自定义的vue指令] */
export function setupDirectives(app: App) { export function setupDirectives(app: App) {
setupNetworkDirective(app); setupNetworkDirective(app);
setupLoginDirective(app); setupLoginDirective(app);

View File

@@ -20,7 +20,7 @@ export default function setupLoginDirective(app: App) {
unmounted(el: HTMLElement, binding) { unmounted(el: HTMLElement, binding) {
if (binding.value === false) return; if (binding.value === false) return;
el.removeEventListener('click', listenerHandler); el.removeEventListener('click', listenerHandler);
}, }
}; };
app.directive('login', loginDirective); app.directive('login', loginDirective);

View File

@@ -18,7 +18,7 @@ export default function setupNetworkDirective(app: App) {
unmounted(el: HTMLElement, binding) { unmounted(el: HTMLElement, binding) {
if (binding.value === false) return; if (binding.value === false) return;
el.removeEventListener('click', listenerHandler); el.removeEventListener('click', listenerHandler);
}, }
}; };
app.directive('network', networkDirective); app.directive('network', networkDirective);

View File

@@ -0,0 +1,16 @@
import type { App, Directive } from 'vue';
import { useAuthStore } from '@/store';
export default function setupLoginDirective(app: App) {
const auth = useAuthStore();
const loginDirective: Directive<HTMLElement, Auth.RoleType | undefined> = {
mounted(el: HTMLElement, binding) {
if (binding.value !== auth.userInfo.userRole) {
el.remove();
}
}
};
app.directive('login', loginDirective);
}

View File

@@ -4,5 +4,5 @@ export enum EnumLoginModule {
'code-login' = '手机验证码登录', 'code-login' = '手机验证码登录',
'register' = '注册', 'register' = '注册',
'reset-pwd' = '重置密码', 'reset-pwd' = '重置密码',
'bind-wechat' = '微信绑定', 'bind-wechat' = '微信绑定'
} }

View File

@@ -2,7 +2,7 @@
export enum EnumContentType { export enum EnumContentType {
json = 'application/json', json = 'application/json',
formUrlencoded = 'application/x-www-form-urlencoded', formUrlencoded = 'application/x-www-form-urlencoded',
formData = 'multipart/form-data', formData = 'multipart/form-data'
} }
/** 缓存的key */ /** 缓存的key */
@@ -12,11 +12,11 @@ export enum EnumStorageKey {
/** 用户token */ /** 用户token */
'token' = '__TOKEN__', 'token' = '__TOKEN__',
/** 用户刷新token */ /** 用户刷新token */
'refresh-koken' = '__REFRESH_TOKEN__', 'refresh-token' = '__REFRESH_TOKEN__',
/** 用户信息 */ /** 用户信息 */
'user-info' = '__USER_INFO__', 'user-info' = '__USER_INFO__',
/** 多页签路由信息 */ /** 多页签路由信息 */
'tab-routes' = '__TAB_ROUTES__', 'tab-routes' = '__TAB_ROUTES__'
} }
/** 数据类型 */ /** 数据类型 */
@@ -31,5 +31,5 @@ export enum EnumDataType {
date = '[object Date]', date = '[object Date]',
regexp = '[object RegExp]', regexp = '[object RegExp]',
set = '[object Set]', set = '[object Set]',
map = '[object Map]', map = '[object Map]'
} }

View File

@@ -1,7 +1,7 @@
/** 布局组件的名称 */ /** 布局组件的名称 */
export enum EnumLayoutComponentName { export enum EnumLayoutComponentName {
basic = 'basic-layout', basic = 'basic-layout',
blank = 'blank-layout', blank = 'blank-layout'
} }
/** 布局模式 */ /** 布局模式 */
@@ -9,20 +9,20 @@ export enum EnumThemeLayoutMode {
'vertical' = '左侧菜单模式', 'vertical' = '左侧菜单模式',
'horizontal' = '顶部菜单模式', 'horizontal' = '顶部菜单模式',
'vertical-mix' = '左侧菜单混合模式', 'vertical-mix' = '左侧菜单混合模式',
'horizontal-mix' = '顶部菜单混合模式', 'horizontal-mix' = '顶部菜单混合模式'
} }
/** 多页签风格 */ /** 多页签风格 */
export enum EnumThemeTabMode { export enum EnumThemeTabMode {
'chrome' = '谷歌风格', 'chrome' = '谷歌风格',
'button' = '按钮风格', 'button' = '按钮风格'
} }
/** 水平模式的菜单位置 */ /** 水平模式的菜单位置 */
export enum EnumThemeHorizontalMenuPosition { export enum EnumThemeHorizontalMenuPosition {
'flex-start' = '居左', 'flex-start' = '居左',
'center' = '居中', 'center' = '居中',
'flex-end' = '居右', 'flex-end' = '居右'
} }
/** 过渡动画类型 */ /** 过渡动画类型 */
@@ -32,5 +32,5 @@ export enum EnumThemeAnimateMode {
'fade-slide' = '滑动', 'fade-slide' = '滑动',
'fade' = '消退', 'fade' = '消退',
'fade-bottom' = '底部消退', 'fade-bottom' = '底部消退',
'fade-scale' = '缩放消退', 'fade-scale' = '缩放消退'
} }

View File

@@ -47,6 +47,6 @@ export default function useCountDown(second: number) {
isCounting, isCounting,
start, start,
stop, stop,
isComplete, isComplete
}; };
} }

View File

@@ -26,7 +26,7 @@ export default function useImageVerify(width = 152, height = 40) {
domRef, domRef,
imgCode, imgCode,
setImgCode, setImgCode,
getImgCode, getImgCode
}; };
} }

View File

@@ -54,6 +54,6 @@ export default function useSmsCode() {
start, start,
isCounting, isCounting,
getSmsCode, getSmsCode,
loading, loading
}; };
} }

View File

@@ -10,7 +10,7 @@ interface ScrollBodyStyle {
export default function useBodyScroll(duration = 300) { export default function useBodyScroll(duration = 300) {
const defaultStyle: ScrollBodyStyle = { const defaultStyle: ScrollBodyStyle = {
overflow: '', overflow: '',
paddingRight: '', paddingRight: ''
}; };
function getInitBodyStyle() { function getInitBodyStyle() {
const { overflow, paddingRight } = document.body.style; const { overflow, paddingRight } = document.body.style;
@@ -42,6 +42,6 @@ export default function useBodyScroll(duration = 300) {
getInitBodyStyle(); getInitBodyStyle();
return { return {
scrollBodyHandler, scrollBodyHandler
}; };
} }

View File

@@ -21,6 +21,6 @@ export default function useBoolean(initValue = false) {
setBool, setBool,
setTrue, setTrue,
setFalse, setFalse,
toggle, toggle
}; };
} }

View File

@@ -15,6 +15,6 @@ export default function useContext<T>(contextName = 'context') {
return { return {
useProvide, useProvide,
useInject, useInject
}; };
} }

View File

@@ -6,6 +6,6 @@ export default function useLoading(initValue = false) {
return { return {
loading, loading,
startLoading, startLoading,
endLoading, endLoading
}; };
} }

View File

@@ -9,6 +9,6 @@ export default function useLoadingEmpty(initLoading = false, initEmpty = false)
startLoading, startLoading,
endLoading, endLoading,
empty, empty,
setEmpty, setEmpty
}; };
} }

View File

@@ -11,7 +11,7 @@ export default function useModalVisible(hideScroll = true) {
const { scrollBodyHandler } = useBodyScroll(); const { scrollBodyHandler } = useBodyScroll();
function modalVisibleWatcher() { function modalVisibleWatcher() {
const stopHandle = watch(visible, async (newValue) => { const stopHandle = watch(visible, async newValue => {
scrollBodyHandler(newValue); scrollBodyHandler(newValue);
}); });
@@ -28,6 +28,6 @@ export default function useModalVisible(hideScroll = true) {
visible, visible,
openModal, openModal,
closeModal, closeModal,
toggleModal, toggleModal
}; };
} }

View File

@@ -13,17 +13,13 @@ export default function useReload() {
async function handleReload(duration = 0) { async function handleReload(duration = 0) {
setFalse(); setFalse();
await nextTick(); await nextTick();
if (duration) { setTimeout(() => {
setTimeout(() => {
setTrue();
}, duration);
} else {
setTrue(); setTrue();
} }, duration);
} }
return { return {
reloadFlag, reloadFlag,
handleReload, handleReload
}; };
} }

View File

@@ -14,6 +14,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { RouterView } from 'vue-router';
import { useAppStore, useThemeStore, useRouteStore } from '@/store'; import { useAppStore, useThemeStore, useRouteStore } from '@/store';
interface Props { interface Props {
@@ -22,7 +23,7 @@ interface Props {
} }
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
showPadding: true, showPadding: true
}); });
const app = useAppStore(); const app = useAppStore();

View File

@@ -1,5 +1,5 @@
<template> <template>
<hover-container class="w-40px h-full" tooltip-content="全屏" @click="toggle"> <hover-container class="w-40px h-full" tooltip-content="全屏" content-class="hover:text-primary" @click="toggle">
<icon-gridicons-fullscreen-exit v-if="isFullscreen" class="text-18px" /> <icon-gridicons-fullscreen-exit v-if="isFullscreen" class="text-18px" />
<icon-gridicons-fullscreen v-else class="text-18px" /> <icon-gridicons-fullscreen v-else class="text-18px" />
</hover-container> </hover-container>

View File

@@ -1,7 +1,7 @@
<template> <template>
<hover-container tooltip-content="github" class="w-40px h-full"> <hover-container tooltip-content="github" class="w-40px h-full" content-class="hover:text-primary">
<a href="https://github.com/honghuangdc/soybean-admin" target="_blank" class="flex-center"> <a href="https://github.com/honghuangdc/soybean-admin" target="_blank" class="flex-center">
<icon-mdi-github class="text-20px text-[#666]" /> <icon-mdi-github class="text-20px" />
</a> </a>
</hover-container> </hover-container>
</template> </template>

View File

@@ -1,16 +1,23 @@
<template> <template>
<n-menu :value="activeKey" mode="horizontal" :options="menus" @update:value="handleUpdateMenu" /> <n-menu
:value="activeKey"
mode="horizontal"
:options="menus"
:inverted="theme.header.inverted"
@update:value="handleUpdateMenu"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import type { MenuOption } from 'naive-ui'; import type { MenuOption } from 'naive-ui';
import { useRouteStore } from '@/store'; import { useRouteStore, useThemeStore } from '@/store';
import { useRouterPush } from '@/composables'; import { useRouterPush } from '@/composables';
const route = useRoute(); const route = useRoute();
const routeStore = useRouteStore(); const routeStore = useRouteStore();
const theme = useThemeStore();
const { routerPush } = useRouterPush(); const { routerPush } = useRouterPush();
const menus = computed(() => routeStore.menus as GlobalMenuOption[]); const menus = computed(() => routeStore.menus as GlobalMenuOption[]);

View File

@@ -1,6 +1,6 @@
<template> <template>
<n-dropdown :options="options" @select="handleDropdown"> <n-dropdown :options="options" @select="handleDropdown">
<hover-container class="px-12px"> <hover-container class="px-12px" content-class="hover:text-primary">
<icon-custom-avatar class="text-32px" /> <icon-custom-avatar class="text-32px" />
<span class="pl-8px text-16px font-medium">{{ auth.userInfo.userName }}</span> <span class="pl-8px text-16px font-medium">{{ auth.userInfo.userName }}</span>
</hover-container> </hover-container>
@@ -19,17 +19,17 @@ const options = [
{ {
label: '用户中心', label: '用户中心',
key: 'user-center', key: 'user-center',
icon: iconifyRender('carbon:user-avatar'), icon: iconifyRender('carbon:user-avatar')
}, },
{ {
type: 'divider', type: 'divider',
key: 'divider', key: 'divider'
}, },
{ {
label: '退出登录', label: '退出登录',
key: 'logout', key: 'logout',
icon: iconifyRender('carbon:logout'), icon: iconifyRender('carbon:logout')
}, }
]; ];
function handleDropdown(optionKey: string) { function handleDropdown(optionKey: string) {
@@ -42,7 +42,7 @@ function handleDropdown(optionKey: string) {
negativeText: '取消', negativeText: '取消',
onPositiveClick: () => { onPositiveClick: () => {
auth.resetAuthStore(); auth.resetAuthStore();
}, }
}); });
} }
} }

View File

@@ -1,8 +1,8 @@
<template> <template>
<dark-mode-container class="global-header flex-y-center h-full"> <dark-mode-container class="global-header flex-y-center h-full" :inverted="theme.header.inverted">
<global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" /> <global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" />
<div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full"> <div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full">
<menu-collapse v-if="showMenuCollape" /> <menu-collapse v-if="showMenuCollapse" />
<global-breadcrumb v-if="theme.header.crumb.visible" /> <global-breadcrumb v-if="theme.header.crumb.visible" />
</div> </div>
<div v-else class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }"> <div v-else class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }">
@@ -29,7 +29,7 @@ import {
GithubSite, GithubSite,
FullScreen, FullScreen,
ThemeMode, ThemeMode,
UserAvatar, UserAvatar
} from './components'; } from './components';
interface Props { interface Props {
@@ -38,7 +38,7 @@ interface Props {
/** 显示头部菜单 */ /** 显示头部菜单 */
showHeaderMenu: GlobalHeaderProps['showHeaderMenu']; showHeaderMenu: GlobalHeaderProps['showHeaderMenu'];
/** 显示菜单折叠按钮 */ /** 显示菜单折叠按钮 */
showMenuCollape: GlobalHeaderProps['showMenuCollape']; showMenuCollapse: GlobalHeaderProps['showMenuCollapse'];
} }
defineProps<Props>(); defineProps<Props>();

View File

@@ -8,6 +8,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { RouterLink } from 'vue-router';
import { routePath } from '@/router'; import { routePath } from '@/router';
import { useAppInfo } from '@/composables'; import { useAppInfo } from '@/composables';

View File

@@ -5,7 +5,7 @@
:closable="false" :closable="false"
preset="card" preset="card"
footer-style="padding: 0; margin: 0" footer-style="padding: 0; margin: 0"
class="w-630px fixed top-50px left-1/2 transform -translate-x-1/2" class="w-630px fixed left-0 right-0 top-50px"
@after-leave="handleClose" @after-leave="handleClose"
> >
<n-input ref="inputRef" v-model:value="keyword" clearable placeholder="请输入关键词搜索" @input="handleSearch"> <n-input ref="inputRef" v-model:value="keyword" clearable placeholder="请输入关键词搜索" @input="handleSearch">
@@ -60,10 +60,10 @@ const show = computed({
}, },
set(val: boolean) { set(val: boolean) {
emit('update:value', val); emit('update:value', val);
}, }
}); });
watch(show, async (val) => { watch(show, async val => {
if (val) { if (val) {
/** 自动聚焦 */ /** 自动聚焦 */
await nextTick(); await nextTick();
@@ -74,7 +74,7 @@ watch(show, async (val) => {
/** 查询 */ /** 查询 */
function search() { function search() {
resultOptions.value = routeStore.searchMenus.filter( resultOptions.value = routeStore.searchMenus.filter(
(menu) => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim()) menu => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim())
); );
if (resultOptions.value?.length > 0) { if (resultOptions.value?.length > 0) {
activePath.value = resultOptions.value[0].path; activePath.value = resultOptions.value[0].path;
@@ -96,7 +96,7 @@ function handleClose() {
function handleUp() { function handleUp() {
const { length } = resultOptions.value; const { length } = resultOptions.value;
if (length === 0) return; if (length === 0) return;
const index = resultOptions.value.findIndex((item) => item.path === activePath.value); const index = resultOptions.value.findIndex(item => item.path === activePath.value);
if (index === 0) { if (index === 0) {
activePath.value = resultOptions.value[length - 1].path; activePath.value = resultOptions.value[length - 1].path;
} else { } else {
@@ -108,7 +108,7 @@ function handleUp() {
function handleDown() { function handleDown() {
const { length } = resultOptions.value; const { length } = resultOptions.value;
if (length === 0) return; if (length === 0) return;
const index = resultOptions.value.findIndex((item) => item.path === activePath.value); const index = resultOptions.value.findIndex(item => item.path === activePath.value);
if (index + 1 === length) { if (index + 1 === length) {
activePath.value = resultOptions.value[0].path; activePath.value = resultOptions.value[0].path;
} else { } else {
@@ -120,7 +120,7 @@ function handleDown() {
function handleEnter() { function handleEnter() {
const { length } = resultOptions.value; const { length } = resultOptions.value;
if (length === 0 || activePath.value === '') return; if (length === 0 || activePath.value === '') return;
const routeItem = resultOptions.value.find((item) => item.path === activePath.value); const routeItem = resultOptions.value.find(item => item.path === activePath.value);
if (routeItem?.meta?.href) { if (routeItem?.meta?.href) {
window.open(activePath.value, '__blank'); window.open(activePath.value, '__blank');
} else { } else {

View File

@@ -6,7 +6,7 @@
class="bg-[#e5e7eb] dark:bg-dark h-56px mt-8px px-14px rounded-4px cursor-pointer flex-y-center justify-between" class="bg-[#e5e7eb] dark:bg-dark h-56px mt-8px px-14px rounded-4px cursor-pointer flex-y-center justify-between"
:style="{ :style="{
background: item.path === active ? theme.themeColor : '', background: item.path === active ? theme.themeColor : '',
color: item.path === active ? '#fff' : '', color: item.path === active ? '#fff' : ''
}" }"
@click="handleTo" @click="handleTo"
@mouseenter="handleMouse(item)" @mouseenter="handleMouse(item)"
@@ -47,7 +47,7 @@ const active = computed({
}, },
set(val: string) { set(val: string) {
emit('update:value', val); emit('update:value', val);
}, }
}); });
/** 鼠标移入 */ /** 鼠标移入 */

View File

@@ -1,7 +1,12 @@
<template> <template>
<div> <div>
<hover-container tooltip-content="搜索" class="w-40px h-full" @click="handleSearch"> <hover-container
<icon-uil-search class="text-20px text-[#666]" /> class="w-40px h-full"
tooltip-content="搜索"
content-class="hover:text-primary"
@click="handleSearch"
>
<icon-uil-search class="text-20px" />
</hover-container> </hover-container>
<search-modal v-model:value="show" /> <search-modal v-model:value="show" />
</div> </div>

View File

@@ -35,7 +35,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
icon: undefined, icon: undefined,
isMini: false, isMini: false
}); });
const { bool: isHover, setTrue, setFalse } = useBoolean(); const { bool: isHover, setTrue, setFalse } = useBoolean();

View File

@@ -49,9 +49,9 @@ const route = useRoute();
const app = useAppStore(); const app = useAppStore();
const theme = useThemeStore(); const theme = useThemeStore();
const { routerPush } = useRouterPush(); const { routerPush } = useRouterPush();
const { title } = useAppInfo();
const showDrawer = computed(() => (props.visible && props.menus.length) || app.mixSiderFixed); const showDrawer = computed(() => (props.visible && props.menus.length) || app.mixSiderFixed);
const { title } = useAppInfo();
const activeKey = computed(() => route.name as string); const activeKey = computed(() => route.name as string);
const expandedKeys = ref<string[]>([]); const expandedKeys = ref<string[]>([]);

View File

@@ -1,5 +1,5 @@
<template> <template>
<dark-mode-container class="flex h-full" @mouseleave="resetFirstDegreeMenus"> <dark-mode-container class="flex h-full" :inverted="theme.sider.inverted" @mouseleave="resetFirstDegreeMenus">
<div class="flex-1 flex-col-stretch h-full"> <div class="flex-1 flex-col-stretch h-full">
<global-logo :show-title="false" :style="{ height: theme.header.height + 'px' }" /> <global-logo :show-title="false" :style="{ height: theme.header.height + 'px' }" />
<n-scrollbar class="flex-1-hidden"> <n-scrollbar class="flex-1-hidden">
@@ -42,7 +42,7 @@ function setActiveParentRouteName(routeName: string) {
} }
const firstDegreeMenus = computed(() => const firstDegreeMenus = computed(() =>
routeStore.menus.map((item) => { routeStore.menus.map(item => {
const { routeName, label } = item; const { routeName, label } = item;
const icon = item?.icon; const icon = item?.icon;
const hasChildren = Boolean(item.children && item.children.length); const hasChildren = Boolean(item.children && item.children.length);
@@ -51,13 +51,13 @@ const firstDegreeMenus = computed(() =>
routeName, routeName,
label, label,
icon, icon,
hasChildren, hasChildren
}; };
}) })
); );
function getActiveParentRouteName() { function getActiveParentRouteName() {
firstDegreeMenus.value.some((item) => { firstDegreeMenus.value.some(item => {
const routeName = route.name as string; const routeName = route.name as string;
const flag = routeName?.includes(item.routeName); const flag = routeName?.includes(item.routeName);
if (flag) { if (flag) {
@@ -83,7 +83,7 @@ function resetFirstDegreeMenus() {
const activeChildMenus = computed(() => { const activeChildMenus = computed(() => {
const menus: GlobalMenuOption[] = []; const menus: GlobalMenuOption[] = [];
routeStore.menus.some((item) => { routeStore.menus.some(item => {
const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length); const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
if (flag) { if (flag) {
menus.push(...(item.children || [])); menus.push(...(item.children || []));

View File

@@ -8,6 +8,7 @@
:options="routeStore.menus" :options="routeStore.menus"
:expanded-keys="expandedKeys" :expanded-keys="expandedKeys"
:indent="18" :indent="18"
:inverted="theme.sider.inverted"
@update:value="handleUpdateMenu" @update:value="handleUpdateMenu"
@update:expanded-keys="handleUpdateExpandedKeys" @update:expanded-keys="handleUpdateExpandedKeys"
/> />

View File

@@ -1,5 +1,5 @@
<template> <template>
<dark-mode-container class="flex-col-stretch h-full"> <dark-mode-container class="flex-col-stretch h-full" :inverted="theme.sider.inverted">
<global-logo v-if="!isHorizontalMix" :show-title="showTitle" :style="{ height: theme.header.height + 'px' }" /> <global-logo v-if="!isHorizontalMix" :show-title="showTitle" :style="{ height: theme.header.height + 'px' }" />
<vertical-menu /> <vertical-menu />
</dark-mode-container> </dark-mode-container>

View File

@@ -1,6 +1,6 @@
<template> <template>
<vertical-sider v-if="!isVerticalMix" class="global-sider" /> <vertical-mix-sider v-if="isVerticalMix" class="global-sider" />
<vertical-mix-sider v-else class="global-sider" /> <vertical-sider v-else class="global-sider" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -38,7 +38,7 @@ type Option = DropdownOption & {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
visible: false, visible: false,
currentPath: '', currentPath: ''
}); });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
@@ -52,7 +52,7 @@ const dropdownVisible = computed({
}, },
set(visible: boolean) { set(visible: boolean) {
emit('update:visible', visible); emit('update:visible', visible);
}, }
}); });
function hide() { function hide() {
@@ -64,29 +64,29 @@ const options = computed<Option[]>(() => [
label: '重新加载', label: '重新加载',
key: 'reload-current', key: 'reload-current',
disabled: props.currentPath !== tab.activeTab, disabled: props.currentPath !== tab.activeTab,
icon: iconifyRender('ant-design:reload-outlined'), icon: iconifyRender('ant-design:reload-outlined')
}, },
{ {
label: '关闭', label: '关闭',
key: 'close-current', key: 'close-current',
disabled: props.currentPath === tab.homeTab.path, disabled: props.currentPath === tab.homeTab.path,
icon: iconifyRender('ant-design:close-outlined'), icon: iconifyRender('ant-design:close-outlined')
}, },
{ {
label: '关闭其他', label: '关闭其他',
key: 'close-other', key: 'close-other',
icon: iconifyRender('ant-design:column-width-outlined'), icon: iconifyRender('ant-design:column-width-outlined')
}, },
{ {
label: '关闭左侧', label: '关闭左侧',
key: 'close-left', key: 'close-left',
icon: iconifyRender('mdi:format-horizontal-align-left'), icon: iconifyRender('mdi:format-horizontal-align-left')
}, },
{ {
label: '关闭右侧', label: '关闭右侧',
key: 'close-right', key: 'close-right',
icon: iconifyRender('mdi:format-horizontal-align-right'), icon: iconifyRender('mdi:format-horizontal-align-right')
}, }
]); ]);
const actionMap = new Map<DropdownKey, () => void>([ const actionMap = new Map<DropdownKey, () => void>([
@@ -94,32 +94,32 @@ const actionMap = new Map<DropdownKey, () => void>([
'reload-current', 'reload-current',
() => { () => {
app.reloadPage(); app.reloadPage();
}, }
], ],
[ [
'close-current', 'close-current',
() => { () => {
tab.removeTab(props.currentPath); tab.removeTab(props.currentPath);
}, }
], ],
[ [
'close-other', 'close-other',
() => { () => {
tab.clearTab([props.currentPath]); tab.clearTab([props.currentPath]);
}, }
], ],
[ [
'close-left', 'close-left',
() => { () => {
tab.clearLeftTab(props.currentPath); tab.clearLeftTab(props.currentPath);
}, }
], ],
[ [
'close-right', 'close-right',
() => { () => {
tab.clearRightTab(props.currentPath); tab.clearRightTab(props.currentPath);
}, }
], ]
]); ]);
function handleDropdown(optionKey: string) { function handleDropdown(optionKey: string) {

View File

@@ -64,7 +64,7 @@ const dropdown = reactive({
visible: false, visible: false,
x: 0, x: 0,
y: 0, y: 0,
currentPath: '', currentPath: ''
}); });
function showDropdown() { function showDropdown() {
dropdown.visible = true; dropdown.visible = true;
@@ -92,7 +92,7 @@ watch(
getActiveTabClientX(); getActiveTabClientX();
}, },
{ {
immediate: true, immediate: true
} }
); );

View File

@@ -3,7 +3,7 @@
type="primary" type="primary"
:class="[{ '!right-330px': app.settingDrawerVisible }, app.settingDrawerVisible ? 'ease-out' : 'ease-in']" :class="[{ '!right-330px': app.settingDrawerVisible }, app.settingDrawerVisible ? 'ease-out' : 'ease-in']"
class="fixed top-240px right-14px z-10000 w-42px h-42px !p-0 transition-all duration-300" class="fixed top-240px right-14px z-10000 w-42px h-42px !p-0 transition-all duration-300"
@click="app.toggleSettingdrawerVisible" @click="app.toggleSettingDrawerVisible"
> >
<icon-ant-design-close-outlined v-if="app.settingDrawerVisible" class="text-24px" /> <icon-ant-design-close-outlined v-if="app.settingDrawerVisible" class="text-24px" />
<icon-ant-design-setting-outlined v-else class="text-24px" /> <icon-ant-design-setting-outlined v-else class="text-24px" />

View File

@@ -44,23 +44,23 @@ const layoutConfig: LayoutConfig = {
vertical: { vertical: {
placement: 'bottom-start', placement: 'bottom-start',
menuClass: 'w-1/3 h-full', menuClass: 'w-1/3 h-full',
mainClass: 'w-2/3 h-3/4', mainClass: 'w-2/3 h-3/4'
}, },
'vertical-mix': { 'vertical-mix': {
placement: 'bottom', placement: 'bottom',
menuClass: 'w-1/4 h-full', menuClass: 'w-1/4 h-full',
mainClass: 'w-2/3 h-3/4', mainClass: 'w-2/3 h-3/4'
}, },
horizontal: { horizontal: {
placement: 'bottom', placement: 'bottom',
menuClass: 'w-full h-1/4', menuClass: 'w-full h-1/4',
mainClass: 'w-full h-3/4', mainClass: 'w-full h-3/4'
}, },
'horizontal-mix': { 'horizontal-mix': {
placement: 'bottom-end', placement: 'bottom-end',
menuClass: 'w-full h-1/4', menuClass: 'w-full h-1/4',
mainClass: 'w-2/3 h-3/4', mainClass: 'w-2/3 h-3/4'
}, }
}; };
const activeConfig = computed(() => layoutConfig[props.mode]); const activeConfig = computed(() => layoutConfig[props.mode]);

View File

@@ -1,6 +1,12 @@
<template> <template>
<n-divider title-placement="center">界面功能</n-divider> <n-divider title-placement="center">界面功能</n-divider>
<n-space vertical size="large"> <n-space vertical size="large">
<setting-menu label="侧边栏反转色">
<n-switch :value="theme.sider.inverted" @update:value="theme.setSiderInverted" />
</setting-menu>
<setting-menu label="头部反转色">
<n-switch :value="theme.header.inverted" @update:value="theme.setHeaderInverted" />
</setting-menu>
<setting-menu label="固定头部和多页签"> <setting-menu label="固定头部和多页签">
<n-switch :value="theme.fixedHeaderAndTab" @update:value="theme.setIsFixedHeaderAndTab" /> <n-switch :value="theme.fixedHeaderAndTab" @update:value="theme.setIsFixedHeaderAndTab" />
</setting-menu> </setting-menu>

View File

@@ -17,7 +17,7 @@ interface Props {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
iconClass: 'text-14px', iconClass: 'text-14px'
}); });
const whiteColors = ['#ffffff', '#fff', 'rgb(255,255,255)']; const whiteColors = ['#ffffff', '#fff', 'rgb(255,255,255)'];

View File

@@ -36,7 +36,7 @@ function clipboardEventListener() {
window.$dialog?.success({ window.$dialog?.success({
title: '操作成功', title: '操作成功',
content: '复制成功,请替换 src/settings/theme.json的内容', content: '复制成功,请替换 src/settings/theme.json的内容',
positiveText: '确定', positiveText: '确定'
}); });
}); });
} }

View File

@@ -1,29 +1,21 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import { setupAssets, setupNaiveUI } from '@/plugins'; import { setupImportAssets } from './plugins';
import { setupRouter } from '@/router'; import { setupStore } from './store';
import { setupStore } from '@/store'; import { setupDirectives } from './directives';
import { setupDirectives } from '@/directives'; import { setupRouter } from './router';
import App from './App.vue'; import App from './App.vue';
async function setupApp() { async function setupApp() {
// 引入静态资源 setupImportAssets();
setupAssets();
const app = createApp(App); const app = createApp(App);
// 挂载pinia状态
setupStore(app); setupStore(app);
// 挂载自定义vue指令
setupDirectives(app); setupDirectives(app);
// 按需引入naiveUI
setupNaiveUI(app);
// 挂载路由
await setupRouter(app); await setupRouter(app);
// 路由准备就绪后挂载 App
app.mount('#app'); app.mount('#app');
} }

View File

@@ -4,7 +4,7 @@ import 'swiper/css/navigation';
import 'swiper/css/pagination'; import 'swiper/css/pagination';
import '../styles/css/global.css'; import '../styles/css/global.css';
/** 引入静态资源(全局引入css、字体等) */ /** import static assets: css, js , font and so on. - [引入静态资源css、js和字体文件等] */
export default function setupAssets() { export default function setupImportAssets() {
// //
} }

View File

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

View File

@@ -1,103 +0,0 @@
import type { App } from 'vue';
import {
create,
NBreadcrumb,
NBreadcrumbItem,
NButton,
NCard,
NCheckbox,
NColorPicker,
NConfigProvider,
NDataTable,
NDescriptions,
NDescriptionsItem,
NDialogProvider,
NDivider,
NDrawer,
NDrawerContent,
NDropdown,
NEmpty,
NForm,
NFormItem,
NGradientText,
NGrid,
NGridItem,
NInput,
NInputGroup,
NInputNumber,
NList,
NListItem,
NLoadingBarProvider,
NModal,
NMenu,
NMessageProvider,
NNotificationProvider,
NPopover,
NScrollbar,
NSelect,
NSpace,
NStatistic,
NSwitch,
NSpin,
NTabs,
NTabPane,
NTag,
NThing,
NTimeline,
NTimelineItem,
NTooltip,
} from 'naive-ui';
/** 按需引入naiveUI */
export default function setupNaiveUI(app: App) {
const naive = create({
components: [
NBreadcrumb,
NBreadcrumbItem,
NButton,
NCard,
NCheckbox,
NColorPicker,
NConfigProvider,
NDataTable,
NDescriptions,
NDescriptionsItem,
NDialogProvider,
NDivider,
NDrawer,
NDrawerContent,
NDropdown,
NEmpty,
NForm,
NFormItem,
NGradientText,
NGrid,
NGridItem,
NInput,
NInputGroup,
NInputNumber,
NList,
NListItem,
NLoadingBarProvider,
NModal,
NMenu,
NMessageProvider,
NNotificationProvider,
NPopover,
NScrollbar,
NSelect,
NSpace,
NStatistic,
NSwitch,
NSpin,
NTabs,
NTabPane,
NTag,
NThing,
NTimeline,
NTimelineItem,
NTooltip,
],
});
app.use(naive);
}

View File

@@ -15,9 +15,9 @@ export async function createDynamicRouteGuard(
const route = useRouteStore(); const route = useRouteStore();
const isLogin = Boolean(getToken()); const isLogin = Boolean(getToken());
// 初始化动态路由 // 初始化权限路由
if (!route.isAddedDynamicRoute) { if (!route.isInitedAuthRoute) {
// 未登录情况下直接回到登录页,登录成功后再加载动态路由 // 未登录情况下直接回到登录页,登录成功后再加载权限路由
if (!isLogin) { if (!isLogin) {
if (to.name === routeName('login')) { if (to.name === routeName('login')) {
next(); next();
@@ -28,18 +28,19 @@ export async function createDynamicRouteGuard(
return false; return false;
} }
await route.initDynamicRoute(router); await route.initAuthRoute(router);
if (to.name === routeName('not-found-page')) { if (to.name === routeName('not-found-page')) {
// 动态路由没有加载导致被not-found-page路由捕获等待动态路由加载好了,回到之前的路由 // 动态路由没有加载导致被not-found-page路由捕获等待权限路由加载好了,回到之前的路由
next({ path: to.fullPath, replace: true, query: to.query }); next({ path: to.fullPath, replace: true, query: to.query });
return false; return false;
} }
// 动态路由已经加载仍然未找到重定向到not-found }
if (to.name === routeName('not-found-page')) {
next({ name: routeName('not-found'), replace: true }); // 权限路由已经加载仍然未找到重定向到not-found
return false; if (to.name === routeName('not-found-page')) {
} next({ name: routeName('not-found'), replace: true });
return false;
} }
return true; return true;

View File

@@ -13,7 +13,7 @@ export function createRouterGuard(router: Router) {
// 页面跳转权限处理 // 页面跳转权限处理
await createPermissionGuard(to, from, next, router); await createPermissionGuard(to, from, next, router);
}); });
router.afterEach((to) => { router.afterEach(to => {
// 设置document title // 设置document title
useTitle(to.meta.title); useTitle(to.meta.title);
// 结束 loadingBar // 结束 loadingBar

View File

@@ -34,14 +34,14 @@ export async function createPermissionGuard(
isLogin && to.name === routeName('login'), isLogin && to.name === routeName('login'),
() => { () => {
next({ name: routeName('root') }); next({ name: routeName('root') });
}, }
], ],
// 不需要登录权限的页面直接通行 // 不需要登录权限的页面直接通行
[ [
!needLogin, !needLogin,
() => { () => {
next(); next();
}, }
], ],
// 未登录状态进入需要登录权限的页面 // 未登录状态进入需要登录权限的页面
[ [
@@ -49,22 +49,22 @@ export async function createPermissionGuard(
() => { () => {
const redirect = to.fullPath; const redirect = to.fullPath;
next({ name: routeName('login'), query: { redirect } }); next({ name: routeName('login'), query: { redirect } });
}, }
], ],
// 登录状态进入需要登录权限的页面,有权限直接通行 // 登录状态进入需要登录权限的页面,有权限直接通行
[ [
isLogin && needLogin && hasPermission, isLogin && needLogin && hasPermission,
() => { () => {
next(); next();
}, }
], ],
[ [
// 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面 // 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面
isLogin && needLogin && !hasPermission, isLogin && needLogin && !hasPermission,
() => { () => {
next({ name: routeName('no-permission') }); next({ name: routeName('no-permission') });
}, }
], ]
]; ];
exeStrategyActions(actions); exeStrategyActions(actions);

View File

@@ -2,20 +2,20 @@ import type { RouterScrollBehavior } from 'vue-router';
import { useTabStore } from '@/store'; import { useTabStore } from '@/store';
export const scrollBehavior: RouterScrollBehavior = (to, from) => { export const scrollBehavior: RouterScrollBehavior = (to, from) => {
return new Promise((resolve) => { return new Promise(resolve => {
const tab = useTabStore(); const tab = useTabStore();
if (to.hash) { if (to.hash) {
resolve({ resolve({
el: to.hash, el: to.hash,
behavior: 'smooth', behavior: 'smooth'
}); });
} }
const { left, top } = tab.getTabScrollPosition(to.path); const { left, top } = tab.getTabScrollPosition(to.path);
const scrollPosition = { const scrollPosition = {
left, left,
top, top
}; };
const { scrollLeft, scrollTop } = document.documentElement; const { scrollLeft, scrollTop } = document.documentElement;

View File

@@ -6,14 +6,14 @@ import { scrollBehavior } from './helpers';
import { createRouterGuard } from './guard'; import { createRouterGuard } from './guard';
const { VITE_HASH_ROUTE = 'false', VITE_BASE_URL } = import.meta.env; const { VITE_HASH_ROUTE = 'false', VITE_BASE_URL } = import.meta.env;
const history = VITE_HASH_ROUTE === 'true' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL);
export const router = createRouter({ export const router = createRouter({
history, history: VITE_HASH_ROUTE === 'true' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
routes: transformAuthRoutesToVueRoutes(constantRoutes), routes: transformAuthRoutesToVueRoutes(constantRoutes),
scrollBehavior, scrollBehavior
}); });
/** setup vue router. - [安装vue路由] */
export async function setupRouter(app: App) { export async function setupRouter(app: App) {
app.use(router); app.use(router);
createRouterGuard(router); createRouterGuard(router);
@@ -21,3 +21,4 @@ export async function setupRouter(app: App) {
} }
export * from './routes'; export * from './routes';
export * from './modules';

View File

@@ -0,0 +1,15 @@
const about: AuthRoute.Route = {
name: 'about',
path: '/about',
component: 'self',
meta: {
title: '关于',
requiresAuth: true,
singleLayout: 'basic',
permissions: ['super', 'admin', 'test'],
icon: 'fluent:book-information-24-regular',
order: 7
}
};
export default about;

View File

@@ -0,0 +1,44 @@
const component: AuthRoute.Route = {
name: 'component',
path: '/component',
component: 'basic',
children: [
{
name: 'component_button',
path: '/component/button',
component: 'self',
meta: {
title: '按钮',
requiresAuth: true,
icon: 'ic:baseline-radio-button-checked'
}
},
{
name: 'component_card',
path: '/component/card',
component: 'self',
meta: {
title: '卡片',
requiresAuth: true,
icon: 'mdi:card-outline'
}
},
{
name: 'component_table',
path: '/component/table',
component: 'self',
meta: {
title: '表格',
requiresAuth: true,
icon: 'mdi:table-large'
}
}
],
meta: {
title: '组件示例',
icon: 'fluent:app-store-24-regular',
order: 3
}
};
export default component;

View File

@@ -0,0 +1,35 @@
const dashboard: AuthRoute.Route = {
name: 'dashboard',
path: '/dashboard',
component: 'basic',
children: [
{
name: 'dashboard_analysis',
path: '/dashboard/analysis',
component: 'self',
meta: {
title: '分析页',
requiresAuth: true,
icon: 'icon-park-outline:analysis'
}
},
{
name: 'dashboard_workbench',
path: '/dashboard/workbench',
component: 'self',
meta: {
title: '工作台',
requiresAuth: true,
permissions: ['super', 'admin'],
icon: 'icon-park-outline:workbench'
}
}
],
meta: {
title: '仪表盘',
icon: 'carbon:dashboard',
order: 1
}
};
export default dashboard;

View File

@@ -0,0 +1,54 @@
const document: AuthRoute.Route = {
name: 'document',
path: '/document',
component: 'basic',
children: [
{
name: 'document_vue',
path: '/document/vue',
component: 'self',
meta: {
title: 'vue文档',
requiresAuth: true,
icon: 'mdi:vuejs'
}
},
{
name: 'document_vue-new',
path: '/document/vue-new',
component: 'self',
meta: {
title: 'vue文档(新版)',
requiresAuth: true,
icon: 'mdi:vuejs'
}
},
{
name: 'document_vite',
path: '/document/vite',
component: 'self',
meta: {
title: 'vite文档',
requiresAuth: true,
icon: 'simple-icons:vite'
}
},
{
name: 'document_project',
path: '/document/project',
meta: {
title: '项目文档(外链)',
requiresAuth: true,
icon: 'mdi:file-link-outline',
href: 'https://docs.soybean.pro/'
}
}
],
meta: {
title: '文档',
icon: 'carbon:document',
order: 2
}
};
export default document;

View File

@@ -0,0 +1,44 @@
const exception: AuthRoute.Route = {
name: 'exception',
path: '/exception',
component: 'basic',
children: [
{
name: 'exception_403',
path: '/exception/403',
component: 'self',
meta: {
title: '异常页403',
requiresAuth: true,
icon: 'ic:baseline-block'
}
},
{
name: 'exception_404',
path: '/exception/404',
component: 'self',
meta: {
title: '异常页404',
requiresAuth: true,
icon: 'ic:baseline-web-asset-off'
}
},
{
name: 'exception_500',
path: '/exception/500',
component: 'self',
meta: {
title: '异常页500',
requiresAuth: true,
icon: 'ic:baseline-wifi-off'
}
}
],
meta: {
title: '异常页',
icon: 'ant-design:exception-outlined',
order: 5
}
};
export default exception;

View File

@@ -0,0 +1,5 @@
import { handleModuleRoutes } from '@/utils';
const modules = import.meta.globEager('./**/*.ts') as AuthRoute.RouteModule;
export const routes = handleModuleRoutes(modules);

View File

@@ -0,0 +1,56 @@
const multiMenu: AuthRoute.Route = {
name: 'multi-menu',
path: '/multi-menu',
component: 'basic',
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
component: 'multi',
children: [
{
name: 'multi-menu_first_second',
path: '/multi-menu/first/second',
component: 'self',
meta: {
title: '二级菜单',
requiresAuth: true,
icon: 'ic:outline-menu'
}
},
{
name: 'multi-menu_first_second-new',
path: '/multi-menu/first/second-new',
component: 'multi',
children: [
{
name: 'multi-menu_first_second-new_third',
path: '/multi-menu/first/second-new/third',
component: 'self',
meta: {
title: '三级菜单',
requiresAuth: true,
icon: 'ic:outline-menu'
}
}
],
meta: {
title: '二级菜单(有子菜单)',
icon: 'ic:outline-menu'
}
}
],
meta: {
title: '一级菜单',
icon: 'ic:outline-menu'
}
}
],
meta: {
title: '多级菜单',
icon: 'carbon:menu',
order: 6
}
};
export default multiMenu;

View File

@@ -0,0 +1,105 @@
const plugin: AuthRoute.Route = {
name: 'plugin',
path: '/plugin',
component: 'basic',
children: [
{
name: 'plugin_map',
path: '/plugin/map',
component: 'self',
meta: {
title: '地图',
requiresAuth: true,
icon: 'mdi:map'
}
},
{
name: 'plugin_video',
path: '/plugin/video',
component: 'self',
meta: {
title: '视频',
requiresAuth: true,
icon: 'mdi:video'
}
},
{
name: 'plugin_editor',
path: '/plugin/editor',
component: 'multi',
children: [
{
name: 'plugin_editor_quill',
path: '/plugin/editor/quill',
component: 'self',
meta: {
title: '富文本编辑器',
requiresAuth: true,
icon: 'mdi:file-document-edit-outline'
}
},
{
name: 'plugin_editor_markdown',
path: '/plugin/editor/markdown',
component: 'self',
meta: {
title: 'markdown编辑器',
requiresAuth: true,
icon: 'ri:markdown-line'
}
}
],
meta: {
title: '编辑器',
icon: 'icon-park-outline:editor'
}
},
{
name: 'plugin_swiper',
path: '/plugin/swiper',
component: 'self',
meta: {
title: 'Swiper插件',
requiresAuth: true,
icon: 'simple-icons:swiper'
}
},
{
name: 'plugin_copy',
path: '/plugin/copy',
component: 'self',
meta: {
title: '剪贴板',
requiresAuth: true,
icon: 'mdi:clipboard-outline'
}
},
{
name: 'plugin_icon',
path: '/plugin/icon',
component: 'self',
meta: {
title: '图标',
requiresAuth: true,
icon: 'ic:baseline-insert-emoticon'
}
},
{
name: 'plugin_print',
path: '/plugin/print',
component: 'self',
meta: {
title: '打印',
requiresAuth: true,
icon: 'ic:baseline-local-printshop'
}
}
],
meta: {
title: '插件示例',
icon: 'clarity:plugin-line',
order: 4
}
};
export default plugin;

View File

@@ -7,24 +7,24 @@ export const constantRoutes: AuthRoute.Route[] = [
path: '/', path: '/',
redirect: '/dashboard/analysis', redirect: '/dashboard/analysis',
meta: { meta: {
title: 'Root', title: 'Root'
}, }
}, },
{ {
name: 'login', name: 'login',
path: '/login', path: '/login',
component: 'self', component: 'self',
props: (route) => { props: route => {
const moduleType = (route.params.module as EnumType.LoginModuleKey) || 'pwd-login'; const moduleType = (route.params.module as EnumType.LoginModuleKey) || 'pwd-login';
return { return {
module: moduleType, module: moduleType
}; };
}, },
meta: { meta: {
title: '登录', title: '登录',
dynamicPath: `/login/:module(${getLoginModuleRegExp()})?`, dynamicPath: `/login/:module(${getLoginModuleRegExp()})?`,
singleLayout: 'blank', singleLayout: 'blank'
}, }
}, },
{ {
name: 'no-permission', name: 'no-permission',
@@ -32,8 +32,8 @@ export const constantRoutes: AuthRoute.Route[] = [
component: 'self', component: 'self',
meta: { meta: {
title: '无权限', title: '无权限',
singleLayout: 'blank', singleLayout: 'blank'
}, }
}, },
{ {
name: 'not-found', name: 'not-found',
@@ -41,8 +41,8 @@ export const constantRoutes: AuthRoute.Route[] = [
component: 'self', component: 'self',
meta: { meta: {
title: '未找到', title: '未找到',
singleLayout: 'blank', singleLayout: 'blank'
}, }
}, },
{ {
name: 'service-error', name: 'service-error',
@@ -50,8 +50,8 @@ export const constantRoutes: AuthRoute.Route[] = [
component: 'self', component: 'self',
meta: { meta: {
title: '服务器错误', title: '服务器错误',
singleLayout: 'blank', singleLayout: 'blank'
}, }
}, },
// 匹配无效路径的路由 // 匹配无效路径的路由
{ {
@@ -60,9 +60,9 @@ export const constantRoutes: AuthRoute.Route[] = [
component: 'blank', component: 'blank',
meta: { meta: {
title: '未找到', title: '未找到',
singleLayout: 'blank', singleLayout: 'blank'
}, }
}, }
]; ];
/** 路由名称 */ /** 路由名称 */

View File

@@ -0,0 +1,10 @@
export function adapterOfDataWithAdapter(res: Service.RequestResult<ApiDemo.DataWithAdapter>): Demo.DataWithAdapter {
const { dataId, dataName } = res.data!;
const result: Demo.DataWithAdapter = {
id: dataId,
name: dataName
};
return result;
}

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