--- url: 'https://docs.soybeanjs.cn/zh/guide/intro.md' --- # 介绍 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite6, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件,代码规范严谨,实现了自动化的文件路由系统。此外,它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。 ## 特性 * **前沿技术应用**:采用 Vue3, Vite6, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。 * **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。 * **严格的代码规范**:遵循 [SoybeanJS 规范](/zh/standard/),集成了eslint, prettier 和 simple-git-hooks,保证代码的规范性。 * **TypeScript**: 支持严格的类型检查,提高代码的可维护性。 * **丰富的主题配置**:内置多样的主题配置,与 UnoCSS 完美结合。 * **内置国际化方案**:轻松实现多语言支持。 * **自动化文件路由系统**:自动生成路由导入、声明和类型。更多细节请查看 [Elegant Router](https://github.com/soybeanjs/elegant-router)。 * **灵活的权限路由**:同时支持前端静态路由和后端动态路由。 * **丰富的页面组件**:内置多样页面和组件,包括403、404、500页面,以及布局组件、标签组件、主题配置组件等。 * **命令行工具**:内置高效的命令行工具,git提交、删除文件、发布等。 * **移动端适配**:完美支持移动端,实现自适应布局。 ## 版本 * **NaiveUI 版本:** * [预览地址](https://naive.soybeanjs.cn/) * [Github 仓库](https://github.com/soybeanjs/soybean-admin) * [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin) * **AntDesignVue 版本:** * [预览地址](https://antd.soybeanjs.cn/) * [Github 仓库](https://github.com/soybeanjs/soybean-admin-antd) * [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-antd) * **ElementPlus 版本:** * [预览地址](https://elp.soybeanjs.cn/) * [Github 仓库](https://github.com/soybeanjs/soybean-admin-element-plus) * [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-element-plus) * **旧版:** * [预览地址](https://legacy.soybeanjs.cn/) * [Github 仓库](https://github.com/soybeanjs/soybean-admin/tree/legacy) ## 分支 为方便用户使用,`main` 分支默认采用精简版,只保留核心框架内容,不包含业务性较高的示例,如果您需要更多的示例参考,可以切换到 `example` 分支,该分支即预览地址所呈现的内容,包含完整的示例菜单。 ## 文档 * 文档地址为 [soybean-admin-docs](https://github.com/soybeanjs/soybean-admin-docs),采用 Vitepress 开发。如发现文档有误,欢迎提 pr 帮助我们改进。 ## 需要掌握的基础知识 本项目基于 Vue3, Vite, TS 开发,并全部采用了 Vue3 的**script-setup**写法,建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助: * [ES6](https://es6.ruanyifeng.com/) * [Vue3](https://vuejs.org/) * [Vite](https://vitejs.dev/) * [TypeScript](https://jkchao.github.io/typescript-book-chinese/#why) * [Vue Router](https://router.vuejs.org/) * [Pinia](https://pinia.vuejs.org/) * [UnoCSS](https://uno.antfu.me/) * [VueUse](https://vueuse.org/) * [NaiveUI](https://www.naiveui.com/zh-CN/os-theme) / [AntDesign Vue](https://www.antdv.com/components/overview-cn/) ## 浏览器支持 本地开发推荐使用`Chrome 100+` 浏览器 支持现代浏览器, 不支持 IE | IE | Edge | Firefox | Chrome | Safari | | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | ## 如何加入我们 * [SoybeanAdmin](https://github.com/honghuangdc/soybean-admin) 还在持续更新中,本项目欢迎您的参与,共同维护,逐步完善,将项目做得更强。项目采用 MIT 开源协议,本着一切免费的原则,原则上不会收取任何费用及版权,可以放心使用。 * 如果你想加入我们,可以多提供一些好的建议或者提交 pr,我们会根据你的活跃度邀请你加入。 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/quick-start.md' --- # 快速开始 本文会帮助你从头启动项目 ## 环境准备 确保你的环境满足以下要求: * **git**: 你需要git来克隆和管理项目版本。[安装教程](../tutorial/git.md) * **NodeJS**: >=18.12.0,推荐 18.19.0 或更高。[安装教程](../tutorial/nodejs.md) * **pnpm**: >= 8.7.0,推荐最新版本。 ### Mock 本项目借助 [Apifox](https://apifox.com/) 的云端mock功能实现mock请求,接口文档:[soybean-admin-mock](https://apifox.com/apidoc/shared-35c8727a-d3ab-47e9-8863-ef8e37df6887?pwd=F6LN1ArU) ## VSCode插件 本项目推荐使用 VSCode 进行开发,项目里面已内置 VSCode 配置,包含推荐的插件和设置。 以下为推荐的插件: * [Auto Close Tag](https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-close-tag) - 自动添加 HTML/XML 结束标签 * [Auto Complete Tag](https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-complete-tag) - 为 HTML/XML 添加关闭标签和自动重命名成对的标签 * [Auto Rename Tag](https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag) - 自动重命名成对的 HTML/XML 标签 * [Color Highlight](https://github.com/naumovs/vscode-ext-color-highlight) - 颜色高亮插件 * [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - 高亮.env 文件 * [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) - 统一不同编辑器的一些配置 * [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 代码检查 * [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) - Git 图形化操作工具 * [GitLens — Git supercharged](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) - 显示具体某行代码的 git 信息 * [Icônes](https://marketplace.visualstudio.com/items?itemName=afzalsayed96.icones) - 搜索 iconify 图标的插件 * [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify 图标实时显示的插件 * [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n 国际化插件 * [javascript console utils](https://marketplace.visualstudio.com/items?itemName=whtouche.vscode-js-console-utils) - 提供快捷键 ctrl+l 直接输入 console.log() * [Material Icon Theme](https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme) - 图标主题,显示文件和文件多种图标 * [One Dark Pro](https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme) - 主题 * [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - 代码格式化插件 * [UnoCSS](https://marketplace.visualstudio.com/items?itemName=antfu.unocss) - unocss 写法提示插件 * [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 服务插件 * [Vue VSCode Snippets](https://marketplace.visualstudio.com/items?itemName=sdras.vue-vscode-snippets) - Vue2、Vue3 写法提示插件 ## 代码获取 ### 从 GitHub 获取代码 ```bash # 克隆代码 git clone https://github.com/soybeanjs/soybean-admin.git ``` ### 从 Gitee 获取代码 ```bash # 克隆代码 git clone https://gitee.com/honghuangdc/soybean-admin.git ``` ::: warning 注意 最新版本的代码以 github 为准。 ::: ### 安装依赖 安装项目依赖 ```bash pnpm i ``` ## 插件配置 ### 安装 Vue - Official,禁用 Vetur * [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 服务插件 ## npm scripts ```json { // 构建打包(prod环境) "build": "vite build --mode prod", // 构建打包(test环境) "build:test": "vite build --mode test", // 删除主项目及子项目的 node_modules, dist, pnpm-lock.yaml "cleanup": "sa cleanup", // 提交代码 (生成符合 Conventional Commits standard 的提交信息) "commit": "sa git-commit", // 本地运行(test环境) "dev": "vite --mode test", // 本地运行(prod环境) "dev:prod": "vite --mode prod", // 生成路由 "gen-route": "sa gen-route", // eslint检查并自动修复 "lint": "eslint . --fix", // 初始化 simple-git-hooks "prepare": "simple-git-hooks", // 本地环境预览构建后的dist "preview": "vite preview", // 发布 "release": "sa release", // vue文件的ts检查 "typecheck": "vue-tsc --noEmit --skipLibCheck", // 更新依赖包 "update-pkg": "sa update-pkg" } ``` ## 目录说明 ``` soybean-admin ├── .vscode //vscode插件和设置 │ ├── extensions.json //vscode推荐的插件 │ ├── launch.json //debug配置文件(debug Vue 和 TS) │ └── settings.json //vscode配置(在该项目中生效,可以复制到用户配置文件中) ├── build //vite构建相关配置和插件 │ ├── config //构建打包配置 │ │ └── proxy.ts //网络请求代理 │ └── plugins //构建插件 │ ├── index.ts //插件汇总 │ ├── router.ts //elegant-router插件 │ ├── unocss.ts //unocss插件 │ └── unplugin.ts //自动导入UI组件、自动解析iconify图标、自动解析本地svg作为图标 ├── packages //子项目 │ ├── axios //网络请求封装 │ ├── color-palette //颜色调色板 │ ├── hooks //组合式函数hooks │ ├── materials //组件物料 │ ├── ofetch //网络请求封装 │ ├── scripts //脚本 │ ├── uno-preset //uno-preset配置 │ └── utils //工具函数 ├── public //公共目录(文件夹里面的资源打包后会在根目录下) │ └── favicon.svg //网站标签图标 ├── src │ ├── assets //静态资源 │ │ ├── imgs //图片 │ │ └── svg-icon //本地svg图标 │ ├── components //全局组件 │ │ ├── advanced //高级组件 │ │ ├── common //公共组件 │ │ └── custom //自定义组件 │ ├── constants //常量 │ │ ├── app.ts //app常量 │ │ ├── business.ts //业务常量 │ │ ├── common.ts //通用常量 │ │ └── reg.ts //正则常量 │ ├── enums //枚举 │ ├── hooks //组合式的函数hooks │ │ ├── business //业务hooks │ │ │ ├── auth //用户权限 │ │ │ └── captcha //验证码 │ │ └── common //通用hooks │ │ ├── echarts //echarts │ │ ├── form //表单 │ │ ├── icon //图标 │ │ ├── router //路由 │ │ └── table //表格 │ ├── layouts //布局组件 │ │ ├── base-layout //基本布局(包含全局头部、多页签、侧边栏、底部等公共部分) │ │ ├── blank-layout //空白布局组件(单个页面) │ │ ├── context //布局组件的上下文状态 │ │ ├── hooks //布局组件的hooks │ │ └── modules //布局组件模块 │ │ ├── global-breadcrumb //全局面包屑 │ │ ├── global-content //全局主体内容 │ │ ├── global-footer //全局底部 │ │ ├── global-header //全局头部 │ │ ├── global-logo //全局Logo │ │ ├── global-menu //全局菜单 │ │ ├── global-search //全局搜索 │ │ ├── global-sider //全局侧边栏 │ │ ├── global-tab //全局标签页 │ │ └── theme-drawer //主题抽屉 │ ├── locales //国际化配置 │ │ ├── langs //语言文件 │ │ ├── dayjs.ts //dayjs的国际化配置 │ │ ├── locale.ts //语言文件汇总 │ │ └── naive.ts //NaiveUI的国际化配置 │ ├── plugins //插件 │ │ ├── assets.ts //各种依赖的静态资源导入(css、scss等) │ │ ├── dayjs.ts //dayjs插件 │ │ ├── iconify.ts //iconify插件 │ │ ├── loading.ts //全局初始化时的加载插件 │ │ └── nprogress.ts //顶部加载条nprogress插件 │ ├── router //vue路由 │ │ ├── elegant //elegant-router插件生成的路由声明、导入和转换等文件 │ │ ├── guard //路由守卫 │ │ ├── routes //路由声明入口 │ │ │ ├── builtin //系统内置路由 根路由和未找到路由 │ │ │ └── index //前端静态路由创建的入口 │ │ └── index.ts //路由插件入口 │ ├── service //网络请求 │ │ ├── api //接口api │ │ └── request //封装的请求函数 │ ├── store //pinia状态管理 │ │ ├── modules //状态管理划分的模块 │ │ │ ├── app //app状态(页面重载、菜单折叠、项目配置的抽屉) │ │ │ ├── auth //auth状态(用户信息、用户权益) │ │ │ ├── route //route状态(动态路由、菜单、路由缓存) │ │ │ ├── tab //tab状态(多页签、缓存页面的滚动位置) │ │ │ └── theme //theme状态(项目主题配置) │ │ └── plugins //状态管理插件 │ ├── styles //全局样式 │ │ ├── css //css │ │ └── scss //scss │ ├── theme //主题配置 │ │ ├── settings.ts //主题默认配置及覆盖配置 │ │ └── vars.ts //主题token对应的css变量 │ ├── typings //TS类型声明文件(*.d.ts) │ │ ├── api.d.ts //请求接口返回的数据的类型声明 │ │ ├── app.d.ts //应用相关的类型声明 │ │ ├── common.d.ts //通用类型声明 │ │ ├── components.d.ts //自动导入的组件的类型声明 │ │ ├── elegant-router.d.ts//插件elegant-router生成的路由声明 │ │ ├── env.d.ts //vue路由描述和请求环境相关的类型声明 │ │ ├── global.d.ts //全局通用类型 │ │ ├── naive-ui.d.ts //NaiveUI类型 │ │ ├── router.d.ts //Vue的路由描述的类型声明 │ │ ├── storage.d.ts //本地缓存的数据类型 │ │ └── union-key.d.ts //联合类型 │ ├── utils //全局工具函数(纯函数,不含状态) │ │ ├── common //通用工具函数 │ │ ├── icon //图标相关工具函数 │ │ ├── service //请求服务配置相关的工具函数 │ │ └── storage //存储相关工具函数 │ ├── views //页面 │ │ ├── _builtin //系统内置页面:登录、异常页等 │ │ ├── about //关于 │ │ ├── function //功能 │ │ ├── home //首页 │ │ ├── manage //系统管理 │ │ ├── multi-menu //多级菜单 │ │ └── user-center //用户中心 │ ├── App.vue //Vue文件入口 │ └── main.ts //项目入口TS文件 ├── .editorconfig //统一编辑器配置 ├── .env //环境文件 ├── .env.prod //生产环境的环境文件 ├── .env.test //测试环境的环境文件 ├── .gitattributes //git属性配置 ├── .gitignore //忽略git提交的配置文件 ├── .npmrc //npm配置 ├── CHANGELOG.md //项目更新日志 ├── eslint.config.js //eslint flat配置文件 ├── index.html //html文件 ├── package.json //npm依赖描述文件 ├── pnpm-lock.yaml //npm包管理器pnpm依赖锁定文件 ├── README.md //项目介绍文档 ├── README.zh-CN.md //项目介绍文档(中文) ├── tsconfig.json //TS配置 ├── uno.config.ts //原子css框架unocss配置 └── vite.config.ts //vite配置 ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/sync.md' --- # 同步代码 1. 在自己的仓库里面新增`soybean-admin`的git地址 ```bash git remote add otherOrigin https://github.com/soybeanjs/soybean-admin.git ``` 2. 拉取代码 ```bash git fetch otherOrigin ``` 3. 通过`cherry-pick`挑选需要更新的git提交 ```bash git cherry-pick [commit id] ``` 4. 代码有冲突时, 先解决冲突,然后执行下面命令,再执行`vim`保存 ```bash git cherry-pick --continue ``` > `vim`保存操作: `esc`,`:`, `wq`, `enter`回车 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/intro.md' --- # 系统主题 系统主题的实现分为两个部分,一部分是组件库的主题配置,另一部分是 UnoCSS 的主题配置。为了统一两个部分的主题配置,在这之上维护了一些主题配置,通过这些主题配置分别控制组件库和 UnoCSS 的主题配置。 ## 原理 * 定义一些主题配置的变量,包括各种主题颜色,布局的参数配置等 * 通过这些配置产出符合组件库的主题变量 * 通过这些配置产出一些主题 tokens 并衍生出对应的 css 变量,再将这些 css 变量传递给 UnoCSS --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/config.md' --- # 主题配置 ## 类型定义 见 App.Theme.ThemeSetting ::: tip 代码位置 src/typings/app.d.ts ::: ## 初始化配置 ```ts export const themeSettings: App.Theme.ThemeSetting = { //默认配置 }; ``` ::: tip 代码位置 src/theme/settings.ts ::: ## 配置覆盖更新 当发布新的版本时,可以通过配置覆盖更新的方式,来更新主题配置 ```ts export const overrideThemeSettings: Partial = { //覆盖配置 }; ``` ::: tip 代码位置 src/theme/settings.ts ::: ## 环境说明 * 当项目处于`开发模式`时,主题配置不会被缓存,可以通过更新 `src/theme/settings.ts` 中的 `themeSettings` 来更新主题配置 > 开发阶段为了能够实时看到主题配置的变化,所以不会缓存主题配置 * 当项目处于`生产模式`时,主题配置会被缓存到 localStorage 中 > 每次发布新版本,可以通过更新 `src/theme/settings.ts` 中的 `overrideThemeSettings` 来覆盖更新主题配置 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/tokens.md' --- # 主题 tokens ## 类型定义 ```ts type ThemeToken = { colors: ThemeTokenColor; boxShadow: { header: string; sider: string; tab: string; }; }; ``` ::: tip 代码位置 src/typings/app.d.ts ::: ## 基于 tokens 的 css 变量 初始化时会在 html 上生成一些 css 变量,这些 css 变量是基于主题 tokens 产出的 ```ts /** Theme vars */ export const themeVars: App.Theme.ThemeToken = { colors: { ...colorPaletteVars, nprogress: 'rgb(var(--nprogress-color))', container: 'rgb(var(--container-bg-color))', layout: 'rgb(var(--layout-bg-color))', inverted: 'rgb(var(--inverted-bg-color))', base_text: 'rgb(var(--base-text-color))' }, boxShadow: { header: 'var(--header-box-shadow)', sider: 'var(--sider-box-shadow)', tab: 'var(--tab-box-shadow)' } }; ``` ::: tip 代码位置 src/theme/vars.ts ::: ## tokens 初始化 ```ts /** * Create theme token * * @param colors Theme colors */ export function createThemeToken(colors: App.Theme.ThemeColor) { const paletteColors = createThemePaletteColors(colors); const themeTokens: App.Theme.ThemeToken = { colors: { ...paletteColors, nprogress: paletteColors.primary, container: 'rgb(255, 255, 255)', layout: 'rgb(247, 250, 252)', inverted: 'rgb(0, 20, 40)', base_text: 'rgb(31, 31, 31)' }, boxShadow: { header: '0 1px 2px rgb(0, 21, 41, 0.08)', sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)', tab: '0 1px 2px rgb(0, 21, 41, 0.08)' } }; const darkThemeTokens: App.Theme.ThemeToken = { colors: { ...themeTokens.colors, container: 'rgb(28, 28, 28)', layout: 'rgb(18, 18, 18)', base_text: 'rgb(224, 224, 224)' }, boxShadow: { ...themeTokens.boxShadow } }; return { themeTokens, darkThemeTokens }; } ``` ::: tip 代码位置 src/store/modules/theme/shared.ts ::: --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/unocss.md' --- # UnoCSS 主题 通过 [Theme Tokens](/zh/guide/theme/tokens) 注入到 UnoCSS 的主题配置中, 借助于 UnoCSS 的能力,可以使用类似 `text-primary bg-primary` 等 class 名称进而统一了组件库和 UnoCSS 的主题颜色的应用。 ```ts import { themeVars } from './src/theme/vars'; export default defineConfig({ theme: { ...themeVars } }); ``` ::: tip 代码位置 ./uno.config.ts ::: ## UnoCSS 的暗黑模式 通过 UnoCSS 提供的预设暗黑模式方案, 只要在 html 上添加 class="dark",则项目中类似于 `dark:text-#000 dark:bg-#333` 的 class 就会生效,从而达到暗黑模式的效果 ```ts export default defineConfig({ presets: [presetUno({ dark: 'class' })] }); ``` ::: tip 代码位置 ./uno.config.ts ::: --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/ui.md' --- # 组件库主题 ## NaiveUI 主题配置 **根据主题颜色产出组件库的主题变量** ```ts /** * Get naive theme * * @param colors Theme colors */ function getNaiveTheme(colors: App.Theme.ThemeColor) { const { primary: colorLoading } = colors; const theme: GlobalThemeOverrides = { common: { ...getNaiveThemeColors(colors) }, LoadingBar: { colorLoading } }; return theme; } /** Naive theme */ const naiveTheme = computed(() => getNaiveTheme(themeColors.value)); ``` ::: tip 代码位置 src/store/modules/theme/shared.ts src/store/modules/theme/index.ts ::: **应用主题变量** ```vue ``` ::: tip 代码位置 src/App.vue ::: ## AntDesignVue 主题配置 **根据主题颜色产出组件库的主题变量** ```ts /** * Get antd theme * * @param colors Theme colors * @param darkMode Is dark mode */ function getAntdTheme(colors: App.Theme.ThemeColor, darkMode: boolean) { const { defaultAlgorithm, darkAlgorithm } = antdTheme; const { primary, info, success, warning, error } = colors; const theme: ConfigProviderProps['theme'] = { token: { colorPrimary: primary, colorInfo: info, colorSuccess: success, colorWarning: warning, colorError: error }, algorithm: [darkMode ? darkAlgorithm : defaultAlgorithm], components: { Menu: { colorSubItemBg: 'transparent' } } }; return theme; } /** Antd theme */ const antdTheme = computed(() => getAntdTheme(themeColors.value, darkMode.value)); ``` ::: tip 代码位置 src/store/modules/theme/shared.ts src/store/modules/theme/index.ts ::: **应用主题变量** ```vue ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/loading.md' --- # 系统加载 ![](../../../assets/loading01.png) ## 样式 * 系统初始化时的加载样式通过html代码方式实现 ::: tip 组件位置 src/plugins/loading.ts ::: * 系统的 Logo 使用 SystemLogo 组件实现 [系统Logo](./logo.md) ## 渲染原理 创建 setupLoading 函数, 它的主要功能是设置页面加载时的动画效果。 这个加载动画包括一个系统Logo、旋转的点阵动画和标题文字,并且所有元素的颜色均基于从本地存储获取的主题色 themeColor 动态生成。 并且在DOM中查找ID为app的元素作为加载动画的挂载点, 如果找到了这个元素,则将其内部HTML替换为刚刚构建的加载动画HTML结构 ```ts export function setupLoading() { const themeColor = localStg.get('themeColor') || '#DB5A6B'; const { r, g, b } = getRgbOfColor(themeColor); const primaryColor = `--primary-color: ${r} ${g} ${b}`; const loadingClasses = [ 'left-0 top-0', 'left-0 bottom-0 animate-delay-500', 'right-0 top-0 animate-delay-1000', 'right-0 bottom-0 animate-delay-1500' ]; const logoWithClass = systemLogo.replace(' { return `
`; }) .join('\n'); const loading = `
${logoWithClass}
${dot}

