14 Commits

Author SHA1 Message Date
xiaoma
9c512002d2 Fixes bug 动态路由配置重构 2021-08-10 17:16:58 +08:00
Ah jung
737f967aab Merge pull request #24 from CasbaL/fix/table-setting-bug
fix(Table): 基本表格设置列固定时,重复添加action列导致的样式错乱问题
2021-08-10 17:11:51 +08:00
casbal
1cdb02c9d7 fix: 基本表格设置列固定时,重复添加action列导致的样式错乱问题 2021-08-10 17:05:52 +08:00
Ah jung
bc8dd21405 Merge pull request #23 from Dishone/main
暗色模式下多页签背景问题
2021-08-10 17:00:38 +08:00
Dishone
2dba60405e 暗色模式下多页签背景问题 2021-08-10 03:21:40 +08:00
xiaoma
eba3047be2 iframe 开启滚动条 2021-08-09 16:32:33 +08:00
xiaoma
0979b5af5d 新增:Form组件支持响应式配置,路由支持外部地址(内联) 2021-08-09 16:16:16 +08:00
xiaoma
ade138997d logo美化,顶部菜单新增logo展示 2021-08-09 10:50:16 +08:00
xiaoma
d388ae5656 fix bug 2021-08-09 10:17:37 +08:00
啊俊
8f05b20ffa fix bug #22 表格列默认开启 ellipsis 属性 2021-08-08 15:17:02 +08:00
啊俊
d973b2a543 fix bug #20 2021-08-07 16:38:54 +08:00
Ah jung
1d5113a663 Merge pull request #21 from zhouyuf/master
添加锁屏时enter键解除锁屏
2021-08-07 16:14:48 +08:00
zhouyuf
f331d9c4c7 添加锁屏时enter键解除锁屏 2021-08-07 15:20:41 +08:00
Ah jung
c647e19d06 文档和预览地址变更 2021-08-07 10:00:03 +08:00
51 changed files with 438 additions and 225 deletions

View File

