chore: update deps

This commit is contained in:
vben
2023-11-08 23:50:11 +08:00
parent 35305b0a9f
commit 3db2285317
169 changed files with 4571 additions and 5952 deletions

4
.gitignore vendored
View File

@@ -19,6 +19,10 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
vite.config.mts.*
vite.config.mjs.*
vite.config.js.*
vite.config.ts.*
# Editor directories and files
.idea

88
.vscode/settings.json vendored
View File

@@ -4,7 +4,6 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"npm.packageManager": "pnpm",
"eslint.packageManager": "pnpm",
"stylelint.packageManager": "pnpm",
"search.exclude": {
"**/node_modules": true,
@@ -19,20 +18,15 @@
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"*.xml": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
"CHANGELOG.md": true
},
"files.exclude": {
"**/.cache": true,
"**/.cache": false,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
@@ -57,81 +51,12 @@
},
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
}
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"vben",
"browserslist",
"tailwindcss",
"esnext",
"antv",
"sider",
"pinia",
"sider",
"nprogress",
"INTLIFY",
"stylelint",
"esno",
"vitejs",
"sortablejs",
"mockjs",
"codemirror",
"iconify",
"commitlint",
"echarts",
"cropperjs",
"logicflow",
"vueuse",
"zxcvbn",
"lintstagedrc",
"brotli",
"tailwindcss",
"sider",
"pnpm",
"antd",
"unref"
],
"cSpell.words": ["vben", "iconify", "pinia", "nprogress"],
// 控制相关文件嵌套展示
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
@@ -144,5 +69,6 @@
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc,.nvmrc",
".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*,.lintstagedrc"
},
"terminal.integrated.scrollback": 10000
"terminal.integrated.scrollback": 10000,
"search.followSymlinks": false
}

View File

@@ -132,7 +132,6 @@ If these plugins are helpful to you, you can give a star support
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - Used for local and development environment data mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - Used for html template conversion and compression
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - Used to pack input .gz|.brotil files
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - Used to quickly generate svg sprite
## Browser support

View File

@@ -142,7 +142,6 @@ pnpm build
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图
## 后台整合示例

View File

View File

@@ -11,7 +11,7 @@ VITE_BUILD_COMPRESS = 'none'
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
VITE_GLOB_API_URL=/vben-api
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

View File

@@ -2,6 +2,6 @@ VITE_USE_MOCK = true
VITE_PUBLIC_PATH = /
VITE_GLOB_API_URL=/req-api
VITE_GLOB_API_URL=/vben-api
VITE_GLOB_API_URL_PREFIX=

View File

@@ -11,7 +11,7 @@ VITE_BUILD_COMPRESS = 'none'
# Basic interface address SPA
VITE_GLOB_API_URL=/req-api
VITE_GLOB_API_URL=/vben-api
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

View File

