mirror of
https://gitcode.com/gh_mirrors/vue/vue-vben-admin
synced 2025-12-30 05:12:24 +00:00
chore: update deps
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
88
.vscode/settings.json
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 雪碧图
|
||||
|
||||
## 后台整合示例
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// TODO:
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 28 KiB |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
35
apps/vben-admin/src/layouts/components/header/index.vue
Normal file
35
apps/vben-admin/src/layouts/components/header/index.vue
Normal 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>
|
||||
45
apps/vben-admin/src/layouts/components/menu/index.vue
Normal file
45
apps/vben-admin/src/layouts/components/menu/index.vue
Normal 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>
|
||||
81
apps/vben-admin/src/layouts/components/menu/useMenuState.ts
Normal file
81
apps/vben-admin/src/layouts/components/menu/useMenuState.ts
Normal 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 };
|
||||
@@ -1 +0,0 @@
|
||||
export { default as BasicLayout } from './basic/index.vue';
|
||||
56
apps/vben-admin/src/layouts/index.vue
Normal file
56
apps/vben-admin/src/layouts/index.vue
Normal 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>
|
||||
@@ -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');
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** 排除在主框架外的路由,这些路由没有菜单和顶部及其他框架内容 */
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="输入您的个人详细信息开始使用!"
|
||||
>
|
||||
|
||||
3
apps/vben-admin/src/views/test/test1.vue
Normal file
3
apps/vben-admin/src/views/test/test1.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>test1</div>
|
||||
</template>
|
||||
3
apps/vben-admin/src/views/test/test2.vue
Normal file
3
apps/vben-admin/src/views/test/test2.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>test2</div>
|
||||
</template>
|
||||
3
apps/vben-admin/src/views/test/test3.vue
Normal file
3
apps/vben-admin/src/views/test/test3.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>test3</div>
|
||||
</template>
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/node.json",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/node.json",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
}
|
||||
13
internal/tsconfig/library.json
Normal file
13
internal/tsconfig/library.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,4 @@ export default defineBuildConfig({
|
||||
clean: true,
|
||||
entries: ['src/index'],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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";`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/node.json",
|
||||
"extends": "@vben/tsconfig/node.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
38
package.json
38
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
39
packages/_preference/src/createPreference.ts
Normal file
39
packages/_preference/src/createPreference.ts
Normal 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 };
|
||||
16
packages/_preference/src/custom/index.ts
Normal file
16
packages/_preference/src/custom/index.ts
Normal 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 };
|
||||
24
packages/_preference/src/index.ts
Normal file
24
packages/_preference/src/index.ts
Normal 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 };
|
||||
@@ -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 |
@@ -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';
|
||||
@@ -1,3 +0,0 @@
|
||||
import { definePackageConfig } from '@vben/vite-config';
|
||||
|
||||
export default definePackageConfig();
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
entries: ['src/index'],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
export * from './onMountedOrActivated';
|
||||
export * from './useContext';
|
||||
export * from './useNamespace';
|
||||
export * from './useRequest';
|
||||
export * from './useWindowSizeFn';
|
||||
export * from '@vueuse/core';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type AnyFunction } from '@vben/types';
|
||||
import { type AnyFunction } from '@vben/typings';
|
||||
import { nextTick, onActivated, onMounted } from 'vue';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/web.json",
|
||||
"extends": "@vben/tsconfig/library.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
8
packages/icons/src/iconify-icons/iconPark.ts
Normal file
8
packages/icons/src/iconify-icons/iconPark.ts
Normal 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 };
|
||||
1
packages/icons/src/iconify-icons/index.ts
Normal file
1
packages/icons/src/iconify-icons/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './iconPark';
|
||||
4
packages/icons/src/index.ts
Normal file
4
packages/icons/src/index.ts
Normal 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';
|
||||
6
packages/icons/src/svg-icons/index.ts
Normal file
6
packages/icons/src/svg-icons/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
const SvgAvatarIcon = h(Icon, { icon: 'svg:avatar' });
|
||||
|
||||
export { SvgAvatarIcon };
|
||||
25
packages/icons/src/svg-icons/init.ts
Normal file
25
packages/icons/src/svg-icons/init.ts
Normal 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 };
|
||||
1
packages/icons/src/svg-icons/svg/avatar.svg
Normal file
1
packages/icons/src/svg-icons/svg/avatar.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 22 KiB |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/web.json",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/web.json",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { definePackageConfig } from '@vben/vite-config';
|
||||
|
||||
export default definePackageConfig();
|
||||
7
packages/shared/README.md
Normal file
7
packages/shared/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# shared
|
||||
|
||||
全局共享包,请勿引入 workspace 依赖
|
||||
|
||||
- styles 共享样式
|
||||
- typings 共享类型
|
||||
- toolkit 共享工具类
|
||||
@@ -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 };
|
||||
@@ -1,3 +0,0 @@
|
||||
// export * from './design';
|
||||
export * from './static';
|
||||
export * from './vben';
|
||||
@@ -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 };
|
||||
@@ -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
Reference in New Issue
Block a user