mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-17 17:26:38 +08:00
refactor(projects): merge branch unocss to main
This commit is contained in:
commit
69d51318ff
2
.env
2
.env
@ -9,4 +9,4 @@ VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
|
||||
# 权限路由模式: static | dynamic
|
||||
VITE_AUTH_ROUTE_MODE=dynamic
|
||||
|
||||
VITE_VISUALIZER=false
|
||||
VITE_ROUTE_HOME_PATH=/dashboard/analysis
|
||||
|
@ -1,13 +1,5 @@
|
||||
/** 请求环境配置 */
|
||||
type ServiceEnv = Record<
|
||||
EnvType,
|
||||
{
|
||||
/** 请求地址 */
|
||||
url: string;
|
||||
/** 代理地址 */
|
||||
proxy: string;
|
||||
}
|
||||
>;
|
||||
type ServiceEnv = Record<EnvType, EnvConfig>;
|
||||
|
||||
/** 环境配置 */
|
||||
const serviceEnvConfig: ServiceEnv = {
|
||||
@ -31,8 +23,8 @@ const serviceEnvConfig: ServiceEnv = {
|
||||
*/
|
||||
export function getEnvConfig(env: ImportMetaEnv) {
|
||||
const { VITE_ENV_TYPE = 'dev' } = env;
|
||||
const envConfig = {
|
||||
http: serviceEnvConfig[VITE_ENV_TYPE]
|
||||
};
|
||||
|
||||
const envConfig = serviceEnvConfig[VITE_ENV_TYPE];
|
||||
|
||||
return envConfig;
|
||||
}
|
||||
|
@ -1 +1,6 @@
|
||||
VITE_VISUALIZER=true
|
||||
|
||||
VITE_COMPRESS=true
|
||||
|
||||
# gzip | brotliCompress | deflate | deflateRaw
|
||||
VITE_COMPRESS_TYPE=gzip
|
||||
|
@ -197,7 +197,12 @@ module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.vue'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -31,6 +31,6 @@
|
||||
"johnsoncodehk.vscode-typescript-vue-plugin",
|
||||
"dariofuzinato.vue-peek",
|
||||
"wscats.vue",
|
||||
"voorjaar.windicss-intellisense"
|
||||
"antfu.unocss"
|
||||
]
|
||||
}
|
||||
|
12
README.md
12
README.md
@ -13,10 +13,10 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
||||
|
||||
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发, 使用高效率的npm包管理器pnpm
|
||||
- **TypeScript**: 应用程序级 JavaScript 的语言
|
||||
- **主题**:丰富可配置的主题、暗黑模式,基于windicss的动态主题颜色
|
||||
- **主题**:丰富可配置的主题、暗黑模式,基于原子css - unocss的动态主题颜色
|
||||
- **代码规范**:丰富的规范插件及极高的代码规范
|
||||
- **权限路由**:简易的路由配置、基于mock的动态路由能快速实现后端动态路由
|
||||
- **请求函数**:基于axios的完善的请求函数封装,提供Promise和hooks两种请求函数
|
||||
- **请求函数**:基于axios的完善的请求函数封装,提供Promise和hooks两种请求函数,加入请求结果数据转换的适配器
|
||||
|
||||
## 预览
|
||||
|
||||
@ -51,12 +51,12 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
||||
## 开发计划
|
||||
|
||||
- [x] 添加前端静态路由
|
||||
- [ ] 集成unocss替换windicss(新分支unocss)
|
||||
- [ ] 用户角色切换示例、按钮级别权限指令
|
||||
- [ ] 最近功能的有关文档更新
|
||||
- [x] 集成unocss替换windicss(新分支unocss)
|
||||
- [x] 用户角色切换示例、按钮级别权限指令
|
||||
- [x] 最近功能的有关文档更新
|
||||
- [ ] 引入ECharts替换AntV G2Plot
|
||||
- [ ] 性能优化(优化递归函数)
|
||||
- [ ] 精简版(新分支thin)
|
||||
- [ ] 性能优化(优化递归函数)
|
||||
- [ ] 表单、表格示例
|
||||
- [ ] 添加锁屏组件、全局Iframe组件
|
||||
- [ ] 示例页面完善
|
||||
|
@ -1,3 +1,2 @@
|
||||
export * from './path';
|
||||
export * from './define';
|
||||
export * from './proxy';
|
||||
|
@ -1,15 +0,0 @@
|
||||
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
|
||||
};
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
import type { ProxyOptions } from 'vite';
|
||||
import { getEnvConfig } from '../../.env-config';
|
||||
|
||||
/**
|
||||
* 设置网络代理
|
||||
* @param viteEnv - vite环境描述
|
||||
* @param isOpenProxy - 是否开启代理
|
||||
* @param envConfig - env环境配置
|
||||
*/
|
||||
export function createViteProxy(viteEnv: ImportMetaEnv) {
|
||||
const isOpenProxy = viteEnv.VITE_HTTP_PROXY === 'true';
|
||||
export function createViteProxy(isOpenProxy: boolean, envConfig: EnvConfig) {
|
||||
if (!isOpenProxy) return undefined;
|
||||
|
||||
const { http } = getEnvConfig(viteEnv);
|
||||
|
||||
const proxy: Record<string, string | ProxyOptions> = {
|
||||
[http.proxy]: {
|
||||
target: http.url,
|
||||
[envConfig.proxy]: {
|
||||
target: envConfig.url,
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(new RegExp(`^${http.proxy}`), '')
|
||||
rewrite: path => path.replace(new RegExp(`^${envConfig.proxy}`), '')
|
||||
}
|
||||
};
|
||||
|
||||
|
6
build/plugins/compress.ts
Normal file
6
build/plugins/compress.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import ViteCompression from 'vite-plugin-compression';
|
||||
|
||||
export default (viteEnv: ImportMetaEnv) => {
|
||||
const { VITE_COMPRESS_TYPE = 'gzip' } = viteEnv;
|
||||
return ViteCompression({ algorithm: VITE_COMPRESS_TYPE });
|
||||
};
|
@ -1,10 +1,7 @@
|
||||
import { loadEnv } from 'vite';
|
||||
import type { ConfigEnv, PluginOption } from 'vite';
|
||||
import type { PluginOption } from 'vite';
|
||||
import { createHtmlPlugin } from 'vite-plugin-html';
|
||||
|
||||
export default (config: ConfigEnv): PluginOption[] => {
|
||||
const viteEnv = loadEnv(config.mode, process.cwd());
|
||||
|
||||
export default (viteEnv: ImportMetaEnv): PluginOption[] => {
|
||||
return createHtmlPlugin({
|
||||
minify: true,
|
||||
inject: {
|
||||
|
@ -1,27 +1,26 @@
|
||||
import type { ConfigEnv, PluginOption } from 'vite';
|
||||
import type { PluginOption } from 'vite';
|
||||
import vue from './vue';
|
||||
import html from './html';
|
||||
import autoImport from './auto-import';
|
||||
import windicss from './windicss';
|
||||
import unplugin from './unplugin';
|
||||
import unocss from './unocss';
|
||||
import mock from './mock';
|
||||
import visualizer from './visualizer';
|
||||
import compress from './compress';
|
||||
|
||||
/**
|
||||
* vite插件
|
||||
* @param configEnv - 环境
|
||||
* @param srcPath - src路径
|
||||
* @param viteEnv - 环境变量配置
|
||||
* @param srcPath - src路径
|
||||
*/
|
||||
export function setupVitePlugins(
|
||||
configEnv: ConfigEnv,
|
||||
srcPath: string,
|
||||
viteEnv: ImportMetaEnv
|
||||
): (PluginOption | PluginOption[])[] {
|
||||
const plugins = [vue, html(configEnv), ...autoImport(srcPath), windicss, mock];
|
||||
export function setupVitePlugins(viteEnv: ImportMetaEnv, srcPath: string): (PluginOption | PluginOption[])[] {
|
||||
const plugins = [...vue, html(viteEnv), ...unplugin(srcPath), unocss, mock];
|
||||
|
||||
if (configEnv.command === 'build' && viteEnv.VITE_VISUALIZER === 'true') {
|
||||
if (viteEnv.VITE_VISUALIZER === 'true') {
|
||||
plugins.push(visualizer);
|
||||
}
|
||||
if (viteEnv.VITE_COMPRESS === 'true') {
|
||||
plugins.push(compress(viteEnv));
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
3
build/plugins/unocss.ts
Normal file
3
build/plugins/unocss.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import unocss from 'unocss/vite';
|
||||
|
||||
export default unocss();
|
@ -1,3 +1,4 @@
|
||||
import DefineOptions from 'unplugin-vue-define-options/vite';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import IconsResolver from 'unplugin-icons/resolver';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
@ -6,6 +7,7 @@ import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||
|
||||
export default (srcPath: string) => {
|
||||
return [
|
||||
DefineOptions(),
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
customCollections: {
|
||||
@ -15,7 +17,8 @@ export default (srcPath: string) => {
|
||||
defaultClass: 'inline-block'
|
||||
}),
|
||||
Components({
|
||||
dts: true,
|
||||
dts: 'src/typings/components.d.ts',
|
||||
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
|
||||
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
|
||||
})
|
||||
];
|
@ -2,5 +2,6 @@ import { visualizer } from 'rollup-plugin-visualizer';
|
||||
|
||||
export default visualizer({
|
||||
gzipSize: true,
|
||||
brotliSize: true
|
||||
brotliSize: true,
|
||||
open: true
|
||||
});
|
||||
|
@ -1,3 +1,6 @@
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
|
||||
export default vue({});
|
||||
const plugins = [vue(), vueJsx()];
|
||||
|
||||
export default plugins;
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div class="right-0 bottom-0 loading-spin-item loading-delay-1500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="loading-title"><%= appTitle %></h2>
|
||||
<div class="loading-title"><%= appTitle %></div>
|
||||
</div>
|
||||
<script src="/resource/loading.js"></script>
|
||||
</div>
|
||||
|
@ -62,7 +62,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/testToken',
|
||||
method: 'post',
|
||||
response: (option: any): Service.MockServiceResult<true | null> => {
|
||||
response: (option: Service.MockOption): Service.MockServiceResult<true | null> => {
|
||||
if (option.headers?.authorization !== token.token) {
|
||||
return {
|
||||
code: 66666,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { MockMethod } from 'vite-plugin-mock';
|
||||
import { filterAuthRoutesByUserPermission } from '../utils';
|
||||
|
||||
const routes: AuthRoute.Route[] = [
|
||||
{
|
||||
@ -231,6 +232,39 @@ const routes: AuthRoute.Route[] = [
|
||||
order: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo',
|
||||
path: '/auth-demo',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'auth-demo_permission',
|
||||
path: '/auth-demo/permission',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '权限切换',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-construction'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo_super',
|
||||
path: '/auth-demo/super',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '超级管理员可见',
|
||||
requiresAuth: true,
|
||||
permissions: ['super'],
|
||||
icon: 'ic:round-supervisor-account'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '权限示例',
|
||||
icon: 'ic:baseline-security',
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'exception',
|
||||
path: '/exception',
|
||||
@ -270,7 +304,7 @@ const routes: AuthRoute.Route[] = [
|
||||
meta: {
|
||||
title: '异常页',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 5
|
||||
order: 6
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -324,7 +358,7 @@ const routes: AuthRoute.Route[] = [
|
||||
meta: {
|
||||
title: '多级菜单',
|
||||
icon: 'carbon:menu',
|
||||
order: 6
|
||||
order: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -335,9 +369,9 @@ const routes: AuthRoute.Route[] = [
|
||||
title: '关于',
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
permissions: ['super', 'admin', 'test'],
|
||||
permissions: ['super', 'admin', 'user'],
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 7
|
||||
order: 8
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -345,12 +379,12 @@ const routes: AuthRoute.Route[] = [
|
||||
function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
|
||||
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
|
||||
|
||||
function sortRoutes(sorts: AuthRoute.Route[]) {
|
||||
return sorts.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
|
||||
}
|
||||
data.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
|
||||
|
||||
const filters = filterAuthRoutesByUserPermission(data, 'admin');
|
||||
|
||||
return {
|
||||
routes: sortRoutes(data),
|
||||
routes: filters,
|
||||
home: routeHomeName
|
||||
};
|
||||
}
|
||||
|
848
mock/model/route.ts
Normal file
848
mock/model/route.ts
Normal file
@ -0,0 +1,848 @@
|
||||
const routes: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
super: [
|
||||
{
|
||||
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,
|
||||
icon: 'icon-park-outline:workbench'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: 'carbon:dashboard',
|
||||
order: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo',
|
||||
path: '/auth-demo',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'auth-demo_permission',
|
||||
path: '/auth-demo/permission',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '权限切换',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-construction'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo_super',
|
||||
path: '/auth-demo/super',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '超级管理员可见',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-supervisor-account'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '权限示例',
|
||||
icon: 'ic:baseline-security',
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
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: 6
|
||||
}
|
||||
},
|
||||
{
|
||||
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: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '关于',
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
}
|
||||
}
|
||||
],
|
||||
admin: [
|
||||
{
|
||||
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,
|
||||
icon: 'icon-park-outline:workbench'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: 'carbon:dashboard',
|
||||
order: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo',
|
||||
path: '/auth-demo',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'auth-demo_permission',
|
||||
path: '/auth-demo/permission',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '权限切换',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-construction'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '权限示例',
|
||||
icon: 'ic:baseline-security',
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
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: 6
|
||||
}
|
||||
},
|
||||
{
|
||||
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: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '关于',
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
}
|
||||
}
|
||||
],
|
||||
user: [
|
||||
{
|
||||
name: 'dashboard',
|
||||
path: '/dashboard',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'dashboard_analysis',
|
||||
path: '/dashboard/analysis',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '分析页',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:analysis'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: 'carbon:dashboard',
|
||||
order: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo',
|
||||
path: '/auth-demo',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'auth-demo_permission',
|
||||
path: '/auth-demo/permission',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '权限切换',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-construction'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '权限示例',
|
||||
icon: 'ic:baseline-security',
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
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: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '关于',
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default routes;
|
35
mock/model/user.ts
Normal file
35
mock/model/user.ts
Normal file
@ -0,0 +1,35 @@
|
||||
interface ModelUser {
|
||||
userId: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
role: Auth.RoleType;
|
||||
}
|
||||
|
||||
const users: ModelUser[] = [
|
||||
{
|
||||
userId: '0',
|
||||
userName: 'Soybean',
|
||||
password: 'soybean',
|
||||
role: 'super'
|
||||
},
|
||||
{
|
||||
userId: '1',
|
||||
userName: 'Super',
|
||||
password: 'super',
|
||||
role: 'super'
|
||||
},
|
||||
{
|
||||
userId: '2',
|
||||
userName: 'Admin',
|
||||
password: 'admin',
|
||||
role: 'admin'
|
||||
},
|
||||
{
|
||||
userId: '3',
|
||||
userName: 'User01',
|
||||
password: 'user01',
|
||||
role: 'user'
|
||||
}
|
||||
];
|
||||
|
||||
export default users;
|
24
mock/utils/index.ts
Normal file
24
mock/utils/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 根据用户权限过滤路由
|
||||
* @param routes - 权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
|
||||
return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户权限过滤单个路由
|
||||
* @param route - 单个权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
|
||||
const hasPermission =
|
||||
!route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
|
||||
|
||||
if (route.children) {
|
||||
const filterChildren = route.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
|
||||
Object.assign(route, { children: filterChildren });
|
||||
}
|
||||
return hasPermission ? [route] : [];
|
||||
}
|
40
package.json
40
package.json
@ -26,10 +26,12 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g2plot": "^2.4.15",
|
||||
"@antv/g2plot": "^2.4.16",
|
||||
"@better-scroll/core": "^2.4.2",
|
||||
"@soybeanjs/vue-admin-layout": "^1.0.3",
|
||||
"@soybeanjs/vue-admin-tab": "^1.0.1",
|
||||
"@vueuse/core": "^8.3.1",
|
||||
"axios": "^0.26.1",
|
||||
"axios": "^0.27.2",
|
||||
"clipboard": "^2.0.10",
|
||||
"colord": "^2.9.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
@ -40,9 +42,7 @@
|
||||
"pinia": "^2.0.13",
|
||||
"print-js": "^1.6.0",
|
||||
"qs": "^6.10.3",
|
||||
"soybean-admin-layout": "^1.0.4",
|
||||
"soybean-admin-tab": "^1.2.3",
|
||||
"swiper": "^8.1.3",
|
||||
"swiper": "^8.1.4",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"vditor": "^3.8.13",
|
||||
"vue": "3.2.33",
|
||||
@ -54,47 +54,49 @@
|
||||
"@amap/amap-jsapi-types": "^0.0.8",
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@iconify/json": "^2.1.30",
|
||||
"@iconify/json": "^2.1.33",
|
||||
"@iconify/vue": "^3.2.1",
|
||||
"@types/bmapgl": "^0.0.5",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/node": "^17.0.25",
|
||||
"@types/node": "^17.0.29",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||
"@typescript-eslint/parser": "^5.20.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
||||
"@typescript-eslint/parser": "^5.21.0",
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"commitizen": "^4.2.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"cz-customizable": "^6.3.0",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.6.0",
|
||||
"eslint-plugin-vue": "^8.7.1",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.4.0",
|
||||
"lint-staged": "^12.4.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.6.2",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"sass": "^1.50.1",
|
||||
"sass": "^1.51.0",
|
||||
"typescript": "^4.6.3",
|
||||
"unocss": "^0.31.17",
|
||||
"unplugin-icons": "^0.14.1",
|
||||
"unplugin-vue-components": "0.18.5",
|
||||
"vite": "^2.9.5",
|
||||
"unplugin-vue-components": "0.19.3",
|
||||
"unplugin-vue-define-options": "^0.6.1",
|
||||
"vite": "^2.9.6",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-html-template": "^1.1.2",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-windicss": "^1.8.4",
|
||||
"vue-tsc": "^0.34.9",
|
||||
"vueuc": "^0.4.32",
|
||||
"windicss": "^3.5.1"
|
||||
"vue-tsc": "^0.34.10",
|
||||
"vueuc": "^0.4.32"
|
||||
}
|
||||
}
|
||||
|
3874
pnpm-lock.yaml
3874
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
import { zhCN, dateZhCN } from 'naive-ui';
|
||||
import { useThemeStore, subscribeStore } from '@/store';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="dark:(bg-[#18181c] text-white text-opacity-82) transition-all duration-300 ease-in-out"
|
||||
class="dark:bg-[#18181c] dark:text-white dark:text-opacity-82 transition-all duration-300 ease-in-out"
|
||||
:class="inverted ? 'bg-[#001428] text-white' : 'bg-white text-[#333639]'"
|
||||
>
|
||||
<slot></slot>
|
||||
|
@ -1,4 +1,6 @@
|
||||
import UAParser from 'ua-parser-js';
|
||||
import { useAuthStore } from '@/store';
|
||||
import { isArray, isString } from '@/utils';
|
||||
|
||||
interface AppInfo {
|
||||
/** 项目名称 */
|
||||
@ -26,3 +28,27 @@ export function useDeviceInfo() {
|
||||
const result = parser.getResult();
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 权限判断 */
|
||||
export function usePermission() {
|
||||
const auth = useAuthStore();
|
||||
|
||||
function hasPermission(permission: Auth.RoleType | Auth.RoleType[]) {
|
||||
const { userRole } = auth.userInfo;
|
||||
|
||||
let has = userRole === 'super';
|
||||
if (!has) {
|
||||
if (isArray(permission)) {
|
||||
has = (permission as Auth.RoleType[]).includes(userRole);
|
||||
}
|
||||
if (isString(permission)) {
|
||||
has = (permission as Auth.RoleType) === userRole;
|
||||
}
|
||||
}
|
||||
return has;
|
||||
}
|
||||
|
||||
return {
|
||||
hasPermission
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
/** 百度地图sdk地址 */
|
||||
export const BAIDU_MAP_SDK_URL =
|
||||
'https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1';
|
||||
export const BAIDU_MAP_SDK_URL = `https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1`;
|
||||
|
||||
/** 高德地图sdk地址 */
|
||||
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
|
||||
|
@ -1,9 +1,11 @@
|
||||
import type { App } from 'vue';
|
||||
import setupNetworkDirective from './network';
|
||||
import setupLoginDirective from './login';
|
||||
import setupPermissionDirective from './permission';
|
||||
|
||||
/** setup custom vue directives. - [安装自定义的vue指令] */
|
||||
export function setupDirectives(app: App) {
|
||||
setupNetworkDirective(app);
|
||||
setupLoginDirective(app);
|
||||
setupPermissionDirective(app);
|
||||
}
|
||||
|
@ -1,16 +1,26 @@
|
||||
import type { App, Directive } from 'vue';
|
||||
import { useAuthStore } from '@/store';
|
||||
import { usePermission } from '@/composables';
|
||||
|
||||
export default function setupLoginDirective(app: App) {
|
||||
const auth = useAuthStore();
|
||||
export default function setupPermissionDirective(app: App) {
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const loginDirective: Directive<HTMLElement, Auth.RoleType | undefined> = {
|
||||
mounted(el: HTMLElement, binding) {
|
||||
if (binding.value !== auth.userInfo.userRole) {
|
||||
el.remove();
|
||||
function updateElVisible(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) {
|
||||
if (!permission) {
|
||||
throw new Error(`need roles: like v-permission="'admin'", v-permission="['admin', 'test]"`);
|
||||
}
|
||||
if (!hasPermission(permission)) {
|
||||
el.parentElement?.removeChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = {
|
||||
mounted(el, binding) {
|
||||
updateElVisible(el, binding.value);
|
||||
},
|
||||
beforeUpdate(el, binding) {
|
||||
updateElVisible(el, binding.value);
|
||||
}
|
||||
};
|
||||
|
||||
app.directive('login', loginDirective);
|
||||
app.directive('permission', permissionDirective);
|
||||
}
|
||||
|
@ -1,3 +1,11 @@
|
||||
/** 用户角色 */
|
||||
export enum EnumUserRole {
|
||||
super = '超级管理员',
|
||||
admin = '管理员',
|
||||
user = '普通用户'
|
||||
// custom = '自定义角色'
|
||||
}
|
||||
|
||||
/** 登录模块 */
|
||||
export enum EnumLoginModule {
|
||||
'pwd-login' = '账密登录',
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<soybean-admin-layout
|
||||
<admin-layout
|
||||
:mode="mode"
|
||||
:min-width="theme.layout.minWidth"
|
||||
:fixed-header-and-tab="theme.fixedHeaderAndTab"
|
||||
@ -25,12 +25,12 @@
|
||||
<template #footer>
|
||||
<global-footer />
|
||||
</template>
|
||||
</soybean-admin-layout>
|
||||
</admin-layout>
|
||||
<setting-drawer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SoybeanAdminLayout from 'soybean-admin-layout';
|
||||
import AdminLayout from '@soybeanjs/vue-admin-layout';
|
||||
import { useAppStore, useThemeStore } from '@/store';
|
||||
import { useBasicLayout } from '@/composables';
|
||||
import { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter } from '../common';
|
||||
|
@ -1,10 +1,17 @@
|
||||
<template>
|
||||
<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">
|
||||
<hover-container
|
||||
tooltip-content="github"
|
||||
class="w-40px h-full"
|
||||
content-class="hover:text-primary"
|
||||
@click="handleClickLink"
|
||||
>
|
||||
<icon-mdi-github class="text-20px" />
|
||||
</a>
|
||||
</hover-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
function handleClickLink() {
|
||||
window.open('https://github.com/honghuangdc/soybean-admin', '_blank');
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<hover-container class="w-40px h-full" @click="app.toggleSiderCollapse">
|
||||
<hover-container class="w-40px h-full" content-class="hover:text-primary" @click="app.toggleSiderCollapse">
|
||||
<icon-line-md-menu-unfold-left v-if="app.siderCollapse" class="text-16px" />
|
||||
<icon-line-md-menu-fold-left v-else class="text-16px" />
|
||||
</hover-container>
|
||||
|
@ -2,12 +2,12 @@
|
||||
<div class="mb-6px px-4px cursor-pointer" @mouseenter="setTrue" @mouseleave="setFalse">
|
||||
<div
|
||||
class="flex-center flex-col py-12px rounded-2px bg-transparent transition-colors duration-300 ease-in-out"
|
||||
:class="{ 'text-primary !bg-primary-active': isActive, 'text-primary': isHover }"
|
||||
:class="{ 'text-primary !bg-primary_active': isActive, 'text-primary': isHover }"
|
||||
>
|
||||
<component :is="icon" :class="[isMini ? 'text-16px' : 'text-20px']" />
|
||||
<p
|
||||
class="pt-8px text-12px overflow-hidden transition-height duration-300 ease-in-out"
|
||||
:class="[isMini ? 'h-0 pt-0' : 'h-20px pt-8px']"
|
||||
class="text-12px overflow-hidden transition-height duration-300 ease-in-out"
|
||||
:class="[isMini ? 'h-0 pt-0' : 'h-24px pt-4px']"
|
||||
>
|
||||
{{ label }}
|
||||
</p>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, nextTick, watch } from 'vue';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { ChromeTab, ButtonTab } from 'soybean-admin-tab';
|
||||
import { ChromeTab, ButtonTab } from '@soybeanjs/vue-admin-tab';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { useThemeStore, useTabStore } from '@/store';
|
||||
import { setTabRoutes } from '@/utils';
|
||||
|
@ -1,21 +1,26 @@
|
||||
import { createApp } from 'vue';
|
||||
import { setupImportAssets } from './plugins';
|
||||
import { setupAssets } from './plugins';
|
||||
import { setupStore } from './store';
|
||||
import { setupDirectives } from './directives';
|
||||
import { setupRouter } from './router';
|
||||
import App from './App.vue';
|
||||
|
||||
async function setupApp() {
|
||||
setupImportAssets();
|
||||
// import assets: js、css
|
||||
setupAssets();
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// store plugin: pinia
|
||||
setupStore(app);
|
||||
|
||||
// vue custom directives
|
||||
setupDirectives(app);
|
||||
|
||||
// vue router
|
||||
await setupRouter(app);
|
||||
|
||||
// mount app
|
||||
app.mount('#app');
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'virtual:windi.css';
|
||||
import 'uno.css';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import '../styles/css/global.css';
|
||||
|
||||
/** import static assets: css, js , font and so on. - [引入静态资源,css、js和字体文件等] */
|
||||
export default function setupImportAssets() {
|
||||
export default function setupAssets() {
|
||||
//
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
import setupImportAssets from './assets';
|
||||
import setupAssets from './assets';
|
||||
|
||||
export { setupImportAssets };
|
||||
export { setupAssets };
|
||||
|
@ -16,7 +16,7 @@ export async function createDynamicRouteGuard(
|
||||
const isLogin = Boolean(getToken());
|
||||
|
||||
// 初始化权限路由
|
||||
if (!route.isInitedAuthRoute) {
|
||||
if (!route.isInitAuthRoute) {
|
||||
// 未登录情况下直接回到登录页,登录成功后再加载权限路由
|
||||
if (!isLogin) {
|
||||
if (to.name === routeName('login')) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { App } from 'vue';
|
||||
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||
import { transformAuthRoutesToVueRoutes } from '@/utils';
|
||||
import { transformAuthRoutesToVueRoutes, transformRouteNameToRoutePath } from '@/utils';
|
||||
import { constantRoutes } from './routes';
|
||||
import { scrollBehavior } from './helpers';
|
||||
import { createRouterGuard } from './guard';
|
||||
@ -20,5 +20,10 @@ export async function setupRouter(app: App) {
|
||||
await router.isReady();
|
||||
}
|
||||
|
||||
/** 路由名称 */
|
||||
export const routeName = (key: AuthRoute.RouteKey) => key;
|
||||
/** 路由路径 */
|
||||
export const routePath = (key: Exclude<AuthRoute.RouteKey, 'not-found-page'>) => transformRouteNameToRoutePath(key);
|
||||
|
||||
export * from './routes';
|
||||
export * from './modules';
|
||||
|
@ -6,9 +6,9 @@ const about: AuthRoute.Route = {
|
||||
title: '关于',
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
permissions: ['super', 'admin', 'test'],
|
||||
permissions: ['super', 'admin', 'user'],
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 7
|
||||
order: 8
|
||||
}
|
||||
};
|
||||
|
||||
|
35
src/router/modules/auth-demo.ts
Normal file
35
src/router/modules/auth-demo.ts
Normal file
@ -0,0 +1,35 @@
|
||||
const authDemo: AuthRoute.Route = {
|
||||
name: 'auth-demo',
|
||||
path: '/auth-demo',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'auth-demo_permission',
|
||||
path: '/auth-demo/permission',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '权限切换',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-construction'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'auth-demo_super',
|
||||
path: '/auth-demo/super',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '超级管理员可见',
|
||||
requiresAuth: true,
|
||||
permissions: ['super'],
|
||||
icon: 'ic:round-supervisor-account'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '权限示例',
|
||||
icon: 'ic:baseline-security',
|
||||
order: 5
|
||||
}
|
||||
};
|
||||
|
||||
export default authDemo;
|
@ -37,7 +37,7 @@ const exception: AuthRoute.Route = {
|
||||
meta: {
|
||||
title: '异常页',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 5
|
||||
order: 6
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ export const constantRoutes: AuthRoute.Route[] = [
|
||||
{
|
||||
name: 'root',
|
||||
path: '/',
|
||||
redirect: '/dashboard/analysis',
|
||||
redirect: import.meta.env.VITE_ROUTE_HOME_PATH,
|
||||
meta: {
|
||||
title: 'Root'
|
||||
}
|
||||
@ -64,16 +64,3 @@ export const constantRoutes: AuthRoute.Route[] = [
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
/** 路由名称 */
|
||||
export const routeName = (key: AuthRoute.RouteKey) => key;
|
||||
|
||||
/** 路由路径 */
|
||||
export function routePath(key: Exclude<AuthRoute.RouteKey, 'not-found-page'>): AuthRoute.RoutePath {
|
||||
const rootPath: AuthRoute.RoutePath = '/';
|
||||
if (key === 'root') return rootPath;
|
||||
const splitMark: AuthRoute.RouteSplitMark = '_';
|
||||
const pathSplitMark = '/';
|
||||
const path = key.split(splitMark).join(pathSplitMark);
|
||||
return (pathSplitMark + path) as AuthRoute.RoutePath;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { createRequest } from './request';
|
||||
import { getEnvConfig } from '~/.env-config';
|
||||
|
||||
const { http } = getEnvConfig(import.meta.env);
|
||||
const envConfig = getEnvConfig(import.meta.env);
|
||||
const isHttpProxy = import.meta.env.VITE_HTTP_PROXY === 'true';
|
||||
|
||||
export const request = createRequest({ baseURL: isHttpProxy ? http.proxy : http.url });
|
||||
export const request = createRequest({ baseURL: isHttpProxy ? envConfig.proxy : envConfig.url });
|
||||
|
||||
export const mockRequest = createRequest({ baseURL: '/mock' });
|
||||
|
@ -87,6 +87,9 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
await this.loginByToken(data);
|
||||
}
|
||||
this.loginLoading = false;
|
||||
},
|
||||
updateUserRole(userRole: Auth.RoleType) {
|
||||
this.userInfo.userRole = userRole;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -7,8 +7,11 @@ import {
|
||||
transformAuthRouteToMenu,
|
||||
transformAuthRoutesToVueRoutes,
|
||||
transformAuthRoutesToSearchMenus,
|
||||
getCacheRoutes
|
||||
getCacheRoutes,
|
||||
filterAuthRoutesByUserPermission,
|
||||
transformRoutePathToRouteName
|
||||
} from '@/utils';
|
||||
import { useAuthStore } from '../auth';
|
||||
import { useTabStore } from '../tab';
|
||||
|
||||
interface RouteState {
|
||||
@ -19,7 +22,7 @@ interface RouteState {
|
||||
*/
|
||||
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];
|
||||
/** 是否初始化了权限路由 */
|
||||
isInitedAuthRoute: boolean;
|
||||
isInitAuthRoute: boolean;
|
||||
/** 路由首页name(前端静态路由时生效,后端动态路由该值会被后端返回的值覆盖) */
|
||||
routeHomeName: AuthRoute.RouteKey;
|
||||
/** 菜单 */
|
||||
@ -33,8 +36,8 @@ interface RouteState {
|
||||
export const useRouteStore = defineStore('route-store', {
|
||||
state: (): RouteState => ({
|
||||
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
|
||||
isInitedAuthRoute: false,
|
||||
routeHomeName: 'dashboard_analysis',
|
||||
isInitAuthRoute: false,
|
||||
routeHomeName: transformRoutePathToRouteName(import.meta.env.VITE_ROUTE_HOME_PATH),
|
||||
menus: [],
|
||||
searchMenus: [],
|
||||
cacheRoutes: []
|
||||
@ -73,9 +76,9 @@ export const useRouteStore = defineStore('route-store', {
|
||||
* @param router - 路由实例
|
||||
*/
|
||||
async initStaticRoute(router: Router) {
|
||||
// 先根据用户权限过滤一下staticRoutes
|
||||
|
||||
this.handleAuthRoutes(staticRoutes, router);
|
||||
const auth = useAuthStore();
|
||||
const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);
|
||||
this.handleAuthRoutes(routes, router);
|
||||
},
|
||||
/**
|
||||
* 初始化权限路由
|
||||
@ -84,6 +87,7 @@ export const useRouteStore = defineStore('route-store', {
|
||||
async initAuthRoute(router: Router) {
|
||||
const { initHomeTab } = useTabStore();
|
||||
const { userId } = getUserInfo();
|
||||
|
||||
if (!userId) return;
|
||||
|
||||
const isDynamicRoute = this.authRouteMode === 'dynamic';
|
||||
@ -94,7 +98,8 @@ export const useRouteStore = defineStore('route-store', {
|
||||
}
|
||||
|
||||
initHomeTab(this.routeHomeName, router);
|
||||
this.isInitedAuthRoute = true;
|
||||
|
||||
this.isInitAuthRoute = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -21,7 +21,7 @@ export const useTabStore = defineStore('tab-store', {
|
||||
name: 'root',
|
||||
path: '/',
|
||||
meta: {
|
||||
title: 'root'
|
||||
title: 'Root'
|
||||
},
|
||||
scrollPosition: {
|
||||
left: 0,
|
||||
@ -53,7 +53,8 @@ export const useTabStore = defineStore('tab-store', {
|
||||
initHomeTab(routeHomeName: string, router: Router) {
|
||||
const routes = router.getRoutes();
|
||||
const findHome = routes.find(item => item.name === routeHomeName);
|
||||
if (findHome) {
|
||||
if (findHome && !findHome.children.length) {
|
||||
// 有子路由的不能作为Tab
|
||||
this.homeTab = getTabRouteByVueRoute(findHome);
|
||||
}
|
||||
},
|
||||
@ -165,16 +166,20 @@ export const useTabStore = defineStore('tab-store', {
|
||||
iniTabStore(currentRoute: RouteLocationNormalizedLoaded) {
|
||||
const theme = useThemeStore();
|
||||
|
||||
const isHome = currentRoute.path === this.homeTab.path;
|
||||
const tabs: GlobalTabRoute[] = theme.tab.isCache ? getTabRoutes() : [];
|
||||
|
||||
const hasHome = isInTabRoutes(tabs, this.homeTab.path);
|
||||
const hasCurrent = isInTabRoutes(tabs, currentRoute.path);
|
||||
if (!hasHome) {
|
||||
if (!hasHome && this.homeTab.name !== 'root') {
|
||||
tabs.unshift(this.homeTab);
|
||||
}
|
||||
|
||||
const isHome = currentRoute.path === this.homeTab.path;
|
||||
const hasCurrent = isInTabRoutes(tabs, currentRoute.path);
|
||||
if (!isHome && !hasCurrent) {
|
||||
tabs.push(getTabRouteByVueRoute(currentRoute));
|
||||
const currentTab = getTabRouteByVueRoute(currentRoute);
|
||||
tabs.push(currentTab);
|
||||
}
|
||||
|
||||
this.tabs = tabs;
|
||||
this.setActiveTab(currentRoute.path);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@import './transition.css';
|
||||
@import './reset.css';
|
||||
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
|
366
src/styles/css/reset.css
Normal file
366
src/styles/css/reset.css
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box; /* 1 */
|
||||
border-width: 0; /* 2 */
|
||||
border-style: solid; /* 2 */
|
||||
border-color: currentColor; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.5; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-moz-tab-size: 4; /* 3 */
|
||||
tab-size: 4; /* 3 */
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0; /* 1 */
|
||||
line-height: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
border-top-width: 1px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font family by default.
|
||||
2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: inherit; /* 1 */
|
||||
color: inherit; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
padding: 0; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
/* background-color: transparent; 2 */
|
||||
background-image: none; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing and border for appropriate elements.
|
||||
*/
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
figure,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1; /* 1 */
|
||||
color: #9ca3af; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block; /* 1 */
|
||||
vertical-align: middle; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Ensure the default browser behavior of the `hidden` attribute.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
}
|
6
src/typings/business.d.ts
vendored
6
src/typings/business.d.ts
vendored
@ -4,10 +4,10 @@ declare namespace Auth {
|
||||
* 用户角色类型(前端静态路由用角色类型进行路由权限的控制)
|
||||
* - super: 超级管理员(该权限具有所有路由数据)
|
||||
* - admin: 管理员
|
||||
* - test: 测试
|
||||
* - normal: 普通用户
|
||||
* - user: 用户
|
||||
* - custom: 自定义角色
|
||||
*/
|
||||
type RoleType = 'super' | 'admin' | 'test' | 'normal';
|
||||
type RoleType = keyof typeof import('@/enum').EnumUserRole;
|
||||
|
||||
/** 用户信息 */
|
||||
interface UserInfo {
|
||||
|
33
components.d.ts → src/typings/components.d.ts
vendored
33
components.d.ts → src/typings/components.d.ts
vendored
@ -1,15 +1,16 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
declare module 'vue' {
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
BetterScroll: typeof import('./src/components/custom/BetterScroll.vue')['default']
|
||||
CountTo: typeof import('./src/components/custom/CountTo.vue')['default']
|
||||
DarkModeContainer: typeof import('./src/components/common/DarkModeContainer.vue')['default']
|
||||
DarkModeSwitch: typeof import('./src/components/common/DarkModeSwitch.vue')['default']
|
||||
GithubLink: typeof import('./src/components/custom/GithubLink.vue')['default']
|
||||
HoverContainer: typeof import('./src/components/common/HoverContainer.vue')['default']
|
||||
BetterScroll: typeof import('./../components/custom/BetterScroll.vue')['default']
|
||||
CountTo: typeof import('./../components/custom/CountTo.vue')['default']
|
||||
DarkModeContainer: typeof import('./../components/common/DarkModeContainer.vue')['default']
|
||||
DarkModeSwitch: typeof import('./../components/common/DarkModeSwitch.vue')['default']
|
||||
GithubLink: typeof import('./../components/custom/GithubLink.vue')['default']
|
||||
HoverContainer: typeof import('./../components/common/HoverContainer.vue')['default']
|
||||
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']
|
||||
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
|
||||
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
|
||||
@ -41,12 +42,12 @@ declare module 'vue' {
|
||||
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']
|
||||
IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default']
|
||||
IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default']
|
||||
IconSelect: typeof import('./src/components/custom/IconSelect.vue')['default']
|
||||
IconSelect: typeof import('./../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']
|
||||
ImageVerify: typeof import('./../components/custom/ImageVerify.vue')['default']
|
||||
LoadingEmptyWrapper: typeof import('./../components/business/LoadingEmptyWrapper.vue')['default']
|
||||
LoginAgreement: typeof import('./../components/business/LoginAgreement.vue')['default']
|
||||
NaiveProvider: typeof import('./../components/common/NaiveProvider.vue')['default']
|
||||
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
||||
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
@ -92,9 +93,11 @@ declare module 'vue' {
|
||||
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']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SystemLogo: typeof import('./../components/common/SystemLogo.vue')['default']
|
||||
WebSiteLink: typeof import('./../components/custom/WebSiteLink.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export { }
|
||||
export {}
|
28
src/typings/env.d.ts
vendored
28
src/typings/env.d.ts
vendored
@ -1,12 +1,3 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue';
|
||||
|
||||
const component: DefineComponent<object, object, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
/**
|
||||
* env环境类型
|
||||
* - dev: 后台开发环境
|
||||
@ -15,6 +6,16 @@ declare module '*.vue' {
|
||||
*/
|
||||
type EnvType = 'dev' | 'test' | 'prod';
|
||||
|
||||
/**
|
||||
* env环境配置
|
||||
*/
|
||||
interface EnvConfig {
|
||||
/** 请求地址 */
|
||||
url: string;
|
||||
/** 代理地址 */
|
||||
proxy: string;
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
/** 项目基本地址 */
|
||||
readonly VITE_BASE_URL: string;
|
||||
@ -27,14 +28,21 @@ interface ImportMetaEnv {
|
||||
/**
|
||||
* 权限路由模式:
|
||||
* - static - 前端声明的静态
|
||||
* - dynamic - 后端返回的动态 */
|
||||
* - dynamic - 后端返回的动态
|
||||
*/
|
||||
readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';
|
||||
/** 路由首页的路径 */
|
||||
readonly VITE_ROUTE_HOME_PATH: Exclude<AuthRoute.RoutePath, '/not-found-page' | '/:pathMatch(.*)*'>;
|
||||
/** vite环境类型 */
|
||||
readonly VITE_ENV_TYPE?: EnvType;
|
||||
/** 开启请求代理 */
|
||||
readonly VITE_HTTP_PROXY?: 'true' | 'false';
|
||||
/** 是否开启打包文件大小结果分析 */
|
||||
readonly VITE_VISUALIZER?: 'true' | 'false';
|
||||
/** 是否开启打包压缩 */
|
||||
readonly VITE_COMPRESS?: 'true' | 'false';
|
||||
/** 压缩算法类型 */
|
||||
readonly VITE_COMPRESS_TYPE?: 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw';
|
||||
/** hash路由模式 */
|
||||
readonly VITE_HASH_ROUTE?: 'true' | 'false';
|
||||
}
|
||||
|
2
src/typings/expose.d.ts
vendored
2
src/typings/expose.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
/** vue 的defineExpose导出的类型 */
|
||||
declare namespace Expose {
|
||||
interface BetterScroll {
|
||||
instance: import('@better-scroll/core');
|
||||
instance: import('@better-scroll/core').BScrollInstance;
|
||||
}
|
||||
}
|
||||
|
3
src/typings/route.d.ts
vendored
3
src/typings/route.d.ts
vendored
@ -36,6 +36,9 @@ declare namespace AuthRoute {
|
||||
| 'plugin_icon'
|
||||
| 'plugin_print'
|
||||
| 'plugin_swiper'
|
||||
| 'auth-demo'
|
||||
| 'auth-demo_permission'
|
||||
| 'auth-demo_super'
|
||||
| 'exception'
|
||||
| 'exception_403'
|
||||
| 'exception_404'
|
||||
|
8
src/typings/system.d.ts
vendored
8
src/typings/system.d.ts
vendored
@ -84,6 +84,14 @@ declare namespace Service {
|
||||
/** 接口消息 */
|
||||
message: string;
|
||||
}
|
||||
|
||||
/** mock的响应option */
|
||||
interface MockOption {
|
||||
url: Record<string, any>;
|
||||
body: Record<string, any>;
|
||||
query: Record<string, any>;
|
||||
headers: Record<string, any>;
|
||||
}
|
||||
}
|
||||
|
||||
/** 主题相关类型 */
|
||||
|
@ -37,7 +37,7 @@ export function getUserInfo() {
|
||||
userId: '',
|
||||
userName: '',
|
||||
userPhone: '',
|
||||
userRole: 'test'
|
||||
userRole: 'user'
|
||||
};
|
||||
const userInfo: Auth.UserInfo = getLocal<Auth.UserInfo>(EnumStorageKey['user-info']) || emptyInfo;
|
||||
return userInfo;
|
||||
|
@ -1,16 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/**
|
||||
* 根据用户权限过滤路由
|
||||
* @param routes - 权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
|
||||
const filters: AuthRoute.Route[] = [];
|
||||
|
||||
routes.forEach(route => {
|
||||
filterAuthRouteByUserPermission(route, permission);
|
||||
});
|
||||
return filters;
|
||||
return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,5 +13,12 @@ export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], perm
|
||||
* @param permission - 权限
|
||||
*/
|
||||
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
|
||||
return [];
|
||||
const hasPermission =
|
||||
!route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
|
||||
|
||||
if (route.children) {
|
||||
const filterChildren = route.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
|
||||
Object.assign(route, { children: filterChildren });
|
||||
}
|
||||
return hasPermission ? [route] : [];
|
||||
}
|
||||
|
@ -31,6 +31,34 @@ export function transformAuthRoutesToSearchMenus(routes: AuthRoute.Route[], tree
|
||||
}, treeMap);
|
||||
}
|
||||
|
||||
/** 将路由名字转换成路由路径 */
|
||||
export function transformRouteNameToRoutePath(
|
||||
name: Exclude<AuthRoute.RouteKey, 'not-found-page'>
|
||||
): AuthRoute.RoutePath {
|
||||
const rootPath: AuthRoute.RoutePath = '/';
|
||||
if (name === 'root') return rootPath;
|
||||
|
||||
const splitMark: AuthRoute.RouteSplitMark = '_';
|
||||
const pathSplitMark = '/';
|
||||
const path = name.split(splitMark).join(pathSplitMark);
|
||||
|
||||
return (pathSplitMark + path) as AuthRoute.RoutePath;
|
||||
}
|
||||
|
||||
/** 将路由路径转换成路由名字 */
|
||||
export function transformRoutePathToRouteName(
|
||||
path: Exclude<AuthRoute.RoutePath, '/not-found-page' | '/:pathMatch(.*)*'>
|
||||
): AuthRoute.RouteKey {
|
||||
if (path === '/') return 'root';
|
||||
|
||||
const pathSplitMark = '/';
|
||||
const routeSplitMark: AuthRoute.RouteSplitMark = '_';
|
||||
|
||||
const name = path.split(pathSplitMark).slice(1).join(routeSplitMark) as AuthRoute.RouteKey;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个权限路由转换成vue路由
|
||||
* @param item - 单个权限路由
|
||||
|
@ -1,6 +1,7 @@
|
||||
export * from './module';
|
||||
export * from './helpers';
|
||||
export * from './cache';
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './breadcrumb';
|
||||
export * from './tab';
|
||||
|
65
src/views/auth-demo/permission/index.vue
Normal file
65
src/views/auth-demo/permission/index.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<n-card title="权限切换" class="h-full shadow-sm rounded-16px">
|
||||
<div class="pb-12px">
|
||||
<n-gradient-text type="primary" :size="20">当前用户的权限:{{ auth.userInfo.userRole }}</n-gradient-text>
|
||||
</div>
|
||||
<n-select
|
||||
:value="auth.userInfo.userRole"
|
||||
class="w-120px"
|
||||
size="small"
|
||||
:options="options"
|
||||
@update:value="auth.updateUserRole"
|
||||
/>
|
||||
<div class="py-12px">
|
||||
<n-gradient-text type="primary" :size="20">权限指令 v-permission</n-gradient-text>
|
||||
</div>
|
||||
<div>
|
||||
<n-button v-permission="'super'" class="mr-12px">super可见</n-button>
|
||||
<n-button v-permission="'admin'" class="mr-12px">admin可见</n-button>
|
||||
<n-button v-permission="['admin', 'user']">admin和test可见</n-button>
|
||||
</div>
|
||||
<div class="py-12px">
|
||||
<n-gradient-text type="primary" :size="20">权限函数 hasPermission</n-gradient-text>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-button v-if="hasPermission('super')">super可见</n-button>
|
||||
<n-button v-if="hasPermission('admin')">admin可见</n-button>
|
||||
<n-button v-if="hasPermission(['admin', 'user'])">admin和user可见</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch } from 'vue';
|
||||
import type { SelectOption } from 'naive-ui';
|
||||
import { EnumUserRole } from '@/enum';
|
||||
import { useAppStore, useAuthStore } from '@/store';
|
||||
import { usePermission } from '@/composables';
|
||||
|
||||
const app = useAppStore();
|
||||
const auth = useAuthStore();
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
interface RoleList {
|
||||
label: string;
|
||||
value: keyof typeof EnumUserRole;
|
||||
}
|
||||
|
||||
const roleList: RoleList[] = [
|
||||
{ label: EnumUserRole.super, value: 'super' },
|
||||
{ label: EnumUserRole.admin, value: 'admin' },
|
||||
{ label: EnumUserRole.user, value: 'user' }
|
||||
];
|
||||
|
||||
const options: SelectOption[] = roleList as unknown as SelectOption[];
|
||||
|
||||
watch(
|
||||
() => auth.userInfo.userRole,
|
||||
async () => {
|
||||
app.reloadPage();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped></style>
|
8
src/views/auth-demo/super/index.vue
Normal file
8
src/views/auth-demo/super/index.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<n-card title="当前页面只有super才能看到" class="h-full shadow-sm rounded-16px"> </n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<style scoped></style>
|
@ -14,12 +14,13 @@ const domRef = ref<HTMLDivElement>();
|
||||
async function renderBaiduMap() {
|
||||
if (!domRef.value) return;
|
||||
await load(true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const map = new AMap.Map(domRef.value, {
|
||||
zoom: 11,
|
||||
center: [114.05834626586915, 22.546789983033168],
|
||||
viewMode: '3D'
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('map: ', map);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -14,12 +14,13 @@ const domRef = ref<HTMLDivElement | null>(null);
|
||||
async function renderBaiduMap() {
|
||||
if (!domRef.value) return;
|
||||
await load(true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const map = new TMap.Map(domRef.value, {
|
||||
center: new TMap.LatLng(39.98412, 116.307484),
|
||||
zoom: 11,
|
||||
viewMode: '3D'
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('map: ', map);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -26,8 +26,8 @@ function printTable() {
|
||||
function printImage() {
|
||||
printJS({
|
||||
printable: [
|
||||
'https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/qq_qrcode.JPG',
|
||||
'https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/qq_qrcode.JPG'
|
||||
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg',
|
||||
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg'
|
||||
],
|
||||
type: 'image',
|
||||
header: 'Multiple Images',
|
||||
|
@ -6,7 +6,7 @@
|
||||
@update:dark="theme.setDarkMode"
|
||||
/>
|
||||
<n-card :bordered="false" size="large" class="z-4 !w-auto rounded-20px shadow-sm">
|
||||
<div class="w-360px <sm:w-300px">
|
||||
<div class="w-300px sm:w-360px">
|
||||
<header class="flex-y-center justify-between">
|
||||
<div class="w-70px h-70px rounded-35px overflow-hidden">
|
||||
<system-logo class="text-70px text-primary" :fill="true" />
|
||||
|
@ -1,25 +1,28 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": [
|
||||
"src/typings/**/*.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.vue",
|
||||
"vite.config.*",
|
||||
"build/**/*.ts",
|
||||
"mock/**/*.ts",
|
||||
".env-config.ts",
|
||||
"components.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"/dist/**",
|
||||
"node_modules"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"~/*": ["./*"]
|
||||
},
|
||||
"types": ["node","unplugin-icons/types/vue","naive-ui/volar"]
|
||||
}
|
||||
"types": [
|
||||
"vite/client",
|
||||
"node",
|
||||
"unplugin-icons/types/vue",
|
||||
"naive-ui/volar"
|
||||
]
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
}
|
||||
|
78
uno.config.ts
Normal file
78
uno.config.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { defineConfig, presetMini } from 'unocss';
|
||||
|
||||
export default defineConfig({
|
||||
presets: [presetMini({ dark: 'class' })],
|
||||
shortcuts: {
|
||||
'wh-full': 'w-full h-full',
|
||||
'flex-center': 'flex justify-center items-center',
|
||||
'flex-col-center': 'flex-center flex-col',
|
||||
'flex-x-center': 'flex justify-center',
|
||||
'flex-y-center': 'flex items-center',
|
||||
'i-flex-center': 'inline-flex justify-center items-center',
|
||||
'i-flex-x-center': 'inline-flex justify-center',
|
||||
'i-flex-y-center': 'inline-flex items-center',
|
||||
'flex-col': 'flex flex-col',
|
||||
'flex-col-stretch': 'flex-col items-stretch',
|
||||
'i-flex-col': 'inline-flex flex-col',
|
||||
'i-flex-col-stretch': 'i-flex-col items-stretch',
|
||||
'flex-1-hidden': 'flex-1 overflow-hidden',
|
||||
'absolute-lt': 'absolute left-0 top-0',
|
||||
'absolute-lb': 'absolute left-0 bottom-0',
|
||||
'absolute-rt': 'absolute right-0 top-0',
|
||||
'absolute-rb': 'absolute right-0 bottom-0',
|
||||
'absolute-tl': 'absolute-lt',
|
||||
'absolute-tr': 'absolute-rt',
|
||||
'absolute-bl': 'absolute-lb',
|
||||
'absolute-br': 'absolute-rb',
|
||||
'absolute-center': 'absolute-lt flex-center wh-full',
|
||||
'fixed-lt': 'fixed left-0 top-0',
|
||||
'fixed-lb': 'fixed left-0 bottom-0',
|
||||
'fixed-rt': 'fixed right-0 top-0',
|
||||
'fixed-rb': 'fixed right-0 bottom-0',
|
||||
'fixed-tl': 'fixed-lt',
|
||||
'fixed-tr': 'fixed-rt',
|
||||
'fixed-bl': 'fixed-lb',
|
||||
'fixed-br': 'fixed-rb',
|
||||
'fixed-center': 'fixed-lt flex-center wh-full',
|
||||
'nowrap-hidden': 'whitespace-nowrap overflow-hidden',
|
||||
'ellipsis-text': 'nowrap-hidden overflow-ellipsis',
|
||||
'transition-base': 'transition-all duration-300 ease-in-out'
|
||||
},
|
||||
theme: {
|
||||
colors: {
|
||||
primary: 'var(--primary-color)',
|
||||
primary_hover: 'var(--primary-color-hover)',
|
||||
primary_pressed: 'var(--primary-color-pressed)',
|
||||
primary_active: 'var(--primary-color-active)',
|
||||
info: 'var(--info-color)',
|
||||
info_hover: 'var(--info-color-hover)',
|
||||
info_pressed: 'var(--info-color-pressed)',
|
||||
info_active: 'var(--info-color-active)',
|
||||
success: 'var(--success-color)',
|
||||
success_hover: 'var(--success-color-hover)',
|
||||
success_pressed: 'var(--success-color-pressed)',
|
||||
success_active: 'var(--success-color-active)',
|
||||
warning: 'var(--warning-color)',
|
||||
warning_hover: 'var(--warning-color-hover)',
|
||||
warning_pressed: 'var(--warning-color-pressed)',
|
||||
warning_active: 'var(--warning-color-active)',
|
||||
error: 'var(--error-color)',
|
||||
error_hover: 'var(--error-color-hover)',
|
||||
error_pressed: 'var(--error-color-pressed)',
|
||||
error_active: 'var(--error-color-active)'
|
||||
},
|
||||
backgroundColor: {
|
||||
dark: '#18181c'
|
||||
},
|
||||
transitionProperty: [
|
||||
'width',
|
||||
'height',
|
||||
'background',
|
||||
'background-color',
|
||||
'padding-left',
|
||||
'border-color',
|
||||
'right',
|
||||
'fill'
|
||||
]
|
||||
}
|
||||
});
|
@ -1,20 +1,27 @@
|
||||
import { fileURLToPath } from 'url';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import { resolvePath, viteDefine, setupVitePlugins, createViteProxy } from './build';
|
||||
import { viteDefine, setupVitePlugins, createViteProxy } from './build';
|
||||
import { getEnvConfig } from './.env-config';
|
||||
|
||||
export default defineConfig(configEnv => {
|
||||
const viteEnv = loadEnv(configEnv.mode, process.cwd()) as ImportMetaEnv;
|
||||
const vitePath = resolvePath('./', import.meta.url);
|
||||
|
||||
const rootPath = fileURLToPath(new URL('./', import.meta.url));
|
||||
const srcPath = `${rootPath}src`;
|
||||
|
||||
const isOpenProxy = viteEnv.VITE_HTTP_PROXY === 'true';
|
||||
const envConfig = getEnvConfig(viteEnv);
|
||||
|
||||
return {
|
||||
base: viteEnv.VITE_BASE_URL,
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': vitePath.root,
|
||||
'@': vitePath.src
|
||||
'~': rootPath,
|
||||
'@': srcPath
|
||||
}
|
||||
},
|
||||
define: viteDefine,
|
||||
plugins: setupVitePlugins(configEnv, vitePath.src, viteEnv),
|
||||
plugins: setupVitePlugins(viteEnv, srcPath),
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
@ -26,7 +33,7 @@ export default defineConfig(configEnv => {
|
||||
host: '0.0.0.0',
|
||||
port: 3200,
|
||||
open: true,
|
||||
proxy: createViteProxy(viteEnv)
|
||||
proxy: createViteProxy(isOpenProxy, envConfig)
|
||||
},
|
||||
build: {
|
||||
brotliSize: false
|
||||
|
@ -1,91 +0,0 @@
|
||||
import { defineConfig } from 'windicss/helpers';
|
||||
|
||||
export default defineConfig({
|
||||
extract: {
|
||||
include: ['src/**/*.{vue,html,jsx,tsx}', 'public/**/*.{html}', './*.html'],
|
||||
exclude: ['node_modules', '.git', './stats.html']
|
||||
},
|
||||
darkMode: 'class',
|
||||
shortcuts: {
|
||||
'wh-full': 'w-full h-full',
|
||||
'flex-center': 'flex justify-center items-center',
|
||||
'flex-col-center': 'flex-center flex-col',
|
||||
'flex-x-center': 'flex justify-center',
|
||||
'flex-y-center': 'flex items-center',
|
||||
'i-flex-center': 'inline-flex justify-center items-center',
|
||||
'i-flex-x-center': 'inline-flex justify-center',
|
||||
'i-flex-y-center': 'inline-flex items-center',
|
||||
'b-flex-col': 'flex flex-col',
|
||||
'flex-col-stretch': 'b-flex-col items-stretch',
|
||||
'i-flex-col': 'inline-flex flex-col',
|
||||
'i-flex-col-stretch': 'i-flex-col items-stretch',
|
||||
'flex-1-hidden': 'flex-1 overflow-hidden',
|
||||
'absolute-lt': 'absolute left-0 top-0',
|
||||
'absolute-lb': 'absolute left-0 bottom-0',
|
||||
'absolute-rt': 'absolute right-0 top-0',
|
||||
'absolute-rb': 'absolute right-0 bottom-0',
|
||||
'absolute-tl': 'absolute-lt',
|
||||
'absolute-tr': 'absolute-rt',
|
||||
'absolute-bl': 'absolute-lb',
|
||||
'absolute-br': 'absolute-rb',
|
||||
'absolute-center': 'absolute-lt flex-center wh-full',
|
||||
'fixed-lt': 'fixed left-0 top-0',
|
||||
'fixed-lb': 'fixed left-0 bottom-0',
|
||||
'fixed-rt': 'fixed right-0 top-0',
|
||||
'fixed-rb': 'fixed right-0 bottom-0',
|
||||
'fixed-tl': 'fixed-lt',
|
||||
'fixed-tr': 'fixed-rt',
|
||||
'fixed-bl': 'fixed-lb',
|
||||
'fixed-br': 'fixed-rb',
|
||||
'fixed-center': 'fixed left-0 top-0 flex-center wh-full',
|
||||
'nowrap-hidden': 'whitespace-nowrap overflow-hidden',
|
||||
'ellipsis-text': 'nowrap-hidden overflow-ellipsis',
|
||||
'transition-base': 'transition-all duration-300 ease-in-out'
|
||||
},
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: 'var(--primary-color)',
|
||||
'primary-hover': 'var(--primary-color-hover)',
|
||||
'primary-pressed': 'var(--primary-color-pressed)',
|
||||
'primary-active': 'var(--primary-color-active)',
|
||||
info: 'var(--info-color)',
|
||||
'info-hover': 'var(--info-color-hover)',
|
||||
'info-pressed': 'var(--info-color-pressed)',
|
||||
'info-active': 'var(--info-color-active)',
|
||||
success: 'var(--success-color)',
|
||||
'success-hover': 'var(--success-color-hover)',
|
||||
'success-pressed': 'var(--success-color-pressed)',
|
||||
'success-active': 'var(--success-color-active)',
|
||||
warning: 'var(--warning-color)',
|
||||
'warning-hover': 'var(--warning-color-hover)',
|
||||
'warning-pressed': 'var(--warning-color-pressed)',
|
||||
'warning-active': 'var(--warning-color-active)',
|
||||
error: 'var(--error-color)',
|
||||
'error-hover': 'var(--error-color-hover)',
|
||||
'error-pressed': 'var(--error-color-pressed)',
|
||||
'error-active': 'var(--error-color-active)'
|
||||
},
|
||||
backgroundColor: {
|
||||
dark: '#18181c',
|
||||
'dark-base': '#101014'
|
||||
},
|
||||
textColor: {
|
||||
'black-base': '#333639',
|
||||
'white-base': 'rgba(255, 255, 255, 0.82)'
|
||||
},
|
||||
transitionProperty: [
|
||||
'width',
|
||||
'height',
|
||||
'background',
|
||||
'background-color',
|
||||
'padding-left',
|
||||
'border-color',
|
||||
'right',
|
||||
'fill'
|
||||
]
|
||||
}
|
||||
},
|
||||
variants: {},
|
||||
plugins: []
|
||||
});
|
Loading…
Reference in New Issue
Block a user