@@ -27,7 +27,7 @@ const fakeUserList = [
avatar: '',
desc: 'tester',
accessToken: 'fakeTestToken',
homePath: '/dashboard/workbench',
homePath: '/workbench',
roles: [
{
roleName: 'Tester',
@@ -40,7 +40,7 @@ const fakeUserList = [
export default (_config: MockConfig): MockMethod[] => {
return [
{
url: '/req-api/login',
url: '/vben-api/login',
timeout: 200,
method: 'post',
response: ({ body }) => {
@@ -63,7 +63,7 @@ export default (_config: MockConfig): MockMethod[] => {
},
},
{
url: '/req-api/getUserInfo',
url: '/vben-api/getUserInfo',
method: 'get',
response: (request) => {
const token = getRequestToken(request);
@@ -77,7 +77,7 @@ export default (_config: MockConfig): MockMethod[] => {
},
},
{
url: '/req-api/logout',
url: '/vben-api/logout',
timeout: 200,
method: 'get',
response: (request) => {

View File

@@ -24,23 +24,24 @@
"type:check": "vue-tsc --noEmit --skipLibCheck"
},
"dependencies": {
"@vben/ant-ui": "workspace:*",
"@vben/design": "workspace:*",
"@vben/antv-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layout": "workspace:*",
"@vben/preference": "workspace:*",
"@vben/share-ui": "workspace:*",
"@vben/shared": "workspace:*",
"@vben/store": "workspace:*",
"@vben/types": "workspace:*",
"ant-design-vue": "^4.0.0-beta.4",
"axios": "^1.4.0",
"dayjs": "^1.11.7",
"pinia": "2.0.35",
"vue": "^3.3.0-beta.5",
"vue-router": "^4.1.6"
"@vben/styles": "workspace:*",
"@vben/toolkit": "workspace:*",
"@vben/typings": "workspace:*",
"ant-design-vue": "^4.0.6",
"axios": "^1.6.0",
"dayjs": "^1.11.10",
"pinia": "2.1.7",
"vue": "^3.3.8",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@iconify/iconify": "^3.1.0",
"vite-plugin-mock": "^3.0.0"
}
}

View File

@@ -1 +0,0 @@
// TODO:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,17 +1,51 @@
<script lang="ts" setup>
import 'dayjs/locale/zh-cn';
import { ConfigProvider } from 'ant-design-vue';
import { usePreference } from '@vben/preference';
import { ConfigProvider, theme } from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { computed } from 'vue';
defineOptions({ name: 'App' });
dayjs.locale(zhCN.locale);
const { setPreference, ...preference } = usePreference();
const algorithm = computed(() => {
const { dark, compact } = preference;
console.log(111, preference);
const algorithms = dark ? [theme.darkAlgorithm] : [theme.defaultAlgorithm];
if (compact) {
algorithms.push(theme.compactAlgorithm);
}
console.log(222, algorithms);
return algorithms;
});
function change() {
setPreference({
dark: !preference.dark.value,
});
console.log(2, preference.dark);
}
</script>
<template>
<ConfigProvider :locale="zhCN">
<ConfigProvider
:locale="zhCN"
:theme="{
algorithm,
token: {
colorPrimary: preference.colorPrimary,
},
}"
>
<button @click="change">change</button>
<RouterView />
</ConfigProvider>
</template>

View File

@@ -1,68 +0,0 @@
<script lang="ts" setup>
import { Menu } from '@vben/ant-ui';
import { VbenAdminLayout } from '@vben/layout';
import { reactive } from 'vue';
defineOptions({ name: 'Dashboard' });
const model = reactive({
sideWidth: 180,
siderCollapse: false,
layout: 'side-nav' as const,
sideMixedExtraVisible: false,
fixedMixedExtra: false,
isMobile: false,
});
const menus = [
{
path: '1',
name: 'Menu 1',
children: [
{
path: '1.1',
name: 'Menu 1.1',
children: [
{
path: '1.1.1',
name: 'Menu 1.1.1',
},
{
path: '1.1.2',
name: 'Menu 1.1.2',
},
],
},
{
path: '1.2',
name: 'Menu 1.2',
},
],
},
{
path: '2',
name: 'Menu 2',
},
];
</script>
<template>
<VbenAdminLayout
v-model:side-collapse="model.siderCollapse"
v-model:mixed-extra-visible="model.sideMixedExtraVisible"
:sideWidth="model.sideWidth"
:layout="model.layout"
:fixed-mixed-extra="model.fixedMixedExtra"
:isMobile="model.isMobile"
>
<template #content>
<RouterView />
</template>
<template #side>
<Menu :menus="menus" />
</template>
<template #side-extra>side-extra</template>
<template #header>header</template>
<template #footer>footer</template>
</VbenAdminLayout>
</template>

View File

@@ -0,0 +1,83 @@
<script lang="ts" setup>
import { useAccessStore } from '@vben/store';
import { createNamespace } from '@vben/toolkit';
import { Avatar, Dropdown, Menu, Modal } from 'ant-design-vue';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { useRouter } from 'vue-router';
interface Props {
/**
* 头像
*/
avatar: string;
/**
* 文本
*/
text: string;
/**
* 主题
*/
theme?: string;
}
withDefaults(defineProps<Props>(), {
avatar: '',
theme: 'light',
});
const { b, is } = createNamespace('user-dropdown');
const router = useRouter();
const accessStore = useAccessStore();
function handleConfirmLogout() {
Modal.confirm({
type: 'info',
title: '温馨提醒',
content: '是否确认退出系统?',
onOk: logout,
});
}
async function logout() {
accessStore.$reset();
router.replace('/login');
}
function handleMenuClick({ key }: MenuInfo) {
switch (key) {
case 'logout':
handleConfirmLogout();
break;
}
}
</script>
<template>
<Dropdown :class="[b(), is(theme)]">
<div>
<Avatar :size="30" class="mr-2" :src="avatar" />
<span class="hidden md:block"> {{ text }} </span>
</div>
<template #overlay>
<Menu @click="handleMenuClick">
<Menu.Item key="logout">退出系统</Menu.Item>
</Menu>
</template>
</Dropdown>
</template>
<style lang="scss" module scoped>
@include b('user-dropdown') {
display: flex;
align-items: center;
padding: 0 12px;
font-size: 14px;
cursor: pointer;
&.is-light {
&:hover {
background-color: #f6f6f6;
}
}
}
</style>

View File

@@ -0,0 +1,35 @@
<script lang="ts" setup>
import { preference } from '@vben/preference';
import { createNamespace } from '@vben/toolkit';
import UserDropdown from './UserDropdown.vue';
interface Props {
/**
* Logo 主题
*/
theme?: string;
}
withDefaults(defineProps<Props>(), {
theme: 'light',
});
const { b } = createNamespace('layout-header');
</script>
<template>
<div :class="[b()]">
<div style="margin-left: auto"></div>
<UserDropdown :avatar="preference.defaultAvatar" :text="preference.appName" />
</div>
</template>
<style lang="scss" module scoped>
@include b('layout-header') {
display: flex;
justify-content: flex-start;
width: 100%;
height: 100%;
border-bottom: 1px solid #eee;
}
</style>

View File

@@ -0,0 +1,45 @@
<script lang="ts" setup>
import { Menu } from '@vben/antv-ui';
import { useAccessStore } from '@vben/store';
import { computed, watch } from 'vue';
import { useMenuState } from './useMenuState';
interface Props {
collapsed?: boolean;
}
defineOptions({ name: 'LayoutMenu' });
const emit = defineEmits<{ 'update:collapsed': [value: boolean] }>();
const props = withDefaults(defineProps<Props>(), {
collapsed: false,
});
const accessStore = useAccessStore();
const menus = computed(() => accessStore.getAccessMenus);
const { selectedKeys, openKeys, updateCollapsed, collapsed: menuCollapsed } = useMenuState();
watch(
() => props.collapsed,
(value) => {
updateCollapsed(value);
},
);
watch(menuCollapsed, (value) => {
emit('update:collapsed', value);
});
</script>
<template>
<Menu
v-model:open-keys="openKeys"
v-model:selected-keys="selectedKeys"
:collapsed="menuCollapsed"
:menus="menus"
class="h-full"
/>
</template>

View File

@@ -0,0 +1,81 @@
import { useAccessStore } from '@vben/store';
import { traverseTreeValues } from '@vben/toolkit';
import { computed, onBeforeMount, reactive, toRefs, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
interface State {
collapsed: boolean;
selectedKeys: string[];
openKeys: string[];
}
const state = reactive<State>({
collapsed: false,
openKeys: [],
selectedKeys: [],
});
/**
* 菜单状态管理
*/
function useMenuState() {
const router = useRouter();
const route = useRoute();
const accessStore = useAccessStore();
/**
* 获取菜单及父级菜单集合
*/
const menuParentPathMap = computed(() => {
const menus = accessStore.getAccessMenus;
const map: Record<string, string[]> = {};
traverseTreeValues(menus, (menu) => {
map[menu.path] = menu.parents || [];
});
return map;
});
/**
* 菜单项点击事件
*/
watch(
() => state.selectedKeys,
(selectedKeys) => {
if (!selectedKeys) {
return;
}
const path = selectedKeys[selectedKeys.length - 1];
// 变更openKeys值
state.openKeys = getOpenKeysByPath(path);
// 点击跳转
router.push(path);
},
);
/**
* 获取菜单的所有父级菜单路径
* @param path
*/
function getOpenKeysByPath(path: string) {
return menuParentPathMap.value?.[path];
}
function updateCollapsed(collapsed: boolean) {
state.collapsed = collapsed;
}
onBeforeMount(() => {
// 页面打开或者刷新后,进行赋值
state.selectedKeys = [route.path];
});
return {
...toRefs(state),
updateCollapsed,
};
}
export { useMenuState };

View File

@@ -1 +0,0 @@
export { default as BasicLayout } from './basic/index.vue';

View File

@@ -0,0 +1,56 @@
<script lang="ts" setup>
import { DoubleLeftIcon, DoubleRightIcon } from '@vben/icons';
import { VbenAdminLayout } from '@vben/layout';
import { preference } from '@vben/preference';
import { Logo } from '@vben/share-ui';
import { reactive } from 'vue';
import LayoutHeader from './components/header/index.vue';
import LayoutMenu from './components/menu/index.vue';
defineOptions({ name: 'BasicLayout' });
const model = reactive({
sideWidth: 210,
sideCollapsed: false,
layout: 'side-nav' as const,
sideMixedExtraVisible: false,
fixedMixedExtra: false,
isMobile: false,
});
</script>
<template>
<VbenAdminLayout
v-model:side-collapsed="model.sideCollapsed"
v-model:mixed-extra-visible="model.sideMixedExtraVisible"
:sideWidth="model.sideWidth"
:layout="model.layout"
:fixed-mixed-extra="model.fixedMixedExtra"
:isMobile="model.isMobile"
>
<template #logo>
<Logo
:src="preference.logo"
:collapsed="model.sideCollapsed"
:text="preference.appName"
theme="dark"
/>
</template>
<template #header>
<LayoutHeader />
</template>
<template #side>
<LayoutMenu />
</template>
<template #collapsed-button> <DoubleRightIcon /></template>
<template #un-collapsed-button><DoubleLeftIcon /></template>
<template #side-extra>side-extra</template>
<template #content>
<div style="height: 3000px">
<RouterView />
</div>
</template>
<template #footer>footer</template>
</VbenAdminLayout>
</template>

View File

@@ -1,26 +1,22 @@
/** uno.css 样式表,需要插件配合 */
import 'uno.css';
import '@vben/design';
import '@vben/styles';
import { initIcons } from '@vben/icons';
import { useStore } from '@vben/store';
import { createApp } from 'vue';
import App from './App.vue';
import { router } from './router';
async function bootstrap() {
const app = createApp(App);
const app = createApp(App);
// 配置 pinia-store
useStore(app);
// 配置 pinia-store
useStore(app);
// 配置路由
app.use(router);
// 配置路由及路由守卫
app.use(router);
// 路由守卫
// setupRouterGuard(router);
// 初始化图标
initIcons();
app.mount('#app');
}
bootstrap();
app.mount('#app');

View File

@@ -1,26 +1,149 @@
import { useUserStore } from '@vben/store';
import type { Router } from 'vue-router';
import { useAccessStore } from '@vben/store';
import { filterTree, mapTree, traverseTreeValues } from '@vben/toolkit';
import type { MenuRecordRaw } from '@vben/typings';
import type { Router, RouteRecordRaw } from 'vue-router';
import { dynamicRoutes } from '../routes';
// 登录页面路由 name
const LOGIN_ROUTE_NAME = 'Login';
// 登录页面路由 path
const LOGIN_ROUTE_PATH = '/login';
// 不需要权限的页面白名单
const WHITE_ROUTE_NAMES = ['Login', 'Index'];
/**
* 权限访问守卫配置
* @param router
*/
function configAccessGuard(router: Router) {
router.beforeEach(async (to, _from, next) => {
const userStore = useUserStore();
const accessToken = userStore.getAccessToken;
router.beforeEach(async (to, from) => {
const accessStore = useAccessStore();
const accessToken = accessStore.getAccessToken;
// 没有 accessToken 逻辑
// accessToken 检查
if (!accessToken) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true
if (!to.meta.requiresAuth) {
next();
return;
// 明确声明忽略权限访问权限,则可以访问
if (to.meta.ignoreAccess) {
return true;
}
// 白名单路由列表检查
if (WHITE_ROUTE_NAMES.includes(to.name as string)) {
return true;
}
// 没有访问权限,跳转登录页面
if (to.fullPath !== LOGIN_ROUTE_PATH) {
return {
name: LOGIN_ROUTE_NAME,
replace: true,
// 携带当前跳转的页面,登录后重新跳转该页面
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
};
}
return to;
}
const accessRoutes = accessStore.getAccessRoutes;
// 是否已经生成过动态路由
if (accessRoutes && accessRoutes.length) {
return true;
}
// 生成路由表
// 当前登录用户拥有的角色标识列表
const userRoles = accessStore.getUserRoles;
const routes = await generatorRoutes(userRoles);
// 动态添加到router实例内
routes.forEach((route) => router.addRoute(route));
const menus = await generatorMenus(routes, router);
// 保存菜单信息和路由信息
accessStore.setAccessMenus(menus);
accessStore.setAccessRoutes(routes);
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
return {
path: redirect,
replace: true,
};
});
}
/**
* 动态生成路由
*/
async function generatorRoutes(roles: string[]): Promise<RouteRecordRaw[]> {
// 根据角色标识过滤路由表,判断当前用户是否拥有指定权限
return filterTree(dynamicRoutes, (route) => hasAuthority(route, roles));
}
/**
* 根据 routes 生成菜单列表
* @param routes
*/
async function generatorMenus(routes: RouteRecordRaw[], router: Router): Promise<MenuRecordRaw[]> {
// 获取所有router最终的path及name
const finalRoutes = traverseTreeValues(router.getRoutes(), ({ name, path }) => {
return {
name,
path,
};
});
router.afterEach((_to) => {});
// 提取树指定结构
const menus = mapTree<RouteRecordRaw & { parent?: string; parents?: string[] }, MenuRecordRaw>(
routes,
(route) => {
// 路由表的路径写法有多种这里从router获取到最终的path并赋值
const matchRoute = finalRoutes.find((finalRoute) => finalRoute.name === route.name);
// 转换为菜单结构
const path = matchRoute?.path ?? route.path;
const { children, meta, name: routeName } = route;
const { title = '', orderNo, icon } = meta || {};
const name = (title || routeName || '') as string;
// 将菜单的所有父级和父级菜单记录到菜单项内
if (children && children.length) {
children.forEach((child: any) => {
child.parents = [...(route.parents || []), path];
child.parent = path;
});
}
return {
parents: route.parents,
parent: route.parent,
name,
path,
orderNo,
icon,
children: children as MenuRecordRaw[],
};
},
);
return menus;
}
/**
* 判断路由是否有权限访问
* @param route
* @param roles
*/
function hasAuthority(route: RouteRecordRaw, access: string[]) {
if (route.meta?.authority) {
return access.some((value) => {
return route.meta?.authority?.includes(value);
});
}
return true;
}
export { configAccessGuard };

View File

@@ -25,11 +25,11 @@ function configCommonGuard(router: Router) {
* 项目守卫配置
* @param router
*/
function setupRouteGuard(router: Router) {
function createRouteGuard(router: Router) {
/** 通用 */
configCommonGuard(router);
/** 权限访问 */
configAccessGuard(router);
}
export { setupRouteGuard };
export { createRouteGuard };

View File

@@ -1,23 +1,10 @@
import { loggerWarning, traverseTreeValues } from '@vben/shared';
import { loggerWarning, traverseTreeValues } from '@vben/toolkit';
import type { RouteRecordName, RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import { createRouteGuard } from './guard';
import { staticRoutes } from './routes';
/**
* @description 获取静态路由所有节点包含子节点的 name并排除不存在 name 字段的路由
*/
const staticRouteNames = traverseTreeValues<RouteRecordRaw, RouteRecordName | undefined>(
staticRoutes,
(route) => {
// 这些路由需要指定 name防止在路由重置时不能删除没有指定 name 的路由
if (!route.name) {
loggerWarning(`The route with the path ${route.path} needs to specify the field name.`);
}
return route.name;
},
);
/**
* @description 创建vue-router实例
*/
@@ -25,15 +12,37 @@ const router = createRouter({
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),
// 应该添加到路由的初始路由列表。
routes: staticRoutes,
scrollBehavior: (_to, _from, savedPosition) => savedPosition || { left: 0, top: 0 },
scrollBehavior: (to, from, savedPosition) => {
if (to.path !== from.path) {
setTimeout(() => {
const app = document.getElementById('app');
if (app) {
app.scrollTop = 0;
}
});
}
return savedPosition || { left: 0, top: 0 };
},
});
/**
* @description 重置所有路由,如有指定白名单除外
*/
function resetRoutes() {
const routes = router.getRoutes();
const { hasRoute, removeRoute } = router;
// 获取静态路由所有节点包含子节点的 name并排除不存在 name 字段的路由
const staticRouteNames = traverseTreeValues<RouteRecordRaw, RouteRecordName | undefined>(
staticRoutes,
(route) => {
// 这些路由需要指定 name防止在路由重置时不能删除没有指定 name 的路由
if (!route.name) {
loggerWarning(`The route with the path ${route.path} needs to specify the field name.`);
}
return route.name;
},
);
const { hasRoute, removeRoute, getRoutes } = router;
const routes = getRoutes();
routes.forEach(({ name }) => {
// 存在于路由表且非白名单才需要删除
if (name && !staticRouteNames.includes(name) && hasRoute(name)) {
@@ -41,5 +50,7 @@ function resetRoutes() {
}
});
}
// 创建路由守卫
createRouteGuard(router);
export { resetRoutes, router };

View File

@@ -1,40 +1,9 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts/index';
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = [
{
path: '/dashboard',
name: 'Dashboard',
component: BasicLayout,
redirect: { name: 'Analysis' },
children: [
{
path: '',
name: 'Analysis',
component: () => import('@/views/dashboard/index.vue'),
},
],
},
];
const Layout = () => import('@/layouts/index.vue');
/** 静态路由列表,访问这些页面可以不需要权限 */
const staticRoutes: RouteRecordRaw[] = [
...dynamicRoutes,
// {
// path: '/:catchAll(.*)',
// redirect: { name: 'Dashboard' },
// },
// 根路由
{
path: '/',
name: 'Root',
redirect: '/dashboard',
meta: {
title: 'Root',
},
},
{
path: '/login',
name: 'Login',
@@ -53,12 +22,74 @@ const staticRoutes: RouteRecordRaw[] = [
},
{
path: '/:path(.*)*',
name: 'PageNotFound',
name: 'NotFound',
component: () => import('@vben/share-ui').then((m) => m.NotFound),
meta: {
title: 'PageNotFound',
title: 'NotFound',
},
},
//
];
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = [
// 根路由
{
path: '/',
name: 'Dashboard',
redirect: '/dashboard',
component: Layout,
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: {
title: 'Dashboard',
},
},
],
},
{
path: '/test',
name: 'Test',
component: Layout,
children: [
{
path: '/test1',
name: 'Test1',
component: () => import('@/views/test/test1.vue'),
meta: {
title: 'Test1',
},
},
{
path: '/test2',
name: 'Test2',
component: () => import('@/views/test/test2.vue'),
meta: {
title: 'Test2',
},
},
{
path: '/test3',
name: 'Test3',
meta: {
title: 'Test3',
},
children: [
{
path: '/test31',
name: 'Test31',
component: () => import('@/views/test/test3.vue'),
meta: {
title: 'Test31',
},
},
],
},
],
},
];
/** 排除在主框架外的路由,这些路由没有菜单和顶部及其他框架内容 */

View File

@@ -1,14 +1,14 @@
import type { UserInfo } from '@vben/types';
import type { UserInfo } from '@vben/typings';
import { request } from '@/services/request';
import type { UserService } from './typing';
import type { UserApi } from './typing';
/**
* 登录
*/
async function userLogin(data: UserService.LoginParams) {
return request<UserService.LoginResult>('/login', { data, method: 'POST' });
async function userLogin(data: UserApi.LoginParams) {
return request<UserApi.LoginResult>('/login', { data, method: 'POST' });
}
/**
@@ -20,4 +20,4 @@ async function getUserInfo() {
export { getUserInfo, userLogin };
export type { UserService } from './typing';
export type { UserApi } from './typing';

View File

@@ -1,4 +1,4 @@
namespace UserService {
namespace UserApi {
/** 登录接口参数 */
export interface LoginParams {
username: string;
@@ -15,4 +15,4 @@ namespace UserService {
}
}
export type { UserService };
export type { UserApi };

View File

@@ -2,7 +2,7 @@
* 该文件可自行根据业务逻辑进行调整
*/
import { useUserStore } from '@vben/store';
import { useAccessStore } from '@vben/store';
import { message } from 'ant-design-vue';
import axios, {
AxiosError,
@@ -11,6 +11,10 @@ import axios, {
InternalAxiosRequestConfig,
} from 'axios';
// 后端需要的 token 存放在header内的key字段
// 可以根据自己的需要修改
const REQUEST_HEADER_TOKEN_KEY = 'Authorization';
type HttpConfig = InternalAxiosRequestConfig;
interface HttpResponse<T = any> {
@@ -87,13 +91,13 @@ axiosInstance.interceptors.request.use(
addRequestSignal(config);
// 携带 getAccessToken 在请求头
const userStore = useUserStore();
const getAccessToken = userStore.getAccessToken;
const accessStore = useAccessStore();
const getAccessToken = accessStore.getAccessToken;
if (getAccessToken) {
config.headers.Authorization = getAccessToken;
config.headers[REQUEST_HEADER_TOKEN_KEY] = getAccessToken;
}
return config as InternalAxiosRequestConfig;
return config;
},
(error: AxiosError) => {
return Promise.reject(error);

View File

@@ -1,18 +1,18 @@
<script setup lang="ts">
import { useRequest } from '@vben/hooks';
import { useUserStore } from '@vben/store';
import { useAccessStore } from '@vben/store';
import { Button, Checkbox, Form, Input } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
import { getUserInfo, userLogin, type UserService } from '@/services';
import { getUserInfo, type UserApi, userLogin } from '@/services';
defineOptions({ name: 'LoginForm' });
const useForm = Form.useForm;
const formModel = reactive<UserService.LoginParams & { rememberMe: boolean }>({
const formModel = reactive<UserApi.LoginParams & { rememberMe: boolean }>({
username: 'vben',
password: '123456',
rememberMe: false,
@@ -34,11 +34,11 @@
});
const router = useRouter();
const userStore = useUserStore();
const accessStore = useAccessStore();
const { validate, validateInfos } = useForm(formModel, formRules);
const { loading, runAsync } = useRequest(userLogin, { manual: true });
const { loading: userInfoLoading, runAsync: runUserInfoAsync } = useRequest(getUserInfo, {
const { loading, runAsync: runUserLogin } = useRequest(userLogin, { manual: true });
const { loading: userInfoLoading, runAsync: runGetUserInfo } = useRequest(getUserInfo, {
manual: true,
});
@@ -53,11 +53,11 @@
if (!values) {
return;
}
const { accessToken } = await runAsync(values);
const { accessToken } = await runUserLogin(values);
if (accessToken) {
userStore.setAccessToken(accessToken);
const userInfo = await runUserInfoAsync();
userStore.setUserInfo(userInfo);
accessStore.setAccessToken(accessToken);
const userInfo = await runGetUserInfo();
accessStore.setUserInfo(userInfo);
router.push(userInfo.homePath);
}
}

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import { preference } from '@vben/preference';
import { Login } from '@vben/share-ui';
import LoginForm from './LoginForm.vue';
@@ -8,7 +9,8 @@
<template>
<Login
app-name="Vben Admin"
:app-name="preference.appName"
:logo="preference.logo"
title="开箱即用的中后台管理系统"
description="输入您的个人详细信息开始使用!"
>

View File

@@ -0,0 +1,3 @@
<template>
<div>test1</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>test2</div>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<div>test3</div>
</template>

View File

@@ -1,11 +1,11 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/web.json",
"extends": "@vben/tsconfig/web.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src", "vite.config.ts", "mock"]
"include": ["src", "vite.config.mts", "mock"]
}

View File

@@ -3,19 +3,15 @@ import { defineApplicationConfig } from '@vben/vite-config';
export default defineApplicationConfig({
overrides: {
optimizeDeps: {
include: [
'@iconify/iconify',
'ant-design-vue/es/locale/zh_CN',
'ant-design-vue/es/locale/en_US',
],
include: ['ant-design-vue/es/locale/zh_CN'],
},
server: {
proxy: {
'/basic-api': {
'/vben-api': {
target: 'http://localhost:3000',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/basic-api/, ''),
rewrite: (path) => path.replace(/^\/vben-api/, ''),
},
},
},

View File

@@ -31,19 +31,19 @@
"stub": "pnpm unbuild --stub"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0",
"eslint-define-config": "^1.20.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsonc": "^2.7.0",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-regexp": "^1.14.0",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-define-config": "^1.24.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsonc": "^2.10.0",
"eslint-plugin-n": "^16.3.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-regexp": "^2.1.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.11.1",
"jsonc-eslint-parser": "^2.2.0",
"vue-eslint-parser": "^9.2.1"
"eslint-plugin-vue": "^9.18.1",
"jsonc-eslint-parser": "^2.4.0",
"vue-eslint-parser": "^9.3.2"
}
}

View File

@@ -32,6 +32,7 @@ export default defineConfig({
plugins: ['vue', '@typescript-eslint', 'import', 'simple-import-sort'],
rules: {
'prettier/prettier': 'error',
'spaced-comment': 'error',
eqeqeq: ['warn', 'always', { null: 'never' }],
'no-console': 'warn',
'no-unused-vars': 'off',
@@ -62,7 +63,7 @@ export default defineConfig({
'n/no-extraneous-import': [
'error',
{
allowModules: ['unbuild', '@vben/vite-config', 'vitest'],
allowModules: ['unbuild', '@vben/vite-config', 'vitest', 'vite'],
},
],

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"extends": "@vben/tsconfig/node.json",
"include": ["src"]
}

View File

@@ -31,7 +31,7 @@
"stub": "pnpm unbuild --stub"
},
"dependencies": {
"prettier": "^2.8.8",
"prettier-plugin-packagejson": "^2.4.3"
"prettier": "^3.0.3",
"prettier-plugin-packagejson": "^2.4.6"
}
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"extends": "@vben/tsconfig/node.json",
"include": ["src"]
}

View File

@@ -31,22 +31,22 @@
"stub": "pnpm unbuild --stub"
},
"dependencies": {
"stylelint-config-recess-order": "^4.0.0",
"stylelint-scss": "^5.0.0"
"stylelint-config-recess-order": "^4.3.0",
"stylelint-scss": "^5.3.1"
},
"devDependencies": {
"postcss": "^8.4.23",
"postcss": "^8.4.31",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.8",
"stylelint": "^15.6.1",
"stylelint-config-recommended": "^12.0.0",
"stylelint-config-recommended-scss": "^11.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^33.0.0",
"stylelint-config-standard-scss": "^9.0.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.0.3",
"stylelint": "^15.11.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-recommended-scss": "^13.1.0",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^11.1.0",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^3.0.0"
"stylelint-prettier": "^4.0.2"
}
}

View File

@@ -23,9 +23,38 @@ export default {
],
rules: {
'prettier/prettier': true,
'at-rule-no-unknown': null,
'scss/at-rule-no-unknown': true,
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'extends',
'ignores',
'include',
'mixin',
'if',
'else',
'media',
'for',
'at-root',
],
},
],
'scss/at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'extends',
'ignores',
'include',
'mixin',
'if',
'else',
'media',
'for',
'at-root',
],
},
],
'selector-not-notation': null,
'import-notation': null,
'function-no-unknown': null,

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"extends": "@vben/tsconfig/node.json",
"include": ["src"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node Server Config",
"extends": "./base.json",
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": false,
"esModuleInterop": true,
"outDir": "./dist",
"baseUrl": "./"
},
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Web Application",
"extends": "./base.json",
"compilerOptions": {
"useDefineForClassFields": true,
"jsx": "preserve",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"noImplicitAny": false,
"declaration": true,
"moduleResolution": "bundler"
}
}

View File

@@ -1,5 +1,5 @@
{
"name": "@vben/ts-config",
"name": "@vben/tsconfig",
"version": "1.0.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
@@ -16,11 +16,11 @@
"base.json",
"node.json",
"web.json",
"node-server.json"
"library.json"
],
"dependencies": {
"@types/node": "^18.16.5",
"@vben/types": "workspace:*",
"vite": "^4.3.5"
"@types/node": "^20.9.0",
"@vben/typings": "workspace:*",
"vite": "^5.0.0-beta.11"
}
}

View File

@@ -5,9 +5,9 @@
"compilerOptions": {
"useDefineForClassFields": true,
"jsx": "preserve",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"noImplicitAny": false,
"moduleResolution": "bundler",
"types": ["vite/client", "@vben/types/global"]
"types": ["vite/client", "@vben/typings/global"]
}
}

View File

@@ -4,7 +4,4 @@ export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@@ -16,11 +16,10 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
"default": "./dist/index.mjs"
}
},
"main": "./dist/index.cjs",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
@@ -31,29 +30,24 @@
"lint": "pnpm eslint .",
"stub": "pnpm unbuild --stub"
},
"dependencies": {
"vite": "^4.3.5"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@vitejs/plugin-vue": "^4.2.1",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"dayjs": "^1.11.7",
"dotenv": "^16.0.3",
"@types/fs-extra": "^11.0.4",
"@vitejs/plugin-vue": "^4.4.0",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"dayjs": "^1.11.10",
"dotenv": "^16.3.1",
"fs-extra": "^11.1.1",
"less": "^4.1.3",
"less": "^4.2.0",
"picocolors": "^1.0.0",
"pkg-types": "^1.0.3",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.62.1",
"unocss": "^0.51.12",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.69.5",
"unocss": "^0.57.2",
"vite": "^5.0.0-beta.11",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.1.0",
"vite-plugin-dts": "^2.3.0",
"vite-plugin-css-injected-by-js": "^3.3.0",
"vite-plugin-dts": "^3.6.3",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^3.0.0",
"vite-plugin-purge-icons": "^0.9.2",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-warmup": "^0.1.0"
"vite-plugin-mock": "^3.0.0"
}
}

View File

@@ -5,7 +5,6 @@ import { readPackageJSON } from 'pkg-types';
import { presetTypography, presetUno } from 'unocss';
import UnoCSS from 'unocss/vite';
import { defineConfig, loadEnv, mergeConfig, type UserConfig } from 'vite';
import { warmup } from 'vite-plugin-warmup';
import { createPlugins } from '../plugins';
import { commonConfig } from './common';
@@ -34,63 +33,47 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
compress: VITE_BUILD_COMPRESS,
});
const pathResolve = (pathname: string) => resolve(root, '.', pathname);
const applicationConfig: UserConfig = {
esbuild: {
drop: isBuild ? ['console', 'debugger'] : [],
},
resolve: {
alias: [
{
find: 'vue',
replacement: 'vue/dist/vue.runtime.esm-bundler.js',
},
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
},
// @/xxxx => src/xxxx
{
find: /@\//,
replacement: pathResolve('src') + '/',
},
// #/xxxx => types/xxxx
{
find: /#\//,
replacement: pathResolve('types') + '/',
replacement: resolve(root, '.', 'src') + '/',
},
],
},
define: defineData,
build: {
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/_entry-[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]',
manualChunks: {
vue: ['vue', 'pinia', 'vue-router'],
icon: ['@purge-icons/generated', 'virtual:svg-icons-register'],
},
// manualChunks: {
// vue: ['vue', 'pinia', 'vue-router'],
// },
},
},
},
plugins: [
...plugins,
warmup({
clientFiles: ['./*.html'],
}),
UnoCSS({
exclude: ['node_modules'],
include: ['**.ts', '**.tsx', '**.vue'],
presets: [presetUno(), presetTypography()],
content: {
pipeline: {
exclude: ['node_modules'],
include: ['**.ts', '**.tsx', '**.vue'],
},
},
}),
],
};
const mergedConfig = mergeConfig(commonConfig, applicationConfig);
return mergeConfig(mergedConfig, overrides);
});
}

