refactor(projects): merge branch unocss to main

This commit is contained in:
Soybean 2022-04-28 00:50:22 +08:00
commit 69d51318ff
72 changed files with 3921 additions and 2117 deletions

2
.env
View File

@ -9,4 +9,4 @@ VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
# 权限路由模式: static dynamic
VITE_AUTH_ROUTE_MODE=dynamic
VITE_VISUALIZER=false
VITE_ROUTE_HOME_PATH=/dashboard/analysis

View File

@ -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;
}

View File

@ -1 +1,6 @@
VITE_VISUALIZER=true
VITE_COMPRESS=true
# gzip | brotliCompress | deflate | deflateRaw
VITE_COMPRESS_TYPE=gzip

View File

@ -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'
}
},

1
.npmrc Normal file
View File

@ -0,0 +1 @@
shamefully-hoist=true

View File

@ -31,6 +31,6 @@
"johnsoncodehk.vscode-typescript-vue-plugin",
"dariofuzinato.vue-peek",
"wscats.vue",
"voorjaar.windicss-intellisense"
"antfu.unocss"
]
}

View File

@ -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组件
- [ ] 示例页面完善

View File

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

View File

@ -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
};
}

View File

@ -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}`), '')
}
};

View 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 });
};

View File

@ -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: {

View File

@ -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
View File

@ -0,0 +1,3 @@
import unocss from 'unocss/vite';
export default unocss();

View File

@ -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' })]
})
];

View File

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

View File

@ -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;

View File

@ -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>

View File

@ -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,

View File

@ -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
View 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
View 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
View 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] : [];
}

View File

@ -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"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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';

View File

@ -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>

View File

@ -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
};
}

View File

@ -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';

View File

@ -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);
}

View File

@ -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);
}

View File

@ -1,3 +1,11 @@
/** 用户角色 */
export enum EnumUserRole {
super = '超级管理员',
admin = '管理员',
user = '普通用户'
// custom = '自定义角色'
}
/** 登录模块 */
export enum EnumLoginModule {
'pwd-login' = '账密登录',

View File

@ -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';

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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';

View File

@ -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');
}

View File

@ -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() {
//
}

View File

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

View File

@ -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')) {

View File

@ -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';

View File

@ -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
}
};

View 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;

View File

@ -37,7 +37,7 @@ const exception: AuthRoute.Route = {
meta: {
title: '异常页',
icon: 'ant-design:exception-outlined',
order: 5
order: 6
}
};

View File

@ -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;
}

View File

@ -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' });

View File

@ -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;
}
}
});

View File

@ -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;
}
}
});

View File

@ -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);
}

View File

@ -1,4 +1,5 @@
@import './transition.css';
@import './reset.css';
html, body, #app {
height: 100%;

366
src/styles/css/reset.css Normal file
View 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;
}

View File

@ -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 {

View File

@ -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,8 +93,10 @@ 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']
}
}

28
src/typings/env.d.ts vendored
View File

@ -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';
}

View File

@ -1,6 +1,6 @@
/** vue 的defineExpose导出的类型 */
declare namespace Expose {
interface BetterScroll {
instance: import('@better-scroll/core');
instance: import('@better-scroll/core').BScrollInstance;
}
}

View File

@ -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'

View File

@ -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>;
}
}
/** 主题相关类型 */

View File

@ -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;

View File

@ -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] : [];
}

View File

@ -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 -

View File

@ -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';

View 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>

View 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>

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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',

View File

@ -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" />

View File

@ -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
View 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'
]
}
});

View File

@ -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

View File

@ -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: []
});