mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-10-28 20:33:43 +08:00
Compare commits
21 Commits
tauri-v0.1
...
legacy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a47fc264b | ||
|
|
edf43cdc25 | ||
|
|
5bd177dec6 | ||
|
|
223c0bbbdb | ||
|
|
3a916b1c4d | ||
|
|
ec4b5f3928 | ||
|
|
d149668dbd | ||
|
|
7c1b8dc968 | ||
|
|
1ea4817f6a | ||
|
|
ecbb96f3a5 | ||
|
|
296a2d2f0e | ||
|
|
8c1ef4b0fd | ||
|
|
04d3330463 | ||
|
|
9e115daeb9 | ||
|
|
3eaf05bd4d | ||
|
|
a195980547 | ||
|
|
f04a929856 | ||
|
|
5b8af29496 | ||
|
|
766369f911 | ||
|
|
f6c6dbd312 | ||
|
|
783648f516 |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -72,5 +72,6 @@
|
|||||||
"[html][css][less][scss][sass][markdown][yaml][yml][jsonc]": {
|
"[html][css][less][scss][sass][markdown][yaml][yml][jsonc]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
}
|
},
|
||||||
|
"prettier": {}
|
||||||
}
|
}
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -5,6 +5,25 @@
|
|||||||
|
|
||||||
[](./LICENSE)  
|
[](./LICENSE)  
|
||||||
|
|
||||||
|
## 注意 SoybeanAdmin 正在重构,全新 1.0 即将发布
|
||||||
|
|
||||||
|
Soybean Admin v1.0 :
|
||||||
|
|
||||||
|
- [x] 采用基于 pnpm 的 monorepo 管理项目
|
||||||
|
- [x] 第三方 soybeanjs 的工具库直接抽离到项目中(ElegantRouter除外),不再作为依赖
|
||||||
|
- [x] 采用全新的路由插件 ElegantRouter
|
||||||
|
- [x] 使用基于 ApiFox 的远程 mock 代替本地 mock
|
||||||
|
- [x] 基于现有路由插件迁移至新路由插件的指南
|
||||||
|
- [x] 代码实现遵循 SoybeanJS 的代码规范
|
||||||
|
- [ ] 项目的 main 分支保留系统核心部分,示例页面和无关核心的插件移至 example 分支
|
||||||
|
- [ ] 完整 1.0 版本的文档
|
||||||
|
|
||||||
|
1.0 源代码:[v1.0-beta](https://github.com/honghuangdc/soybean-admin/tree/v1.0-beta)
|
||||||
|
|
||||||
|
> 同时推出开源的 [AntDesignVue 版本](https://github.com/soybeanjs/soybean-admin-antd) 和 ElementPlus 版本
|
||||||
|
|
||||||
|
> 新开项目建议直接使用 [v1.0-beta](https://github.com/honghuangdc/soybean-admin/tree/v1.0-beta) 或 [AntDesignVue 版本](https://github.com/soybeanjs/soybean-admin-antd)
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
[Soybean Admin](https://github.com/honghuangdc/soybean-admin) 是一个基于 Vue3、Vite3、TypeScript、NaiveUI、Pinia 和 UnoCSS 的清新优雅的中后台模版,它使用了最新流行的前端技术栈,内置丰富的主题配置,有着极高的代码规范,基于文件的路由系统以及基于 Mock 的动态权限路由,开箱即用的中后台前端解决方案,也可用于学习参考。
|
[Soybean Admin](https://github.com/honghuangdc/soybean-admin) 是一个基于 Vue3、Vite3、TypeScript、NaiveUI、Pinia 和 UnoCSS 的清新优雅的中后台模版,它使用了最新流行的前端技术栈,内置丰富的主题配置,有着极高的代码规范,基于文件的路由系统以及基于 Mock 的动态权限路由,开箱即用的中后台前端解决方案,也可用于学习参考。
|
||||||
@@ -165,7 +184,7 @@ docker run -d -p 80:80 soybean-admin-image
|
|||||||
<img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" />
|
<img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>添加本人微信,欢迎来技术交流,业务咨询</p>
|
<p>添加本人微信,欢迎来业务咨询(技术交流请加QQ群)</p>
|
||||||
<img src="https://s2.loli.net/2023/06/07/sVyCUFBvzQ9f5b7.jpg" style="width:200px" />
|
<img src="https://s2.loli.net/2023/06/07/sVyCUFBvzQ9f5b7.jpg" style="width:200px" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import unocss from '@unocss/vite';
|
|||||||
import progress from 'vite-plugin-progress';
|
import progress from 'vite-plugin-progress';
|
||||||
import VueDevtools from 'vite-plugin-vue-devtools';
|
import VueDevtools from 'vite-plugin-vue-devtools';
|
||||||
import pageRoute from '@soybeanjs/vite-plugin-vue-page-route';
|
import pageRoute from '@soybeanjs/vite-plugin-vue-page-route';
|
||||||
import { webUpdateNotice } from '@plugin-web-update-notification/vite';
|
|
||||||
import unplugin from './unplugin';
|
import unplugin from './unplugin';
|
||||||
import mock from './mock';
|
import mock from './mock';
|
||||||
import visualizer from './visualizer';
|
import visualizer from './visualizer';
|
||||||
@@ -28,15 +27,7 @@ export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | Plugin
|
|||||||
...unplugin(viteEnv),
|
...unplugin(viteEnv),
|
||||||
unocss(),
|
unocss(),
|
||||||
mock(viteEnv),
|
mock(viteEnv),
|
||||||
progress(),
|
progress()
|
||||||
webUpdateNotice({
|
|
||||||
notificationProps: {
|
|
||||||
title: '👋 有新版本了',
|
|
||||||
description: '点击刷新页面获取最新版本',
|
|
||||||
buttonText: '刷新',
|
|
||||||
dismissButtonText: '忽略'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (viteEnv.VITE_VISUALIZER === 'Y') {
|
if (viteEnv.VITE_VISUALIZER === 'Y') {
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
<html lang="zh-cmn-Hans">
|
<html lang="zh-cmn-Hans">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="Expires" content="0" />
|
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
|
||||||
<meta http-equiv="Cache-control" content="no-cache" />
|
|
||||||
<meta http-equiv="Cache" content="no-cache" />
|
|
||||||
<link rel="icon" href="/favicon.svg" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>%VITE_APP_NAME%</title>
|
<title>%VITE_APP_NAME%</title>
|
||||||
|
|||||||
43
package.json
43
package.json
@@ -59,8 +59,8 @@
|
|||||||
"@antv/g2": "4.2.10",
|
"@antv/g2": "4.2.10",
|
||||||
"@better-scroll/core": "2.5.1",
|
"@better-scroll/core": "2.5.1",
|
||||||
"@soybeanjs/vue-materials": "0.2.0",
|
"@soybeanjs/vue-materials": "0.2.0",
|
||||||
"@vueuse/core": "10.4.1",
|
"@vueuse/core": "10.5.0",
|
||||||
"axios": "1.5.0",
|
"axios": "1.5.1",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"colord": "2.9.3",
|
"colord": "2.9.3",
|
||||||
"crypto-js": "4.1.1",
|
"crypto-js": "4.1.1",
|
||||||
@@ -68,57 +68,56 @@
|
|||||||
"echarts": "5.4.3",
|
"echarts": "5.4.3",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"naive-ui": "2.34.4",
|
"naive-ui": "2.35.0",
|
||||||
"pinia": "2.1.6",
|
"pinia": "2.1.6",
|
||||||
"print-js": "1.6.0",
|
"print-js": "1.6.0",
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"socket.io-client": "4.7.2",
|
"socket.io-client": "4.7.2",
|
||||||
"swiper": "10.2.0",
|
"swiper": "10.3.1",
|
||||||
"ua-parser-js": "1.0.36",
|
"ua-parser-js": "1.0.36",
|
||||||
"vditor": "3.9.5",
|
"vditor": "3.9.6",
|
||||||
"vue": "3.3.4",
|
"vue": "3.3.4",
|
||||||
"vue-i18n": "9.4.1",
|
"vue-i18n": "9.5.0",
|
||||||
"vue-router": "4.2.4",
|
"vue-router": "4.2.5",
|
||||||
"vuedraggable": "4.1.0",
|
"vuedraggable": "4.1.0",
|
||||||
"wangeditor": "4.7.15",
|
"wangeditor": "4.7.15",
|
||||||
"xgplayer": "3.0.9"
|
"xgplayer": "3.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@amap/amap-jsapi-types": "0.0.13",
|
"@amap/amap-jsapi-types": "0.0.13",
|
||||||
"@iconify/json": "2.2.118",
|
"@iconify/json": "2.2.128",
|
||||||
"@iconify/vue": "4.1.1",
|
"@iconify/vue": "4.1.1",
|
||||||
"@plugin-web-update-notification/vite": "^1.6.5",
|
"@soybeanjs/cli": "0.7.4",
|
||||||
"@soybeanjs/cli": "0.7.1",
|
|
||||||
"@soybeanjs/vite-plugin-vue-page-route": "0.0.10",
|
"@soybeanjs/vite-plugin-vue-page-route": "0.0.10",
|
||||||
"@types/bmapgl": "0.0.7",
|
"@types/bmapgl": "0.0.7",
|
||||||
"@types/crypto-js": "4.1.2",
|
"@types/crypto-js": "4.1.2",
|
||||||
"@types/node": "20.6.3",
|
"@types/node": "20.8.4",
|
||||||
"@types/qs": "6.9.8",
|
"@types/qs": "6.9.8",
|
||||||
"@types/ua-parser-js": "0.7.37",
|
"@types/ua-parser-js": "0.7.37",
|
||||||
"@unocss/preset-uno": "0.56.0",
|
"@unocss/preset-uno": "0.56.5",
|
||||||
"@unocss/transformer-directives": "0.56.0",
|
"@unocss/transformer-directives": "0.56.5",
|
||||||
"@unocss/vite": "0.56.0",
|
"@unocss/vite": "0.56.5",
|
||||||
"@vitejs/plugin-vue": "4.3.4",
|
"@vitejs/plugin-vue": "4.4.0",
|
||||||
"@vitejs/plugin-vue-jsx": "3.0.2",
|
"@vitejs/plugin-vue-jsx": "3.0.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.49.0",
|
"eslint": "8.51.0",
|
||||||
"eslint-config-soybeanjs": "0.5.6",
|
"eslint-config-soybeanjs": "0.5.7",
|
||||||
"mockjs": "1.1.0",
|
"mockjs": "1.1.0",
|
||||||
"rollup-plugin-visualizer": "5.9.2",
|
"rollup-plugin-visualizer": "5.9.2",
|
||||||
"sass": "1.67.0",
|
"sass": "1.69.3",
|
||||||
"simple-git-hooks": "2.9.0",
|
"simple-git-hooks": "2.9.0",
|
||||||
"tsx": "3.12.10",
|
"tsx": "3.13.0",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"unplugin-icons": "0.17.0",
|
"unplugin-icons": "0.17.0",
|
||||||
"unplugin-vue-components": "0.25.2",
|
"unplugin-vue-components": "0.25.2",
|
||||||
"vite": "4.4.9",
|
"vite": "4.4.11",
|
||||||
"vite-plugin-compression": "0.5.1",
|
"vite-plugin-compression": "0.5.1",
|
||||||
"vite-plugin-mock": "2.9.8",
|
"vite-plugin-mock": "2.9.8",
|
||||||
"vite-plugin-progress": "0.0.7",
|
"vite-plugin-progress": "0.0.7",
|
||||||
"vite-plugin-pwa": "0.16.5",
|
"vite-plugin-pwa": "0.16.5",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "2.0.1",
|
||||||
"vite-plugin-vue-devtools": "1.0.0-rc.4",
|
"vite-plugin-vue-devtools": "1.0.0-rc.5",
|
||||||
"vue-tsc": "1.8.13"
|
"vue-tsc": "1.8.19"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
|||||||
2035
pnpm-lock.yaml
generated
2035
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ import { useRouter } from 'vue-router';
|
|||||||
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
|
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
|
||||||
import { useRouteStore } from '@/store';
|
import { useRouteStore } from '@/store';
|
||||||
import { useBasicLayout } from '@/composables';
|
import { useBasicLayout } from '@/composables';
|
||||||
|
import { $t } from '@/locales';
|
||||||
import SearchResult from './search-result.vue';
|
import SearchResult from './search-result.vue';
|
||||||
import SearchFooter from './search-footer.vue';
|
import SearchFooter from './search-footer.vue';
|
||||||
|
|
||||||
@@ -82,14 +83,12 @@ watch(show, async val => {
|
|||||||
|
|
||||||
/** 查询 */
|
/** 查询 */
|
||||||
function search() {
|
function search() {
|
||||||
resultOptions.value = routeStore.searchMenus.filter(
|
resultOptions.value = routeStore.searchMenus.filter(menu => {
|
||||||
menu => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim())
|
const trimKeyword = keyword.value.toLocaleLowerCase().trim();
|
||||||
);
|
const title = (menu.meta.i18nTitle ? $t(menu.meta.i18nTitle) : menu.meta.title).toLocaleLowerCase();
|
||||||
if (resultOptions.value?.length > 0) {
|
return trimKeyword && title.includes(trimKeyword);
|
||||||
activePath.value = resultOptions.value[0].path;
|
});
|
||||||
} else {
|
activePath.value = resultOptions.value[0]?.path ?? '';
|
||||||
activePath.value = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
@mouseenter="handleMouse(item)"
|
@mouseenter="handleMouse(item)"
|
||||||
>
|
>
|
||||||
<svg-icon :icon="item.meta.icon" :local-icon="item.meta.localIcon" />
|
<svg-icon :icon="item.meta.icon" :local-icon="item.meta.localIcon" />
|
||||||
<span class="flex-1 ml-5px">{{ item.meta?.title }}</span>
|
<span class="flex-1 ml-5px">
|
||||||
|
{{ (item.meta?.i18nTitle && $t(item.meta?.i18nTitle)) || item.meta?.title }}
|
||||||
|
</span>
|
||||||
<icon-ant-design-enter-outlined class="icon text-20px p-2px mr-3px" />
|
<icon-ant-design-enter-outlined class="icon text-20px p-2px mr-3px" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useThemeStore } from '@/store';
|
import { useThemeStore } from '@/store';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
defineOptions({ name: 'SearchResult' });
|
defineOptions({ name: 'SearchResult' });
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getTopLevelMenu } from './helpers';
|
||||||
/**
|
/**
|
||||||
* 获取面包屑数据
|
* 获取面包屑数据
|
||||||
* @param activeKey - 当前页面路由的key
|
* @param activeKey - 当前页面路由的key
|
||||||
@@ -17,13 +18,9 @@ export function getBreadcrumbByRouteKey(activeKey: string, menus: App.GlobalMenu
|
|||||||
*/
|
*/
|
||||||
function getBreadcrumbMenu(activeKey: string, menus: App.GlobalMenuOption[]) {
|
function getBreadcrumbMenu(activeKey: string, menus: App.GlobalMenuOption[]) {
|
||||||
const breadcrumbMenu: App.GlobalMenuOption[] = [];
|
const breadcrumbMenu: App.GlobalMenuOption[] = [];
|
||||||
menus.some(menu => {
|
const topLevelMenu = getTopLevelMenu(activeKey, menus);
|
||||||
const flag = activeKey.includes(menu.routeName);
|
const options = topLevelMenu ? getBreadcrumbMenuItem(activeKey, topLevelMenu as App.GlobalMenuOption) : [];
|
||||||
if (flag) {
|
breadcrumbMenu.push(...options);
|
||||||
breadcrumbMenu.push(...getBreadcrumbMenuItem(activeKey, menu));
|
|
||||||
}
|
|
||||||
return flag;
|
|
||||||
});
|
|
||||||
return breadcrumbMenu;
|
return breadcrumbMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,3 +17,18 @@ function getConstantRouteName(route: AuthRoute.Route) {
|
|||||||
}
|
}
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路由名称查找顶级菜单
|
||||||
|
* @param routeName - 当前页面路由的key
|
||||||
|
* @param menus - 菜单数据
|
||||||
|
*/
|
||||||
|
export function getTopLevelMenu(routeName: string, menus: App.GlobalMenuOption[]): App.GlobalMenuOption | undefined {
|
||||||
|
return menus.find(item => {
|
||||||
|
if (item.routeName === routeName) return true;
|
||||||
|
if (Array.isArray(item.children)) {
|
||||||
|
return getTopLevelMenu(routeName, item.children);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,18 +63,29 @@ export function translateMenuLabel(menus: App.GlobalMenuOption[]): App.GlobalMen
|
|||||||
* @param menus - 菜单数据
|
* @param menus - 菜单数据
|
||||||
*/
|
*/
|
||||||
export function getActiveKeyPathsOfMenus(activeKey: string, menus: App.GlobalMenuOption[]) {
|
export function getActiveKeyPathsOfMenus(activeKey: string, menus: App.GlobalMenuOption[]) {
|
||||||
const keys = menus.map(menu => getActiveKeyPathsOfMenu(activeKey, menu)).flat(1);
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveKeyPathsOfMenu(activeKey: string, menu: App.GlobalMenuOption) {
|
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
if (activeKey.startsWith(menu.routeName)) {
|
const lists: App.GlobalMenuOption[] = [];
|
||||||
keys.push(menu.routeName);
|
function traverse(list: App.GlobalMenuOption[], parent: App.GlobalMenuOption | null = null) {
|
||||||
}
|
list.forEach((t: App.GlobalMenuOption) => {
|
||||||
if (menu.children) {
|
lists.push(t);
|
||||||
keys.push(...menu.children.map(item => getActiveKeyPathsOfMenu(activeKey, item as App.GlobalMenuOption)).flat(1));
|
if (parent) {
|
||||||
|
t.parent = parent;
|
||||||
|
}
|
||||||
|
if (t.children) {
|
||||||
|
traverse(t.children, t);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
traverse(JSON.parse(JSON.stringify(menus)));
|
||||||
|
lists.forEach((t: App.GlobalMenuOption) => {
|
||||||
|
if (t.routeName === activeKey) {
|
||||||
|
let temp = t;
|
||||||
|
while (temp) {
|
||||||
|
keys.push(temp.routeName);
|
||||||
|
temp = temp.parent as App.GlobalMenuOption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user