View File

@@ -6,15 +6,14 @@ const commonConfig: UserConfig = {
server: {
host: true,
},
build: {
reportCompressedSize: false,
chunkSizeWarningLimit: 1500,
chunkSizeWarningLimit: 2000,
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@vben/design/shared";`,
additionalData: `@import "@vben/styles/shared";`,
},
},
},

View File

@@ -17,9 +17,9 @@ interface DefineOptions {
}
function definePackageConfig(defineOptions: DefineOptions = {}) {
const { overrides = {}, options = {} } = defineOptions;
const { overrides = {}, options } = defineOptions;
const root = process.cwd();
const { extraCss } = options;
const { extraCss } = options || {};
return defineConfig(async () => {
const { dependencies = {}, peerDependencies = {} } = await readPackageJSON(root);

View File

@@ -1,11 +1,9 @@
import { type PluginOption } from 'vite';
import purgeIcons from 'vite-plugin-purge-icons';
import { createAppConfigPlugin } from './appConfig';
import { configCompressPlugin } from './compress';
import { configHtmlPlugin } from './html';
import { configMockPlugin } from './mock';
import { configSvgIconsPlugin } from './svgSprite';
import { configVisualizerConfig } from './visualizer';
interface Options {
@@ -25,12 +23,6 @@ async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyz
// vite-plugin-html
vitePlugins.push(configHtmlPlugin({ isBuild }));
// vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin({ isBuild }));
// vite-plugin-purge-icons
vitePlugins.push(purgeIcons());
// The following plugins only work in the production environment
if (isBuild) {
// rollup-plugin-gzip

View File

@@ -1,17 +0,0 @@
/**
* Vite Plugin for fast creating SVG sprites.
* https://github.com/anncwb/vite-plugin-svg-icons
*/
import { resolve } from 'node:path';
import type { PluginOption } from 'vite';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
export function configSvgIconsPlugin({ isBuild }: { isBuild: boolean }) {
const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild,
});
return svgIconsPlugin as PluginOption;
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"extends": "@vben/tsconfig/node.json",
"include": ["src"]
}

View File

@@ -1,9 +1,5 @@
{
"folders": [
{
"name": "server",
"path": "apps/test-server"
},
{
"name": "@vben/vben-admin",
"path": "apps/vben-admin"
@@ -21,25 +17,13 @@
"path": "internal/stylelint-config"
},
{
"name": "@vben/ts-config",
"name": "@vben/tsconfig",
"path": "internal/ts-config"
},
{
"name": "@vben/vite-config",
"path": "internal/vite-config"
},
{
"name": "@vben/apis",
"path": "packages/apis"
},
{
"name": "@vben/assets",
"path": "packages/assets"
},
{
"name": "@vben/design",
"path": "packages/design"
},
{
"name": "@vben/hooks",
"path": "packages/hooks"
@@ -49,20 +33,28 @@
"path": "packages/layout"
},
{
"name": "@vben/shared",
"path": "packages/shared"
"name": "@vben/styles",
"path": "packages/shared/styles"
},
{
"name": "@vben/toolkit",
"path": "packages/shared/toolkit"
},
{
"name": "@vben/typings",
"path": "packages/shared/typings"
},
{
"name": "@vben/store",
"path": "packages/store"
},
{
"name": "@vben/types",
"path": "packages/types"
"name": "@vben/antv-ui",
"path": "packages/ui/antv-ui"
},
{
"name": "@vben/ui",
"path": "packages/ui"
"name": "@vben/share-ui",
"path": "packages/ui/share-ui"
},
{
"name": "root",

View File

@@ -46,34 +46,32 @@
"path": "node_modules/cz-git"
}
},
"dependencies": {
"@vben/design": "workspace:*"
},
"devDependencies": {
"@commitlint/cli": "^17.6.3",
"@commitlint/config-conventional": "^17.6.3",
"@types/jsdom": "^21.1.1",
"@commitlint/cli": "^18.2.0",
"@commitlint/config-conventional": "^18.1.0",
"@types/jsdom": "^21.1.5",
"@vben/eslint-config": "workspace:*",
"@vben/prettier-config": "workspace:*",
"@vben/stylelint-config": "workspace:*",
"@vben/ts-config": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/tsconfig": "workspace:*",
"@vben/vite-config": "workspace:*",
"cross-env": "^7.0.3",
"cz-git": "^1.6.1",
"czg": "^1.6.1",
"cz-git": "^1.7.1",
"czg": "^1.7.1",
"husky": "^8.0.3",
"jsdom": "^22.0.0",
"lint-staged": "13.2.2",
"rimraf": "^5.0.0",
"turbo": "^1.9.3",
"typescript": "^5.0.4",
"unbuild": "^1.2.1",
"vite": "^4.3.5",
"vitest": "^0.31.0"
"jsdom": "^22.1.0",
"lint-staged": "15.0.2",
"rimraf": "^5.0.5",
"turbo": "^1.10.16",
"typescript": "^5.2.2",
"unbuild": "^2.0.0",
"vite": "^5.0.0-beta.11",
"vitest": "^0.34.6"
},
"packageManager": "pnpm@8.3.0",
"packageManager": "pnpm@8.10.2",
"engines": {
"node": ">=16.15.1",
"pnpm": ">=8.3.0"
"node": ">=18.7.0",
"pnpm": ">=8.5.0"
}
}

View File

@@ -1,5 +1,5 @@
{
"name": "@vben/ant-ui",
"name": "@vben/preference",
"version": "1.0.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": {
@@ -8,42 +8,34 @@
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "packages/ui/ant-ui"
"directory": "packages/preference"
},
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": {
".": {
"development": "./src/index.ts",
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./custom": {
"development": "./src/custom/index.ts",
"types": "./src/custom/index.ts",
"default": "./src/custom/index.ts"
}
},
"main": "./src/index.ts",
"module": "./src/index.ts",
"types": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"//build": "pnpm vite build",
"clean": "pnpm rimraf .turbo node_modules dist",
"lint": "pnpm eslint ."
},
"dependencies": {
"@vben/hooks": "workspace:*",
"@vben/shared": "workspace:*",
"@vben/types": "workspace:*",
"ant-design-vue": "^4.0.0-beta.4",
"vue": "^3.3.0-beta.5",
"vue-router": "^4.1.6"
},
"publishConfig": {
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
}
}
"@vben/store": "workspace:*",
"@vben/typings": "workspace:*",
"defu": "^6.1.3",
"vue": "^3.3.8"
}
}