@@ -1,3 +1,31 @@
# CHANGELOG
## 1.5.4 (2021-08-10)
### 🐛 Bug Fixes
- `暗色模式下多页签背景问题 ` 合并 [#23](https://github.com/jekip/naive-ui-admin/pull/23) 感谢 [@Dishone](https://github.com/Dishone)
- `表格设置列重复添加action列样式错乱问题` 合并 [#24](https://github.com/jekip/naive-ui-admin/pull/24) 感谢 [@CasbaL](https://github.com/CasbaL)
- ### ✨ Features
-(破坏性更新)
- 优化 `动态路由配置` 取消`constantRouterComponents.ts`,中组件映射配置,更名为 `router-icons.ts`
- 优化 `admin_info接口结构`roles 更名为permissionsroles.roleName更名为label
- 优化 多级路由,当没有配置时,`redirect` `redirect` 默认为第一个子路由,配置则优先按配置
- 依赖升级
# 1.5.3 (2021-08-09)
### 🐛 Bug Fixes
- 修复顶部菜单,选中联动
- 修复混合菜单模式,切换其他模式菜单未重置
- 实例基础列表,和表格组件实例,开启横向滚动特性
- `naiveui` 升级成最新版
- ### ✨ Features
- table组件默认开启 `ellipsis` 特性
# 1.5.2 (2021-08-06)
### 🐛 Bug Fixes
- 修复已知bug

View File

@@ -12,13 +12,13 @@
## 在线预览
- [naive-ui-admin](https://jekip.github.io)
- [naive-ui-admin](https://naive-ui-admin.vercel.app)
账号admin密码123456随意
## 文档
[文档地址](https://jekip.github.io/docs/)
[文档地址](https://naive-ui-admin-docs.vercel.app)
## 准备

View File

@@ -4,7 +4,7 @@ const menusList = [
{
path: '/dashboard',
name: 'Dashboard',
component: 'Layout',
component: 'LAYOUT',
redirect: '/dashboard/console',
meta: {
icon: 'DashboardOutlined',
@@ -14,7 +14,7 @@ const menusList = [
{
path: 'console',
name: 'dashboard_console',
component: 'DashboardConsole',
component: '/dashboard/console/console',
meta: {
title: '主控台',
},
@@ -22,7 +22,7 @@ const menusList = [
{
path: 'monitor',
name: 'dashboard_monitor',
component: 'DashboardMonitor',
component: '/dashboard/monitor/monitor',
meta: {
title: '监控页',
},
@@ -30,7 +30,7 @@ const menusList = [
{
path: 'workplace',
name: 'dashboard_workplace',
component: 'DashboardWorkplace',
component: '/dashboard/workplace/workplace',
meta: {
hidden: true,
title: '工作台',

View File

@@ -13,25 +13,25 @@ const adminInfo = {
desc: 'manager',
password: Random.string('upper', 4, 16),
token,
roles: [
permissions: [
{
roleName: '主控台',
label: '主控台',
value: 'dashboard_console',
},
{
roleName: '监控页',
label: '监控页',
value: 'dashboard_monitor',
},
{
roleName: '工作台',
label: '工作台',
value: 'dashboard_workplace',
},
{
roleName: '基础列表',
label: '基础列表',
value: 'basic_list',
},
{
roleName: '基础列表删除',
label: '基础列表删除',
value: 'basic_list_delete',
},
],

View File

@@ -1,6 +1,6 @@
{
"name": "naive-ui-admin",
"version": "1.5.2",
"version": "1.5.4",
"author": {
"name": "Ahjung",
"email": "735878602@qq.com",
@@ -38,7 +38,7 @@
"makeit-captcha": "^1.2.5",
"mitt": "^2.1.0",
"mockjs": "^1.1.0",
"naive-ui": "^2.16.0",
"naive-ui": "^2.16.2",
"pinia": "^2.0.0-beta.3",
"qs": "^6.10.1",
"vfonts": "^0.1.0",
@@ -87,7 +87,7 @@
"stylelint-scss": "^3.19.0",
"tailwindcss": "^2.2.7",
"typescript": "^4.3.5",
"vite": "2.3.6",
"vite": "2.4.4",
"vite-plugin-compression": "^0.3.1",
"vite-plugin-html": "^2.0.7",
"vite-plugin-mock": "^2.9.3",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -102,11 +102,7 @@
type="primary"
text
icon-placement="right"
v-if="
isInline &&
getSchema.length > (getProps.gridProps?.cols || 0) &&
getProps.showAdvancedButton
"
v-if="overflow && isInline && getProps.showAdvancedButton"
@click="unfoldToggle"
>
<template #icon>
@@ -212,6 +208,7 @@
return {
...gridProps,
collapsed: isInline.value ? gridCollapsed.value : false,
responsive: 'screen',
};
});

View File

@@ -49,6 +49,7 @@
type="password"
autofocus
v-model:value="loginParams.password"
@keyup.enter="onLogin"
placeholder="请输入登录密码"
>
<template #suffix>

View File

@@ -233,9 +233,6 @@
getCacheColumns,
setCacheColumnsField,
emit,
getSize: () => {
return unref(getBindValues).size;
},
};
const getCanResize = computed(() => {
@@ -288,7 +285,6 @@
densitySelect,
updatePage,
updatePageSize,
updateCheckedRowKeys,
pagination,
tableAction,
};

View File

@@ -52,6 +52,8 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
return hasPermission(column.auth) && isIfShow(column);
})
.map((column) => {
//默认 ellipsis 为true
column.ellipsis = typeof column.ellipsis === 'undefined' ? { tooltip: true } : false;
const { edit } = column;
if (edit) {
column.render = renderEditCell(column);
@@ -92,7 +94,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
const { actionColumn } = unref(propsRef);
if (!actionColumn) return;
// @ts-ignore
columns.push({
!columns.find((col) => col.key === 'action') && columns.push({
...actionColumn,
});
}
@@ -127,7 +129,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
}
//获取
function getColumns() {
function getColumns(): BasicColumn[] {
const columns = toRaw(unref(getColumnsRef));
return columns.map((item) => {
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined };

View File

@@ -1,6 +1,6 @@
// @ts-ignore
import { NButton } from 'naive-ui';
import { RoleEnum } from '@/enums/roleEnum';
import { PermissionsEnum } from '@/enums/permissionsEnum';
// @ts-ignore
export interface ActionItem extends NButton.props {
onClick?: Fn;
@@ -11,7 +11,7 @@ export interface ActionItem extends NButton.props {
disabled?: boolean;
divider?: boolean;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
auth?: PermissionsEnum | PermissionsEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
}

View File

@@ -101,8 +101,8 @@
const state = reactive({
showModal: false,
previewUrl: '',
originalImgList: [],
imgList: [],
originalImgList: [] as string[],
imgList: [] as string[],
});
//赋值默认图片显示
@@ -176,7 +176,7 @@
const result = res[infoField];
//成功
if (code === ResultEnum.SUCCESS) {
let imgUrl = getImgUrl(result.photo);
let imgUrl: string = getImgUrl(result.photo);
state.imgList.push(imgUrl);
state.originalImgList.push(result.photo);
emit('uploadChange', state.originalImgList);
@@ -220,6 +220,7 @@
&:hover {
background: 0 0;
.upload-card-item-info::before {
opacity: 1;
}

View File

@@ -7,8 +7,8 @@ export function usePermission() {
* 检查权限
* @param accesses
*/
function _someRoles(accesses: string[]) {
return userStore.getRoles.some((item) => {
function _somePermissions(accesses: string[]) {
return userStore.getPermissions.some((item) => {
const { value }: any = item;
return accesses.includes(value);
});
@@ -20,7 +20,7 @@ export function usePermission() {
* */
function hasPermission(accesses: string[]): boolean {
if (!accesses || !accesses.length) return true;
return _someRoles(accesses);
return _somePermissions(accesses);
}
/**
@@ -28,9 +28,9 @@ export function usePermission() {
* @param accesses
*/
function hasEveryPermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles;
const permissionsList = userStore.getPermissions;
if (Array.isArray(accesses)) {
return accesses.every((access) => !!rolesList[access]);
return accesses.every((access) => !!permissionsList[access]);
}
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
}
@@ -41,9 +41,9 @@ export function usePermission() {
* @param accessMap
*/
function hasSomePermission(accesses: string[]): boolean {
const rolesList = userStore.getRoles;
const permissionsList = userStore.getPermissions;
if (Array.isArray(accesses)) {
return accesses.some((access) => !!rolesList[access]);
return accesses.some((access) => !!permissionsList[access]);
}
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
}

View File

@@ -278,11 +278,7 @@
function togNavMode(mode) {
settingStore.navMode = mode;
// if (mode === 'header-dark') {
// settingStore.setNavTheme('dark');
// } else {
// settingStore.setNavTheme('light');
// }
settingStore.menuSetting.mixMenu = false;
}
return {

View File

@@ -5,6 +5,10 @@
class="layout-header-left"
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
>
<div class="logo">
<img src="~@/assets/images/logo.png" alt="" />
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
</div>
<AsideMenu
v-model:collapsed="collapsed"
v-model:location="getMenuLocation"
@@ -353,6 +357,27 @@
display: flex;
align-items: center;
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 64px;
line-height: 64px;
overflow: hidden;
white-space: nowrap;
padding-left: 10px;
img {
width: auto;
height: 32px;
margin-right: 10px;
}
.title {
margin-bottom: 0;
}
}
::v-deep(.ant-breadcrumb span:last-child .link-text) {
color: #515a6e;
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="logo">
<img src="~@/assets/images/logo.png" alt="" />
<h2 v-show="!collapsed" class="title">&nbsp;NaiveUiAdmin</h2>
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
</div>
</template>
@@ -29,10 +29,10 @@
img {
width: auto;
height: 32px;
margin-right: 10px;
}
.title {
color: white;
margin-bottom: 0;
}
}

View File

@@ -20,6 +20,7 @@
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { generatorMenu, generatorMenuMix } from '@/utils';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
export default defineComponent({
name: 'Menu',
@@ -40,7 +41,8 @@
default: 'left',
},
},
setup(props) {
emits: ['update:collapsed'],
setup(props, { emit }) {
// 当前路由
const currentRoute = useRoute();
const router = useRouter();
@@ -50,6 +52,10 @@
const selectedKeys = ref<string>(currentRoute.name as string);
const headerMenuSelectKey = ref<string>('');
const { getNavMode } = useProjectSetting();
const navMode = getNavMode;
// 获取当前打开的子菜单
const matched = currentRoute.matched;
@@ -64,7 +70,10 @@
});
const getSelectedKeys = computed(() => {
return props.location === 'left' ? unref(selectedKeys) : unref(headerMenuSelectKey);
let location = props.location;
return location === 'left' || (location === 'header' && unref(navMode) === 'horizontal')
? unref(selectedKeys)
: unref(headerMenuSelectKey);
});
// 监听分割菜单
@@ -72,6 +81,9 @@
() => settingStore.menuSetting.mixMenu,
() => {
updateMenu();
if (props.collapsed) {
emit('update:collapsed', !props.collapsed);
}
}
);

View File

@@ -5,6 +5,7 @@
'tabs-view-fix': multiTabsSetting.fixed,
'tabs-view-fixed-header': isMultiHeaderFixed,
'tabs-view-default-background': getDarkTheme === false,
'tabs-view-dark-background': getDarkTheme === true,
}"
:style="getChangeStyle"
>
@@ -59,7 +60,7 @@
placement="bottom-end"
:options="TabsMenuOptions"
>
<div class="tabs-close-btn" @click.prevent>
<div class="tabs-close-btn">
<n-icon size="16" color="#515a6e">
<DownOutlined />
</n-icon>
@@ -371,6 +372,7 @@
break;
}
updateNavScroll();
state.showDropdown = false;
};
function getCurrentScrollOffset() {
@@ -641,7 +643,11 @@
.tabs-view-default-background {
background: #f5f7f9;
}
.tabs-view-dark-background {
background: #101014;
}
.tabs-view-fix {
position: fixed;
z-index: 5;

View File

@@ -13,7 +13,7 @@ export const ErrorPageRoute: AppRouteRecordRaw = {
children: [
{
path: '/:path(.*)*',
name: 'ErrorPage',
name: 'ErrorPageSon',
component: ErrorPage,
meta: {
title: 'ErrorPage',

View File

@@ -1,16 +0,0 @@
import { renderIcon } from '@/utils/index';
import { DashboardOutlined } from '@vicons/antd';
// import { RouterTransition } from '@/components/transition'
//前端路由映射表
export const constantRouterComponents = {
Layout: () => import('@/layout/index.vue'), //布局
DashboardConsole: () => import('@/views/dashboard/console/console.vue'), // 主控台
DashboardMonitor: () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
DashboardWorkplace: () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
};
//前端路由图标映射表
export const constantRouterIcon = {
DashboardOutlined: renderIcon(DashboardOutlined),
};

View File

@@ -1,12 +1,19 @@
import { adminMenus } from '@/api/system/menu';
import { constantRouterComponents, constantRouterIcon } from './constantRouterComponents';
import { constantRouterIcon } from './router-icons';
import router from '@/router/index';
import { constantRouter } from '@/router/index';
import { RouteRecordRaw } from 'vue-router';
import { Layout, ParentLayout } from '@/router/constant';
import type { AppRouteRecordRaw } from '@/router/types';
const Iframe = () => import('@/views/iframe/index.vue');
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
LayoutMap.set('LAYOUT', Layout);
LayoutMap.set('IFRAME', Iframe);
/**
* 格式化 后端 结构信息并递归生成层级路由表
*
* @param routerMap
* @param parent
* @returns {*}
@@ -19,21 +26,24 @@ export const routerGenerator = (routerMap, parent?): any[] => {
// 路由名称,建议唯一
name: item.name || '',
// 该路由对应页面的 组件
component: constantRouterComponents[item.component],
component: item.component,
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: {
...item.meta,
label: item.meta.title,
icon: constantRouterIcon[item.meta.icon] || null,
permission: item.meta.permission || null,
permissions: item.meta.permissions || null,
},
};
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
currentRouter.path = currentRouter.path.replace('//', '/');
// 重定向
item.redirect && (currentRouter.redirect = item.redirect);
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
//如果未定义 redirect 默认第一个子路由为 redirect
!item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`);
// Recursion
currentRouter.children = routerGenerator(item.children, currentRouter);
}
@@ -43,7 +53,6 @@ export const routerGenerator = (routerMap, parent?): any[] => {
/**
* 动态生成菜单
* @param token
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
@@ -51,7 +60,8 @@ export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
adminMenus()
.then((result) => {
const routeList = routerGenerator(result);
const asyncRoutesList = [...constantRouter, ...routeList];
asyncImportRoute(routeList);
const asyncRoutesList = [...routeList, ...constantRouter];
asyncRoutesList.forEach((item) => {
router.addRoute(item);
});
@@ -62,3 +72,56 @@ export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
});
});
};
/**
* 查找views中对应的组件文件
* */
let viewsModules: Record<string, () => Promise<Recordable>>;
export const asyncImportRoute = (routes: AppRouteRecordRaw[] | undefined): void => {
viewsModules = viewsModules || import.meta.glob('../views/**/*.{vue,tsx}');
if (!routes) return;
routes.forEach((item) => {
if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME';
}
const { component, name } = item;
const { children } = item;
if (component) {
const layoutFound = LayoutMap.get(component as string);
if (layoutFound) {
item.component = layoutFound;
} else {
item.component = dynamicImport(viewsModules, component as string);
}
} else if (name) {
item.component = ParentLayout;
}
children && asyncImportRoute(children);
});
};
/**
* 动态导入
* */
export const dynamicImport = (
viewsModules: Record<string, () => Promise<Recordable>>,
component: string
) => {
const keys = Object.keys(viewsModules);
const matchKeys = keys.filter((key) => {
let k = key.replace('../views', '');
const lastIndex = k.lastIndexOf('.');
k = k.substring(0, lastIndex);
return k === component;
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return viewsModules[matchKey];
}
if (matchKeys?.length > 1) {
console.warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure'
);
return;
}
};

View File

@@ -1,6 +1,6 @@
import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { ErrorPageRoute, RedirectRoute } from '@/router/base';
import { RedirectRoute } from '@/router/base';
import { PageEnum } from '@/enums/pageEnum';
import { createRouterGuards } from './router-guards';
@@ -40,7 +40,7 @@ export const LoginRoute: RouteRecordRaw = {
};
//需要验证权限
export const asyncRoutes = [ErrorPageRoute, ...routeModuleList];
export const asyncRoutes = [...routeModuleList];
//普通路由 无需验证权限
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];

View File

@@ -9,7 +9,7 @@ const routes: Array<RouteRecordRaw> = [
name: 'about',
component: Layout,
meta: {
sort: 9,
sort: 10,
isRoot: true,
activeMenu: 'about_index',
},

View File

@@ -24,7 +24,7 @@ const routes: Array<RouteRecordRaw> = [
meta: {
title: 'Dashboard',
icon: renderIcon(DashboardOutlined),
permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
permissions: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
sort: 0,
},
children: [
@@ -33,7 +33,7 @@ const routes: Array<RouteRecordRaw> = [
name: `${routeName}_console`,
meta: {
title: '主控台',
permission: ['dashboard_console'],
permissions: ['dashboard_console'],
},
component: () => import('@/views/dashboard/console/console.vue'),
},
@@ -42,7 +42,7 @@ const routes: Array<RouteRecordRaw> = [
// name: `${ routeName }_monitor`,
// meta: {
// title: '监控页',
// permission: ['dashboard_monitor']
// permissions: ['dashboard_monitor']
// },
// component: () => import('@/views/dashboard/monitor/monitor.vue')
// },
@@ -52,7 +52,7 @@ const routes: Array<RouteRecordRaw> = [
meta: {
title: '工作台',
keepAlive: true,
permission: ['dashboard_workplace'],
permissions: ['dashboard_workplace'],
},
component: () => import('@/views/dashboard/workplace/workplace.vue'),
},

View File

@@ -6,12 +6,12 @@ import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/external',
name: 'https://jekip.github.io/docs/',
name: 'https://naive-ui-admin-docs.vercel.app',
component: Layout,
meta: {
title: '项目文档',
icon: renderIcon(DocumentTextOutline),
sort: 8,
sort: 9,
},
},
];

View File

@@ -0,0 +1,42 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DesktopOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
const IFrame = () => import('@/views/iframe/index.vue');
const routes: Array<RouteRecordRaw> = [
{
path: '/frame',
name: 'Frame',
redirect: '/frame/docs',
component: Layout,
meta: {
title: '外部页面',
sort: 8,
icon: renderIcon(DesktopOutline),
},
children: [
{
path: 'docs',
name: 'frame-docs',
meta: {
title: '项目文档(内嵌)',
frameSrc: 'https://naive-ui-admin-docs.vercel.app',
},
component: IFrame,
},
{
path: 'naive',
name: 'frame-naive',
meta: {
title: 'NaiveUi(内嵌)',
frameSrc: 'https://www.naiveui.com',
},
component: IFrame,
},
],
},
];
export default routes;

View File

@@ -1,9 +1,11 @@
import type { RouteRecordRaw } from 'vue-router';
import { isNavigationFailure, Router } from 'vue-router';
import { useUserStoreWidthOut } from '@/store/modules/user';
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
import { ACCESS_TOKEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage';
import { PageEnum } from '@/enums/pageEnum';
import { ErrorPageRoute } from '@/router/base';
const LOGIN_PATH = PageEnum.BASE_LOGIN;
@@ -29,7 +31,7 @@ export function createRouterGuards(router: Router) {
const token = storage.get(ACCESS_TOKEN);
if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true
// You can access without permissions. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) {
next();
return;
@@ -60,9 +62,15 @@ export function createRouterGuards(router: Router) {
// 动态添加可访问路由表
routes.forEach((item) => {
router.addRoute(item);
router.addRoute(item as unknown as RouteRecordRaw);
});
//添加404
const isErrorPage = router.getRoutes().findIndex((item) => item.name === ErrorPageRoute.name);
if (isErrorPage === -1) {
router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw);
}
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };

View File

@@ -0,0 +1,7 @@
import { renderIcon } from '@/utils/index';
import { DashboardOutlined } from '@vicons/antd';
//前端路由图标映射表
export const constantRouterIcon = {
DashboardOutlined: renderIcon(DashboardOutlined),
};

View File

@@ -1,6 +1,5 @@
import type { RouteRecordRaw, RouteMeta } from 'vue-router';
import { defineComponent } from 'vue';
import { RoleEnum } from '@/enums/roleEnum';
export type Component<T extends any = any> =
| ReturnType<typeof defineComponent>
@@ -23,7 +22,7 @@ export interface Meta {
title: string;
// 是否忽略权限
ignoreAuth?: boolean;
roles?: RoleEnum[];
permissions?: string[];
// 是否不缓存
noKeepAlive?: boolean;
// 是否固定在tab上

View File

@@ -41,7 +41,7 @@ const setting = {
//显示图标
showIcon: false,
},
//菜单权限模式 ROLE 前端固定角色 BACK 动态获取
permissionMode: 'ROLE',
//菜单权限模式 FIXED 前端固定路由 BACK 动态获取
permissionMode: 'FIXED',
};
export default setting;

View File

@@ -88,12 +88,12 @@ export const useAsyncRouteStore = defineStore({
},
async generateRoutes(data) {
let accessedRouters;
const roleList = data.roles || [];
const permissionsList = data.permissions || [];
const routeFilter = (route) => {
const { meta } = route;
const { permission } = meta || {};
if (!permission) return true;
return roleList.some((role) => permission.includes(role.value));
const { permissions } = meta || {};
if (!permissions) return true;
return permissionsList.some((item) => permissions.includes(item.value));
};
const { getPermissionMode } = useProjectSetting();
const permissionMode = unref(getPermissionMode);

View File

@@ -13,7 +13,7 @@ export interface IUserState {
username: string;
welcome: string;
avatar: string;
roles: any[];
permissions: any[];
info: any;
}
@@ -24,7 +24,7 @@ export const useUserStore = defineStore({
username: '',
welcome: '',
avatar: '',
roles: [],
permissions: [],
info: Storage.get(CURRENT_USER, {}),
}),
getters: {
@@ -37,8 +37,8 @@ export const useUserStore = defineStore({
getNickname(): string {
return this.username;
},
getRoles(): [any][] {
return this.roles;
getPermissions(): [any][] {
return this.permissions;
},
getUserInfo(): object {
return this.info;
@@ -51,8 +51,8 @@ export const useUserStore = defineStore({
setAvatar(avatar: string) {
this.avatar = avatar;
},
setRoles(roles) {
this.roles = roles;
setPermissions(permissions) {
this.permissions = permissions;
},
setUserInfo(info) {
this.info = info;
@@ -83,12 +83,12 @@ export const useUserStore = defineStore({
getUserInfo()
.then((res) => {
const result = res;
if (result.roles && result.roles.length) {
const roles = result.roles;
that.setRoles(roles);
if (result.permissions && result.permissions.length) {
const permissionsList = result.permissions;
that.setPermissions(permissionsList);
that.setUserInfo(result);
} else {
reject(new Error('getInfo: roles must be a non-null array !'));
reject(new Error('getInfo: permissionsList must be a non-null array !'));
}
that.setAvatar(result.avatar);
resolve(res);
@@ -101,7 +101,7 @@ export const useUserStore = defineStore({
// 登出
async logout() {
this.setRoles([]);
this.setPermissions([]);
this.setUserInfo('');
storage.remove(ACCESS_TOKEN);
storage.remove(CURRENT_USER);

View File

@@ -81,7 +81,7 @@ export function generatorMenuMix(routerMap: Array<any>, routerName: string, loca
* 递归组装子菜单
* */
export function getChildrenRouter(routerMap: Array<any>) {
return routerMap.map((item) => {
return filterRouter(routerMap).map((item) => {
const isRoot = isRootRouter(item);
const info = isRoot ? item.children[0] : item;
const currentMenu = {

View File

@@ -22,12 +22,16 @@
</n-descriptions-item>
<n-descriptions-item label="文档地址">
<div class="flex items-center">
<a href="https://jekip.github.io/docs/" class="py-2" target="_blank">查看文档地址</a>
<a href="https://naive-ui-admin-docs.vercel.app" class="py-2" target="_blank"
>查看文档地址</a
>
</div>
</n-descriptions-item>
<n-descriptions-item label="预览地址">
<div class="flex items-center">
<a href="https://jekip.github.io/" class="py-2" target="_blank">查看预览地址</a>
<a href="https://naive-ui-admin.vercel.app" class="py-2" target="_blank"
>查看预览地址</a
>
</div>
</n-descriptions-item>
<n-descriptions-item label="Github">

View File

@@ -5,10 +5,12 @@ export const columns = [
{
title: 'id',
key: 'id',
width: 100,
},
{
title: '编码',
key: 'no',
width: 100,
},
{
title: '名称',
@@ -22,6 +24,7 @@ export const columns = [
{
title: '头像',
key: 'avatar',
width: 100,
render(row) {
return h(NAvatar, {
size: 48,
@@ -47,29 +50,33 @@ export const columns = [
},
edit: true,
width: 200,
ellipsis: false,
},
{
title: '开始日期',
key: 'beginTime',
edit: true,
width: 250,
width: 160,
editComponent: 'NDatePicker',
editComponentProps: {
type: 'datetime',
format: 'yyyy-MM-dd HH:mm:ss',
},
ellipsis: false,
},
{
title: '结束日期',
key: 'endTime',
width: 200,
width: 160,
},
{
title: '创建时间',
key: 'date',
width: 160,
},
{
title: '停留时间',
key: 'time',
width: 80,
},
];

View File

@@ -8,6 +8,7 @@
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
:scroll-x="1360"
@update:checked-row-keys="onCheckedRow"
>
<template #toolbar>

View File

@@ -5,19 +5,22 @@ export const columns = [
{
title: 'id',
key: 'id',
width: 100,
},
{
title: '编码',
key: 'no',
width: 100,
},
{
title: '名称',
key: 'name',
width: 200,
width: 100,
},
{
title: '头像',
key: 'avatar',
width: 100,
render(row) {
return h(NAvatar, {
size: 48,
@@ -28,21 +31,22 @@ export const columns = [
{
title: '地址',
key: 'address',
width: 200,
width: 150,
},
{
title: '开始日期',
key: 'beginTime',
width: 200,
width: 160,
},
{
title: '结束日期',
key: 'endTime',
width: 200,
width: 160,
},
{
title: '状态',
key: 'status',
width: 100,
render(row) {
return h(
NTag,
@@ -58,9 +62,11 @@ export const columns = [
{
title: '创建时间',
key: 'date',
width: 160,
},
{
title: '停留时间',
key: 'time',
width: 80,
},
];

View File

@@ -7,10 +7,10 @@
:request="loadDataTable"
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
@edit-end="editEnd"
@edit-change="onEditChange"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1360"
>
<template #toolbar>
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
@@ -35,19 +35,6 @@
pageSize: 5,
name: 'xiaoMa',
},
actionColumn: {
width: 150,
title: '操作',
key: 'action',
fixed: 'right',
align: 'center',
render(record) {
return h(TableAction, {
style: 'button',
actions: createActions(record),
});
},
},
});
function handleEdit(record) {

View File

@@ -11,6 +11,7 @@
@edit-end="editEnd"
@edit-change="onEditChange"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1510"
>
<template #toolbar>
<n-button type="primary" @click="reloadTable">刷新数据</n-button>

View File

@@ -5,10 +5,12 @@ export const columns = [
{
title: 'id',
key: 'id',
width: 100,
},
{
title: '编码',
key: 'no',
width: 100,
},
{
title: '名称',
@@ -23,6 +25,7 @@ export const columns = [
{
title: '头像',
key: 'avatar',
width: 100,
render(row) {
return h(NAvatar, {
size: 48,
@@ -49,23 +52,25 @@ export const columns = [
},
edit: true,
width: 200,
ellipsis: false,
},
{
title: '开始日期',
key: 'beginTime',
editRow: true,
edit: true,
width: 250,
width: 160,
editComponent: 'NDatePicker',
editComponentProps: {
type: 'datetime',
format: 'yyyy-MM-dd HH:mm:ss',
},
ellipsis: false,
},
{
title: '结束日期',
key: 'endTime',
width: 200,
width: 160,
},
{
title: '状态',
@@ -81,9 +86,11 @@ export const columns = [
{
title: '创建时间',
key: 'date',
width: 160,
},
{
title: '停留时间',
key: 'time',
width: 80,
},
];

View File

@@ -1,7 +1,7 @@
<template>
<div class="console">
<!--数据卡片-->
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8" :cols="4">
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
<n-grid-item>
<NCard
title="访问量"
@@ -19,14 +19,14 @@
<div class="text-sn">
日同比
<CountTo :startVal="1" suffix="%" :endVal="visits.rise" />
<n-icon size="12" style="color: #00ff6f">
<n-icon size="12" color="#00ff6f">
<component is="CaretUpOutlined" />
</n-icon>
</div>
<div class="text-sn">
周同比
<CountTo :startVal="1" suffix="%" :endVal="visits.decline" />
<n-icon size="12" style="color: #ffde66">
<n-icon size="12" color="#ffde66">
<component is="CaretDownOutlined" />
</n-icon>
</div>
@@ -91,14 +91,14 @@
<div class="text-sn">
日同比
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
<n-icon size="12" style="color: #00ff6f">
<n-icon size="12" color="#00ff6f">
<component is="CaretUpOutlined" />
</n-icon>
</div>
<div class="text-sn">
周同比
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
<n-icon size="12" style="color: #ffde66">
<n-icon size="12" color="#ffde66">
<component is="CaretDownOutlined" />
</n-icon>
</div>
@@ -130,14 +130,14 @@
<div class="text-sn">
月同比
<CountTo :startVal="1" suffix="%" :endVal="volume.rise" />
<n-icon size="12" style="color: #00ff6f">
<n-icon size="12" color="#00ff6f">
<component is="CaretUpOutlined" />
</n-icon>
</div>
<div class="text-sn">
月同比
<CountTo :startVal="1" suffix="%" :endVal="volume.decline" />
<n-icon size="12" style="color: #ffde66">
<n-icon size="12" color="#ffde66">
<component is="CaretDownOutlined" />
</n-icon>
</div>
@@ -156,14 +156,14 @@
<!--导航卡片-->
<div class="mt-4">
<n-grid cols="1 s:2 m:3 l:8 xl:8 2xl:8" responsive="screen" :x-gap="16" :y-gap="8" :cols="8">
<n-grid cols="1 s:2 m:3 l:8 xl:8 2xl:8" responsive="screen" :x-gap="16" :y-gap="8">
<n-grid-item v-for="(item, index) in iconList" :key="index">
<NCard content-style="padding-top: 0;" size="small" :bordered="false">
<template #footer>
<div class="cursor-pointer">
<p class="flex justify-center">
<span>
<n-icon :size="item.size" class="flex-1" :style="{ color: `${item.color}` }">
<n-icon :size="item.size" class="flex-1" :color="item.color">
<component :is="item.icon" v-on="item.eventObject || {}" />
</n-icon>
</span>

View File

@@ -33,14 +33,7 @@
</n-grid>
</n-card>
</div>
<n-grid
class="mt-4"
cols="2 s:1 m:1 l:2 xl:2 2xl:2"
responsive="screen"
:x-gap="12"
:y-gap="9"
:cols="2"
>
<n-grid class="mt-4" cols="2 s:1 m:1 l:2 xl:2 2xl:2" responsive="screen" :x-gap="12" :y-gap="9">
<n-gi>
<n-card
:segmented="{ content: 'hard' }"
@@ -75,7 +68,7 @@
>
<div class="flex">
<span>
<n-icon size="30" style="color: #42b983">
<n-icon size="30" color="#42b983">
<LogoVue />
</n-icon>
</span>
@@ -91,7 +84,7 @@
>
<div class="flex">
<span>
<n-icon size="30" style="color: #e44c27">
<n-icon size="30" color="#e44c27">
<Html5Outlined />
</n-icon>
</span>
@@ -107,7 +100,7 @@
>
<div class="flex">
<span>
<n-icon size="30" style="color: #dd0031">
<n-icon size="30" color="#dd0031">
<LogoAngular />
</n-icon>
</span>
@@ -123,7 +116,7 @@
>
<div class="flex">
<span>
<n-icon size="30" style="color: #61dafb">
<n-icon size="30" color="#61dafb">
<LogoReact />
</n-icon>
</span>
@@ -238,7 +231,7 @@
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" style="color: #68c755">
<n-icon size="30" color="#68c755">
<DashboardOutlined />
</n-icon>
</span>
@@ -248,7 +241,7 @@
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" style="color: #fab251">
<n-icon size="30" color="#fab251">
<ProfileOutlined />
</n-icon>
</span>
@@ -258,7 +251,7 @@
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" style="color: #1890ff">
<n-icon size="30" color="#1890ff">
<FileProtectOutlined />
</n-icon>
</span>
@@ -268,7 +261,7 @@
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" style="color: #f06b96">
<n-icon size="30" color="#f06b96">
<ApartmentOutlined />
</n-icon>
</span>
@@ -278,7 +271,7 @@
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" style="color: #7238d1">
<n-icon size="30" color="#7238d1">
<SettingOutlined />
</n-icon>
</span>

View File

@@ -29,9 +29,9 @@
<style lang="less" scoped>
.page-container {
width: 100%;
background-color: white;
border-radius: 4px;
padding: 50px 0;
height: 100vh;
.text-center {
h1 {

View File

@@ -29,9 +29,9 @@
<style lang="less" scoped>
.page-container {
width: 100%;
background-color: white;
border-radius: 4px;
padding: 50px 0;
height: 100vh;
.text-center {
h1 {

View File

@@ -29,9 +29,9 @@
<style lang="less" scoped>
.page-container {
width: 100%;
background-color: white;
border-radius: 4px;
padding: 50px 0;
height: 100vh;
.text-center {
h1 {

1
src/views/frame/docs.vue Normal file
View File

@@ -0,0 +1 @@
<template> 项目文档 </template>

View File

@@ -0,0 +1,72 @@
<template>
<n-spin :show="loading">
<div class="frame">
<iframe :src="frameSrc" class="frame-iframe" ref="frameRef"></iframe>
</div>
</n-spin>
</template>
<script lang="ts">
import { defineComponent, ref, unref, onMounted, nextTick } from 'vue';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'IFrame',
setup() {
const currentRoute = useRoute();
const loading = ref(false);
const frameRef = ref<HTMLFrameElement | null>(null);
const frameSrc = ref<string>('');
if (unref(currentRoute.meta)?.frameSrc) {
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
}
function hideLoading() {
loading.value = false;
}
function init() {
nextTick(() => {
const iframe = unref(frameRef);
if (!iframe) return;
const _frame = iframe as any;
if (_frame.attachEvent) {
_frame.attachEvent('onload', () => {
hideLoading();
});
} else {
iframe.onload = () => {
hideLoading();
};
}
});
}
onMounted(() => {
loading.value = true;
init();
});
return {
loading,
frameRef,
frameSrc,
};
},
});
</script>
<style lang="less" scoped>
.frame {
width: 100%;
height: 100vh;
&-iframe {
width: 100%;
height: 100%;
overflow: hidden;
border: 0;
box-sizing: border-box;
}
}
</style>

View File

@@ -5,14 +5,17 @@ export const columns = [
{
title: 'id',
key: 'id',
width: 100,
},
{
title: '名称',
key: 'name',
width: 100,
},
{
title: '头像',
key: 'avatar',
width: 100,
render(row) {
return h(NAvatar, {
size: 48,
@@ -27,48 +30,21 @@ export const columns = [
ifShow: (_column) => {
return true; // 根据业务控制是否显示
},
width: 150,
},
{
title: '开始日期',
key: 'beginTime',
width: 160,
},
{
title: '结束日期',
key: 'endTime',
width: 160,
},
{
title: '创建时间',
key: 'date',
width: 100,
},
// {
// title: '操作',
// key: 'actions',
// width: 150,
// //简单写一下例子,不建议这么写,过段时间,这里封二次封装
// render() {
// return [
// h(
// NButton,
// {
// size: 'small',
// type: 'error',
// style: 'margin-right:10px',
// onClick: () => {
// }
// },
// { default: () => '删除' }
// ),
// h(
// NButton,
// {
// size: 'small',
// onClick: () => {
//
// }
// },
// { default: () => '编辑' }
// )
// ]
// }
// }
];

View File

@@ -13,6 +13,7 @@
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
>
<template #tableTitle>
<n-button type="primary" @click="addTable">
@@ -230,7 +231,7 @@
name: 'xiaoMa',
},
actionColumn: {
width: 250,
width: 220,
title: '操作',
key: 'action',
fixed: 'right',
@@ -284,7 +285,7 @@
});
const [register, {}] = useForm({
gridProps: { cols: '4' },
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});

View File

@@ -16,18 +16,9 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"experimentalDecorators": true,
"lib": [
"dom",
"esnext"
],
"types": [
"vite/client",
"jest"
],
"typeRoots": [
"./node_modules/@types/",
"./types"
],
"lib": ["dom", "esnext"],
"types": ["vite/client", "jest"],
"typeRoots": ["./node_modules/@types/", "./types"],
"noImplicitAny": false,
"skipLibCheck": true,
"paths": {
@@ -40,7 +31,6 @@
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"types/*.ts",
"types/**/*.d.ts",
"types/**/*.ts",
"build/**/*.ts",
@@ -48,9 +38,5 @@
"mock/**/*.ts",
"vite.config.ts"
],
"exclude": [
"node_modules",
"dist",
"**/*.js"
]
"exclude": ["node_modules", "dist", "**/*.js"]
}

View File

@@ -2696,11 +2696,6 @@ esbuild@0.11.3:
resolved "https://registry.nlark.com/esbuild/download/esbuild-0.11.3.tgz#b57165b907be4ffba651f6450538ce8d8c1d5eb0"
integrity sha1-tXFluQe+T/umUfZFBTjOjYwdXrA=
esbuild@^0.12.5:
version "0.12.15"
resolved "https://registry.nlark.com/esbuild/download/esbuild-0.12.15.tgz?cache=0&sync_timestamp=1625545660518&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fesbuild%2Fdownload%2Fesbuild-0.12.15.tgz#9d99cf39aeb2188265c5983e983e236829f08af0"
integrity sha1-nZnPOa6yGIJlxZg+mD4jaCnwivA=
esbuild@^0.12.6, esbuild@^0.12.8:
version "0.12.14"
resolved "https://registry.nlark.com/esbuild/download/esbuild-0.12.14.tgz?cache=0&sync_timestamp=1625183314696&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fesbuild%2Fdownload%2Fesbuild-0.12.14.tgz#43157dbd0b36d939247d4eb4909a4886ac40f82e"
@@ -4999,10 +4994,10 @@ mute-stream@0.0.7:
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
naive-ui@^2.16.0:
version "2.16.0"
resolved "https://registry.nlark.com/naive-ui/download/naive-ui-2.16.0.tgz#42d8b6120ab061e46a316ac074c5b788139cd744"
integrity sha1-Qti2EgqwYeRqMWrAdMW3iBOc10Q=
naive-ui@^2.16.2:
version "2.16.2"
resolved "https://registry.nlark.com/naive-ui/download/naive-ui-2.16.2.tgz#f7d4b84f15529bc8f367644edcdb2c14c7912372"
integrity sha1-99S4TxVSm8jzZ2RO3NssFMeRI3I=
dependencies:
"@css-render/plugin-bem" "^0.15.4"
"@css-render/vue3-ssr" "^0.15.4"
@@ -5569,7 +5564,7 @@ postcss@^8.1.10:
nanoid "^3.1.23"
source-map-js "^0.6.2"
postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.10, postcss@^8.3.5:
postcss@^8.1.6, postcss@^8.2.1, postcss@^8.3.5:
version "8.3.5"
resolved "https://registry.nlark.com/postcss/download/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709"
integrity sha1-mCIWsRNBK8IKhiiekeuZSVKltwk=
@@ -5578,6 +5573,15 @@ postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.10, postcss@^8.3.5:
nanoid "^3.1.23"
source-map-js "^0.6.2"
postcss@^8.3.6:
version "8.3.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea"
integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"
source-map-js "^0.6.2"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -6858,17 +6862,17 @@ vite-plugin-style-import@^1.0.1:
es-module-lexer "^0.6.0"
magic-string "^0.25.7"
vite@2.3.6:
version "2.3.6"
resolved "https://registry.nlark.com/vite/download/vite-2.3.6.tgz#1f7cfde88a51a802d69000c7bac85d481c2e871c"
integrity sha1-H3z96IpRqALWkADHushdSBwuhxw=
vite@2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.4.4.tgz#8c402a07ad45f168f6eb5428bead38f3e4363e47"
integrity sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==
dependencies:
esbuild "^0.12.5"
postcss "^8.2.10"
resolve "^1.19.0"
esbuild "^0.12.8"
postcss "^8.3.6"
resolve "^1.20.0"
rollup "^2.38.5"
optionalDependencies:
fsevents "~2.3.1"
fsevents "~2.3.2"
vooks@^0.2.4, vooks@^0.2.6:
version "0.2.6"