mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-12-25 14:20:21 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65c21812bb | ||
|
|
c3c975ee11 | ||
|
|
833018a831 | ||
|
|
3eb7f6f593 | ||
|
|
efcfa576d5 | ||
|
|
487213b648 | ||
|
|
4ee0d94f1b | ||
|
|
5fa822f4d4 | ||
|
|
8e6e787543 | ||
|
|
9917b5e53c | ||
|
|
8f3e855f41 | ||
|
|
808051b29d | ||
|
|
906aed5e75 | ||
|
|
2d64a2e57c | ||
|
|
0c70a9e083 |
@@ -182,7 +182,7 @@ module.exports = {
|
||||
pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'vuex', 'pinia', 'naive-ui']
|
||||
}
|
||||
],
|
||||
'import/no-unresolved': ['error', { ignore: ['uno.css', '~icons/*'] }],
|
||||
'import/no-unresolved': ['error', { ignore: ['uno.css', '~icons/*', 'virtual:svg-icons-register'] }],
|
||||
'import/prefer-default-export': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'no-param-reassign': [
|
||||
|
||||
1
.npmrc
1
.npmrc
@@ -1,2 +1,3 @@
|
||||
registry=https://registry.npmmirror.com/
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
|
||||
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -4,32 +4,23 @@
|
||||
"antfu.iconify",
|
||||
"antfu.unocss",
|
||||
"christian-kohler.path-intellisense",
|
||||
"coenraads.bracket-pair-colorizer-2",
|
||||
"dariofuzinato.vue-peek",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"dsznajder.es7-react-js-snippets",
|
||||
"eamodio.gitlens",
|
||||
"editorconfig.editorconfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"formulahendry.auto-complete-tag",
|
||||
"formulahendry.auto-close-tag",
|
||||
"formulahendry.auto-rename-tag",
|
||||
"jimdong.naive-ui-snippets",
|
||||
"kisstkondoros.vscode-gutter-preview",
|
||||
"lokalise.i18n-ally",
|
||||
"mhutchie.git-graph",
|
||||
"miguelsolorio.fluent-icons",
|
||||
"mikestead.dotenv",
|
||||
"naumovs.color-highlight",
|
||||
"pkief.material-icon-theme",
|
||||
"pranaygp.vscode-css-peek",
|
||||
"ritwickdey.liveserver",
|
||||
"steoates.autoimport",
|
||||
"vue.volar",
|
||||
"vue.vscode-typescript-vue-plugin",
|
||||
"whtouche.vscode-js-console-utils",
|
||||
"xabikos.javascriptsnippets",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"zhuangtongfa.material-theme"
|
||||
]
|
||||
}
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
@@ -18,7 +17,6 @@
|
||||
"@": "/src",
|
||||
"~@": "/src"
|
||||
},
|
||||
"i18n-ally.displayLanguage": "zh",
|
||||
"material-icon-theme.activeIconPack": "angular",
|
||||
"material-icon-theme.files.associations": {},
|
||||
"material-icon-theme.folders.associations": {
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -2,6 +2,22 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.9.6](https://github.com/honghuangdc/soybean-admin/compare/v0.9.5...v0.9.6) (2022-06-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **projects:** 本地svg动态渲染图标 ([c3c975e](https://github.com/honghuangdc/soybean-admin/commit/c3c975ee1142987b7ded0107bf91d0080d5651fe)), closes [#61](https://github.com/honghuangdc/soybean-admin/issues/61)
|
||||
* **projects:** 上下结构,菜单支持横向滚动 ([808051b](https://github.com/honghuangdc/soybean-admin/commit/808051b29dd682e1cbcf0e211774efb9cc12713a))
|
||||
* **projects:** 新增Antv G2图表示例 ([2d64a2e](https://github.com/honghuangdc/soybean-admin/commit/2d64a2e57c8d83c8d06f210eeefef8f31b3abeb9))
|
||||
* **projects:** 增加设置当前Tab页签名称功能 ([487213b](https://github.com/honghuangdc/soybean-admin/commit/487213b64853765e2bd186474e4607572624a33e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **projects:** 设置tab标题导致meta属性丢失 ([efcfa57](https://github.com/honghuangdc/soybean-admin/commit/efcfa576d52a7eab644f3b4c65af153442887fab))
|
||||
* **projects:** 修复顶部菜单的位置失效问题 ([4ee0d94](https://github.com/honghuangdc/soybean-admin/commit/4ee0d94f1bde83c788fc0dcb084402359c04fb1b))
|
||||
|
||||
### [0.9.5](https://github.com/honghuangdc/soybean-admin/compare/v0.9.4...v0.9.5) (2022-06-06)
|
||||
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -61,9 +61,13 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -71,14 +75,15 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
||||
|
||||
- [x] 引入ECharts替换AntV G2Plot
|
||||
- [x] 图表示例:ECharts、AntV G2
|
||||
- [ ] 多页签:支持query、hash等参数,同一页面支持多个Tab
|
||||
- [x] 多页签:支持query、hash等参数,同一页面支持多个Tab
|
||||
- [ ] 缓存主题配置
|
||||
- [ ] 添加锁屏组件、全局Iframe组件
|
||||
- [ ] 示例页面完善
|
||||
- [ ] 表单、表格示例
|
||||
- [ ] 性能优化(优化递归函数)
|
||||
- [ ] 精简版(新分支thin)
|
||||
- [ ] 文档完善
|
||||
- [ ] 表单、表格示例
|
||||
- [ ] i18n国际化
|
||||
- [ ] element-plus版本
|
||||
- [ ] 其他UI版本
|
||||
- [ ] soybean-admin cli工具(选择不同UI)
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './plugins';
|
||||
export * from './config';
|
||||
export * from './utils';
|
||||
|
||||
@@ -10,10 +10,9 @@ import compress from './compress';
|
||||
/**
|
||||
* vite插件
|
||||
* @param viteEnv - 环境变量配置
|
||||
* @param srcPath - src路径
|
||||
*/
|
||||
export function setupVitePlugins(viteEnv: ImportMetaEnv, srcPath: string): (PluginOption | PluginOption[])[] {
|
||||
const plugins = [...vue, html(viteEnv), ...unplugin(srcPath), unocss, mock];
|
||||
export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | PluginOption[])[] {
|
||||
const plugins = [...vue, html(viteEnv), ...unplugin, unocss, mock];
|
||||
|
||||
if (viteEnv.VITE_VISUALIZER === 'true') {
|
||||
plugins.push(visualizer);
|
||||
|
||||
@@ -4,22 +4,32 @@ import IconsResolver from 'unplugin-icons/resolver';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
|
||||
import { getSrcPath } from '../utils';
|
||||
|
||||
export default (srcPath: string) => {
|
||||
return [
|
||||
DefineOptions(),
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
customCollections: {
|
||||
custom: FileSystemIconLoader(`${srcPath}/assets/svg`)
|
||||
},
|
||||
scale: 1,
|
||||
defaultClass: 'inline-block'
|
||||
}),
|
||||
Components({
|
||||
dts: 'src/typings/components.d.ts',
|
||||
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
|
||||
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
|
||||
})
|
||||
];
|
||||
};
|
||||
const srcPath = getSrcPath();
|
||||
|
||||
const customIconPath = `${srcPath}/assets/svg`;
|
||||
|
||||
export default [
|
||||
DefineOptions(),
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
customCollections: {
|
||||
custom: FileSystemIconLoader(customIconPath)
|
||||
},
|
||||
scale: 1,
|
||||
defaultClass: 'inline-block'
|
||||
}),
|
||||
Components({
|
||||
dts: 'src/typings/components.d.ts',
|
||||
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
|
||||
resolvers: [NaiveUiResolver(), IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' })]
|
||||
}),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [customIconPath],
|
||||
symbolId: 'icon-custom-[dir]-[name]',
|
||||
inject: 'body-last',
|
||||
customDomId: '__CUSTOM_SVG_ICON__'
|
||||
})
|
||||
];
|
||||
|
||||
20
build/utils/index.ts
Normal file
20
build/utils/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* 获取项目根路径
|
||||
* @descrition 结尾不带斜杠
|
||||
*/
|
||||
export function getRootPath() {
|
||||
return path.resolve(process.cwd());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目src路径
|
||||
* @param srcName - src目录名称(默认: "src")
|
||||
* @descrition 结尾不带斜杠
|
||||
*/
|
||||
export function getSrcPath(srcName = 'src') {
|
||||
const rootPath = getRootPath();
|
||||
|
||||
return `${rootPath}/${srcName}`;
|
||||
}
|
||||
@@ -250,7 +250,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
meta: {
|
||||
title: '图标',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-insert-emoticon'
|
||||
customIcon: 'custom-icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -709,7 +709,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
meta: {
|
||||
title: '图标',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-insert-emoticon'
|
||||
customIcon: 'custom-icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
57
package.json
57
package.json
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"name": "soybean-admin",
|
||||
"version": "0.9.5",
|
||||
"license": "MIT",
|
||||
"version": "0.9.6",
|
||||
"author": {
|
||||
"name": "Soybean",
|
||||
"email": "honghuangdc@gmail.com",
|
||||
"url": "https://github.com/honghuangdc"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env VITE_ENV_TYPE=dev vite",
|
||||
"dev:test": "cross-env VITE_ENV_TYPE=test vite",
|
||||
@@ -28,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/data-set": "^0.11.8",
|
||||
"@antv/g2": "^4.2.2",
|
||||
"@antv/g2": "^4.2.3",
|
||||
"@better-scroll/core": "^2.4.2",
|
||||
"@soybeanjs/vue-admin-layout": "^1.0.4",
|
||||
"@soybeanjs/vue-admin-tab": "^1.0.2",
|
||||
@@ -38,18 +42,18 @@
|
||||
"colord": "^2.9.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.3",
|
||||
"echarts": "^5.3.2",
|
||||
"echarts": "^5.3.3",
|
||||
"form-data": "^4.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.30.0",
|
||||
"naive-ui": "^2.30.4",
|
||||
"pinia": "^2.0.14",
|
||||
"print-js": "^1.6.0",
|
||||
"qs": "^6.10.3",
|
||||
"swiper": "^8.2.2",
|
||||
"qs": "^6.10.5",
|
||||
"swiper": "^8.2.4",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"vditor": "^3.8.15",
|
||||
"vue": "3.2.36",
|
||||
"vue-router": "^4.0.15",
|
||||
"vue": "3.2.37",
|
||||
"vue-router": "^4.0.16",
|
||||
"wangeditor": "^4.7.15",
|
||||
"xgplayer": "^2.31.6"
|
||||
},
|
||||
@@ -57,19 +61,19 @@
|
||||
"@amap/amap-jsapi-types": "^0.0.8",
|
||||
"@commitlint/cli": "^17.0.2",
|
||||
"@commitlint/config-conventional": "^17.0.2",
|
||||
"@iconify/json": "^2.1.56",
|
||||
"@iconify/json": "^2.1.62",
|
||||
"@iconify/vue": "^3.2.1",
|
||||
"@types/bmapgl": "^0.0.5",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/node": "^17.0.40",
|
||||
"@types/node": "^17.0.44",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
||||
"@typescript-eslint/parser": "^5.28.0",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"commitizen": "^4.2.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
@@ -80,26 +84,35 @@
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "9.1.0",
|
||||
"eslint-plugin-vue": "9.1.1",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.0",
|
||||
"lint-staged": "^13.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier": "^2.7.0",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"sass": "^1.52.2",
|
||||
"sass": "^1.52.3",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^4.7.3",
|
||||
"unocss": "^0.37.4",
|
||||
"unocss": "^0.39.0",
|
||||
"unplugin-icons": "^0.14.3",
|
||||
"unplugin-vue-components": "0.19.6",
|
||||
"unplugin-vue-define-options": "^0.6.1",
|
||||
"vite": "^2.9.9",
|
||||
"vite": "^2.9.12",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-eslint-parser": "^9.0.2",
|
||||
"vue-tsc": "^0.36.1"
|
||||
}
|
||||
"vue-tsc": "^0.37.8"
|
||||
},
|
||||
"homepage": "https://github.com/honghuangdc/soybean-admin",
|
||||
"repository": {
|
||||
"url": "https://github.com/honghuangdc/soybean-admin.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/honghuangdc/soybean-admin/issues"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
1985
pnpm-lock.yaml
generated
1985
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
src/assets/svg/custom-icon.svg
Normal file
1
src/assets/svg/custom-icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="M19 10c0 1.38-2.12 2.5-3.5 2.5s-2.75-1.12-2.75-2.5h-1.5c0 1.38-1.37 2.5-2.75 2.5S5 11.38 5 10h-.75c-.16.64-.25 1.31-.25 2a8 8 0 0 0 8 8a8 8 0 0 0 8-8c0-.69-.09-1.36-.25-2H19m-7-6C9.04 4 6.45 5.61 5.07 8h13.86C17.55 5.61 14.96 4 12 4m10 8a10 10 0 0 1-10 10A10 10 0 0 1 2 12A10 10 0 0 1 12 2a10 10 0 0 1 10 10m-10 5.23c-1.75 0-3.29-.73-4.19-1.81L9.23 14c.45.72 1.52 1.23 2.77 1.23s2.32-.51 2.77-1.23l1.42 1.42c-.9 1.08-2.44 1.81-4.19 1.81Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 702 B |
24
src/components/custom/SvgIcon.vue
Normal file
24
src/components/custom/SvgIcon.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" width="1em" height="1em" class="inline-block">
|
||||
<use :xlink:href="symbolId" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/** 前缀 */
|
||||
prefix?: string;
|
||||
/** 图标名称(图片的文件名) */
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
prefix: 'icon-custom'
|
||||
});
|
||||
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.icon}`);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<n-menu
|
||||
:value="activeKey"
|
||||
mode="horizontal"
|
||||
:options="menus"
|
||||
:inverted="theme.header.inverted"
|
||||
@update:value="handleUpdateMenu"
|
||||
/>
|
||||
<div class="flex-1-hidden h-full px-10px">
|
||||
<n-scrollbar :x-scrollable="true" class="flex-1-hidden h-full" content-class="h-full">
|
||||
<div class="flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }">
|
||||
<n-menu
|
||||
:value="activeKey"
|
||||
mode="horizontal"
|
||||
:options="menus"
|
||||
:inverted="theme.header.inverted"
|
||||
@update:value="handleUpdateMenu"
|
||||
/>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -29,4 +35,8 @@ function handleUpdateMenu(_key: string, item: MenuOption) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
:deep(.n-menu-item-content-header) {
|
||||
overflow: inherit !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
<menu-collapse v-if="showMenuCollapse" />
|
||||
<global-breadcrumb v-if="theme.header.crumb.visible" />
|
||||
</div>
|
||||
<div v-else class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }">
|
||||
<header-menu />
|
||||
</div>
|
||||
<header-menu v-else />
|
||||
<div class="flex justify-end h-full">
|
||||
<global-search />
|
||||
<github-site />
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'uno.css';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import 'virtual:svg-icons-register';
|
||||
import '../styles/css/global.css';
|
||||
|
||||
/** import static assets: css, js , font and so on. - [引入静态资源,css、js和字体文件等] */
|
||||
|
||||
@@ -50,6 +50,16 @@ export const useTabStore = defineStore('tab-store', {
|
||||
setActiveTab(fullPath: string) {
|
||||
this.activeTab = fullPath;
|
||||
},
|
||||
/**
|
||||
* 设置当前路由对应的页签title
|
||||
* @param title - tab名称
|
||||
*/
|
||||
setActiveTabTitle(title: string) {
|
||||
const item = this.tabs.find(tab => tab.fullPath === this.activeTab);
|
||||
if (item) {
|
||||
item.meta.title = title;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 初始化首页页签路由
|
||||
* @param routeHomeName - 路由首页的name
|
||||
|
||||
2
src/typings/route.d.ts
vendored
2
src/typings/route.d.ts
vendored
@@ -94,6 +94,8 @@ declare namespace AuthRoute {
|
||||
keepAlive?: boolean;
|
||||
/** 菜单和面包屑对应的图标 */
|
||||
icon?: string;
|
||||
/** 自定义的菜单和面包屑对应的图标 */
|
||||
customIcon?: string;
|
||||
/** 是否在菜单中隐藏(一些列表、表格的详情页面需要通过参数跳转,所以不能显示在菜单中) */
|
||||
hide?: boolean;
|
||||
/** 外链链接 */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { h } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import SvgIcon from '@/components/custom/SvgIcon.vue';
|
||||
|
||||
/**
|
||||
* 动态渲染iconify
|
||||
@@ -17,3 +18,21 @@ export function iconifyRender(icon: string, color?: string, size?: number) {
|
||||
}
|
||||
return () => h(Icon, { icon, style });
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态渲染自定义图标
|
||||
* @param icon - 图标名称
|
||||
* @param color - 图标颜色
|
||||
* @param size - 图标大小
|
||||
*/
|
||||
export function customIconRender(icon: string, color?: string, size?: number) {
|
||||
const style: { color?: string; size?: string } = {};
|
||||
if (color) {
|
||||
style.color = color;
|
||||
}
|
||||
if (size) {
|
||||
style.size = `${size}px`;
|
||||
}
|
||||
|
||||
return () => h(SvgIcon, { icon, style });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { iconifyRender } from '../common';
|
||||
import { iconifyRender, customIconRender } from '../common';
|
||||
|
||||
/** 路由不转换菜单 */
|
||||
function hideInMenu(route: AuthRoute.Route) {
|
||||
@@ -6,13 +6,21 @@ function hideInMenu(route: AuthRoute.Route) {
|
||||
}
|
||||
|
||||
/** 给菜单添加可选属性 */
|
||||
function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: GlobalMenuOption[]) {
|
||||
const item = { ...menuItem };
|
||||
if (icon) {
|
||||
Object.assign(item, { icon: iconifyRender(icon) });
|
||||
function addPartialProps(config: {
|
||||
menu: GlobalMenuOption;
|
||||
icon?: string;
|
||||
customIcon?: string;
|
||||
children?: GlobalMenuOption[];
|
||||
}) {
|
||||
const item = { ...config.menu };
|
||||
if (config.icon) {
|
||||
Object.assign(item, { icon: iconifyRender(config.icon) });
|
||||
}
|
||||
if (children) {
|
||||
Object.assign(item, { children });
|
||||
if (config.customIcon) {
|
||||
Object.assign(item, { icon: customIconRender(config.customIcon) });
|
||||
}
|
||||
if (config.children) {
|
||||
Object.assign(item, { children: config.children });
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@@ -30,16 +38,17 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuO
|
||||
if (route.children) {
|
||||
menuChildren = transformAuthRouteToMenu(route.children);
|
||||
}
|
||||
const menuItem: GlobalMenuOption = addPartialProps(
|
||||
{
|
||||
const menuItem: GlobalMenuOption = addPartialProps({
|
||||
menu: {
|
||||
key: routeName,
|
||||
label: meta.title,
|
||||
routeName,
|
||||
routePath: path
|
||||
},
|
||||
meta?.icon,
|
||||
menuChildren
|
||||
);
|
||||
icon: meta.icon,
|
||||
customIcon: meta.customIcon,
|
||||
children: menuChildren
|
||||
});
|
||||
|
||||
if (!hideInMenu(route)) {
|
||||
globalMenu.push(menuItem);
|
||||
|
||||
@@ -5,16 +5,24 @@
|
||||
<n-button @click="handleToTabDetail">跳转Tab Detail</n-button>
|
||||
<n-button @click="handleToTabMultiDetail(1)">跳转Tab Multi Detail 1</n-button>
|
||||
<n-button @click="handleToTabMultiDetail(2)">跳转Tab Multi Detail 2</n-button>
|
||||
<n-input-group>
|
||||
<n-input v-model:value="title" />
|
||||
<n-button type="primary" @click="handleSetTitle">设置当前Tab页标题</n-button>
|
||||
</n-input-group>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { routeName } from '@/router';
|
||||
import { useTabStore } from '@/store';
|
||||
import { useRouterPush } from '@/composables';
|
||||
|
||||
const { routerPush } = useRouterPush();
|
||||
const tabStore = useTabStore();
|
||||
const title = ref('');
|
||||
|
||||
function handleToTabDetail() {
|
||||
routerPush({ name: routeName('function_tab-detail'), query: { name: 'abc' }, hash: '#DEMO_HASH' });
|
||||
@@ -23,6 +31,14 @@ function handleToTabDetail() {
|
||||
function handleToTabMultiDetail(num: number) {
|
||||
routerPush({ name: routeName('function_tab-multi-detail'), query: { name: 'abc', num }, hash: '#DEMO_HASH' });
|
||||
}
|
||||
|
||||
function handleSetTitle() {
|
||||
if (!title.value) {
|
||||
window.$message?.warning('请输入要设置的标题名称');
|
||||
} else {
|
||||
tabStore.setActiveTabTitle(title.value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<n-card :bordered="false" class="rounded-16px shadow-sm">
|
||||
<div ref="scatterRef" class="h-400px"></div>
|
||||
</n-card>
|
||||
<n-card :bordered="false" class="rounded-16px shadow-sm">
|
||||
<div ref="areaRef" class="h-400px"></div>
|
||||
</n-card>
|
||||
<n-card :bordered="false" class="rounded-16px shadow-sm">
|
||||
<div ref="radarRef" class="h-400px"></div>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
@@ -24,6 +30,8 @@ const pieRef = ref<HTMLElement | null>(null);
|
||||
const lineRef = ref<HTMLElement | null>(null);
|
||||
const barRef = ref<HTMLElement | null>(null);
|
||||
const scatterRef = ref<HTMLElement | null>(null);
|
||||
const areaRef = ref<HTMLElement | null>(null);
|
||||
const radarRef = ref<HTMLElement | null>(null);
|
||||
|
||||
function renderPieChart() {
|
||||
if (!pieRef.value) return;
|
||||
@@ -241,58 +249,229 @@ function renderBarChart() {
|
||||
}
|
||||
|
||||
function renderScatterChart() {
|
||||
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json')
|
||||
const colorMap = {
|
||||
Asia: '#1890FF',
|
||||
Americas: '#2FC25B',
|
||||
Europe: '#FACC14',
|
||||
Oceania: '#223273'
|
||||
};
|
||||
|
||||
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/bubble.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!scatterRef.value) return;
|
||||
|
||||
const chart = new Chart({
|
||||
container: scatterRef.value,
|
||||
autoFit: true,
|
||||
height: 500
|
||||
});
|
||||
chart.data(data);
|
||||
// 为各个字段设置别名
|
||||
chart.scale({
|
||||
height: { nice: true },
|
||||
weight: { nice: true }
|
||||
LifeExpectancy: {
|
||||
alias: '人均寿命(年)',
|
||||
nice: true
|
||||
},
|
||||
Population: {
|
||||
type: 'pow',
|
||||
alias: '人口总数'
|
||||
},
|
||||
GDP: {
|
||||
alias: '人均国内生产总值($)',
|
||||
nice: true
|
||||
},
|
||||
Country: {
|
||||
alias: '国家/地区'
|
||||
}
|
||||
});
|
||||
chart.axis('GDP', {
|
||||
label: {
|
||||
formatter(value) {
|
||||
return `${(+value / 1000).toFixed(0)}k`;
|
||||
} // 格式化坐标轴的显示
|
||||
}
|
||||
});
|
||||
chart.tooltip({
|
||||
showTitle: false,
|
||||
showCrosshairs: true,
|
||||
crosshairs: {
|
||||
type: 'xy'
|
||||
},
|
||||
itemTpl:
|
||||
'<li class="g2-tooltip-list-item" data-index={index} style="margin-bottom:4px;">' +
|
||||
'<span style="background-color:{color};" class="g2-tooltip-marker"></span>' +
|
||||
'{name}<br/>' +
|
||||
'{value}' +
|
||||
'</li>'
|
||||
showMarkers: false
|
||||
});
|
||||
chart.legend('Population', false); // 该图表默认会生成多个图例,设置不展示 Population 和 Country 两个维度的图例
|
||||
chart
|
||||
.point()
|
||||
.position('height*weight')
|
||||
.color('gender')
|
||||
.shape('circle')
|
||||
.tooltip('gender*height*weight', (gender, height, weight) => {
|
||||
return {
|
||||
name: gender,
|
||||
value: `${height}(cm), ${weight}(kg)`
|
||||
};
|
||||
.position('GDP*LifeExpectancy')
|
||||
.size('Population', [4, 65])
|
||||
.color('continent', val => {
|
||||
const key = val as keyof typeof colorMap;
|
||||
return colorMap[key];
|
||||
})
|
||||
.style({
|
||||
fillOpacity: 0.85
|
||||
.shape('circle')
|
||||
.tooltip('Country*Population*GDP*LifeExpectancy')
|
||||
.style('continent', val => {
|
||||
const key = val as keyof typeof colorMap;
|
||||
return {
|
||||
lineWidth: 1,
|
||||
strokeOpacity: 1,
|
||||
fillOpacity: 0.3,
|
||||
opacity: 0.65,
|
||||
stroke: colorMap[key]
|
||||
};
|
||||
});
|
||||
chart.interaction('legend-highlight');
|
||||
chart.interaction('element-active');
|
||||
chart.render();
|
||||
});
|
||||
}
|
||||
|
||||
function renderAreaChart() {
|
||||
if (!areaRef.value) return;
|
||||
|
||||
const data = [
|
||||
{ country: 'Asia', year: '1750', value: 502 },
|
||||
{ country: 'Asia', year: '1800', value: 635 },
|
||||
{ country: 'Asia', year: '1850', value: 809 },
|
||||
{ country: 'Asia', year: '1900', value: 5268 },
|
||||
{ country: 'Asia', year: '1950', value: 4400 },
|
||||
{ country: 'Asia', year: '1999', value: 3634 },
|
||||
{ country: 'Asia', year: '2050', value: 947 },
|
||||
{ country: 'Africa', year: '1750', value: 106 },
|
||||
{ country: 'Africa', year: '1800', value: 107 },
|
||||
{ country: 'Africa', year: '1850', value: 111 },
|
||||
{ country: 'Africa', year: '1900', value: 1766 },
|
||||
{ country: 'Africa', year: '1950', value: 221 },
|
||||
{ country: 'Africa', year: '1999', value: 767 },
|
||||
{ country: 'Africa', year: '2050', value: 133 },
|
||||
{ country: 'Europe', year: '1750', value: 163 },
|
||||
{ country: 'Europe', year: '1800', value: 203 },
|
||||
{ country: 'Europe', year: '1850', value: 276 },
|
||||
{ country: 'Europe', year: '1900', value: 628 },
|
||||
{ country: 'Europe', year: '1950', value: 547 },
|
||||
{ country: 'Europe', year: '1999', value: 729 },
|
||||
{ country: 'Europe', year: '2050', value: 408 },
|
||||
{ country: 'Oceania', year: '1750', value: 200 },
|
||||
{ country: 'Oceania', year: '1800', value: 200 },
|
||||
{ country: 'Oceania', year: '1850', value: 200 },
|
||||
{ country: 'Oceania', year: '1900', value: 460 },
|
||||
{ country: 'Oceania', year: '1950', value: 230 },
|
||||
{ country: 'Oceania', year: '1999', value: 300 },
|
||||
{ country: 'Oceania', year: '2050', value: 300 }
|
||||
];
|
||||
const chart = new Chart({
|
||||
container: areaRef.value,
|
||||
autoFit: true,
|
||||
height: 500
|
||||
});
|
||||
|
||||
chart.data(data);
|
||||
chart.scale('year', {
|
||||
type: 'linear',
|
||||
tickInterval: 50
|
||||
});
|
||||
chart.scale('value', {
|
||||
nice: true
|
||||
});
|
||||
|
||||
chart.tooltip({
|
||||
showCrosshairs: true,
|
||||
shared: true
|
||||
});
|
||||
|
||||
chart.area().adjust('stack').position('year*value').color('country');
|
||||
chart.line().adjust('stack').position('year*value').color('country');
|
||||
|
||||
chart.interaction('element-highlight');
|
||||
|
||||
chart.render();
|
||||
}
|
||||
|
||||
function renderRadarChart() {
|
||||
if (!radarRef.value) return;
|
||||
|
||||
const data = [
|
||||
{ item: 'Design', a: 70, b: 30 },
|
||||
{ item: 'Development', a: 60, b: 70 },
|
||||
{ item: 'Marketing', a: 50, b: 60 },
|
||||
{ item: 'Users', a: 40, b: 50 },
|
||||
{ item: 'Test', a: 60, b: 70 },
|
||||
{ item: 'Language', a: 70, b: 50 },
|
||||
{ item: 'Technology', a: 50, b: 40 },
|
||||
{ item: 'Support', a: 30, b: 40 },
|
||||
{ item: 'Sales', a: 60, b: 40 },
|
||||
{ item: 'UX', a: 50, b: 60 }
|
||||
];
|
||||
const { DataView } = DataSet;
|
||||
const dv = new DataView().source(data);
|
||||
dv.transform({
|
||||
type: 'fold',
|
||||
fields: ['a', 'b'], // 展开字段集
|
||||
key: 'user', // key字段
|
||||
value: 'score' // value字段
|
||||
});
|
||||
|
||||
const chart = new Chart({
|
||||
container: radarRef.value,
|
||||
autoFit: true,
|
||||
height: 500
|
||||
});
|
||||
chart.data(dv.rows);
|
||||
chart.scale('score', {
|
||||
min: 0,
|
||||
max: 80
|
||||
});
|
||||
chart.coordinate('polar', {
|
||||
radius: 0.8
|
||||
});
|
||||
chart.tooltip({
|
||||
shared: true,
|
||||
showCrosshairs: true,
|
||||
crosshairs: {
|
||||
line: {
|
||||
style: {
|
||||
lineDash: [4, 4],
|
||||
stroke: '#333'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
chart.axis('item', {
|
||||
line: null,
|
||||
tickLine: null,
|
||||
grid: {
|
||||
line: {
|
||||
style: {
|
||||
lineDash: null
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
chart.axis('score', {
|
||||
line: null,
|
||||
tickLine: null,
|
||||
grid: {
|
||||
line: {
|
||||
type: 'line',
|
||||
style: {
|
||||
lineDash: null
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chart.line().position('item*score').color('user').size(2);
|
||||
chart.point().position('item*score').color('user').shape('circle').size(4).style({
|
||||
stroke: '#fff',
|
||||
lineWidth: 1,
|
||||
fillOpacity: 1
|
||||
});
|
||||
chart.area().position('item*score').color('user');
|
||||
chart.render();
|
||||
}
|
||||
|
||||
function init() {
|
||||
renderPieChart();
|
||||
renderLineChart();
|
||||
renderBarChart();
|
||||
renderScatterChart();
|
||||
renderAreaChart();
|
||||
renderRadarChart();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<web-site-link label="iconify地址:" link="https://icones.js.org/" class="mt-10px" />
|
||||
</template>
|
||||
</n-card>
|
||||
<n-card title="SvgIcon 示例" class="mt-10px shadow-sm rounded-16px">
|
||||
<n-card title="自定义图标示例" class="mt-10px shadow-sm rounded-16px">
|
||||
<div class="pb-12px text-16px">
|
||||
在src/assets/svg文件夹下的svg文件,通过在template里面以 icon-custom-{文件名} 直接渲染,动态渲染需要import组件
|
||||
在src/assets/svg文件夹下的svg文件,通过在template里面以 icon-custom-{文件名} 直接渲染
|
||||
</div>
|
||||
<div class="grid grid-cols-10">
|
||||
<div class="mt-5px flex-x-center">
|
||||
@@ -27,8 +27,11 @@
|
||||
<div class="mt-5px flex-x-center">
|
||||
<icon-custom-cast class="text-20px text-error" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-12px text-16px">通过SvgIcon组件动态渲染, 菜单通过meta的customIcon属性渲染自定义图标</div>
|
||||
<div class="grid grid-cols-10">
|
||||
<div v-for="(item, index) in customIcons" :key="index" class="mt-5px flex-x-center">
|
||||
<component :is="item" class="text-30px text-primary" />
|
||||
<svg-icon :icon="item" class="text-30px text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
@@ -39,16 +42,10 @@
|
||||
import { ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { icons } from './icons';
|
||||
import CustomActivity from '~icons/custom/activity.svg';
|
||||
import CustomAtSign from '~icons/custom/at-sign.svg';
|
||||
import CustomCast from '~icons/custom/cast.svg';
|
||||
import CustomChrome from '~icons/custom/chrome.svg';
|
||||
import CustomCopy from '~icons/custom/copy.svg';
|
||||
import CustomWind from '~icons/custom/wind.svg';
|
||||
|
||||
const selectValue = ref('');
|
||||
|
||||
const customIcons = [CustomActivity, CustomAtSign, CustomCast, CustomChrome, CustomCopy, CustomWind];
|
||||
const customIcons = ['custom-icon', 'activity', 'at-sign', 'cast', 'chrome', 'copy', 'wind'];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -11,9 +11,9 @@ const { load } = useScriptTag(BAIDU_MAP_SDK_URL);
|
||||
|
||||
const domRef = ref<HTMLDivElement>();
|
||||
|
||||
async function renderBaiduMap() {
|
||||
if (!domRef.value) return;
|
||||
async function renderMap() {
|
||||
await load(true);
|
||||
if (!domRef.value) return;
|
||||
const map = new BMap.Map(domRef.value);
|
||||
const point = new BMap.Point(114.05834626586915, 22.546789983033168);
|
||||
map.centerAndZoom(point, 15);
|
||||
@@ -21,7 +21,7 @@ async function renderBaiduMap() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderBaiduMap();
|
||||
renderMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,19 +11,19 @@ const { load } = useScriptTag(GAODE_MAP_SDK_URL);
|
||||
|
||||
const domRef = ref<HTMLDivElement>();
|
||||
|
||||
async function renderBaiduMap() {
|
||||
if (!domRef.value) return;
|
||||
async function renderMap() {
|
||||
await load(true);
|
||||
if (!domRef.value) return;
|
||||
const map = new AMap.Map(domRef.value, {
|
||||
zoom: 11,
|
||||
center: [114.05834626586915, 22.546789983033168],
|
||||
viewMode: '3D'
|
||||
});
|
||||
window.console.log(map);
|
||||
map.getCenter();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderBaiduMap();
|
||||
renderMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,19 +11,19 @@ const { load } = useScriptTag(TENCENT_MAP_SDK_URL);
|
||||
|
||||
const domRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
async function renderBaiduMap() {
|
||||
if (!domRef.value) return;
|
||||
async function renderMap() {
|
||||
await load(true);
|
||||
const map = new TMap.Map(domRef.value, {
|
||||
if (!domRef.value) return;
|
||||
// eslint-disable-next-line no-new
|
||||
new TMap.Map(domRef.value, {
|
||||
center: new TMap.LatLng(39.98412, 116.307484),
|
||||
zoom: 11,
|
||||
viewMode: '3D'
|
||||
});
|
||||
window.console.log(map);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderBaiduMap();
|
||||
renderMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
@@ -24,5 +25,5 @@
|
||||
"naive-ui/volar"
|
||||
]
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { fileURLToPath } from 'url';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import { viteDefine, setupVitePlugins, createViteProxy } from './build';
|
||||
import { getRootPath, getSrcPath, viteDefine, setupVitePlugins, createViteProxy } from './build';
|
||||
import { getEnvConfig } from './.env-config';
|
||||
|
||||
export default defineConfig(configEnv => {
|
||||
const viteEnv = loadEnv(configEnv.mode, process.cwd()) as ImportMetaEnv;
|
||||
|
||||
const rootPath = fileURLToPath(new URL('./', import.meta.url));
|
||||
const srcPath = `${rootPath}src`;
|
||||
const rootPath = getRootPath();
|
||||
const srcPath = getSrcPath();
|
||||
|
||||
const isOpenProxy = viteEnv.VITE_HTTP_PROXY === 'true';
|
||||
const envConfig = getEnvConfig(viteEnv);
|
||||
@@ -21,7 +20,7 @@ export default defineConfig(configEnv => {
|
||||
}
|
||||
},
|
||||
define: viteDefine,
|
||||
plugins: setupVitePlugins(viteEnv, srcPath),
|
||||
plugins: setupVitePlugins(viteEnv),
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
|
||||
Reference in New Issue
Block a user