View File

@@ -0,0 +1,39 @@
import { usePreferenceStore } from '@vben/store';
import type { DeepPartial, Preference } from '@vben/typings';
import { defu } from 'defu';
import { computed, reactive, toRefs } from 'vue';
interface usePreferenceOptions extends Preference {
setPreference: (preference: DeepPartial<Preference>) => void;
}
function createUsePreference(defaultPreference: Preference) {
return (): usePreferenceOptions => {
const preferenceStore = usePreferenceStore();
const customPreference = computed(() => {
const preference = preferenceStore.getPreference;
return defu<Preference, [Preference]>(preference, defaultPreference);
});
const reactivePreference = reactive(customPreference);
function setPreference(partialPreference: DeepPartial<Preference>) {
const realPreference = defu<Preference, [Preference]>(partialPreference, defaultPreference);
preferenceStore.setPreference(realPreference);
}
return {
...toRefs(reactivePreference),
setPreference,
};
};
}
function createPreference(preference: Preference) {
return function definePreference(customPreference: DeepPartial<Preference>) {
return defu<Preference, [Preference]>(customPreference, preference);
};
}
export { createPreference, createUsePreference, type usePreferenceOptions };

View File

@@ -0,0 +1,16 @@
import type { Preference } from '@vben/typings';
import { createUsePreference, type usePreferenceOptions } from '../createPreference';
import { createPreference, preference as defaultPreference } from '../index';
const definePreference = createPreference(defaultPreference);
const preference = definePreference({
// 自定义配置
});
const usePreference = createUsePreference(preference);
export { createPreference, preference, usePreference };
export type { Preference, usePreferenceOptions };