${$t('system.title')}

`; const app = document.getElementById('app'); if (app) { app.innerHTML = loading; } } ``` ::: tip 代码位置 src/plugins/loading.ts ::: 最后要将 setupLoading 函数注册到 main.ts 中 ```typescript async function setupApp() { setupLoading(); app.mount('#app'); } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/theme/logo.md' --- # 概述 系统Logo 由组件 `SystemLogo` 来实现,它是一个 `SFC` 组件,可以通过 `props` 来设置它的样式。 ```vue ``` ::: tip 代码位置 src/components/common/system-logo.vue ::: > 具体实现原理参考 [本地Icon](/zh/guide/icon/intro) --- --- url: 'https://docs.soybeanjs.cn/zh/guide/icon/intro.md' --- # 系统图标 ## iconify 图标渲染原理 基于 iconify 的 svg 的 json 数据,通过 unplugin-icons 插件,将 svg 数据转换成 vue 组件 * [unplugin-icons](https://github.com/antfu/unplugin-icons) * [iconify](https://github.com/iconify/iconify) * [Iconify icon sets](https://icon-sets.iconify.design) * [Journey with Icons Continues](https://antfu.me/posts/journey-with-icons-continues) ## 本地 svg 图标渲染原理 通过 `unplugin-icons` 插件 与 `vite-plugin-svg-icons` 插件,将本地 svg 文件转换成 vue 组件 > 本地 svg 图标需要放在 src/assets/svg-icon 目录下 ## 相关配置 **.env 配置文件** * VITE\_ICON\_PREFIX: iconify 图标前缀 * VITE\_ICON\_LOCAL\_PREFIX: 本地 svg 图标前缀,格式遵循 {VITE\_ICON\_PREFIX}-{local icon name} ## 请注意 > 根据 svg 图标渲染原理,已被转换为静态资源,这意味着一旦 svg 文件被加载并转换为组件,它们将成为您项目的一部分,不会自动检测和更新源文件的更改。因此,如果您修改了 svg 文件并希望在项目中看到更改的效果,您需要重新启动项目。 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/icon/usage.md' --- # 图标教程 ## 一、静态用法:直接写在 template 中 * **iconify** * 安装 vscode 智能提示的插件: [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) * 找图标:网址 或者 vscode 安装 - [Icônes](https://marketplace.visualstudio.com/items?itemName=afzalsayed96.icones) * 确定图标名字:找到图标后复制名字 如:'mdi-emoticon',则对应的 vue 的 template 为 ```html
``` ::: tip 提示 'icon-' 为预设的前缀, 在.env 里面设置变量 VITE\_ICON\_PREFIX ::: * 设置样式:同 html 标签一样直接应用 style 属性或者 class 属性; 通过设置 color 和 font-size 属性设置对应的颜色和大小 * **本地 svg 图标** * 在 src/assets/svg-icon 目录下选择一个 svg,取它的文件名,例如: 'custom-icon.svg' * 则对应的 vue 的 template 为 ```html ``` ::: tip 提示 'icon-local' 为预设的前缀, 在.env 里面设置变量 VITE\_ICON\_LOCAL\_PREFIX ::: ## 二、动态渲染: 根据图标名称渲染对应图标 * **iconify** * 确定图标名字,如:'mdi-emoticon' * 动态渲染 ```html ``` * 多个图标动态渲染 ```html ``` * **本地 svg 图标** * 确定 svg 文件名,例如: 'custom-icon.svg' * 动态渲染 ```html ``` ::: tip 提示 svg-icon 为全局组件,已经注册过了,直接在 template 中应用,icon 属性为 iconify 图标名称, local-icon 为本地 svg 图标的文件名 ::: ## 三、通过 render 函数渲染: 适用于 NaiveUI 的图标渲染 * 确定图标名字,如:iconify: **'mdi-emoticon'**, 或者本地 svg 图标 'custom-icon.svg' * 使用 `useSvgIcon` ```typescript import { useSvgIcon } from '@/hooks/common/icon'; const { SvgIconVNode } = useSvgIcon(); SvgIconVNode({ icon: 'ant-design:close-outlined', fontSize: 18 }); // iconify SvgIconVNode({ localIcon: 'custom-icon' }); // 本地svg图标 ``` ## 四、离线加载:添加指定的离线 iconify 图标集合 * **使用步骤** * 安装依赖 ```bash ## 包含图标组件数据 pnpm add @iconify/vue ## 包含离线图标数据 pnpm add @iconify/json ``` ::: tip 提示 项目中已经引入相关依赖,直接在组件内引用即可 ::: * 准备离线图标集合数据 如:我们需要在项目中使用 `Ant Design` 图标库,则可以按照以下方式引入离线图标 ```typescript import AntDesign from '@iconify/json/json/ant-design.json'; ``` * 在页面中使用 `addCollection` 方法添加离线图标 ```typescript import { addCollection } from '@iconify/vue'; ``` * **代码示例** ```vue ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/intro.md' --- # 系统路由 本系统的路由基于插件 [Elegant Router](https://github.com/soybeanjs/elegant-router),详细用法请查看插件文档。 ::: danger 警告 由于使用了 `` 标签支持页面过渡动画,所以在页面的 `.vue` 文件的 `template` 中只能有一个根元素,注释和纯文本都不行,必须只有一个根标签元素。 相关文档: [Transition | Vue.js (vuejs.org)](https://cn.vuejs.org/guide/built-ins/transition.html#the-transition-component) ::: ## 自动生成 启动项目后,插件会自动生成 src/router/elegant 目录,该目录下的文件为自动生成的路由导入、路由定义和路由转换的文件 > \[!IMPORTANT] > 路由是文件的副产物,所以删除路由的操作是删除文件,路由会跟随文件一起消失。 路由支持修改的内容只有 `component` 和 `meta` 信息,自动生成的操作不会影响到这两个属性。 ## 配置属性 ### 1. type RouteKey **解释:** 联合类型 RouteKey 声明所有的路由 key,方便统一管理路由, 该类型由插件 [Elegant Router](https://github.com/soybeanjs/elegant-router) 根据 views 下面的页面文件自动生成 ::: tip 代码位置 src/typings/elegant-router.d.ts ::: ### 2. type RoutePath **解释:** 路由的路径 path,该类型与 RouteKey 一一对应 ### 3. type RouteMeta ```typescript // 路由元信息接口 interface RouteMeta { /** * 路由标题 * * 可用于文档标题中 */ title: string; /** * 路由的国际化键值 * * 如果设置,将用于i18n,此时title将被忽略 */ i18nKey?: App.I18n.I18nKey; /** * 路由的角色列表 * * 当前用户拥有至少一个角色时,允许访问该路由,角色列表为空时,表示无需权限 */ roles?: string[]; /** 是否缓存该路由 */ keepAlive?: boolean; /** * 是否为常量路由 * * 无需登录,并且该路由在前端定义 */ constant?: boolean; /** * Iconify 图标 * * 可用于菜单或面包屑中 */ icon?: string; /** * 本地图标 * * 存在于 "src/assets/svg-icon" 目录下,如果设置,将忽略icon属性 */ localIcon?: string; /** 路由排序顺序 */ order?: number; /** 路由的外部链接 */ href?: string; /** 是否在菜单中隐藏该路由 */ hideInMenu?: boolean; /** * 进入该路由时激活的菜单键 * * 该路由不在菜单中 * * @example * 假设路由是"user_detail",如果设置为"user_list",则会激活"用户列表"菜单项 */ activeMenu?: import('@elegant-router/types').RouteKey; /** 默认情况下,相同路径的路由会共享一个标签页,若设置为true,则使用多个标签页 */ multiTab?: boolean; /** 若设置,路由将在标签页中固定显示,其值表示固定标签页的顺序(首页是特殊的,它将自动保持fixed) */ fixedIndexInTab?: number; /** 路由查询参数,如果设置的话,点击菜单进入该路由时会自动携带的query参数 */ query?: { key: string; value: string }[] | null; /** * 该路由是否仅在开发环境中可用 * * 当设置为 true 时,即使路由模式为 "dynamic",该路由也只会在 `import.meta.env.DEV` 为 true 时加载 */ isDev?: boolean; } ``` ::: tip 提示 icon 图标值从这里获取: ::: ## 注意 如果在 views 中创建了一个路由页面,在别的地方调用但不在菜单那边显示,那么需要设置 meta 中的 `hideInMenu: true` ```typescript { name: '403', path: '/403', component: 'layout.blank$view.403', meta: { title: '403', i18nKey: 'route.403', hideInMenu: true } } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/create.md' --- # 路由创建 ## 命令创建 通过执行 `pnpm gen-route` 命令,可以快速创建路由文件 **路由名称的命名规则** * 一级路由: `demo`, `demo-page`, `route1` > 名称为小写加连字符`-`的形式 * 二级路由: `demo2_child`, `demo2-page_child`, `route2_child` > 路由的层级用下划线`_`分隔,两边仍然遵守一级路由的命名规则 * 三级及三级以上路由: `demo3_child_child`, `demo3-page_child_child_child` ## 手动创建 **手动创建路由文件,需要遵循以下规则:** 每层路由的文件夹名称为路由名称,文件夹下的 `index.vue` 或者 `[id].vue` 为路由组件 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/structure.md' --- # 路由结构 ## 一级路由(单级路由) ### 文件夹结构 ``` views ├── about │ └── index.vue ``` ### 生成的路由 ```ts { name: 'about', path: '/about', component: 'layout.base$view.about', meta: { title: 'about' } }, ``` > 它是一个单级路由,为了添加布局,组件属性将布局和视图组件组合在一起,用美元符号“$”分割 ### 转换成的Vue路由 ```ts { path: '/about', component: BaseLayout, children: [ { name: 'about', path: '', component: () => import('@/views/about/index.vue'), meta: { title: 'about' } } ] }, ``` ## 二级路由 ### 文件夹结构 ``` views ├── list │ ├── home │ │ └── index.vue │ ├── detail │ │ └── index.vue ``` **错误示例** ``` views ├── list │ ├── index.vue │ ├── detail │ │ └── index.vue ``` > 请不要出现上述 index.vue 和文件夹同级的情况,这种情况不在约定的规则中 ### 生成的路由 ```ts { name: 'list', path: '/list', component: 'layout.base', meta: { title: 'list' }, children: [ { name: 'list_home', path: '/list/home', component: 'view.list_home', meta: { title: 'list_home' } }, { name: 'list_detail', path: '/list/detail', component: 'view.list_detail', meta: { title: 'list_detail' } } ] } ``` > 二级路由的路由数据也是有两层的,第一层路由是布局组件,第二层路由是页面组件 ### 转换成的Vue路由 ```ts { name: 'list', path: '/list', component: BaseLayout, redirect: { name: 'list_home' }, meta: { title: 'list' }, children: [ { name: 'list_home', path: '/list/home', component: () => import('@/views/list/home/index.vue'), meta: { title: 'list_home' } }, { name: 'list_detail', path: '/list/detail', component: () => import('@/views/list/detail/index.vue'), meta: { title: 'list_detail' } } ] }, ``` > 路由数据的第一层包含重定向的配置,默认重定向到第一个子路由 ## 多级路由(三级路由及以上) ### 文件夹结构 * 文件夹层级深 ``` views ├── multi-menu │ ├── first │ │ ├── child │ │ │ └── index.vue │ ├── second │ │ ├── child │ │ │ ├── home │ │ │ │ └── index.vue ``` * 两层文件夹层级(推荐) ``` views ├── multi-menu │ ├── first_child │ │ └── index.vue │ ├── second_child_home │ │ └── index.vue ``` > 通过下划线符号 `_` 来分割路由层级,这样可以避免文件夹层级过深 ### 生成的路由 ```ts { name: 'multi-menu', path: '/multi-menu', component: 'layout.base', meta: { title: 'multi-menu' }, children: [ { name: 'multi-menu_first', path: '/multi-menu/first', meta: { title: 'multi-menu_first' }, children: [ { name: 'multi-menu_first_child', path: '/multi-menu/first/child', component: 'view.multi-menu_first_child', meta: { title: 'multi-menu_first_child' } } ] }, { name: 'multi-menu_second', path: '/multi-menu/second', meta: { title: 'multi-menu_second' }, children: [ { name: 'multi-menu_second_child', path: '/multi-menu/second/child', meta: { title: 'multi-menu_second_child' }, children: [ { name: 'multi-menu_second_child_home', path: '/multi-menu/second/child/home', component: 'view.multi-menu_second_child_home', meta: { title: 'multi-menu_second_child_home' } } ] } ] } ] } ``` > 如果路由层级大于 2,生成的路由数据是一个递归结构 ### 转换成的Vue路由 ```ts { name: 'multi-menu', path: '/multi-menu', component: BaseLayout, redirect: { name: 'multi-menu_first' }, meta: { title: 'multi-menu' }, children: [ { name: 'multi-menu_first', path: '/multi-menu/first', redirect: { name: 'multi-menu_first_child' }, meta: { title: 'multi-menu_first' } }, { name: 'multi-menu_first_child', path: '/multi-menu/first/child', component: () => import('@/views/multi-menu/first_child/index.vue'), meta: { title: 'multi-menu_first_child' } }, { name: 'multi-menu_second', path: '/multi-menu/second', redirect: { name: 'multi-menu_second_child' }, meta: { title: 'multi-menu_second' }, }, { name: 'multi-menu_second_child', path: '/multi-menu/second/child', redirect: { name: 'multi-menu_second_child_home' }, meta: { title: 'multi-menu_second_child' }, }, { name: 'multi-menu_second_child_home', path: '/multi-menu/second/child/home', component: () => import('@/views/multi-menu/second_child_home/index.vue'), meta: { title: 'multi-menu_second_child_home' } } ] }, ``` > 转换的 Vue 路由只有两层,第一层是布局组件,第二层是重定向路由或者页面路由 ## 忽略文件夹的聚合路由 以下划线 `_` 开头的文件夹名称会被忽略,不会出现在路由中,其下的文件会被聚合到上一级的路由中 ### 文件夹结构 ``` views ├── _error │ ├── 403 │ │ └── index.vue │ ├── 404 │ │ └── index.vue │ ├── 500 │ │ └── index.vue ``` ### 生成的路由 ```ts { name: '403', path: '/403', component: 'layout.base$view.403', meta: { title: '403' } }, { name: '404', path: '/404', component: 'layout.base$view.404', meta: { title: '404' } }, { name: '500', path: '/500', component: 'layout.base$view.500', meta: { title: '500' } }, ``` ## 参数路由 ### 文件夹结构 ``` views ├── user │ └── [id].vue ``` ### 生成的路由 ```ts { name: 'user', path: '/user/:id', component: 'layout.base$view.user', props: true, meta: { title: 'user' } } ``` ### 高级的参数路由 ```ts import type { RouteKey } from '@elegant-router/types'; ElegantVueRouter({ routePathTransformer(routeName, routePath) { const routeKey = routeName as RouteKey; if (routeKey === 'user') { return '/user/:id(\\d+)'; } return routePath; } }); ``` ## 自定义路由 自定义路由只用于生成路由的类型声明,不会生成路由数据,需要手动创建路由数据 ### 自定义路由配置 ```ts ElegantVueRouter({ customRoutes: { map: { root: '/', notFound: '/:pathMatch(.*)*' }, names: ['two-level_route'] } }); ``` **生成的路由key** ```ts type RouteMap = { root: '/'; notFound: '/:pathMatch(.*)*'; 'two-level': '/two-level'; 'two-level_route': '/two-level/route'; }; type CustomRouteKey = 'root' | 'notFound' | 'two-level' | 'two-level_route'; ``` ### 自定义路由的component **复用已经存在的页面路由component** ```ts import type { CustomRoute } from '@elegant-router/types'; const customRoutes: CustomRoute[] = [ { name: 'root', path: '/', redirect: { name: '403' } }, { name: 'not-found', path: '/:pathMatch(.*)*', component: 'layout.base$view.404' }, { name: 'two-level', path: '/two-level', component: 'layout.base', children: [ { name: 'two-level_route', path: '/two-level/route', component: 'view.about' } ] } ]; ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/component.md' --- # 路由组件 ## 布局组件 * **layout.base**: 具有公共部分的布局,如全局头部、侧边栏、底部等 * **layout.blank**: 空白布局 ## 页面组件 * **view.\[RouteKey]**: 页面组件 > 例如:`view.home`, `view.multi-menu_first_child` ## 布局和页面的混合组件 * **layout.base$view.\[RouteKey]**: 布局和页面的混合组件 > 例如:`layout.base$view.home`, `layout.base$view.multi-menu_first_child` ::: tip 提示 该类型组件表示单级路由 ::: --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/cache.md' --- # 路由缓存 ## 原理 路由缓存是通过 `vue-router` 的 `keep-alive` 组件实现的。`keep-alive` 组件会缓存组件的状态,当组件再次被访问时,会直接从缓存中取出组件,而不是重新创建一个新的组件。 由于 `keep-alive` 组件使用的是组件的 `name` 属性来作为缓存的 key,项目中的页面组件都已经通过 `@elegant-router/vue` 插件自动注入了 `name` 属性,所以只需要在路由数据中设置 `meta` 属性的 `keepAlive` 字段即可。 `vue-router` 的多级路由缓存是有问题的,因次项目中的路由数据都被转换成了二级路由,保证每个路由都能正常缓存。 ## 用法 通过设置路由数据的 `meta` 属性中的 `keepAlive` 字段,可以控制路由是否缓存。 ```ts { name: 'about', path: '/about', component: 'layout.base$view.about', meta: { title: 'about', keepAlive: true } } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/guard.md' --- # 路由守卫 路由守卫是路由跳转过程中的统一拦截层,负责登录校验、动态路由初始化、权限校验、进度条以及页面标题等逻辑,确保用户在进入每个页面前都经过正确的鉴权与初始化流程。 ## 整体结构 项目的路由守卫位于 `src/router/guard` 目录,入口为 `createRouterGuard`,它在创建路由实例后被调用,组合了三个相互独立的守卫: ::: tip 代码位置 src/router/guard/index.ts ::: ```ts export function createRouterGuard(router: Router) { createProgressGuard(router); // 进度条守卫 createRouteGuard(router); // 路由切换与权限守卫 createDocumentTitleGuard(router); // 文档标题守卫 } ``` * **createProgressGuard**:在 `beforeEach` 中开启进度条,在 `afterEach` 中结束进度条。 * **createRouteGuard**:在 `beforeEach` 中处理登录校验、动态路由初始化与权限校验,是路由守卫的核心。 * **createDocumentTitleGuard**:在 `afterEach` 中根据路由的 `meta` 设置文档标题。 ## 进度条守卫 `createProgressGuard` 借助挂载在 `window` 上的 `NProgress` 实现页面切换时的顶部进度条:进入时调用 `start`,完成后调用 `done`。 ```ts export function createProgressGuard(router: Router) { router.beforeEach(() => { window.NProgress?.start?.(); }); router.afterEach(() => { window.NProgress?.done?.(); }); } ``` ## 路由切换守卫 `createRouteGuard` 是路由守卫的核心逻辑,全部在 `beforeEach` 中完成。它先调用 `initRoute` 进行初始化与重定向判断,若返回了目标地址则直接跳转;否则继续进行登录与权限校验。 ### initRoute:初始化与重定向 `initRoute` 负责常量路由与动态路由的初始化,并处理被 `not-found` 路由捕获的情况,主要分为以下几个阶段: 1. **常量路由未初始化**:调用 `initConstantRoute` 初始化常量路由,并重定向回原始路由(携带原有的 `query` 与 `hash`)。 2. **未登录**:若目标路由是常量路由(且非 `not-found`),允许直接访问;否则重定向到登录页,并通过 `getRouteQueryOfLoginRoute` 携带 `redirect` 查询参数。 3. **已登录但动态路由未初始化**:调用 `initAuthRoute` 初始化权限路由,若当前是被 `not-found` 捕获的路由,则在初始化完成后重定向回原始路由。 4. **动态路由已初始化**:若非 `not-found` 路由则放行;若是被 `not-found` 捕获,则进一步判断该路由是否存在,存在但无权访问时重定向到 `403`。 ### 登录与权限校验 `initRoute` 返回 `null` 后,`beforeEach` 会继续进行登录状态与角色权限的校验: * 已登录时访问 `login` 路由,自动跳转到根路由 `root`。 * 路由无需登录(`meta.constant`)时直接放行,交由 `handleRouteSwitch` 处理。 * 需要登录但未登录时,跳转到 `login` 并携带 `redirect: to.fullPath`。 * 已登录但无权访问时(既非超级管理员、`meta.roles` 也未命中),跳转到 `403`。 * 校验通过后,由 `handleRouteSwitch` 完成切换;其中若路由带有 `meta.href`,则以新窗口打开外链并停留在当前页面。 ::: tip 代码位置 src/router/guard/route.ts ::: ## 文档标题守卫 `createDocumentTitleGuard` 在 `afterEach` 中根据路由 `meta` 设置浏览器标题:若存在 `i18nKey` 则使用 `$t` 进行国际化翻译,否则使用 `title` 字段。 ```ts export function createDocumentTitleGuard(router: Router) { router.afterEach(to => { const { i18nKey, title } = to.meta; const documentTitle = i18nKey ? $t(i18nKey) : title; useTitle(documentTitle); }); } ``` ## 路由守卫流程图 下图完整展示了上述守卫的执行流程,可结合上文配合阅读: ![](../../../assets/router-guard-flow.png) [高清PDF](/router-guard-flow.pdf) --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/push.md' --- # 路由跳转 项目内可使用普通的 `router.push` 等常规方式进行路由跳转,亦可使用项目内提供的 `useRouterPush` 进行跳转(推荐),本篇主要介绍 `useRouterPush` 。 ## 介绍 该hook对 `router.push` 进行二次封装,主要目的是代替 `router.push` 使用,通过该hook可更便捷得进行跳转, `useRouterPush` 返回一个对象,包含以下属性和方法: * routerPush: Vue Router 的 push 方法。 * routerBack: Vue Router 的 back 方法。 * routerPushByKey: 根据路由key跳转的方法。 * toLogin: 跳转到登录页的方法。 * toggleLoginModule: 切换登录模块的方法。 * redirectFromLogin: 从登录页重定向的方法。 ::: warning 注意 在 `setup` 外使用时需要给 `useRouterPush` 传入 `false`。 ::: ## 详细说明 `routerPush` 和 `routerBack` 都是原有属性,就不再赘述了,这里主要介绍一下后面几个。 ### routerPushByKey 这里的 `key` 指的是路由的 `name` 属性,例如某个路由的配置为: ```json { "name": "soybean", "path": "/soybean-page", "component": "layout.base$view.soybean-page" } ``` 则跳转到该路由的代码为: ```ts import { useRouterPush } from '@/hooks/common/router'; const { routerPushByKey } = useRouterPush(); routerPushByKey('soybean'); ``` 它支持传入可选参数 `query` 或是 `params`。 ### toLogin 字面意思,快速跳转到登录页,注意跳转前要清除登录信息,否则在路由守卫一样会被拦截回首页的。 ### toggleLoginModule 该方法传入参数类型为 ```ts /** * The login module * * - pwd-login: password login * - code-login: phone code login * - register: register * - reset-pwd: reset password * - bind-wechat: bind wechat */ type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat'; ``` 作用是根据传入的 `LoginModule` 改变登录页挂载的登录功能模块,您可以自行删除或是扩展更多的模块,只需要确保类型是正确的就可以了。 ### redirectFromLogin 在登录成功选用,相比手动push到首页,它更见名知意。 它会根据登录页的 `redirect` 查询参数来决定重定向到哪个路由,如果没有 `redirect` 参数,则默认跳转到首页。 ## 使用 ```vue ``` ```ts import { useRouterPush } from '@/hooks/common/router'; // 注意传入false const { routerPushByKey } = useRouterPush(false); function backToRoot() { routerPushByKey('root') } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/router/dynamic.md' --- # 路由权限 ## 指引 ### 固定路由(无需权限即可进入的路由) 在静态路由模式下,路由权限是通过 `meta.constant` 来控制的, `constant` 为 `true` 的路由无需登录即可访问; 而在动态路由模式下,无需登录即可访问的路由需要在 `fetchGetConstantRoutes` 接口中返回,也就是说,在 `fetchGetUserRoutes` 中返回`constant` 为 `true` 的路由是不会生效的,仍需登录才能访问; ### 权限路由 在静态路由模式下默认的路由都需要登录才能访问,需要配置权限可以添加 `meta.roles` 字段,该字段类型为 `string[]` ,在 `UserInfo` 中配置,若能匹配到该角色则允许进入,否则不允许进入,匹配发生在前置路由守卫阶段; 在动态路由模式下仍可沿用 `meta.roles` ,但是一般可直接让后端根据角色权限控制路由表的返回,不返回无权的路由即可; ## 动态路由 修改路由的来源,静态路由的路由表来源于 `./src/router/elegant/routes.ts` ,动态路由的路由表来源于 `fetchGetConstantRoutes` 与 `fetchGetUserRoutes` 接口。 > \[!WARNING] 注意 > 接口返回的路由表的类型必须与前端静态的路由表类型一致,在尝试修改前建议先熟悉本项目的特色路由插件与路由表结构 ### 开启/关闭 通过在 `env` 文件中配置 `VITE_AUTH_ROUTE_MODE` 变量来开启/关闭动态路由模式。 ::: tip 代码位置 .env ::: ```dotenv:line-numbers=14 # auth route mode: static | dynamic VITE_AUTH_ROUTE_MODE=dynamic ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/request/intro.md' --- # 请求 ## 多个请求环境 开发项目经常会用到多个请求环境地址:如用户开发环境的后台地址、用于测试环境的后台地址、用于预生产环境的后台地址和用于生产环境的地址等 在环境文件中配置多个请求地址,然后在请求函数中根据环境变量来判断使用哪个请求地址 目前项目的环境文件有 `.env.prod`, `.env.test` ## 请求相关配置介绍 `.env` 文件中的配置项 * `VITE_SERVICE_SUCCESS_CODE`: 后端请求成功的 code * `VITE_SERVICE_LOGOUT_CODES`: 后端请求失败并需要用户退出登录的 code,多个 code 用 `,` 分隔 * `VITE_SERVICE_MODAL_LOGOUT_CODES`: 后端请求失败并需要用户退出登录的 code(通过弹窗形式提醒),多个 code 用 `,` 分隔 * `VITE_SERVICE_EXPIRED_TOKEN_CODES`: 后端请求失败并刷新 token 的 code,多个 code 用 `,` 分隔 `.env.test` 或 `.env.prod` 文件中的配置项 * `VITE_SERVICE_BASE_URL`: 请求的基础地址 * `VITE_OTHER_SERVICE_BASE_URL`: 其他请求的基础地址 ### 请求函数介绍 1. **请求函数:createRequest 和 createFlatRequest** `createRequest`: 返回的请求实例直接返回 Axios 响应数据(可转换) `createFlatRequest`: 返回的请求实例会将响应数据和错误信息包装在一个扁平的对象中,以统一的格式返回结果。 2. **createRequest/createFlatRequest 参数** `axiosConfig`: axios 配置,传入 baseUrl,定义一些其他配置:如:请求的超时时间、请求头等 `options`: 配置入参校验等逻辑(见下方的`RequestOption`) ```ts interface RequestOption { /** * 请求发送之前执行,用来修改请求配置,例如:添加请求头 token */ onRequest: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise; /** * 判断后端响应是否成功,通过对比后端返回的 code 来判断 */ isBackendSuccess: (response: AxiosResponse) => boolean; /** * 后端请求在业务上表示失败时调用的异步函数,例如:处理 token 过期 */ onBackendFail: ( response: AxiosResponse, instance: AxiosInstance ) => Promise | Promise; /** * 当 responseType 为 json 时,转换后端响应的数据 */ transformBackendResponse(response: AxiosResponse): any | Promise; /** * 当请求失败时调用的函数(包括请求失败和后端业务上的失败请求),例如:处理错误信息 */ onError: (error: AxiosError) => void | Promise; } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/request/usage.md' --- # 使用 ## 获取请求的基础路径 ```ts const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); ``` > `isHttpProxy` 用于判断是否使用代理,`baseURL` 表示环境文件中 `VITE_SERVICE_BASE_URL` 的值,`otherBaseURL` 用于其他请求,通过 `VITE_OTHER_SERVICE_BASE_URL` 配置。 > `getServiceBaseURL` 方法用于获取请求的基础路径,根据环境变量 `import.meta.env` 和 `isHttpProxy` 判断是否使用代理。 ## 导入请求实例创建函数 可以选择 `createRequest` 或者 `createFlatRequest` 创建请求实例。 ```ts import { createFlatRequest, createRequest } from '@sa/axios'; ``` 具体见下方的案例 ## 确认创建请求实例函数的范型参数 * 请求结果的数据类型: `App.Service.Response`,默认请求用该类型,请根据自己的后端返回数据类型进行修改 > 因为不同的类型会影响到 `RequestOption` 中的 `isBackendSuccess` 和 `transformBackendResponse` 以及错误信息的字段的参数类型 > 其他请求实例的数据类型请自行再定义新的类型声明 * 请求实例的状态类型: `InstanceState`,用于存储请求实例的一些状态,例如:表示是否正在刷新 token, 是否有错误弹窗展示等,根据自己的业务需求自行定义状态类型 ## 创建请求实例 `request` 示例 ```ts import type { AxiosResponse } from 'axios'; import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'; import { useAuthStore } from '@/store/modules/auth'; import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; import { $t } from '@/locales'; import { handleRefreshToken } from './shared'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); interface InstanceState { /** 是否有请求正在执行刷新token */ isRefreshingToken: boolean; } export const request = createFlatRequest( { baseURL, headers: { apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2' } }, { async onRequest(config) { const { headers } = config; // 添加 token 到请求头 const token = localStg.get('token'); const Authorization = token ? `Bearer ${token}` : null; Object.assign(headers, { Authorization }); return config; }, isBackendSuccess(response) { // 当后端返回的 code 为 "0000"(默认) 时,表示请求成功 // 如果需要修改这个逻辑,可以在 `.env` 文件中修改 `VITE_SERVICE_SUCCESS_CODE` return response.data.code === import.meta.env.VITE_SERVICE_SUCCESS_CODE; }, async onBackendFail(response, instance) { const authStore = useAuthStore(); function handleLogout() { authStore.resetStore(); } function logoutAndCleanup() { handleLogout(); window.removeEventListener('beforeunload', handleLogout); } // 当后端返回的 code 在 `logoutCodes` 中时,表示用户需要退出登录 const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; if (logoutCodes.includes(response.data.code)) { handleLogout(); return null; } // 当后端返回的 code 在 `modalLogoutCodes` 中时,表示用户需要退出登录,通过弹窗形式提醒 const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; if (modalLogoutCodes.includes(response.data.code)) { // 防止用户刷新页面 window.addEventListener('beforeunload', handleLogout); window.$dialog?.error({ title: 'Error', content: response.data.msg, positiveText: $t('common.confirm'), maskClosable: false, onPositiveClick() { logoutAndCleanup(); }, onClose() { logoutAndCleanup(); } }); return null; } // 当后端返回的 code 在 `expiredTokenCodes` 中时,表示 token 过期,需要刷新 token // `refreshToken` 接口不能返回 `expiredTokenCodes` 中的错误码,否则会死循环,应该返回 `logoutCodes` 或 `modalLogoutCodes` const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { request.state.isRefreshingToken = true; const refreshConfig = await handleRefreshToken(response.config); request.state.isRefreshingToken = false; if (refreshConfig) { return instance.request(refreshConfig) as Promise; } } return null; }, transformBackendResponse(response) { return response.data.data; }, onError(error) { // 当请求失败时,可以在这里处理显示错误信息的逻辑 let message = error.message; let backendErrorCode = ''; // 获取后端返回的错误信息和错误码 if (error.code === BACKEND_ERROR_CODE) { message = error.response?.data?.msg || message; backendErrorCode = error.response?.data?.code || ''; } // 错误信息通过弹窗形式显示 const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; if (modalLogoutCodes.includes(backendErrorCode)) { return; } // 当 token 过期时,刷新 token 并重试请求,所以不需要显示错误信息 const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; if (expiredTokenCodes.includes(backendErrorCode)) { return; } window.$message?.error?.(message); } } ); ``` ## 使用请求实例 ```ts /** * 登录 * * @param loginRes 登录参数 */ export function fetchLogin(loginRes: Api.Auth.LoginReq) { return request({ url: '/auth/accounts/login', method: 'post', data: loginRes }); } ``` 需要定义请求成功后的数据类型,例如:`Api.Auth.LoginToken`,并传入 `request` 函数中。 * 如果 request 函数是通过 `createFlatRequest` 创建的,请求成功后的数据类型会被包装在一个对象中,可以通过 `data` 字段获取。 ```ts async function login() { const { error, data } = await fetchLogin({ username: 'admin', password: 'admin' }); if (!error) { // 请求成功 } } ``` * 如果 request 函数是通过 `createRequest` 创建的,请求成功后的数据类型会直接返回,不会被包装在对象中。 ```ts async function login() { const data = await fetchLogin({ username: 'admin', password: 'admin' }); if (data) { // 请求成功 } } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/request/proxy.md' --- # 代理 ## 概述 项目中通过函数 `createServiceConfig` 创建服务的基础路径和匹配代理的字符串 ::: tip 代码位置 @/utils/service.ts ::: 然后在函数 `createViteProxy` 中根据上述获取的配置创建代理 ## 开启/关闭 通过 `env` 文件的 `VITE_HTTP_PROXY` 开启或关闭代理 ::: tip 代码位置 \~.env ::: 在 `@/service/request/index.ts` 里,通过给 `getServiceBaseURL` 的第二个参数传入根据代码运行环境与 `VITE_HTTP_PROXY` 共同判断出的 `isHttpProxy` 来决定该URL是否需要处理代理,您可以在这里通过传入不同的参数解构获取所需的请求URL ``` const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); const { otherBaseURL } = getServiceBaseURL(import.meta.env, false); ``` ## 原理 SoybeanAdmin 为了简化配置代理的过程,特意将匹配字符串设定为 `/proxy-default/` (其他请求 `proxy-{key}`),这样在配置代理时,只需要将请求的地址中的 `/proxy-default/` 替换为实际的请求地址即可,这样就可以实现代理的配置。 ```ts { '/proxy-default': { target: 'https://default.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/proxy-default/, ''), }, '/proxy-demo': { target: 'https://demo.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/proxy-demo/, ''), } } ``` ### 注意 这里介绍2种容易混淆的配置: 1. 假设一个请求的路径为 `https://example.com/api/user`, 大多数会这样配置代理 ```ts { '/api': { target: 'https://example.com', changeOrigin: true, } } ``` > 这时候 `/api` 既是作为匹配字符串,也是作为请求的路径。所以这里没有 `rewrite` 的配置,因为请求的路径和匹配字符串是一样的。 2. 假设一个请求的路径为 `https://example.com/user`, 但是配置代理时,匹配字符串为 `/api` ```ts { '/api': { target: 'https://example.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), } } ``` > 这时候 `/api` 是作为匹配字符串,`user` 是作为请求的路径。所以这里需要配置 `rewrite`,将匹配字符串去掉。 在 SoybeanAdmin 中,使用的是第二种包含`rewrite`配置,因为为了支持多个服务的代理,同时避免多个服务包含相同的`/api`路径,所以SoybeanAdmin 选择创建了类似 `/proxy-*` 作为匹配字符串和请求的路径分开。避免冲突。 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/request/backend.md' --- # 对接后端 ## 确认后端的返回结果的数据结构类型 默认如下: `App.Service.Response`: ```ts type Response = { /** 业务状态码 */ code: string; /** 响应信息 */ msg: string; /** 响应数据 */ data: T; }; ``` > 请根据自己的后端返回数据类型进行修改 ## 配置后端请求成功的 code 更改 `.env` 的配置 `VITE_SERVICE_SUCCESS_CODE` > 环境文件加载的配置为字符串类型,如果后端返回的 code 为数字类型,对比时需要转换同一类型再比较。 ## 配置其他后端请求相关的 code 参考请求介绍中的[配置项](./intro.md#请求相关配置介绍) --- --- url: 'https://docs.soybeanjs.cn/zh/guide/cli/intro.md' --- # 命令行 ## 概述 项目中的 `sa` 命令行工具提供了一些常用的功能 * `cleanup`: 删除目录: node\_modules, dist, 等 * `update-pkg`: 更新 package.json 依赖版本 * `git-commit`: 生成符合 Conventional Commits 标准的提交信息 * `git-commit-verify`: 验证 git 提交信息,确保符合 Conventional Commits 标准 * `changelog`: 生成 changelog * `release`: 发布,更新版本,生成 changelog,提交代码 * `gen-route`: 生成路由 > `sa` 命令是由 `packages/scripts` 提供 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/cli/command.md' --- # 命令 ## cleanup 删除目录: node\_modules, dist, 等 ```bash sa cleanup ``` ## update-pkg 更新 package.json 依赖版本 ```bash sa update-pkg ``` ## git-commit 生成符合 Conventional Commits 标准的提交信息 ```bash sa git-commit ``` ## git-commit-verify 验证 git 提交信息,确保符合 Conventional Commits 标准 ```bash sa git-commit-verify ``` ## changelog 生成 changelog ```bash sa changelog ``` ## release 发布,更新版本,生成 changelog,提交代码 ```bash sa release ``` ## gen-route 生成路由 ```bash sa gen-route ``` --- --- url: 'https://docs.soybeanjs.cn/zh/guide/cli/git-hooks.md' --- # Git Hooks ## 写在前面 > 大部份用JS写的代码,都是习惯于用运行时去判断代码有没有问题 > > 比如用JS开发对接后端是这么一个流程,等接口好了后,调接口,拿到数据,再想办法把数据塞到页面里面 > > 如果说是非常简单的页面,这个流程看上去是没啥毛病,如果是稍微复杂点的,这种流程写出来的代码就成了💩山的地基了,后期各种问题的排查就很头痛了 TypeScript是能写出高质量代码的一大利器,定义数据类型的过程就是在定义前端数据模型的过程,一旦数据模型确定了,关于数据模型的各种操作都可以用纯函数来实现, 然后,将数据结合响应式时,只需要把对应的纯函数引进来做个简单的二次封装即可,大部份人不适应TS的类型,主要是开发模式没有切换,可以尝试使用这个模式进行几段开发体验TypeScript的魅力,然后再决定是否移除 `git-hooks`。 使用 `git-hooks` 作提交前校验,不但有助于提升自己的代码水平、降低项目的维护成本,更有助于自己去接手他人的代码,在诸多好处下为数不多的坏处仅在于一小段时间的学习(SoybeanAdmin项目内的类型覆盖率100%,皆为最佳实践,也是您上手TypeScript的最佳时机)与适应,您可以尝试一小段时间(欢迎随时在交流群中与我们讨论)后再决定是否移除 `git-hooks`。 ## 移除git-hooks ::: details 方式一:临时关闭校验 建议初期尽可能完全遵循完善的类型校验,仅在有需要时通过临时取消提交校验的方式略微跳过几次。 ```shell git add . git commit -m "commit message" # [!code --] git commit -m "commit message" --no-verify # [!code ++] git push ``` ::: ::: details 方式二:永久关闭校验 > \[!CAUTION] 不推荐 > 1、把 `package.json` 的 `simple-git-hooks` 里面的命令删掉 > > 2、执行 `simple-git-hooks` 命令 > > ::: --- --- url: 'https://docs.soybeanjs.cn/zh/standard.md' --- # 代码规范 在前端开发过程中,代码的规范化不仅有助于提高代码的可读性和可维护性,还能减少团队协作中的摩擦,提升开发效率。本规范旨在为前端开发提供一套统一的标准,涵盖代码格式化检查、命名规范、Vue写法规范和TypeScript写法规范等方面的内容。 通过遵循本规范,开发者可以确保代码的一致性,减少因风格差异引起的代码审查问题,并且在项目的长期维护中受益匪浅。希望本规范能够为您的开发工作提供有力的支持和指导。 --- --- url: 'https://docs.soybeanjs.cn/zh/standard/lint.md' --- # 格式化检查 ## 使用 ESLint 和 Prettier 进行代码格式化 SoybeanJS团队使用[`@soybeanjs/eslint-config`](https://github.com/soybeanjs/eslint-config)来进行代码格式化。这个配置包含了ESLint和Prettier的配置,以及一些自定义的规则。 ## 代码检查 ### lint-staged 安装 `lint-staged`: ```bash pnpm i lint-staged -D ``` 在 `package.json` 中添加: ```json { "lint-staged": { "*": "eslint --fix" } } ``` ### simple-git-hooks 安装 `simple-git-hooks`: ```bash pnpm i simple-git-hooks -D ``` 在 `package.json` 中添加git钩子: ```json { "simple-git-hooks": { "commit-msg": "pnpm sa git-commit-verify", "pre-commit": "pnpm typecheck && pnpm lint-staged" } } ``` 在 `package.json` 中添加脚本: ```json { "scripts": { "prepare": "simple-git-hooks" } } ``` ::: tip 提示 变更 `simple-git-hooks` 配置或取消 `simple-git-hooks` 时,先更改 `package.json` 中的`simple-git-hooks`对应的配置,然后运行 `pnpm run prepare`使其生效。 ::: --- --- url: 'https://docs.soybeanjs.cn/zh/standard/naming.md' --- # 命名规范 * 文件和文件夹命名: 统一用小写加连字符`-`命名,多个单词用连字符连接 ``` views ├── home │ └── index.vue ``` * Vue 组件名称 * 组件名称统一用 PascalCase 法命名,多个单词首字母大写 ```vue ``` * iconify 图标组件名称统一用 kebab-case 法命名,多个单词用中划线连接 ```vue ``` > 方便iconify插件直接展示图标 * 构造函数、class 类、TS 类型命名:统一用 PascalCase 法命名,多个单词首字母大写 ```ts function Person() {} class Person {} type Person = { name: string; }; interface Person { name: string; } ``` * 变量、普通函数命名:统一用 camelCase 法命名,多个单词首字母小写 ```ts let num: number = 1; function getNum() {} ``` * 常量命名:统一用大写字母命名,多个单词用下划线连接 ```ts const MAX_COUNT = 10; ``` * 样式的命名:统一用小写字母命名,多个单词用中划线连接 ```css .container { } .container-item { } ``` * 请求函数命名:统一以`fetch`开头,后面跟请求的资源名称 ```ts function fetchUser() {} ``` --- --- url: 'https://docs.soybeanjs.cn/zh/standard/vue.md' --- # Vue 写法规范 ## SFC顺序 ### script * import 导入语句 * 建议按照以下依赖顺序: 1. vue 2. vue-router 3. pinia 4. @vueuse/core 5. UI库 6. 其他依赖 7. 项目内部依赖(monorepo) 8. 别名导入 9. 相对路径导入 * 类型单独使用 `import type` 导入,并在相同依赖的下面 例如: ```ts import { ref } from 'vue'; import type { Ref } from 'vue'; ``` * defineOptions * Props 类型定义 ```ts interface Props { prop1: string; prop2: number; } ``` * defineProps ```ts defineProps(); const props = defineProps(); // 用到props时 ``` * Emits 类型定义 ```ts interface Emits { emit1: (arg1: string) => void; emit2: (arg1: number) => void; } // 或者 interface Emits { emit1: [arg1: string]; emit2: [arg1: number]; } ``` * defineEmits ```ts defineEmits(); const emit = defineEmits(); // 用到emit时 ``` * 导入的hooks函数 例如:useRouter, useRoute, 以及自行封装的hooks ```ts const router = useRouter(); const route = useRoute(); const appStore = useAppStore(); const { loading, startLoading, endLoading } = useLoading(); ``` * 组件逻辑定义 ```ts const count = ref(0); const increment = () => { count.value++; }; const visible = ref(false); const toggleVisible = () => { visible.value = !visible.value; }; ``` * 必要的`init`函数,所有的初始化逻辑都放在这里 ```ts async function init() { await fetchData(); } ``` * watch和watchEffect ```ts watchEffect(() => { console.log(count.value); }); watch( () => count.value, (newValue, oldValue) => { console.log(newValue, oldValue); } ); ``` * 生命周期钩子 ```ts // 相当于在`created`钩子中执行 init(); // 或者 onMounted(() => { init(); }); ``` * defineExpose ```ts const exposed = { count, increment }; defineExpose(exposed); ``` --- --- url: 'https://docs.soybeanjs.cn/zh/standard/ts.md' --- # TS 写法规范 TypeScript 是 SoybeanAdmin 的开发基石,良好的类型约束能在编译阶段发现错误、提升代码的可读性与可维护性。本章节汇总了在严格模式下编写 TypeScript 的常见约定,帮助你写出类型安全、风格统一的代码。 ## 开启严格模式 始终在 `tsconfig.json` 中开启 `strict` 模式,它会一并启用 `strictNullChecks`、`noImplicitAny` 等一系列检查,是类型安全的基础。 ```json { "compilerOptions": { "strict": true } } ``` ## 优先类型推断 让编译器自动推断类型,避免冗余的类型注解。只在编译器无法推断、或需要明确约束意图时才显式标注。 ```ts // 推荐:编译器可推断为 number const count = 1; const list = [1, 2, 3]; // 不推荐:多余的注解 const count: number = 1; ``` 函数的返回值通常也可以推断,但对外暴露的公共 API 建议显式标注返回类型,以稳定接口契约。 ## interface 与 type 的取舍 两者在多数场景可以互换,约定如下: * 描述对象结构、需要被继承或合并时,优先使用 `interface`。 * 表达联合类型、交叉类型、元组或工具类型时,使用 `type`。 ```ts // 对象结构用 interface interface User { id: number; name: string; } // 联合 / 工具类型用 type type Status = 'pending' | 'success' | 'failed'; type Nullable = T | null; ``` ## 避免 any `any` 会关闭类型检查,应尽量避免。当类型确实未知时,使用 `unknown`,并在使用前通过类型收窄缩小范围。 ```ts function parse(input: unknown) { if (typeof input === 'string') { // 此处 input 被收窄为 string return input.trim(); } return ''; } ``` ## 善用泛型与工具类型 用泛型抽象可复用的逻辑,用内置工具类型(`Partial`、`Required`、`Pick`、`Omit`、`Record` 等)从已有类型派生新类型,避免重复定义。 ```ts interface User { id: number; name: string; email: string; } type UserPreview = Pick; type UserPatch = Partial; type UserMap = Record; function identity(value: T): T { return value; } ``` ## 命名约定 类型、接口、枚举统一使用 PascalCase;泛型参数通常用单个大写字母,如 `T`(Type)、`K`(Key)、`V`(Value)。变量与函数的命名详见[命名规范](./naming)。 ```ts interface MenuItem {} type RequestResult = Promise<{ data: T }>; function pluck(obj: T, key: K): T[K] { return obj[key]; } ``` ## 用常量对象替代部分枚举 对于简单的字面量集合,推荐用 `const` 对象配合 `as const`,或直接用字面量联合类型,它们更轻量,且不会生成额外的运行时代码。 ```ts // 字面量联合 type Theme = 'light' | 'dark'; // const 对象 + as const const ROLE = { Admin: 'admin', User: 'user' } as const; type Role = (typeof ROLE)[keyof typeof ROLE]; // 'admin' | 'user' ``` ## 函数与空值处理 为函数参数标注类型;可选参数用 `?`,并优先用默认值替代手动判空。开启 `strictNullChecks` 后,配合可选链 `?.` 与空值合并 `??` 处理可能为空的值。 ```ts function greet(name: string, greeting = 'Hello'): string { return `${greeting}, ${name}`; } // 可选链 + 空值合并 const len = user?.name?.length ?? 0; ``` 注意 `??` 仅在值为 `null` 或 `undefined` 时取右侧,而 `||` 会把 `0`、`''`、`false` 也视为假值,二者语义不同。 ## 使用 import type 导入类型 仅导入类型时使用 `import type`,明确区分类型导入与值导入,有助于编译器擦除类型、避免不必要的副作用。 ```ts import { ref } from 'vue'; import type { Ref } from 'vue'; import type { User } from './types'; ``` --- --- url: 'https://docs.soybeanjs.cn/zh/standard/synthesis.md' --- # 综合 除了格式化、命名、Vue 与 TypeScript 等单项规范外,项目的工程化质量还体现在目录组织、导入习惯、注释、环境变量与协作流程这些贯穿全局的约定上。本页汇总这类「综合规范」,作为对其它规范页的补充。以下内容多为业界通行的最佳实践与推荐约定,团队可结合实际情况裁剪。 ## 目录与文件组织 按「功能 / 模块」而非「文件类型」组织代码,让一个功能相关的文件尽量内聚在一起,降低跨目录跳转的成本。 * 单一职责:一个文件、一个组件、一个函数只做一件事,文件过大时及时拆分。 * 就近原则:仅在某个模块内部使用的组件、类型、工具函数,放在该模块目录下;被多处复用时再上移到公共目录。 * 命名一致:目录与文件名遵循[命名规范](./naming),统一使用小写加连字符 `-`。 ```text views └── user ├── index.vue # 页面入口 ├── modules # 仅本页使用的局部组件 │ └── user-search.vue └── components # 可在本模块复用的组件 ``` > 这是一种推荐的组织思路,具体目录结构请以项目实际为准。 ## 导入顺序与路径别名 项目约定使用 `@/` 作为 `src` 目录的路径别名,避免出现 `../../../` 这类难以维护的相对路径。 ```ts // 推荐:使用别名 import { useAppStore } from '@/store/modules/app'; // 不推荐:深层相对路径 import { useAppStore } from '../../../store/modules/app'; ``` 导入语句建议按「第三方依赖 → 项目内部(别名)→ 相对路径」分组,组与组之间用空行分隔;仅导入类型时使用 `import type`。Vue 单文件组件内部更细致的导入顺序详见 [Vue 写法规范](./vue)。 ```ts import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useAppStore } from '@/store/modules/app'; import type { MenuItem } from '@/typings/menu'; import { formatTitle } from './shared'; ``` ## 注释规范 好的代码以「自解释」为先,注释用来补充代码无法表达的信息,而非复述代码本身。 * 解释「为什么」而非「做什么」:记录设计取舍、边界条件、踩坑由来等背景信息。 * 公共函数、复杂逻辑与对外暴露的 API 建议使用 JSDoc 标注参数与返回值,便于编辑器提示。 * 避免无意义注释,及时删除被注释掉的「死代码」(版本历史交给 Git 管理)。 * 临时方案统一用 `// TODO:` / `// FIXME:` 标记,方便检索与后续跟进。 ```ts /** * 将菜单数组转换为路由树 * * @param menus 后端返回的扁平菜单列表 * @returns 嵌套的路由配置 */ function transformMenuToRoutes(menus: MenuItem[]) { // 后端不保证顺序,这里需按 order 字段排序后再构建树 // ... } ``` ## 环境变量管理 项目基于 Vite,约定通过 `.env` 系列文件管理环境变量,不同环境使用不同文件: * `.env`:所有环境共享的基础变量。 * `.env.development` / `.env.production`:分别对应开发与生产环境。 * 仅 `VITE_` 前缀的变量会被注入到客户端代码中(通过 `import.meta.env.VITE_xxx` 访问);未加前缀的变量仅在构建期可见,**切勿**用前缀变量存放密钥等敏感信息。 ```bash # .env VITE_APP_TITLE=SoybeanAdmin # .env.development VITE_SERVICE_BASE_URL=http://localhost:8080 ``` > 上述变量名仅为示例。真实的环境变量请以项目根目录下的 `.env` 文件为准,包含敏感信息的本地文件应通过 `.gitignore` 排除,不要提交到仓库。 ## Git 提交信息规范 提交信息统一遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范,格式为 `type(scope): subject`,便于自动生成 changelog 并保持提交历史清晰。常用 `type` 包括 `feat`、`fix`、`docs`、`refactor`、`perf`、`chore` 等。 ```text feat(router): 支持动态路由权限校验 fix(request): 修复 token 过期未刷新的问题 ``` 推荐使用 `@soybeanjs/cli` 提供的交互式命令生成规范提交信息,并由 Git 钩子在提交时自动校验。相关工具与命令详见[工具规范](./tools)。 ## 代码评审(PR)约定 代码合入主分支前应经过 Pull Request 评审,这是保证质量、沉淀团队共识的关键环节。推荐遵循以下基本约定: * **小而聚焦**:一个 PR 只解决一个问题,控制改动规模,便于评审与回溯。 * **描述清晰**:在 PR 描述中说明改动背景、方案与影响范围,必要时附上截图或复现步骤。 * **自检先行**:提交前确保本地 `pnpm typecheck` 与 `pnpm lint` 通过,CI 全绿后再请求评审。 * **对事不对人**:评审意见聚焦代码本身;作者对每条意见给出回应或修改,达成一致后再合并。 --- --- url: 'https://docs.soybeanjs.cn/zh/standard/tools.md' --- # 工具规范 SoybeanAdmin 通过一套统一的工具链来保证代码质量、规范 Git 工作流并提升开发效率。本页对项目中实际使用的工具做整体概览,包括代码质量工具、Git 工作流工具、包管理以及 `@soybeanjs/cli` 命令行,帮助你快速了解各工具的职责与协作方式。 ## 工具链概览 | 类别 | 工具 | 作用 | | --- | --- | --- | | 代码质量 | [`@soybeanjs/eslint-config`](https://github.com/soybeanjs/eslint-config)(ESLint + Prettier) | 代码检查与格式化 | | Git 工作流 | `simple-git-hooks`、`lint-staged` | 提交前校验与暂存文件检查 | | 命令行 | [`@soybeanjs/cli`](https://github.com/soybeanjs/cli)(`soy` / `sa`) | 提交、清理、发布、更新依赖等 | | 包管理 | `pnpm` | 安装、运行脚本与版本管理 | ## 代码质量:ESLint + Prettier 项目使用 [`@soybeanjs/eslint-config`](https://github.com/soybeanjs/eslint-config) 统一处理代码检查与格式化,它在内部整合了 ESLint 与 Prettier,并附带一系列自定义规则。 `eslint.config.js` 使用 Flat Config 形式,按需开启 `vue` 与 `markdown` 等能力: ```js import { defineConfig } from '@soybeanjs/eslint-config'; export default defineConfig({ vue: true, formatter: { markdown: true } }); ``` 执行检查与自动修复: ```bash pnpm lint ``` > 该脚本对应 `eslint . --fix`。更多关于 ESLint、Prettier 与 `lint-staged`、`simple-git-hooks` 的配置细节,请参阅 [格式化检查](./lint)。 ## Git 工作流工具 为保证每次提交的质量与提交信息的规范,项目通过 `simple-git-hooks` 配置 Git 钩子,并配合 `lint-staged` 仅检查本次暂存的文件: ```json { "simple-git-hooks": { "commit-msg": "pnpm sa git-commit-verify", "pre-commit": "pnpm typecheck && pnpm lint-staged" }, "lint-staged": { "*": "eslint --fix" } } ``` * `pre-commit`:提交前先做类型检查(`typecheck`),再对暂存文件执行 ESLint 自动修复。 * `commit-msg`:通过 `sa git-commit-verify` 校验提交信息是否符合 [Conventional Commits](https://www.conventionalcommits.org/) 规范。 推荐使用 `@soybeanjs/cli` 提供的交互式提交命令来生成规范的提交信息,避免手写出错: ```bash # 交互式生成符合 Conventional Commits 规范的提交信息 pnpm commit # 生成中文提交信息 pnpm commit:zh ``` > 如需了解 Git 钩子的设计初衷与移除方式,请参阅 [Git Hooks](../guide/cli/git-hooks)。 ## 包管理:pnpm 项目使用 [`pnpm`](https://pnpm.io/) 作为包管理器,并在 `package.json` 的 `engines` 中约束了运行环境: ```json { "engines": { "node": ">=20.19.0", "pnpm": ">=8.7.0" } } ``` 常用命令: ```bash # 安装依赖 pnpm install # 启动文档开发服务 pnpm dev # 构建文档 pnpm build ``` ## 命令行工具:@soybeanjs/cli [`@soybeanjs/cli`](https://github.com/soybeanjs/cli) 提供了 `soy`(亦可用 `sa`)命令,封装了一系列常用的工程化能力。这些命令已在 `package.json` 的 `scripts` 中按需绑定: | 命令 | 说明 | 对应脚本 | | --- | --- | --- | | `git-commit` | 交互式生成符合 Conventional Commits 规范的提交信息 | `pnpm commit` | | `git-commit-verify` | 校验提交信息是否符合规范(用于 `commit-msg` 钩子) | — | | `cleanup` | 删除 `node_modules`、`dist` 等目录 | `pnpm cleanup` | | `ncu` | 更新 `package.json` 中的依赖版本 | `pnpm update-pkg` | | `release` | 更新版本、生成 changelog 并提交代码 | `pnpm release` | | `changelog` | 生成 changelog | — | > 命令的完整列表与说明请参阅 [命令行](../guide/cli/intro) 与 [命令](../guide/cli/command)。 ## 推荐的编辑器与插件 推荐使用 [Visual Studio Code](https://code.visualstudio.com/) 进行开发,并安装以下插件以获得完整的开发体验: * **Vue - Official(Volar)**:提供 Vue 单文件组件的语法高亮、类型推断与智能提示。 * **ESLint**:实时显示并在保存时自动修复代码风格问题,与项目的 `@soybeanjs/eslint-config` 配合使用。 为配合 ESLint 自动修复,建议在 VS Code 的 `settings.json` 中开启保存时修复: ```json { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" } } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/tutorial.md' --- # 教程 在现代前端开发中,掌握各种开发工具和环境的安装与配置是至关重要的。无论是版本控制工具Git,还是JavaScript运行环境Node.js,亦或是调试工具,都是前端开发者日常工作中不可或缺的部分。本教程将详细介绍如何在Mac系统上安装和配置这些工具,帮助你快速搭建起高效的前端开发环境。 通过本教程,你将学会: * Git的安装与基本使用:包括如何安装Git、配置Git以及基本的Git命令使用。 * Node.js的安装与配置:包括如何安装Node.js、配置npm以及使用fnm管理Node.js版本。 * 调试工具的使用:包括如何在VS Code中进行前端代码的调试,设置断点,查看变量等。 希望通过本教程,你能够顺利搭建起前端开发环境,提高开发效率,享受前端开发的乐趣。 --- --- url: 'https://docs.soybeanjs.cn/zh/tutorial/git.md' --- # Git 通过[git](https://git-scm.com/)网站来下载 git 工具 ## 初始化git * 设置用户信息 ```bash git config --global user.name "Soybean" git config --global user.email "soybeanjs@outlook.com" ``` * 生成密钥 ```bash ssh-keygen ``` > 选择过程中直接回车 ::: tip 提示 完整命令: ```bash ssh-keygen -t rsa -C "soybeanjs@outlook.com" ``` > -t rsa表示生成rsa密钥,-C表示注释,后面跟上注释内容 ::: * 上传git密钥 在用户目录下找到 .ssh/id\_rsa.pub,打开,将内容复制到git代码平台的ssh keys中 ## git常见命令 * 同步main分支最新代码到当前分支 ```bash git pull origin main git rebase origin/main ``` > 如果当前分支是main分支,可以直接使用`git pull --rebase` 遇到冲突时,解决冲突后,使用以下命令继续rebase ```bash git add . git rebase --continue ``` * 修改最近一次commit的时间 ```bash git commit --amend --date="2022-07-29T23:45" ``` * 合并多个commit ```bash git rebase -i HEAD~n # n为要合并commit的个数 ``` \-- 复制commit到当前分支 ```bash git cherry-pick ``` > 默认会保持commit的信息,如果需要不产生提交记录,可以使用`git cherry-pick -n ` --- --- url: 'https://docs.soybeanjs.cn/zh/tutorial/nodejs.md' --- # NodeJS 安装教程 由于我们在日常开发过程中会遇到很多基于不同 NodeJS 版本开发的项目,有的项目需要使用低版本的 NodeJS,有的项目需要使用高版本的 NodeJS,所以我们需要安装 NodeJS 的版本管理工具。 下面推荐几个工具供大家参考 * ## nvm ### 安装 1.打开终端,输入以下命令: #### macos ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash ``` #### windows ```bash https://github.com/coreybutler/nvm-windows/releases/download/1.2.2/nvm-setup.exe ``` 2.安装完成后,关闭终端,重新打开终端,输入以下命令: ```bash nvm --version ``` 3.如果出现版本号,则表示安装成功。 ### 安装 NodeJS 1. 输入以下命令,列出所有可用的 NodeJS 版本: ```bash nvm ls-remote ``` 2. 选择一个版本进行安装,例如安装 NodeJS 14.17.0: ```bash nvm install 18.20.5 ``` 3. 安装完成后,输入以下命令,切换到该版本: ```bash nvm use 18.20.5 ``` 4. 输入以下命令,查看当前使用的 NodeJS 版本: ```bash node -v ``` 5. 输入以下命令,查看所有已安装的 NodeJS 版本: ```bash nvm ls ``` * ## fnm ### windows #### 安装chocolatey 1. 用管理员模式打开 windows Terminal 2. 执行下面命令 ```bash Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) ``` 3. 输入choco -v,测试是否安装成功 > choco安装软件,都要用管理员模式打开 windows Terminal ### 安装fnm 1. 以管理员模式打开Terminal 2. 执行命令: ```bash choco install fnm ``` ### 测试fnm命令 1. 打开Powershell 2. 输入 `fnm -h` 测试命令是否正常 ```bash fnm -h ``` ### 环境变量配置 #### Powershell 1. 在下面的目录新建profile.ps1文件 ```other %USERPROFILE%\Documents\WindowsPowerShell\profile.ps1 ``` > %USERPROFILE%: 表示用户目录,直接在文件管理的地址栏输入 %USERPROFILE%,然后回车 > WindowsPowerShell为新建的目录, 如果安装node后命令仍然无法识别,将文件夹名称改为PowerShell 2. 将下面的代码写入到上面的配置文件里面 ```bash fnm env --use-on-cd | Out-String | Invoke-Expression ``` #### cmd 1. 搜索 cmd 2. 打开文件所在位置 3. 对 “命令提示符” 右键,点击属性 4. 修改 目标 为下面的值 ```other %windir%\system32\cmd.exe /k %USERPROFILE%\bashrc.cmd ``` 5. 进入用户目录,添加文件 bashrc.cmd 6. 将下面的代码写入到上面的配置文件里面 ```bash @echo off FOR /f "tokens=*" %%z IN ('fnm env --use-on-cd') DO CALL %%z ``` #### git bash 进入用户目录,在git bash的配置文件 .bash\_profile 添加下面的代码 ```bash eval $(fnm env | sed 1d) export PATH=$(cygpath $FNM_MULTISHELL_PATH):$PATH if [[ -f .node-version || -f .nvmrc ]]; then fnm use fi ``` #### VSCode内置的cmd 在配置文件settings.json里面添加如下代码: ```json "terminal.integrated.defaultProfile.windows": "Default Cmd", "terminal.integrated.profiles.windows": { "Default Cmd":{ "path": "C:\\Windows\\System32\\cmd.exe", "args": ["/k", "%USERPROFILE%\\bashrc.cmd"] } } ``` ### Mac #### 安装fnm ```bash curl -fsSL https://fnm.vercel.app/install | bash ``` #### 设置fnm环境 1. 在.zshrc中加入下面的代码 ```bash eval "$(fnm env --use-on-cd)" ``` 2. 刷新.zshrc ```bash source ~/.zshrc ``` #### fnm使用 #### 安装NodeJS ```other fnm install 16 fnm install 14 fnm install 12 ``` #### 使用NodeJS ```other fnm use 16 fnm use 14 fnm use 12 ``` #### 测试node命令 ```other node -v ``` #### fnm切换node默认版本 ```other fnm default 14 #默认使用版本14,每次打开terminal的node版本就是14 ``` #### fnm更多用法 ```other fnm -h ``` > 或者访问 --- --- url: 'https://docs.soybeanjs.cn/zh/tutorial/debug.md' --- # 调试 ## 概述 在软件开发的世界里,调试就像是开发者的放大镜和手术刀,它不仅能帮助我们: * 🔍 快速定位并修复代码错误 * 🔄 深入理解代码执行流程 * 📊 实时监控变量状态 * 💾 分析内存使用情况 * ⚡ 优化程序性能 本文将带你探索如何使用 VSCode 强大的调试功能,让调试过程变得轻松高效。 ## JavaScript 和 TypeScript 调试 ### tsx - TypeScript 执行利器 [`tsx`](https://tsx.is/) 是Node.js对运行TypeScript的增强,它让 TypeScript 代码的执行变得简单直接: * 零配置执行 TypeScript 文件 * 支持 ES 模块和 CommonJS * 内置源码映射支持 * 优秀的性能表现 ```bash # 安装 tsx npm install -g tsx # 执行 TypeScript 文件 tsx your-file.ts ``` 通过 VSCode 的调试配置,我们可以轻松实现断点调试、变量监控等高级功能。下一节,我们将详细介绍如何配置 VSCode 的调试环境。 > 💡 提示:VSCode 的调试功能与 Node.js 的调试器完美集成,让你可以像调试 JavaScript 一样轻松调试 TypeScript 代码。 ### tsx 调试步骤 1. 首先全局安装依赖 `tsx` ```bash npm i -g tsx ``` 2. 添加以下调试配置到项目中 `.vscode/launch.json` 中 ```json { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "TS Debugger", "runtimeExecutable": "tsx", "skipFiles": ["/**", "${workspaceFolder}/node_modules/**"], "program": "${file}" } ] } ``` 3. 调试测试 * 新增文件 `debug.ts` * 输入以下代码 ```ts function transformToKebabCase(input: string): string { return input.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } function start() { const input = 'HelloWorld'; const result = transformToKebabCase(input); return result; } start(); ``` 4. 按照图片中的步骤进行调试 ![](../../assets/VSCode调试指南01.png) ![](../../assets/VSCode调试指南02.png) ## Vue 调试 ### 调试步骤 1. 添加以下调试配置到项目中 `.vscode/launch.json` 中 ```json { "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Vue Debugger", "url": "http://localhost:9527", "webRoot": "${workspaceFolder}" } ] } ``` > 配置中的url里的端口号和项目本地开发运行时的端口号保持一致 2. 本地启动项目 3. 测试调试 * 打开页面文件`about/index.vue`, 在`onMounted`中添加断点 * 选择`Vue Debugger`, 点击启动调试 - 浏览器进入about页面,然后会自动跳转回VSCode ![](../../assets/VSCode调试指南03.png) > 同理,例如当测试点击按钮后执行的逻辑,在点击事件中添加相应断点,然后在页面上点击即可触发调试 ### 断点类型 * 在组件methods中设置断点 * 在生命周期钩子中设置断点 * 在计算属性中设置断点 * 在watch中设置断点 * 在路由守卫中设置断点 > 记得在开发环境启用source map以获得最佳调试体验: ```ts // vite.config.ts export default defineConfig({ build: { sourcemap: true } ``` --- --- url: 'https://docs.soybeanjs.cn/zh/tutorial/software.md' --- # 软件安装教程 * ## ApiPost 简介:接口调试以及 mock 工具 [官方网址](https://www.apipost.cn/download.html) * ## Postman 简介:接口调试工具 [官方网址](https://www.postman.com/downloads/) * ## Charles 简介:抓包工具 [官方网址](https://www.charlesproxy.com/) [下载地址](https://www.charlesproxy.com/download/) * ## Fiddler 简介:抓包工具 [官方网址](https://www.telerik.com/fiddler) * ## Typora 简介:Markdown 编辑器 [官方网址](https://typora.io/) * ## Xshell 简介:远程连接工具 [官方网址](https://www.netsarang.com/zh/xshell-download/) * ## Xftp 简介:远程文件传输工具 [官方网址](https://www.netsarang.com/zh/xftp-download/) --- --- url: 'https://docs.soybeanjs.cn/zh/tutorial/other.md' --- # 其他 除了 Git、Node.js 和调试工具之外,搭建一个顺手的前端开发环境还需要一个合适的包管理器、稳定的镜像源以及趁手的编辑器配置。本节整理了一些通用且常用的小技巧,帮助你少踩坑、提高效率。 ## 安装与使用 pnpm SoybeanAdmin 使用 [pnpm](https://pnpm.io/zh/) 作为包管理器,它安装速度快、节省磁盘空间,并且能严格管理依赖。 推荐使用 Node.js 自带的 [Corepack](https://nodejs.org/api/corepack.html) 来启用 pnpm(Node.js 16.13 及以上版本内置): ```bash # 启用 corepack corepack enable # 准备并激活指定版本的 pnpm(可选,指定版本更可控) corepack prepare pnpm@latest --activate ``` 如果你不想使用 corepack,也可以直接用 npm 全局安装: ```bash npm i -g pnpm ``` 安装完成后,验证版本: ```bash pnpm -v ``` > 项目的 `package.json` 中通过 `engines` 字段约束了运行环境(Node.js `>=20.19.0`、pnpm `>=8.7.0`),请确保本地版本满足要求,否则安装依赖时可能会报错。 常用命令一览: ```bash pnpm install # 安装依赖 pnpm dev # 启动本地开发服务 pnpm build # 构建生产包 ``` ## 配置镜像源 国内网络环境下,直接从官方源安装依赖可能会比较慢。可以将镜像源切换到 [npmmirror](https://npmmirror.com/)(淘宝镜像)来加速。 查看与设置 npm 镜像源: ```bash # 查看当前镜像源 npm config get registry # 设置为 npmmirror 镜像 npm config set registry https://registry.npmmirror.com # 恢复为官方源 npm config set registry https://registry.npmjs.org ``` pnpm 的设置方式与 npm 类似: ```bash pnpm config set registry https://registry.npmmirror.com ``` 如果你经常需要在多个镜像源之间切换,可以使用 [nrm](https://github.com/Pana/nrm) 来管理: ```bash # 全局安装 nrm npm i -g nrm # 列出所有可用的镜像源 nrm ls # 切换到 taobao 镜像 nrm use taobao ``` ## 推荐的 VS Code 插件 下面这些插件能显著提升使用 SoybeanAdmin 进行开发的体验: * **Vue - Official**(Volar):Vue 3 官方插件,提供语法高亮、类型检查和模板智能提示。 * **ESLint**:在编辑器中实时显示代码规范问题,并支持保存时自动修复。 * **UnoCSS**:为原子化 CSS 提供智能提示与高亮(项目使用 UnoCSS)。 * **TypeScript Vue Plugin**:增强 `.vue` 文件中的 TypeScript 支持。 * **Iconify IntelliSense**:在代码中预览图标。 > 提示:项目通常会在 `.vscode/extensions.json` 中预置推荐插件列表,打开项目后 VS Code 会自动提示安装。 一个常用的设置是开启「保存时自动格式化与修复」,在 VS Code 的 `settings.json` 中加入: ```json { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" } } ``` ## 常见问题排查 在依赖安装或启动项目遇到异常时,可以按下面的思路逐步排查: * **Node 版本不匹配**:先用 `node -v` 确认版本是否满足项目要求,必要时通过 fnm / nvm 切换(参见 Node.js 章节)。 * **依赖安装失败或行为异常**:尝试删除 `node_modules` 与 lockfile 后重新安装。 ```bash rm -rf node_modules pnpm-lock.yaml pnpm install ``` > 注意:删除 `pnpm-lock.yaml` 会重新计算依赖版本,团队协作时一般应保留 lockfile,仅在确有需要时删除。 * **缓存导致的怪异问题**:清理 pnpm 的缓存与存储。 ```bash pnpm store prune ``` * **包管理器混用**:同一项目应统一使用 pnpm,避免与 npm / yarn 混用产生多份 lockfile。 --- --- url: 'https://docs.soybeanjs.cn/zh/recommend.md' --- # 前言 在这部分文档中,我们将为您介绍一些我们觉得有意思的各类技术。尽管目前这些技术可能尚未在我们的项目中使用,但我们认为它们是非常有价值的内容。我们希望通过这些介绍,能够帮助到您。 我们深知技术的发展日新月异,新的技术层出不穷。虽然我们无法保证这些介绍中的技术一定适用于每个项目,但我们相信它们具有一定的参考价值。无论您是对这些技术感兴趣,还是希望在项目中尝试新的解决方案,我们都希望能够为您提供一些有用的信息。 请继续阅读后面的内容,了解更多关于这些技术的介绍。如果其中的介绍或其他信息有误,请随时与我们联系,我们将非常乐意为您提供帮助和纠正。谢谢! 祝您阅读愉快! --- --- url: 'https://docs.soybeanjs.cn/zh/recommend/soybean-cli.md' --- # @soybeanjs/cli ## 相关链接 * [文档](https://github.com/soybeanjs/cli/blob/main/README.md) * [GitHub](https://github.com/soybeanjs/cli) ## 介绍 SoybeanJS 的命令行工具,包含六种便捷命令: | 命令 | 作用 | | ----------------- | ------------------------------------------------------------------------------------ | | git-commit | 生成符合 Angular 规范的 git 提交信息 (在提交信息添加前缀`!`可以表示破坏性更新的提交) | | git-commit-verify | 校验 git 的提交信息是否符合 Angular 规范 | | cleanup | 快速、完整的清空依赖和构建产物 | | ncu | 命令 npm-check-updates, 升级依赖 | | changelog | 根据两次 tag 生成 changelog (--total: 根据所有 tag 生成 changelog) | | release | 发布:更新版本号、生成 changelog、提交代码 | --- --- url: 'https://docs.soybeanjs.cn/zh/recommend/alova.md' --- # Alova ## 相关链接 * [文档](https://alova.js.org/zh-CN) * [GitHub](https://github.com/alovajs/alova) ## 介绍 alova 是一个流程简化的下一代请求工具,它可以将你的 API 集成工作流从 7 个步骤极致地简化为 1 个步骤,你只需要选择 API 即可使用。 有别于@tanstack/react-request、swrjs、ahooks的useRequest等库,alova是一个完整的请求方案,alova 让你的请求集成变得非常简单,并且保持更高效的 Client-Server 数据交互。此外,你可以在客户端和服务端环境中(包括 SSR)使用alova。 此外,alova 还具有以下特性: * 与 axios 相似的 api 设计,学习成本更低; * 高性能的客户端和服务端请求策略,让应用更流畅; * 灵活性高,alova 可以在任何 js 环境下,与任何 UI 框架协作使用,并且提供了统一的使用体验和完美的代码迁移; * 多级缓存模式和请求共享机制,提升请求性能并降低服务端压力; * api 代码的高聚合组织,每个 api 的请求参数、缓存行为、响应数据转换等都将聚集在相同的代码块中,这对于管理大量的 api 有很大的优势; --- --- url: 'https://docs.soybeanjs.cn/zh/recommend/page-spy.md' --- # PageSpy ## 相关链接 * [GitHub](https://github.com/HuolalaTech/page-spy-web) * [官方文档](https://pagespy.org) * [Bilibili 视频](https://space.bilibili.com/3493272492181886/) ## 介绍 ### 背景 控制台在日常开发中是必不可少的效率工具,项目问题总是第一时间通过它排查。但有些时候无法使用控制,因此而导致排查问题需要花费很多时间和人力,这就是 PageSpy 想去解决的问题。 看看下面的场景你是否遇到过: * **真机调试 H5**:以往有些产品提供了可以在 H5 上查看信息的面板,但真机屏幕太小操作不便、显示不友好,以及数据会被截断; * **远程办公、异地协同**:传统沟通方式如邮件、电话、视频会议等,沟通问题的周期长、效率不高、故障信息不全面,容易误解误判; * **用户设备白屏**:除了需要提前获知出现问题的用户信息,定位问题的方式包括查看数据监控、日志分析,甚至还要跑到客户现场等,这些方式依赖排障人员要理解业务场景、技术实现; * **全局的 "问题反馈" 组件**:大多注重用户体验的网站,为了在产品出现故障后能收到反馈并及时解决,会在产品端为用户提供反馈问题的表单组件。从用户的角度这确实会提升好感,但用户提交的内容可能对于排查问题的帮助并不大,根本原因是:用户提交的基本上是文字概述和截图,或许还包含用户信息,但开发者更希望看到的是: * 用户的操作轨迹; * 伴随着操作,程序的运行时行为数据。例如:打印的日志、发出的网络请求以及响应数据等内容; 正如本地开发我们就是这样使用控制台的,不是吗? ### 能力 PageSpy 按使用场景分为 **在线实时调试** 和 **离线日志回放** 两种模式,提供了 Console、Network、Page、Storage 以及 System 信息面板,还可以发送代码到项目端上执行;能够让开发者们提升排障的效率,同时也能减少沟通的时间。 目前 PageSpy 在 Web / 小程序 / ReactNative / OpenHarmony 平台上都已经有稳定的 SDK。 --- --- url: 'https://docs.soybeanjs.cn/zh/recommend/klona.md' --- # klona ## 相关链接 * [GitHub](https://github.com/lukeed/klona) ## 介绍 `klona` 是一个非常小巧(240B 到 501B)且高效的工具库,用于深拷贝 JavaScript 对象、数组、日期、正则表达式等多种数据类型。 ### 特性 * 超小体积和高性能 * 深拷贝/递归复制 * 安全处理复杂数据类型,包括:`Array`, `Date`, `Map`, `Object`, `RegExp`, `Set`, `TypedArray` 等。 与浅拷贝(例如 `Object.assign`)不同,深拷贝会递归地遍历源输入并复制其 *值* —— 而不是其值的 *引用* —— 到该输入的一个新实例中。结果是一个结构上相同的克隆,它独立于原始源进行操作并控制自己的值。 > **为什么叫 "klona"?** 这是瑞典语中的 "clone"。 ## 安装 ```bash npm install --save klona ``` ## 不同模式 `klona` 提供了多种"版本",让你可以只引入你需要的功能! #### `klona/json` > **大小 (gzip):** 240 字节 > **可用性:** CommonJS, ES Module, UMD > **能力:** JSON 数据类型 ```javascript import { klona } from 'klona/json'; ``` #### `klona/lite` > **大小 (gzip):** 354 字节 > **可用性:** CommonJS, ES Module, UMD > **能力:** 扩展了 `klona/json`,增加了对自定义类、Date 和 RegExp 的支持。 ```javascript import { klona } from 'klona/lite'; ``` #### `klona` (默认) > **大小 (gzip):** 451 字节 > **可用性:** CommonJS, ES Module, UMD > **能力:** 扩展了 `klona/lite`,增加了对 Map, Set, DataView, ArrayBuffer, TypedArray 的支持。 ```javascript import { klona } from 'klona'; ``` #### `klona/full` > **大小 (gzip):** 501 字节 > **可用性:** CommonJS, ES Module, UMD > **能力:** 扩展了 `klona`,增加了对 Symbol 属性和不可枚举属性的支持。 ```javascript import { klona } from 'klona/full'; ``` ## 使用方法 ```javascript import { klona } from 'klona'; const input = { foo: 1, bar: { baz: 2, bat: { hello: 'world' } } }; const output = klona(input); // 与原始对象完全相同 // assert.deepStrictEqual(input, output); // 在 Node.js 环境中断言 // 深层更新... output.bar.bat.hola = 'mundo'; output.bar.baz = 99; // ...不会影响源对象! console.log(JSON.stringify(input, null, 2)); // { // "foo": 1, // "bar": { // "baz": 2, // "bat": { // "hello": "world" // } // } // } ``` ## API ### `klona(input)` 返回: `typeof input` 返回输入值的深拷贝/克隆。 --- --- url: 'https://docs.soybeanjs.cn/zh/guide/hooks/use-table.md' --- # useTable 函数 `useTable` 是一个用于管理表格数据、列和加载状态的 Vue Hook。它提供了一种灵活的方式来处理数据获取、分页、列可见性等常见表格功能。 本指南将介绍如何使用最新的 `useTable`,以及其针对 Naive UI 的封装 `useNaiveTable` 与 `useNaivePaginatedTable`。 ## 快速对比 * `useTable`:不绑定任何 UI 库,仅处理请求、数据转换、列配置与列显隐(checks)管理。 * `useNaiveTable`:在 `useTable` 基础上,适配 Naive UI 的列定义,提供 `scrollX`,并在 i18n 切换时自动刷新列。 * `useNaivePaginatedTable`:在 `useNaiveTable` 基础上集成分页(`PaginationProps`)、移动端分页 `mobilePagination`、`getDataByPage` 等。 * `useTableOperate`:围绕“新增/编辑/批量删除/单删”的通用 UI 状态和回调封装。 * `defaultTransform`:把后端统一分页结构转换为 `PaginationData`。 ## `useTable` `useTable` 只关注“数据驱动”,不关心具体表格组件/UI 库,它提供了灵活的选项来配置数据获取、转换和列管理。 ### 函数签名 ```typescript export default function useTable( options: UseTableOptions ); ``` ### `UseTableOptions` 接口 ```typescript export interface UseTableOptions { /** * 获取表格数据的 API 函数 */ api: () => Promise; /** * 是否启用分页 */ pagination?: Pagination; /** * 将 API 响应转换为表格数据的函数 */ transform: Transform; /** * 列定义的工厂函数 */ columns: () => Column[]; /** * 获取列检查项的函数 */ getColumnChecks: (columns: Column[]) => TableColumnCheck[]; /** * 根据检查项获取最终列的函数 */ getColumns: (columns: Column[], checks: TableColumnCheck[]) => Column[]; /** * 数据获取完成后的回调函数 */ onFetched?: (data: GetApiData) => void | Promise; /** * 是否立即获取数据 * * @default true */ immediate?: boolean; } ``` ### 返回值 ```ts { loading: Ref; empty: Ref; data: Ref; columns: ComputedRef; columnChecks: Ref; reloadColumns: () => void; getData: () => Promise; } ``` ### 说明 * `columnChecks` 决定列的显示与隐藏;`columns()` 是“工厂函数”(每次取值都会重新生成列定义)。 * `reloadColumns` 在不丢失“勾选状态”的前提下,按当前工厂函数输出重建列(例如语言切换后标题变化时调用)。 * 当 `pagination` 为 `true` 时,`transform` 返回 `PaginationData`,`useTable` 会把其中的 `data` 用作表格数据。 ### 使用示例 ```typescript import { useTable } from '@sa/hooks'; import type { UseTableOptions } from '@sa/hooks'; import type { PaginationData } from '@sa/hooks'; import type { DataTableColumns } from 'naive-ui'; interface User { id: number; name: string; email: string; } interface UserResponse { data: User[]; total: number; } const { loading, data, columns, getData } = useTable, false>({ api: fetchUsers, // 一个返回 Promise 的函数 transform: response => response.data, columns: () => [ { key: 'id', title: 'ID' }, { key: 'name', title: 'Name' }, { key: 'email', title: 'Email' } ], getColumnChecks: cols => cols.map(col => ({ key: col.key as string, title: col.title!, checked: true, visible: true })), getColumns: (cols, checks) => cols.filter(col => checks.find(c => c.key === col.key)?.checked) }); // 获取数据 getData(); ``` ## `useNaiveTable` `useNaiveTable` 是对 `useTable` 的 Naive UI 版本封装,使用 `NaiveUI.TableColumn`,并提供横向滚动宽度 `scrollX`,内置 i18n 变更时的列刷新。 额外选项: * `getColumnVisible?: (column: NaiveUI.TableColumn) => boolean` * 控制列是否出现在“列显隐面板”(例如 `selection/expand` 列可选择不显示在面板中)。 额外返回: * `scrollX: ComputedRef`(根据 `width/minWidth` 汇总,便于 NDataTable 横向滚动)。 说明: * 不再需要你提供 `getColumnChecks` 与 `getColumns`,封装内部已适配 Naive UI 的列显隐处理。 * 内部会为 `selection/expand` 这类无 `key` 的列生成内部 `key` 以参与显隐控制。 ### 函数签名 ```typescript export function useNaiveTable(options: UseNaiveTableOptions); ``` ### `UseNaiveTableOptions` 接口 ```typescript export type UseNaiveTableOptions = Omit< UseTableOptions, Pagination>, 'pagination' | 'getColumnChecks' | 'getColumns' > & { /** * get column visible * * @param column * * @default true * * @returns true if the column is visible, false otherwise */ getColumnVisible?: (column: NaiveUI.TableColumn) => boolean; }; ``` ### 使用示例 ```typescript import { useNaiveTable } from '@/hooks/common/table'; /** get user list */ function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) { return request({ url: '/systemManage/getUserList', method: 'get', params }); } const searchParams: Api.SystemManage.UserSearchParams = reactive({ current: 1, size: 999, status: null, userName: null, userGender: null, nickName: null, userPhone: null, userEmail: null }); const { loading, data, columns, getData, scrollX } = useNaiveTable({ api: () => fetchGetUserList(), transform: response => { const { data: list, error } = response; if (!error) { return list.records; } return []; }, columns }); // 获取数据 getData(); ``` > 注意:`fetchGetUserList` 需要明确的返回类型,`useNaiveTable` 可以不传递泛型参数,直接推导出类型。 ## `useNaivePaginatedTable` `useNaivePaginatedTable` 是针对需要分页的 Naive UI `DataTable` 组件的封装。 ### 函数签名 ```typescript export function useNaivePaginatedTable( options: UseNaivePaginatedTableOptions ); ``` ### `UseNaivePaginatedTableOptions` 接口 ```typescript type UseNaivePaginatedTableOptions = UseNaiveTableOptions & { paginationProps?: Omit; /** * whether to show the total count of the table * * @default true */ showTotal?: boolean; onPaginationParamsChange?: (params: PaginationParams) => void | Promise; }; ``` ### 使用示例 ```typescript import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table'; /** get role list */ function fetchGetRoleList(params?: Api.SystemManage.RoleSearchParams) { return request({ url: '/systemManage/getRoleList', method: 'get', params }); } const searchParams: Api.SystemManage.RoleSearchParams = reactive({ current: 1, size: 10, roleName: null, roleCode: null, status: null }); const { loading, data, columns, pagination, getDataByPage } = useNaivePaginatedTable({ api: fetchGetRoleList, transform: response => defaultTransform(response), onPaginationParamsChange: ({ page, pageSize }) => { // 把分页参数同步到搜索参数(关键) searchParams.current = page; searchParams.size = pageSize; }, columns: () => [ { type: 'selection', align: 'center', width: 48 }, { key: 'index', title: $t('common.index'), width: 64, align: 'center', render: (_, index) => index + 1 }, { key: 'roleName', title: $t('page.manage.role.roleName'), align: 'center', minWidth: 120 } // ...其他列 ] }); // 常用的增删改查辅助状态和方法 const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted // ...其他方法 } = useTableOperate(data, 'id', getData); ``` ## useTableOperate(表格操作辅助) 封装“新增/编辑/批量删除/单删”的 UI 状态与回调,配合抽屉/弹窗使用。 签名: ```ts export function useTableOperate( data: Ref, idKey: keyof TableData, getData: () => Promise ); ``` 入参: ```typescript { data: Ref, // 表格当前数据(编辑时根据 `id` 定位行数据)。 idKey: keyof TableData, // 主键字段名(如 `id`)。 getData: () => Promise // 删除后的刷新函数。 } ``` 返回: ```ts { drawerVisible: Ref; openDrawer: () => void; closeDrawer: () => void; operateType: ShallowRef; handleAdd: () => void; editingData: ShallowRef; handleEdit: (id: TableData[keyof TableData]) => void; checkedRowKeys: ShallowRef; onBatchDeleted: () => Promise; // (批量删除成功后调用:清空勾选 + 刷新) onDeleted: () => Promise; // (单项删除成功后调用:刷新) } ``` 说明: * `handleEdit(id)` 会在 `data` 中查找该行并打开抽屉,将行数据赋给 `editingData`。 * 删除类操作成功后调用 `onBatchDeleted/onDeleted` 即可处理状态并刷新。 ## defaultTransform(统一分页数据转换) 如果接口返回结构为: * `FlatResponseData>` * 其 `data` 通常包含:`records`、`current`、`size`、`total`。 可直接使用 `defaultTransform` 将其转换为 `PaginationData`: > 如果接口返回的结构有其他差异,可自行编写类似于 `defaultTransform` 的转换函数传给 `transform` 参数,请务必明确返回类型。 ## 列显隐与横向滚动 * 列显隐面板:`columnChecks` 即“可见列”集合,结合 `v-model:columns` 绑定到你的“列表头操作”组件(如 `TableHeaderOperation`)。 * Naive 适配中 `selection/expand` 列也会参与显隐(内部有稳定 `key`)。 * 横向滚动:`scrollX = ∑(column.width || column.minWidth || 120)`。建议为列设置 `width/minWidth`,否则采用默认最小宽度 120。 ## i18n 与列刷新 * `useNaiveTable/useNaivePaginatedTable` 内部会监听 `appStore.locale` 并触发 `reloadColumns()`,保证标题 `$t(...)` 在语言切换后即时更新。 * 纯 `useTable` 场景若使用 i18n,需要自行调用 `reloadColumns()`。 ## 与示例页面对照 以“用户管理/角色管理”页面为例(`src/views/manage/user/index.vue`、`src/views/manage/role/index.vue`): * 通过 `useNaivePaginatedTable` 提供 `columns`、`columnChecks`、`data`、`loading`、`getData`、`getDataByPage`、`mobilePagination`、`scrollX`。 * `TableHeaderOperation` 中使用 `v-model:columns="columnChecks"` 控制“列显隐”。 * `NDataTable`: * 绑定 `:columns`、`:data`、`:loading`、`:scroll-x="scrollX"`; * `remote`; * `:row-key="row => row.id"`; * `:pagination="mobilePagination"`。 * `useTableOperate` 管理新增/编辑抽屉与删除后的刷新。 ## 常见问题与最佳实践 * 何时使用 `getDataByPage(1)`?当筛选条件变化时,从第一页拉取。 * 忘记同步页码?务必在 `onPaginationParamsChange` 中把 `page/pageSize` 同步回搜索参数。 * `immediate` 默认 `true`:初始化会拉一次数据;如不需要,可传 `immediate: false` 并手动调用 `getData()`。 * `NDataTable` 的 `row-key` 必须稳定,确保选择、展开等功能正常。 * 移动端优化:使用 `mobilePagination`,小屏更合适的分页展示。 --- --- url: 'https://docs.soybeanjs.cn/zh/cooperate.md' --- # 合作事项 我们非常感谢大家对 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的支持!为了进一步回馈社区,并助力企业和开发者实现个性化需求,我们现提供多种合作服务,期待与您携手共赢。 ## 1、定制化管理后台开发 针对企业和开发者的特定业务需求,我们提供基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的定制化管理后台开发服务。我们的团队具备丰富的行业经验,能够迅速理解并实现您的需求,打造高效、灵活且安全的定制化解决方案。 * **定制开发**:我们将根据您的具体需求,提供从需求分析、UI设计到功能实现的全方位服务,确保项目高效交付。 * **功能扩展**:在 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 基础上,扩展您所需的特定功能模块,提升管理后台的功能和用户体验。 ## 2、企业外包服务 我们承接各类企业级外包项目,特别是在管理后台系统的开发、集成与运维方面。我们以精益求精的态度,确保项目的质量和进度,为您的业务提供强有力的技术支持。 * **项目开发**:无论是全新的项目,还是现有系统的优化与集成,我们都将为您量身打造高效可靠的解决方案。 * **系统集成与维护**:我们也提供基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的系统集成与长期维护服务,确保您的系统稳定、安全地运行。 ## 3、联系方式 如有合作意向或项目咨询,请通过以下方式与我们联系: * **Email**: * **GitHub Issues**: 欢迎通过 [GitHub Issues](https://github.com/soybeanjs/soybean-admin/issues/new) 联系我们,进行初步的合作洽谈。 * **商务合作微信**: honghuangdc 期待与您开展深入合作,共同推动 SoybeanAdmin 项目及其在更多领域的成功应用! --- --- url: 'https://docs.soybeanjs.cn/zh/awesome.md' --- # 周边生态 这里收录了 SoybeanAdmin 周边的开源项目、衍生版本与实用资源,涵盖各类技术栈的后台管理方案。欢迎社区一起共建与补充。 ## 开源项目或作品 ### Admin 类型 | 项目名称 | 描述 | 地址 | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | skyroc-admin | 基于Soybean,实现的React版本! 采用 React18 / Redux-toolkit /Antd/Vite5/Typescript 采用最原生最简洁的方式来实现, 前端清新优雅高颜值,后端 结构清晰,优雅易懂,功能强大,期待您的体验! | https://github.com/Ohh-889/skyroc-admin | | RuoYi-Plus-Soybean | RuoYi-Plus-Soybean 是一个现代化的企业级多租户管理系统,它结合了 [RuoYi-Vue-Plus](https://plus-doc.dromara.org/#/) 的强大后端功能和 [Soybean Admin](https://github.com/soybeanjs/soybean-admin) 的现代化前端特性,为开发者提供了完整的企业管理解决方案。 | https://gitee.com/xlsea/ruoyi-plus-soybean | | pea | 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等,适配 soybean-admin 开发的简单权限系统。 | https://github.com/haitang1894/pea | | electron-mock-admin | 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的 mock | https://github.com/lixin59/electron-mock-api | | T-Shell | 是一个可配置命令提示的终端模拟器和 SSH 客户端 | https://github.com/TheBlindM/T-Shell | | MalusAdmin | 基于 Vue3/TypeScript/NaiveUI 和 NET7 & Sqlsugar 开发的后台管理框架。采用最原生最简洁的方式来实现, 前端清新优雅高颜值,后端 结构清晰,优雅易懂,功能强大 | https://github.com/pridejoy/MalusAdmin | | PanisAdmin | 采用SpringBoot3、SaToken、MySQL等框架开发,二次修改 soybean-admin,适配动态菜单/按钮级别的鉴权,保留原汁原味、清新优雅、高颜值的后台管理系统脚手架。 | https://github.com/paynezhuang/panis-admin | | snail-job | 一款兼具 “高性能、高颜值、高活跃” 的分布式任务重试和分布式任务调度平台。 | https://github.com/aizuda/snail-job | | SuperApi | 快速将你的 idea 变成线上稳定运行的产品! 无实体建库建表,对无实体库表进行增删改查,支持 15 种条件查询,以及分页,列表,无限级树形列表 等功能的 API 部署! 拥有接口文档,Auth 授权,接口限流,获取客户端真实 IP,先进的服务器缓存组件,动态 API 等功能,期待您的体验! | https://github.com/TmmTop/SuperApi | | FastSoyAdmin | 基于 FastAPI+Vue3+Naive UI 的现代化轻量管理平台。 | https://github.com/sleep1223/fast-soy-admin | | web-firewall | web-firewall基于golang+vue3 开发的Web Linux防火墙,前端使用SoybeanAdmin框架,后端使用goframe2,数据库支持 sqlite3(默认)/postgresql ,它可以在Linux系统中基于nfatables用于替代firewalld工具。 | https://github.com/moreKing/web-firewall | | soybean-admin-nestjs | 基于 NestJS 和 CQRS 的后台管理系统脚手架,融合 DDD 驱动设计和 NestJS monorepo 结构,内置基础权限管理。为开发者提供一个灵活、模块化的起点,助力构建基础的管理系统。 | https://github.com/soybeanjs/soybean-admin-nestjs | | soybean-admin-quarkus | 基于 Kotlin 和 Quarkus 的后台管理系统脚手架,融合 DDD 驱动设计、CQRS 和事件溯源。采用 Gradle 构建,旨在为开发者提供一个轻量级、高性能的现代化管理系统开发框架。 | https://github.com/soybeanjs/soybean-admin-quarkus | | jzero-admin | 基于 golang [go-zero](https://github.com/zeromicro/go-zero) 框架扩展的 [jzero](https://github.com/jzero-io/jzero) 脚手架开发,配备服务端/数据库/客户端 SDK 代码自动生成并支持多模块插件化的下一代后台管理系统。 | https://github.com/jzero-io/jzero-admin | | ba | 基于[goFrame](https://github.com/gogf/gf)框架开发的后端服务对接soybean-admin,适配动态路由,接口鉴权。 | https://github.com/xiatianYa/Ba-Server | | Vue3NaiveAdmin | Vue3NaiveAdmin基于soybean-admin,对接了NestAdmin后端服务。NestAdmin是一个简单高效的的后台管理系统,基于最新的前端技术栈,包括 Nestjs, TypeScript, TypeOrm, Mysql 和 Redis。 适用于WEB全栈人员快速开发后台管理系统。 | https://github.com/mrzym99/vue3-naive-admin | | soybean-admin-go | 基于gin+gorm+gen框架开发的go语言后端服务对接soybean-admin的example分支,适配动态路由,接口鉴权限。 | https://github.com/WgoW/soybean-admin-go | | DoraCMS | DoraCMS 是一个基于 EggJS 3.x + Vue 3 + TypeScript 的现代化内容管理系统,采用 pnpm monorepo 架构管理,后台管理基于 soybean-admin。它不仅仅是一个 CMS 系统,更是一个优秀的企业级应用架构实践。 | https://github.com/doramart/DoraCMS | --- --- url: 'https://docs.soybeanjs.cn/zh/faq.md' --- # 常见问题 ::: tip 这里列举了一些常见的问题。如果没有找到可以在 [github issue](https://github.com/honghuangdc/soybean-admin/issues) 反馈。 ::: ## 前言 遇到问题,可以尝试以下的解决方案 * 请先找出关键性的错误信息以及必要问题上下文 * 尝试使用搜索引擎、技术网站、AI 工具等搜索错误的关键词 | [Google](https://google.com) | [Bing](https://www.bing.com/) | ChatGPT | [StackoverFlow](https://stackoverflow.com/) | | ---------------------------- | ----------------------------- | ------- | ------------------------------------------- | * 若是错误为依赖包的问题,请尝试去依赖包的 Github 的 Issues 中搜索 * 尝试请教认识的朋友或技术大佬 * 在SoybeanAdmin官方交流群里面提问,请尽量描述清楚问题,以便大家更好的帮助你,可以参考 [提问的智慧](https://github.com/tvvocold/How-To-Ask-Questions-The-Smart-Way) ## SoybeanAdmin 缓存方面的问题 **问题背景** SoybeanAdmin 的项目配置默认是 `localStorage` , 初始化时对项目的主题涉及的数据进行持久化 项目的缓存分为两方面 * LocalStorage * SessionStorage **缓存要点** 1. 对于本框架缓存方面的使用主要集中在下列几个方法中: * set:通过给方法传递必填参数 `key` 、`value` 和可选参数 `expire` 对数据进行缓存 * get:通过给方法传递必填参数 `key` 获取缓存的数据 * remove:通过给方法传递必填参数 `key` 移除指定的缓存数据 * clear:通过调用该方法,清除当前所有的 `Storage` 相关的缓存数据 2. 缓存的数据类型需要预先在 src/typings/storage.d.ts 里面定义好 ## 关于修改文件相关的问题 1. 当修改 `.env` 等环境文件及 `vite.config.ts` 文件时,vite 会自动重启服务。 > 但是自动重启有几率出现问题,请重新运行项目即可解决。 2. 当修改 `.vue` 或者 `.ts` 时, vite 进行热部署时有几率造成页面卡顿导致无法看到 > 实时修改的效果,`F5` 刷新即可解决 ## 前端静态路由添加菜单后没显示 📢 有热心群友反馈:刚接触项目时,先添加组件再添加静态路由,但是页面上无法渲染菜单和页面,项目不报错 问题背景 项目初始化路由时,该同学的顶级路由数据 meta 中含有 `hideInMenu` 属性为 true 所以菜单和页面都无法显示出来 ::: tip 组件位置 src/typings/router.d.ts ::: 跳转查看 [`RouteMeta`](../guide/router/intro.md#配置属性) **解决方案:** 去除 `hideInMenu` 属性即可正常显示菜单和页面 ## 项目中的权限路由模式如何理解,相应的渲染路由的数据格式怎么定义 **问题背景** 项目中的权限路由模式分为: * 静态路由 静态路由指的是前端项目:`src/router/routes.ts` 中的路由数据 项目能够根据在这个路径下定义进行路由数据的解析,并自动渲染出菜单信息 * 动态路由 动态路由指的是后台项目传递过来的路由数据 > 项目使用动态路由模式进行数据渲染时,会自动覆盖路由首页的 name 值 ## Tab 页签刷新后一片空白 📢 有热心群友反馈,项目在开发环境中存在 `Tab 页切换出现空白页的情况` *** 这是由于开启了路由切换动画,且对应的页面组件存在多个根元素时导致的, 可以通过在页面最外层添加一个 `
` ( 或者) 即可 ❌ **错误示范** ```vue ``` ✔ **正确示范** ```vue ``` ## 组件命名问题 > 📢 有热心群友反馈:为了延续项目高质量代码的风格,想学习一种相对科学的命名方式,但苦于没有具体的格式规范 **命名规范** * 文件命名: 统一用小写字母命名,多个单词用中划线连接 ``` views ├── home ├── demo-page ``` * Vue 组件名称 * 组件名称统一用 PascalCase 法命名,多个单词首字母大写 ```vue ``` * iconify 图标组件名称统一用 kebab-case 法命名,多个单词用中划线连接 ```vue ``` > 方便iconify插件直接展示图标 * 构造函数、class 类、TS 类型命名:统一用 PascalCase 法命名,多个单词首字母大写 ```ts function Person() {} class Person {} type Person = { name: string; }; interface Person { name: string; } ``` * 变量、普通函数命名:统一用 camelCase 法命名,多个单词首字母小写 ```ts let num: number = 1; function getNum() {} ``` * 常量命名:统一用大写字母命名,多个单词用下划线连接 ```ts const MAX_COUNT = 10; ``` * 样式的命名:统一用小写字母命名,多个单词用中划线连接 ```css .container { } .container-item { } ``` ## 环境问题 > 如果出现依赖安装报错,启动报错等。先检查电脑环境有没有安装齐全。 本地环境需要具备 * [Git](https://git-scm.com/) *** * **NodeJS**: >=18.0.0,推荐 18.19.0 或更高。 > 你可以使用 [volta](https://volta.sh/) 或 [fnm](https://github.com/Schniz/fnm) 来管理你的NodeJS版本。 * **pnpm**: >= 8.0.0,推荐最新版本。 ## 依赖安装问题 * 检查网络问题 * 检查镜像源问题 * 检查依赖包版本问题 **镜像配置** > 项目默认镜像配置文件 .npmrc 的配置项说明 🎯 文件位置:`.npmrc` ``` registry=https://registry.npmmirror.com/ shamefully-hoist=true ignore-workspace-root-check=true ``` * `registry`:指定了 npm 包的镜像源,本项目中使用的镜像源是淘宝的最新镜像。 * `shamefully-hoist`:该选项用于将依赖项 hoist 到尽可能高的节点上,提高依赖项的共用 * `ignore-workspace-root-check`:在跟路径安装依赖时,忽略工作区根检查,即不用加上 `-w` 参数 > 完整代码指路 [SoybeanAdmin🔜](https://github.com/soybeanjs/soybean-admin/blob/main/.npmrc) ## 代码如何保持最新 如果你使用了该项目进行项目开发。开发之中想同步最新的代码。你可以设置多个源的方式 * 克隆代码 ```bash git clone https://github.com/soybeanjs/soybean-admin.git ``` * 添加自己的 git 源地址 ```bash # up 为源名称,可以随意设置 # gitUrl为自己的 git 源地址 git remote add up gitUrl; ``` 3. 提交代码到自己的 git ```bash # 提交代码到自己的 git 仓库 # main为分支名 需要自行根据情况修改 git push up main # 同步自己的代码 # main为分支名 需要自行根据情况修改 git pull up main ``` 4. 如何同步开源最新代码 ```bash git pull origin main ``` > 使用 Git 进行代码管理的时候,先更新,遇到冲突先解决,然后再合并 ## 为什么是 dayjs Day.js 是一个极简的 JavaScript 库,可以为现代浏览器解析、验证、操作和显示日期和时间。 **为什么使用 Day.js?** 文件大小只有 2KB 左右,下载、解析和执行的 JavaScript 更少,为代码留下更多的时间。 **沙箱机制** 所有更改 Day.js 对象的 API 操作都将返回一个新的实例。这有助于防止错误和避免长时间的调试会话。 **国际化** Day.js 对国际化有很大的支持。但是,除非您使用它们,否则它们都不会包含在您的构建中。 ## 跨域问题 ### 概念 跨域(Cross-Origin)指的是在浏览器中,当前网页从一个不同的域名、端口或协议请求资源,导致安全策略限制,从而出现跨域问题。 **跨域的形成原因** * 同源策略:浏览器的安全策略限制了页面只能请求同一域名下的资源,其他域名下的资源不能访问。 * 域名不同:请求的资源在不同的域名下,例如 \[http://www.aaa.com] 和 \[http://www.bbb.com] * 端口不同:请求的资源在同一域名下,但端口不同,例如 \[http://www.xxx.com] 和 \[http://www.xxx.com:8080] * 协议不同:请求的资源在同一域名下,但协议不同,例如 \[http://www.xxx.com] 和 \[https://www.xxx.com] **正向代理和反向代理** 1. 正向代理 *正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户.* > 在本项目中指的是通过配置 `Vite` 实现正向代理 2. 反向代理 反向代理(Reverse Proxy)是一种服务器配置,它允许一个中间服务器来接收来自客户端的请求,然后将这些请求转发给后端的一个或多个服务器。客户端通常不知道它们实际上与后端服务器进行通信,因为所有的交互都通过反向代理服务器进行。 > 一般是将 dist 目录部署到 `Nginx` 服务器后,通过配置 `nginx.conf` 实现反向代理 ### 常见解决方案 实际的开发场景可能遇到的跨域有两种情况, **本地开发跨域** SoybeanAdmin 目前已经内置了全自动的代理配置,详情查看 [代理](/zh/guide/request/proxy.html) > 本地开发环境中,默认开启本地代理 **生产环境跨域** 项目部署至生产环境后,一般使用 Nginx 进行请求转发至后台服务器,详情可以给 ai 提供一下请求地址,跟它要具体的配置步骤 > 如果后端服务允许 CROS 通过, 前台服务则不需要额外配置 ## vscode的i18nAlly插件无法新增多语言 参考:[fix(utils): 修复windows系统下使用vscode的i18nAlly插件无法新增多语言的问题 #630](https://github.com/soybeanjs/soybean-admin/pull/630) ## 项目中使用 Iframe 嵌入本地的 HTML 时出现 404 的问题 📢 有热心群友反馈:在项目开发过程中,业务需要在项目中使用 Iframe 嵌入本地的 HTML 文件, 但是嵌入后无法显示页面内容,显示的是 404 页面 **问题背景** 整个项目都是单页面应用,所以从路径里去加载不同的 HTML 本身就不支持,要么创建多页面应用,要么在单页面应用里通过 iframe 去加载其它的 HTML。 **解决方案** 集成 `vite-plugin-mpa` 插件。 ## 打包后刷新,页面404 **问题背景** 项目build之后: * 开发环境: 用live server等插件在本地启动打包后的index.html,刷新页面404 * 生产环境: 部署到服务器,刷新页面404 **问题原因** 系统默认使用的路由模式是 `history` 模式,而 `Nginx` 等web服务器默认是基于静态文件的,在请求 `/login` 等地址的时候,`Nginx` 会去寻找 `login.html` 这个文件,找不到就会报404了,所以该模式需要后端配合将所有访问都指向 `index.html`,将具体的路由信息交由 `vue-router` 处理。 **解决方案** 开发环境预览打包产物: * 使用 `pnpm preview` 命令启动预览。 生产环境: * `Nginx` 配置参考(其他web服务器自行搜索) ```java # nginx.conf server { listen 80; listen [::]:80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; // [!code ++] } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } ``` * 修改路由模式 如果无法修改web服务器,可以通过修改前端路由模式为 `hash` 避免该问题 ::: tip 代码位置 ./env ::: ```dotenv{5} # whether to enable http proxy when is dev mode VITE_HTTP_PROXY=Y # vue-router mode: hash | history | memory // [!code focus:2] VITE_ROUTER_HISTORY_MODE=hash # success code of backend service, when the code is received, the request is successful VITE_SERVICE_SUCCESS_CODE=0000 ``` --- --- url: 'https://docs.soybeanjs.cn/zh/other/donate.md' --- # 捐赠 如果您认为本项目对您有所帮助,可以请 Soybean 喝杯咖啡以示支持,Soybean 的开源动力离不开您的支持和鼓励。 ![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/donation.png) ## 捐赠列表 ::: tip 🎉 致谢 诚挚感谢曾经为 SoybeanAdmin 发展添柴助力的老板们,下放列表非全部,仅做部分展示。望谅解~ ::: > 信息来源于订单记录,有缺漏、需修改或删除的请在各大官方群组联系团队成员。 | 捐赠人 | 金额 | 时间 | 留言 | | --------------- | --------- | ------------------- | --------------------------------------------------------------------- | | mufeng889 | ¥1190.00 | 2025-03-10 13:21:40 | 感谢学到了很多 能从你的指导中 不断学习形成自己的风格 | | 二·九 | ¥520.00 | 2025-10-25 15:42:37 | 创作不易!学习付费 | | 爱笑的boy | ¥300.00 | 2022-04-14 20:31:13 | 感谢感谢,工资15k了 | | 兰永懿 | ¥250.00 | 2024-04-22 16:38:33 | 使用了你的antd版本,感谢你的付出 | | 卑鄙的仓鼠 | ¥200.00 | 2025-11-04 11:49:32 | | | 606 | ¥199.00 | 2023-05-18 17:03:44 | 空 | | 晓庄💪 | ¥166.66 | 2024-09-09 09:09:09 | 感谢开源项目,学习到了。 | | 青菜白玉汤 | ¥147.90 | 2025-03-24 09:49:01 | 支持开源项目,希望发展的更好 | | 竹山居士 | ¥120.00 | 2021-10-03 15:56:52 | 空 | | 江建 | ¥100.00 | 2025-07-23 16:02:37 | | | 王奇奇 | ¥100.00 | 2025-06-25 10:23:11 | | | 马铃薯头 | ¥100.00 | 2025-5-20 19:25:49 | 有了 Soybean 才有了 ruoyi-plus-soybean! | | 江湖故人 | ¥100.00 | 2025-04-28 22:03:14 | 空 | | 自强²ᴛR@ᴘᴛɪᴍɪsᴛ | ¥100.00 | 2025-03-15 23:00:42 | 刚开始接手中后台,大佬的开源帮助很大。才找到这个入口:) 请大佬咖啡☕️ | | 条形码 | ¥100.00 | 2024-10-11 19:19:45 | 空 | | \*帆 | ¥100.00 | 2024-07-16 23:16:26 | 空 | | Reality. | ¥100.00 | 2023-06-16 15:56:01 | 菜鸡需要websocket封装例子 | | xi | ¥100.00 | 2023-01-08 11:16:49 | 空 | | 一心 | ¥88.00 | 2025-02-24 11:29:12 | 空 | | Joe | ¥88.00 | 2024-09-13 10:19:59 | 快中秋了,请大佬吃月饼 | | 小白 | ¥68.00 | 2022-09-04 00:17:25 | 空 | | \*\*广 | ¥66.66 | 2025-08-26 16:58:31 | | | 庞 | ¥66.66 | 2025-08-26 15:53:10 | | | up | ¥66.66 | 2025-4-11 17:40:34 | 空 | | \*攀 | ¥66.66 | 2024-09-12 22:03:46 | 空 | | NopAGoGo | ¥66.00 | 2023-06-12 17:41:24 | 空 | | 李芳 | ¥58.88 | 2025-09-06 00:14:14 | | | Mr.奇淼 | ¥52.00 | 2024-05-20 13:14:00 | 开源人一起冲 | | 易申 | ¥50.00 | 2024-09-10 19:22:17 | 空 | | 啵啵脆的饼干 | ¥50.00 | 2024-09-05 20:00:32 | 请大佬喝咖啡 | | 守望海 | ¥50.00 | 2024-04-26 15:37:49 | 空 | | 👿 | ¥50.00 | 2023-09-14 12:01:11 | 空 | | 十五 | ¥50.00 | 2022-05-09 20:26:52 | 空 | | 玺 | ¥49.00 | 2022-02-26 14:50:54 | 空 | | 聆听 | ¥47.66 | 2025-5-20 18:26:58 | 空 | | 一寸灰 | ¥35.78 | 2025-04-11 11:02:31 | What can i say | | 🚈唯🔥 | ¥33.00 | 2022-06-23 14:15:35 | 搞个辛巴克🧋 | | 小寳 | ¥30.00 | 2024-03-01 09:18:55 | 空 | | 小楼昨夜 | ¥28.80 | 2022-12-21 14:12:08 | 喝杯咖啡 | | 月亮守护者 | ¥28.00 | 2024-08-05 22:21:43 | 空 | | 我记得 | ¥20.00 | 2025-11-03 15:58:10 | | | 周小瑜 | ¥20.00 | 2025-07-03 08:51:22 | | | Quiteer | ¥20.00 | 2025-04-30 13:52:19 | 空 | | 往生 | ¥20.00 | 2025-4-20 19:36:17 | 感谢大佬 | | via | ¥20.00 | 2025-04-02 14:29:09 | 空 | | '@\_@ | ¥20.00 | 2024-09-30 09:10:18 | 空 | | 某君 | ¥20.00 | 2024-08-28 16:18:33 | 空 | | Bruce | ¥20.00 | 2024-03-27 15:27:57 | 空 | | nn | ¥20.00 | 2023-09-05 15:04:39 | 空 | | BL | ¥19.89 | 2024-10-30 10:41:21 | 梅开二度,作者加快soybean组件更新啊,我好抄😍😍😍 | | Coke | ¥16.00 | 2024-10-25 23:17:00 | 感谢你的贡献 | | 尧文 | ¥15.00 | 2025-07-21 16:32:46 | | | 流量 | ¥15.00 | 2025-06-06 15:13:09 | | | 💥Fighting | ¥15.00 | 2024-10-24 16:06:00 | 空 | | later yangs | ¥15.00 | 2024-05-21 10:38:24 | 空 | | 七星剑客zzz | ¥15.00 | 2024-03-28 21:12:56 | 请大佬喝水 | | zzz | ¥12.00 | 2023-10-02 17:18:02 | 空 | | MT | ¥10.00 | 2025-10-17 10:45:26 | 我要上电视 | | \*\*国 | ¥10.00 | 2024-09-23 16:54:51 | niubility | | 空白 | ¥10.00 | 2024-08-01 22:34:13 | 空 | | 雨 | ¥10.00 | 2024-01-09 11:26:30 | 空 | | H | ¥10.00 | 2024-01-03 09:55:27 | 支持作者 | | \*康 | ¥10.00 | 2023-09-27 15:41:47 | 空 | | hubbub | ¥10.00 | 2023-07-30 00:07:53 | 空 | | \*俊 | ¥10.00 | 2023-05-26 12:24:46 | 空 | | Bruce | ¥10.00 | 2023-04-19 02:35:36 | Bruce | | Hood | ¥10.00 | 2022-08-23 16:03:33 | 空 | | \*健 | ¥10.00 | 2022-06-22 19:55:20 | 空 |