View File

@@ -0,0 +1,24 @@
import type { Preference } from '@vben/typings';
import {
createPreference,
createUsePreference,
type usePreferenceOptions,
} from './createPreference';
const preference: Preference = {
appName: 'Vben Admin',
logo: '/logo.png',
defaultAvatar:
'https://cdn.jsdelivr.net/gh/vbenjs/vben-cdn-static@0.1.0/vben-admin/default-avatar.jpg',
colorPrimary: '#0960bd',
dark: false,
compact: false,
};
const usePreference = createUsePreference(preference);
export { createPreference, preference, usePreference };
export type { Preference, usePreferenceOptions };

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/web.json",
"extends": "@vben/tsconfig/library.json",
"include": ["src"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,8 +0,0 @@
/**
* @description 用于管理图片及图标资源
*/
export { default as logoImage } from './iamges/logo.png';
export { default as loginBgSvg } from './svg/login-bg.svg';
export { default as loginBgDarkSvg } from './svg/login-bg-dark.svg';
export { default as loginSloganSvg } from './svg/login-slogan.svg';
export { default as pageNotFoundSvg } from './svg/page-not-found.svg';

View File

@@ -1,3 +0,0 @@
import { definePackageConfig } from '@vben/vite-config';
export default definePackageConfig();

View File

@@ -1,10 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@@ -1,27 +0,0 @@
html,
body,
#app {
width: 100%;
height: 100%;
}
// 滚动条样式重置
::-webkit-scrollbar {
width: 7px;
height: 8px;
}
::-webkit-scrollbar-track {
background-color: rgb(0 0 0 / 5%);
}
::-webkit-scrollbar-thumb {
background-color: rgb(144 147 153 / 30%);
border-radius: 2px;
box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
}
::-webkit-scrollbar-thumb:hover {
background-color: #b6b7b9;
}

View File

@@ -16,13 +16,12 @@
"exports": {
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"types": "./src/index.ts",
"default": "./dist/index.mjs"
}
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
@@ -33,11 +32,11 @@
"stub": "pnpm unbuild --stub"
},
"dependencies": {
"@vben/types": "workspace:*",
"@vueuse/core": "^10.1.2",
"@vueuse/integrations": "^10.1.2",
"vue": "^3.3.0-beta.5",
"vue-hooks-plus": "^1.6.5"
"@vben/typings": "workspace:*",
"@vueuse/core": "^10.5.0",
"@vueuse/integrations": "^10.5.0",
"vue": "^3.3.8",
"vue-hooks-plus": "^1.8.5"
},
"publishConfig": {
"exports": {

View File

@@ -2,7 +2,6 @@
export * from './onMountedOrActivated';
export * from './useContext';
export * from './useNamespace';
export * from './useRequest';
export * from './useWindowSizeFn';
export * from '@vueuse/core';

View File

@@ -1,4 +1,4 @@
import { type AnyFunction } from '@vben/types';
import { type AnyFunction } from '@vben/typings';
import { nextTick, onActivated, onMounted } from 'vue';
/**

View File

@@ -1,4 +1,4 @@
import { type AnyFunction } from '@vben/types';
import { type AnyFunction } from '@vben/typings';
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core';
interface UseWindowSizeFnOptions {

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/web.json",
"extends": "@vben/tsconfig/library.json",
"include": ["src"]
}

View File

@@ -1,7 +1,6 @@
{
"name": "@vben/assets",
"name": "@vben/icons",
"version": "1.0.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": {
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
@@ -9,10 +8,9 @@
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "packages/assets"
"directory": "packages/icons"
},
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": {
".": {
@@ -20,5 +18,17 @@
}
},
"main": "./src/index.ts",
"module": "./src/index.ts"
"module": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"clean": "pnpm rimraf .turbo node_modules dist",
"lint": "pnpm eslint ."
},
"dependencies": {
"@iconify/vue": "^4.1.1",
"@vben/toolkit": "workspace:*",
"vue": "^3.3.8"
}
}

View File

@@ -0,0 +1,8 @@
import { Icon } from '@iconify/vue';
import { h } from 'vue';
const DoubleLeftIcon = h(Icon, { icon: 'icon-park-outline:double-left' });
const DoubleRightIcon = h(Icon, { icon: 'icon-park-outline:double-right' });
export { DoubleLeftIcon, DoubleRightIcon };

View File

@@ -0,0 +1 @@
export * from './iconPark';

View File

@@ -0,0 +1,4 @@
export * from './iconify-icons/index';
export * from './svg-icons/index';
export * from './svg-icons/init';
export { Icon } from '@iconify/vue';

View File

@@ -0,0 +1,6 @@
import { Icon } from '@iconify/vue';
import { h } from 'vue';
const SvgAvatarIcon = h(Icon, { icon: 'svg:avatar' });
export { SvgAvatarIcon };

View File

@@ -0,0 +1,25 @@
import { addIcon } from '@iconify/vue';
/**
* 自定义的svg图片转化为组件
* @example ./svg/avatar.svg
* <Icon icon="svg:avatar"></Icon>
*/
async function initIcons() {
const svgEagers = import.meta.glob('./svg/**', { eager: true, as: 'raw' });
await Promise.all(
Object.entries(svgEagers).map((svg) => {
const [key, body] = svg;
// ./svg/xxxx.svg => xxxxxx
const iconName = key.replace(/\.\/svg\//, '').replace(/\.svg/, '');
addIcon(`svg:${iconName}`, {
body,
});
}),
);
}
export { initIcons };

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/web.json",
"extends": "@vben/tsconfig/web.json",
"include": ["src"]
}

View File

@@ -14,33 +14,21 @@
"type": "module",
"exports": {
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
"default": "./src/index.ts"
}
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"main": "./src/index.ts",
"module": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "pnpm vite build",
"clean": "pnpm rimraf .turbo node_modules dist",
"lint": "pnpm eslint ."
},
"dependencies": {
"@vben/hooks": "workspace:*",
"@vueuse/core": "^10.1.2",
"vue": "^3.3.0-beta.5"
},
"publishConfig": {
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.mjs"
}
}
"@vben/toolkit": "workspace:*",
"@vueuse/core": "^10.5.0",
"vue": "^3.3.8"
}
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useNamespace } from '@vben/hooks';
import { createNamespace } from '@vben/toolkit';
import type { CSSProperties } from 'vue';
import { computed, watchEffect } from 'vue';
@@ -57,7 +57,7 @@
sideVisible?: boolean;
/**
* 侧边栏宽度
* @default 180
* @default 210
*/
sideWidth?: number;
/**
@@ -74,12 +74,12 @@
* 侧边菜单折叠状态
* @default false
*/
sideCollapse?: boolean;
sideCollapsed?: boolean;
/**
* 侧边菜单折叠宽度
* @default 48
*/
sideCollapseWidth?: number;
sideCollapsedWidth?: number;
/**
* padding
* @default 16
@@ -163,9 +163,9 @@
sideVisible: true,
sideWidth: 180,
sideMixedWidth: 80,
sideCollapse: false,
sideCollapseWidth: 48,
sideBackgroundColor: '#fff',
sideCollapsed: false,
sideCollapsedWidth: 48,
sideBackgroundColor: '#001628',
contentPadding: 16,
contentPaddingBottom: 16,
contentPaddingTop: 16,
@@ -182,23 +182,23 @@
fixedMixedExtra: false,
});
const emit = defineEmits(['update:mixed-extra-visible', 'update:side-collapse']);
const emit = defineEmits(['update:mixed-extra-visible', 'update:side-collapsed']);
const { b, e } = useNamespace('layout');
const { b, e } = createNamespace('layout');
const sideCollapseState = computed({
const sideCollapsedState = computed({
get() {
return props.sideCollapse;
return props.sideCollapsed;
},
set(collapse) {
emit('update:side-collapse', collapse);
emit('update:side-collapsed', collapse);
},
});
/**
* 动态获取侧边区域是否可见
*/
const getSideVisible = computed(() => {
const SideVisible = computed(() => {
const { layout, sideVisible } = props;
return layout !== 'header-nav' && sideVisible;
});
@@ -214,11 +214,11 @@
/**
* 动态获取侧边宽度
*/
const getSiderWidth = computed(() => {
const { layout, sideWidth, isMobile, sideCollapseWidth, sideMixedWidth } = props;
const siderWidth = computed(() => {
const { layout, sideWidth, isMobile, sideCollapsedWidth, sideMixedWidth } = props;
let width = 0;
if (sideCollapseState.value) {
width = isMobile ? 0 : sideCollapseWidth;
if (sideCollapsedState.value) {
width = isMobile ? 0 : sideCollapsedWidth;
} else {
if (layout === 'side-mixed-nav' && !isMobile) {
width = sideMixedWidth;
@@ -247,12 +247,19 @@
/**
* 遮罩可见性
*/
const maskVisible = computed(() => !sideCollapseState.value && props.isMobile);
const maskVisible = computed(() => !sideCollapsedState.value && props.isMobile);
/**
* header fixed值
*/
const getHeaderFixed = computed(() => (props.layout === 'mixed-nav' ? true : props.headerFixed));
const headerFixed = computed(() => (props.layout === 'mixed-nav' ? true : props.headerFixed));
const headerWidth = computed(() => {
if (headerFixed.value && !['mixed-nav', 'header-nav'].includes(props.layout)) {
return `calc(100% - ${siderWidth.value}px)`;
}
return '100%';
});
/**
* tab top 值
@@ -275,7 +282,7 @@
});
watchEffect(() => {
sideCollapseState.value = props.isMobile;
sideCollapsedState.value = props.isMobile;
});
function handleExtraVisible(visible: boolean) {
@@ -283,7 +290,7 @@
}
function handleClickMask() {
sideCollapseState.value = true;
sideCollapsedState.value = true;
}
</script>
@@ -291,19 +298,31 @@
<div :class="b()">
<slot></slot>
<LayoutSide
v-if="getSideVisible"
v-if="SideVisible"
v-model:collapsed="sideCollapsedState"
:show="!fullContent"
:width="getSiderWidth"
:width="siderWidth"
:side-extra-width="sideWidth"
:mixed-extra-visible="mixedExtraVisible"
:z-index="sideZIndex"
:dom-visible="!isMobile"
:is-side-mixed="isSideMixed"
:header-height="headerHeight"
:padding-top="sidePaddingTop"
:fixed-mixed-extra="fixedMixedExtra"
:background-color="sideBackgroundColor"
@extra-visible="handleExtraVisible"
>
<template v-if="isSideMode" #logo>
<slot name="logo"></slot>
</template>
<template #collapsed-button>
<slot name="collapsed-button"></slot>
</template>
<template #un-collapsed-button>
<slot name="un-collapsed-button"></slot>
</template>
<slot name="side"></slot>
<template #extra>
<slot name="side-extra"></slot>
@@ -316,10 +335,14 @@
:show="!fullContent"
:z-index="zIndex"
:height="headerHeight"
:fixed="getHeaderFixed"
:width="headerWidth"
:fixed="headerFixed"
:full-width="!isSideMode"
:background-color="headerBackgroundColor"
>
<template v-if="!isSideMode" #logo>
<slot name="logo"></slot>
</template>
<slot name="header"></slot>
</LayoutHeader>
@@ -329,7 +352,7 @@
:top="tabTop"
:z-index="zIndex"
:height="tabHeight"
:fixed="getHeaderFixed"
:fixed="headerFixed"
>
<slot name="tab"></slot>
</LayoutTab>
@@ -362,6 +385,8 @@
<style scoped module lang="scss">
@include b('layout') {
display: flex;
width: 100%;
min-height: 100%;
@include e('main') {
display: flex;

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useNamespace } from '@vben/hooks';
import { createNamespace } from '@vben/toolkit';
import type { CSSProperties } from 'vue';
import { computed } from 'vue';
@@ -39,7 +39,7 @@
fixed: true,
});
const { b } = useNamespace('footer');
const { b } = createNamespace('footer');
const style = computed((): CSSProperties => {
const { backgroundColor, height, fixed, zIndex, show } = props;

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { useNamespace } from '@vben/hooks';
import { createNamespace } from '@vben/toolkit';
import type { CSSProperties } from 'vue';
import { computed } from 'vue';
import { computed, useSlots } from 'vue';
defineOptions({ name: 'VbenLayoutHeader' });
@@ -25,6 +25,11 @@
* @default 60
*/
height?: number;
/**
* 宽度
* @default 100%
*/
width?: string;
/**
* 是否固定在顶部
* @default true
@@ -42,9 +47,12 @@
zIndex: 0,
height: 60,
fixed: true,
width: '100%',
});
const { b, e } = useNamespace('header');
const slots = useSlots();
const { b, e } = createNamespace('header');
const hiddenHeaderStyle = computed((): CSSProperties => {
const { height, show, fixed } = props;
@@ -58,8 +66,9 @@
});
const style = computed((): CSSProperties => {
const { backgroundColor, height, fixed, zIndex, show, fullWidth } = props;
const { backgroundColor, height, fixed, zIndex, show, fullWidth, width } = props;
const right = !show || !fullWidth ? undefined : 0;
return {
position: fixed ? 'fixed' : 'static',
marginTop: show ? 0 : `-${height}px`,
@@ -67,13 +76,15 @@
height: `${height}px`,
zIndex,
right,
width,
};
});
</script>
<template>
<div :style="hiddenHeaderStyle" :class="e('hide')"></div>
<div v-if="fixed" :style="hiddenHeaderStyle" :class="e('hide')"></div>
<header :style="style" :class="b()">
<slot v-if="slots.logo" name="logo"></slot>
<slot></slot>
</header>
</template>
@@ -81,6 +92,7 @@
<style scoped module lang="scss">
@include b('header') {
top: 0;
display: flex;
width: 100%;
transition: all 0.3s ease 0s;

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { useNamespace } from '@vben/hooks';
import { createNamespace } from '@vben/toolkit';
import { onClickOutside } from '@vueuse/core';
import type { CSSProperties } from 'vue';
import { computed, ref, shallowRef, watchEffect } from 'vue';
import { computed, ref, shallowRef, useSlots, watchEffect } from 'vue';
defineOptions({ name: 'VbenLayoutSide' });
@@ -22,6 +22,15 @@
* @default 0
*/
zIndex?: number;
/**
* 头部高度
*/
headerHeight: number;
/**
* 折叠区域高度
* @default 32
*/
collapsedHeight?: number;
/**
* 背景颜色
*/
@@ -56,6 +65,15 @@
* @default false
*/
mixedExtraVisible?: boolean;
/**
* 显示折叠按钮
* @default false
*/
showCollapsedButton?: boolean;
/**
* 折叠状态
*/
collapsed?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
@@ -64,15 +82,22 @@
width: 180,
sideExtraWidth: 180,
paddingTop: 60,
collapsedHeight: 32,
isSideMixed: false,
fixedMixedExtra: false,
mixedExtraVisible: false,
domVisible: true,
showCollapsedButton: true,
collapsed: false,
});
const { b, e } = useNamespace('side');
const { b, e } = createNamespace('side');
const slots = useSlots();
const emit = defineEmits(['extraVisible']);
const emit = defineEmits<{
extraVisible: [visible: boolean];
'update:collapsed': [value: boolean];
}>();
const asideRef = shallowRef<HTMLDivElement | null>();
const extraVisible = ref(false);
@@ -93,6 +118,7 @@
const style = computed((): CSSProperties => {
const { paddingTop, zIndex } = props;
return {
...hiddenSideStyle.value,
paddingTop: `${paddingTop}px`,
@@ -111,6 +137,30 @@
};
});
const headerStyle = computed((): CSSProperties => {
const { headerHeight } = props;
return {
height: `${headerHeight}px`,
};
});
const contentStyle = computed((): CSSProperties => {
const { headerHeight, collapsedHeight } = props;
return {
height: `calc(100% - ${headerHeight + collapsedHeight}px)`,
};
});
const collapseStyle = computed((): CSSProperties => {
const { collapsedHeight } = props;
return {
height: `${collapsedHeight}px`,
};
});
watchEffect(() => {
extraVisible.value = props.fixedMixedExtra ? true : props.mixedExtraVisible;
});
@@ -124,12 +174,30 @@
}
}
});
function handleCollapsed() {
emit('update:collapsed', !props.collapsed);
}
</script>
<template>
<div v-if="domVisible" :style="hiddenSideStyle" :class="e('hide')"></div>
<aside :style="style" :class="b()">
<slot></slot>
<div v-if="slots.logo" :style="headerStyle">
<slot name="logo"></slot>
</div>
<div :style="contentStyle" :class="e('content')">
<slot></slot>
</div>
<div
v-if="showCollapsedButton"
:class="e('collapsed-button')"
:style="collapseStyle"
@click="handleCollapsed"
>
<slot v-if="collapsed" name="collapsed-button"></slot>
<slot v-else name="un-collapsed-button"></slot>
</div>
<div v-if="isSideMixed" ref="asideRef" :style="extraStyle" :class="e('extra')">
<slot name="extra"></slot>
</div>
@@ -142,7 +210,7 @@
top: 0;
left: 0;
height: 100%;
overflow: hidden;
// overflow: hidden;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
transition: all 0.2s ease 0s;
@@ -151,6 +219,20 @@
transition: all 0.2s ease 0s;
}
@include e('content') {
overflow: auto;
}
@include e('collapsed-button') {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
color: rgb(255 255 255 / 65%);
cursor: pointer;
background-color: rgb(255 255 255 / 10%);
}
@include e('extra') {
position: fixed;
top: 0;

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useNamespace } from '@vben/hooks';
import { createNamespace } from '@vben/toolkit';
import type { CSSProperties } from 'vue';
import { computed } from 'vue';
@@ -39,7 +39,7 @@
top: 0,
});
const { b, e } = useNamespace('tab');
const { b, e } = createNamespace('tab');
const hiddenStyle = computed((): CSSProperties => {
const { height, zIndex, top, fixed } = props;

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/web.json",
"extends": "@vben/tsconfig/web.json",
"include": ["src"]
}

View File

@@ -1,3 +0,0 @@
import { definePackageConfig } from '@vben/vite-config';
export default definePackageConfig();

View File

@@ -0,0 +1,7 @@
# shared
全局共享包,请勿引入 workspace 依赖
- styles 共享样式
- typings 共享类型
- toolkit 共享工具类

View File

@@ -1,50 +0,0 @@
// /**
// * @description 应用主题色预设列表
// */
// const APP_THEME_PRESET_COLORS: string[] = [
// '#0960bd',
// '#0084f4',
// '#009688',
// '#536dfe',
// '#ff5c93',
// '#ee4f12',
// '#0096c7',
// '#9c27b0',
// '#ff9800',
// ];
// /**
// * @description 应用顶部Header主题颜色预设
// */
// const APP_HEADER_THEME_PRESET_COLORS: string[] = [
// '#ffffff',
// '#151515',
// '#009688',
// '#5172DC',
// '#018ffb',
// '#409eff',
// '#e74c3c',
// '#24292e',
// '#394664',
// '#001529',
// '#383f45',
// ];
// /**
// * @description 应用菜单主题颜色预设
// */
// const APP_MENU_THEME_PRESET_COLORS: string[] = [
// '#001529',
// '#212121',
// '#273352',
// '#ffffff',
// '#191b24',
// '#191a23',
// '#304156',
// '#001628',
// '#28333E',
// '#344058',
// '#383f45',
// ];
// export { APP_HEADER_THEME_PRESET_COLORS, APP_MENU_THEME_PRESET_COLORS, APP_THEME_PRESET_COLORS };

View File

@@ -1,3 +0,0 @@
// export * from './design';
export * from './static';
export * from './vben';

View File

@@ -1,12 +0,0 @@
/**
* @description 默认 logo 图
*/
const LOGO_IMAGE = 'https://cdn.jsdelivr.net/gh/vbenjs/vben-cdn-static@0.1.0/vben-admin/logo.png';
/**
* @description 默认头像
*/
const DEFAULT_AVATAR_IMAGE =
'https://cdn.jsdelivr.net/gh/vbenjs/vben-cdn-static@0.1.0/vben-admin/default-avatar.jpg';
export { DEFAULT_AVATAR_IMAGE, LOGO_IMAGE };

View File

@@ -1,21 +0,0 @@
/**
* @description github 仓库地址
*/
const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin';
/**
* @description 项目文档地址
*/
const DOC_URL = 'https://doc.vvbin.cn/';
/**
* @description 站点预览地址
*/
const SITE_URL = 'https://vben.vvbin.cn/';
/**
* @description 全局命名空间,用于 CSS 等需要的地方用做前缀
*/
const GLOBAL_NAMESPACE = 'vben';
export { DOC_URL, GITHUB_URL, GLOBAL_NAMESPACE, SITE_URL };

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