diff --git a/.github/ISSUE_TEMPLATE/bug-report_cn.yaml b/.github/ISSUE_TEMPLATE/bug-report_cn.yaml index b1de7e61c8..747459d770 100644 --- a/.github/ISSUE_TEMPLATE/bug-report_cn.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report_cn.yaml @@ -1,7 +1,7 @@ name: ๐Ÿž Bugๆไบค description: ๅœจไฝฟ็”จ่ฝฏไปถๆˆ–ๅŠŸ่ƒฝ็š„่ฟ‡็จ‹ไธญ้‡ๅˆฐไบ†้”™่ฏฏ title: '[Bug]: ' -labels: [ "bug?" ] +labels: ['bug?'] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/bug-report_en.yaml b/.github/ISSUE_TEMPLATE/bug-report_en.yaml index a3a2f124fc..790d7ad747 100644 --- a/.github/ISSUE_TEMPLATE/bug-report_en.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report_en.yaml @@ -1,7 +1,7 @@ name: ๐Ÿž Bug Report description: Encountered an error while using the software or feature title: '[Bug]: ' -labels: [ "bug?" ] +labels: ['bug?'] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature_request_cn.yaml b/.github/ISSUE_TEMPLATE/feature_request_cn.yaml index c98162f5e2..24e3e6aa70 100644 --- a/.github/ISSUE_TEMPLATE/feature_request_cn.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request_cn.yaml @@ -1,9 +1,9 @@ --- name: ๐Ÿš€ ๅŠŸ่ƒฝ่ฏทๆฑ‚ description: ๆๅ‡บไธ€ไธชๆƒณๆณ•ไปฅๅธฎๅŠฉๆˆ‘ไปฌๆ”น่ฟ›W&B -title: "[ๅŠŸ่ƒฝ]: " +title: '[ๅŠŸ่ƒฝ]: ' labels: - - "ๅŠŸ่ƒฝ่ฏทๆฑ‚" + - 'ๅŠŸ่ƒฝ่ฏทๆฑ‚' body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature_request_en.yaml b/.github/ISSUE_TEMPLATE/feature_request_en.yaml index 07fc41c0ba..7c155bf42e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request_en.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request_en.yaml @@ -1,9 +1,9 @@ --- name: ๐Ÿš€ Feature Request description: Suggest an idea to help us improve W&B -title: "[Feature]: " +title: '[Feature]: ' labels: - - "feature_request" + - 'feature_request' body: - type: markdown diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 450ec86583..389b1789c6 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml new file mode 100644 index 0000000000..075c7b32a1 --- /dev/null +++ b/.github/workflows/opencode.yml @@ -0,0 +1,34 @@ +name: opencode + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + opencode: + if: | + contains(github.event.comment.body, ' /oc') || + startsWith(github.event.comment.body, '/oc') || + contains(github.event.comment.body, ' /opencode') || + startsWith(github.event.comment.body, '/opencode') + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: read + issues: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Run opencode + uses: anomalyco/opencode/github@latest + env: + GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + with: + model: google/gemini-3-pro-preview diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0bf7c92d50..fb5362ff3a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,19 +6,19 @@ permissions: on: push: tags: - - "v*" + - 'v*' jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v6 with: - node-version: 18.x + node-version: lts/* - run: npx githublogen env: diff --git a/.gitignore b/.gitignore index fdd159a961..136db7c15f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ package-lock.json yarn.lock .VSCodeCounter +src-tauri/target +.codegraph diff --git a/.npmrc b/.npmrc index dfc2d6865e..5e4086a7f3 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1 @@ registry=https://registry.npmmirror.com/ -shamefully-hoist=true -ignore-workspace-root-check=true -link-workspace-packages=true diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000000..3ac4fd8db0 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,11 @@ +{ + "ignorePatterns": ["src/typings/components.d.ts", "src/typings/elegant-router.d.ts", "src/router/elegant"], + "printWidth": 120, + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid", + "htmlWhitespaceSensitivity": "ignore", + "experimentalSortPackageJson": { + "sortScripts": true + } +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000000..cbe5f7f705 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,14 @@ +{ + "categories": { + "correctness": "error", + "suspicious": "error" + }, + "plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "vue"], + "rules": { + "import/no-unassigned-import": "off", + "unicorn/consistent-function-scoping": "off", + "unicorn/no-array-sort": "off", + "unicorn/no-array-reverse": "off", + "unicorn/require-module-specifiers": "off" + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0b689451b3..f64ee7a4d2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,11 +5,11 @@ "antfu.unocss", "dbaeumer.vscode-eslint", "editorconfig.editorconfig", - "esbenp.prettier-vscode", "lokalise.i18n-ally", "mhutchie.git-graph", "mikestead.dotenv", "naumovs.color-highlight", + "oxc.oxc-vscode", "pkief.material-icon-theme", "sdras.vue-vscode-snippets", "vue.volar", diff --git a/.vscode/settings.json b/.vscode/settings.json index ee081a6008..6de4f13be8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,11 @@ { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", - "source.organizeImports": "never" + "source.fixAll.oxc": "explicit" }, - "editor.formatOnSave": false, - "eslint.validate": [ - "html", - "css", - "scss", - "json", - "jsonc", - "javascript", - "javascriptreact", - "typescript", - "typescriptreact", - "vue" - ], + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true, + "eslint.validate": ["vue"], "i18n-ally.displayLanguage": "zh-cn", "i18n-ally.enabledParsers": ["ts"], "i18n-ally.enabledFrameworks": ["vue"], @@ -23,9 +13,11 @@ "i18n-ally.keystyle": "nested", "i18n-ally.localesPaths": ["src/locales/langs"], "i18n-ally.parsers.typescript.compilerOptions": { - "moduleResolution": "node" + "moduleResolution": "node", + "ignoreDeprecations": "6.0", + "rootDir": ".", + "baseUrl": "." }, - "prettier.enable": false, - "typescript.tsdk": "node_modules/typescript/lib", + "oxc.fmt.configPath": ".oxfmtrc.json", "unocss.root": ["./"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a724f248..75bdff856c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,463 @@ # Changelog +## [v2.2.0](https://github.com/soybeanjs/soybean-admin/compare/v2.1.1...v2.2.0) (2026-05-13) + +###    ๐Ÿšจ Breaking Changes + +- **pnpm**: update pnpm from v10 to v11  -  by @soybeanjs [(1f8e4)](https://github.com/soybeanjs/soybean-admin/commit/1f8e4eca) + +###    ๐Ÿก Chore + +- **.gitignore**: add src-tauri/target to ignore list  -  by @soybeanjs [(f3975)](https://github.com/soybeanjs/soybean-admin/commit/f39753e1) +- **deps**: update deps  -  by @soybeanjs [(77299)](https://github.com/soybeanjs/soybean-admin/commit/77299691) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   + +## [v2.1.1](https://github.com/soybeanjs/soybean-admin/compare/v2.1.0...v2.1.1) (2026-05-13) + +###    ๐Ÿž Bug Fixes + +- **assets**: + - add ts-expect-error comment for svg icons type error  -  by @soybeanjs [(f8f0c)](https://github.com/soybeanjs/soybean-admin/commit/f8f0c6c4) +- **bin**: + - update import statement for index file  -  by @soybeanjs [(4549d)](https://github.com/soybeanjs/soybean-admin/commit/4549ddda) +- **components**: + - correction of wrong label bar configuration explanation.  -  by **Azir-11** [(6887b)](https://github.com/soybeanjs/soybean-admin/commit/6887b016) +- **i18n-ally**: + - add missing TypeScript compiler options. fixed #907  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/907 [(a5f85)](https://github.com/soybeanjs/soybean-admin/commit/a5f85810) +- **projects**: + - resolve width abnormal issue of NColorPicker.  -  by **Azir-11** [(75ccd)](https://github.com/soybeanjs/soybean-admin/commit/75ccd008) + - fix wrong new tab opening for external link navigation.  -  by **Azir-11** [(af1b5)](https://github.com/soybeanjs/soybean-admin/commit/af1b5f1d) + - fix ts and lint error  -  by @soybeanjs [(9afa2)](https://github.com/soybeanjs/soybean-admin/commit/9afa21e4) +- **tsconfig**: + - change moduleResolution to 'bundler' across multiple packages  -  by @soybeanjs [(c0105)](https://github.com/soybeanjs/soybean-admin/commit/c0105c55) +- **vscode**: + - add oxc.vscode extension and update settings for formatting. fixed #904  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/904 [(f8bd9)](https://github.com/soybeanjs/soybean-admin/commit/f8bd921a) +- **workflows**: + - update actions/checkout and actions/setup-node to v6  -  by @soybeanjs [(b5b50)](https://github.com/soybeanjs/soybean-admin/commit/b5b5087c) + +###    ๐Ÿ›  Optimizations + +- **mix-menu**: use stable Symbol.for as MixMenu context key. fixed #919  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/919 [(e1667)](https://github.com/soybeanjs/soybean-admin/commit/e1667669) + +###    ๐Ÿ’… Refactors + +- **hooks**: simplify useContext implementation and improve type definitions  -  by @soybeanjs [(f292d)](https://github.com/soybeanjs/soybean-admin/commit/f292d32d) + +###    ๐Ÿ“– Documentation + +- **readme**: add SoybeanUI section with description and badges  -  by @soybeanjs [(3a7a3)](https://github.com/soybeanjs/soybean-admin/commit/3a7a36b7) + +###    ๐Ÿก Chore + +- **deps**: + - update deps  -  by @soybeanjs [(a43a7)](https://github.com/soybeanjs/soybean-admin/commit/a43a7b3b) + - update deps  -  by **soybean-js** [(9afb3)](https://github.com/soybeanjs/soybean-admin/commit/9afb335e) + - update deps  -  by @soybeanjs [(e8fa9)](https://github.com/soybeanjs/soybean-admin/commit/e8fa90b4) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   +[soybean-js](mailto:soybeanjs@163.com), [Azir-11](mailto:2075125282@qq.com) + +## [v2.1.0](https://github.com/soybeanjs/soybean-admin/compare/v2.0.2...v2.1.0) (2026-03-09) + +###    ๐Ÿšจ Breaking Changes + +- **projects**: integrate oxlint and oxfmt  -  by **soybeanfe** [(6ff74)](https://github.com/soybeanjs/soybean-admin/commit/6ff74c0c) + +###    ๐Ÿš€ Features + +- **components**: + - Column settings support fixed columns.  -  by @m-xlsea [(70658)](https://github.com/soybeanjs/soybean-admin/commit/70658643) + - Add โ€œSelect Allโ€ to TableColumnSetting  -  by @wenyuanw [(0081b)](https://github.com/soybeanjs/soybean-admin/commit/0081b9c0) +- **logo**: + - use new logo  -  by @soybeanjs [(5aac5)](https://github.com/soybeanjs/soybean-admin/commit/5aac540a) +- **projects**: + - add the plugin: vite-plugin-vue-transition-root-validator, to optimize the development experience.  -  by **Azir-11** [(30e3c)](https://github.com/soybeanjs/soybean-admin/commit/30e3cdc7) +- **types**: + - Added type definition `force` to router push options.  -  by @m-xlsea [(a3794)](https://github.com/soybeanjs/soybean-admin/commit/a37949f2) +- **workflows**: + - add opencode workflow for issue and PR comment triggers  -  by @soybeanjs [(dacee)](https://github.com/soybeanjs/soybean-admin/commit/dacee143) + +###    ๐Ÿž Bug Fixes + +- **projects**: + - fix the long list TableColumnSetting component exceeds the viewport.  -  by **skyfeiz** [(cbfb9)](https://github.com/soybeanjs/soybean-admin/commit/cbfb932f) + - fix NButton props conflicts.  -  by **skyfeiz** [(54107)](https://github.com/soybeanjs/soybean-admin/commit/54107aca) + - ensure HTML lang attribute is updated when setting locale  -  by @pan0xc [(f96c3)](https://github.com/soybeanjs/soybean-admin/commit/f96c3c9e) + - correct HTML lang attribute to standard format  -  by @pan0xc [(b520d)](https://github.com/soybeanjs/soybean-admin/commit/b520db3e) +- **router**: + - simplify route guard logic and remove unnecessary next calls  -  by **soybeanfe** [(3c2cb)](https://github.com/soybeanjs/soybean-admin/commit/3c2cbb74) + +###    ๐Ÿ›  Optimizations + +- **projects**: + - modify the injection location of the token.  -  by **Azir-11** [(9d48c)](https://github.com/soybeanjs/soybean-admin/commit/9d48ca5f) + - optimize unocss config  -  by **soybeanfe** [(6fc6f)](https://github.com/soybeanjs/soybean-admin/commit/6fc6f1c9) + +###    ๐Ÿ“– Documentation + +- **projects**: V2 has been released.  -  by **skyfeiz** [(d7394)](https://github.com/soybeanjs/soybean-admin/commit/d73947a5) + +###    ๐Ÿก Chore + +- **deps**: + - update deps  -  by @soybeanjs [(232e1)](https://github.com/soybeanjs/soybean-admin/commit/232e1ac4) + - update deps  -  by @soybeanjs [(2a023)](https://github.com/soybeanjs/soybean-admin/commit/2a0231da) + - update deps  -  by **soybeanfe** [(b867c)](https://github.com/soybeanjs/soybean-admin/commit/b867c290) + +###    ๐ŸŽจ Styles + +- **projects**: fix lint code and format code  -  by **soybeanfe** [(781a1)](https://github.com/soybeanjs/soybean-admin/commit/781a18f4) + +###    โค๏ธ Contributors + +[![pan0xc](https://github.com/pan0xc.png?size=48)](https://github.com/pan0xc)  [![m-xlsea](https://github.com/m-xlsea.png?size=48)](https://github.com/m-xlsea)  [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)  [![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)   +[soybeanfe](mailto:honghuangdc@gmail.com), [Azir-11](mailto:2075125282@qq.com), [skyfeiz](mailto:webzhangfei@163.com),  + +## [v2.0.2](https://github.com/soybeanjs/soybean-admin/compare/v2.0.1...v2.0.2) (2025-12-23) + +###    ๐Ÿž Bug Fixes + +- **hooks**: update pagination pageSize after data fetch.  -  by **Azir-11** [(64226)](https://github.com/soybeanjs/soybean-admin/commit/64226d9b) +- **projects**: fix the incorrect judgment of home by pin tab.  -  by **Azir-11** [(62a43)](https://github.com/soybeanjs/soybean-admin/commit/62a43c39) + +###    ๐Ÿก Chore + +- **deps**: update deps  -  by @soybeanjs [(ec9f9)](https://github.com/soybeanjs/soybean-admin/commit/ec9f9af9) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   +[Azir-11](mailto:2075125282@qq.com) + +## [v2.0.1](https://github.com/soybeanjs/soybean-admin/compare/v2.0.0...v2.0.1) (2025-12-04) + +###    ๐Ÿš€ Features + +- **docs**: + - update QQ group image in README  -  by @soybeanjs [(46081)](https://github.com/soybeanjs/soybean-admin/commit/46081c36) +- **projects**: + - support theme presets to only set partial content.  -  by **Azir-11** [(9da84)](https://github.com/soybeanjs/soybean-admin/commit/9da847fb) + - support theme perset to override component library presets.  -  by **Azir-11** [(60517)](https://github.com/soybeanjs/soybean-admin/commit/605173a1) + - support pinning and unpinning of tabs  -  by **hooke** [(b8a76)](https://github.com/soybeanjs/soybean-admin/commit/b8a767d7) + - hybrid layout mode auto select first deepest child menu  -  by @paynezhuang [(94019)](https://github.com/soybeanjs/soybean-admin/commit/9401925f) + +###    ๐Ÿž Bug Fixes + +- **docs**: update project name in ecosystem section of README  -  by @soybeanjs [(bb232)](https://github.com/soybeanjs/soybean-admin/commit/bb232bf8) +- **types**: add missing property in theme presets  -  by **ๅˆ˜็’** [(4a9cf)](https://github.com/soybeanjs/soybean-admin/commit/4a9cf6c3) + +###    ๐Ÿ›  Optimizations + +- **projects**: simplify some theme preset configurations.  -  by **Azir-11** [(c6d97)](https://github.com/soybeanjs/soybean-admin/commit/c6d97dba) + +###    ๐Ÿ“– Documentation + +- **projects**: add link to ecosystem document.  -  by **Azir-11** [(c472a)](https://github.com/soybeanjs/soybean-admin/commit/c472a943) + +###    ๐Ÿก Chore + +- **deps**: + - update deps  -  by @soybeanjs [(f8dc6)](https://github.com/soybeanjs/soybean-admin/commit/f8dc639e) + - update deps  -  by @soybeanjs [(7cf40)](https://github.com/soybeanjs/soybean-admin/commit/7cf4083b) +- **other**: + - remove Prettier's recommendation.  -  by **Azir-11** [(73e9a)](https://github.com/soybeanjs/soybean-admin/commit/73e9a0fe) +- **styles**: + - format code  -  by @soybeanjs [(098cd)](https://github.com/soybeanjs/soybean-admin/commit/098cd50e) + +###    ๐ŸŽจ Styles + +- **projects**: modify homepage prompt title to tip.  -  by **Azir-11** [(91a26)](https://github.com/soybeanjs/soybean-admin/commit/91a261c1) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)  [![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)   +[hooke](mailto:hellohooke@foxmail.com), [Azir-11](mailto:2075125282@qq.com), [ๅˆ˜็’](mailto:hi.alue@qq.com) + +## [v2.0.0](https://github.com/soybeanjs/soybean-admin/compare/v1.3.15...v2.0.0) (2025-11-02) + +###    ๐Ÿšจ Breaking Changes + +- **hooks**: refactor useTable and enhance type definitions  -  by @soybeanjs [(8cc51)](https://github.com/soybeanjs/soybean-admin/commit/8cc5177c) +- **projects**: optimize layout mode, split horizontal mix component into two layouts, and rename the component.  -  by **Azir** [(b6ac3)](https://github.com/soybeanjs/soybean-admin/commit/b6ac3106) +- **request**: remove cancelRequest method and related logic from request instances  -  by @soybeanjs [(b4e12)](https://github.com/soybeanjs/soybean-admin/commit/b4e12530) + +###    ๐Ÿš€ Features + +- **components**: + - add the IconTooltip component.  -  by **Azir-11** [(a55b4)](https://github.com/soybeanjs/soybean-admin/commit/a55b4dc0) + - replace NTooltip with IconTooltip and optimize the layout of related components.  -  by **Azir-11** [(40057)](https://github.com/soybeanjs/soybean-admin/commit/4005763c) +- **global-tab**: + - add support for switching tabs with right mouse button click  -  by @soybeanjs [(b2c91)](https://github.com/soybeanjs/soybean-admin/commit/b2c919b6) +- **hooks**: + - add scrollX computation for total table width in useNaiveTable  -  by @Lruihao [(358e1)](https://github.com/soybeanjs/soybean-admin/commit/358e1297) +- **packages**: + - materials support slider-tab. closed #823  -  by @CyberShen in https://github.com/soybeanjs/soybean-admin/issues/823 [(61fa4)](https://github.com/soybeanjs/soybean-admin/commit/61fa4b7f) +- **projects**: + - refactor theme drawer with tabbed layout for better UX.  -  by **Azir** [(8ba71)](https://github.com/soybeanjs/soybean-admin/commit/8ba71a08) + - Add current time display option for watermark  -  by @wenyuanw in https://github.com/soybeanjs/soybean-admin/issues/772 [(f238f)](https://github.com/soybeanjs/soybean-admin/commit/f238fcbd) + - add 'vertical-hybrid-header-first' layout mode  -  by @wenyuanw [(b4e5c)](https://github.com/soybeanjs/soybean-admin/commit/b4e5c6d9) + - add prompt information for scrolling mode and tab bar caching.  -  by **Azir-11** [(29a2a)](https://github.com/soybeanjs/soybean-admin/commit/29a2a5c6) + - support theme preset function.  -  by **Azir-11** [(257f1)](https://github.com/soybeanjs/soybean-admin/commit/257f1183) + - modify the default value of the reset cache policy to 'refresh'.  -  by **Azir-11** [(3c0a5)](https://github.com/soybeanjs/soybean-admin/commit/3c0a5282) + - optimize tabs cache cleaning strategy. close #820.  -  by **Azir-11** in https://github.com/soybeanjs/soybean-admin/issues/820 [(ef7ac)](https://github.com/soybeanjs/soybean-admin/commit/ef7acc62) + - support closing tabs with middle mouse button click  -  by @wenyuanw [(a8d1e)](https://github.com/soybeanjs/soybean-admin/commit/a8d1e5d2) + - support set global redius  -  by **CyberShen123** [(24c6d)](https://github.com/soybeanjs/soybean-admin/commit/24c6df52) + - support set global redius  -  by **CyberShen123** [(3549c)](https://github.com/soybeanjs/soybean-admin/commit/3549c4db) + - compatible with the new Echarts API and optimized styles.  -  by **Azir-11** [(9755c)](https://github.com/soybeanjs/soybean-admin/commit/9755c313) +- **styles**: + - add text-autospace property to improve text layout  -  by @wenyuanw [(345aa)](https://github.com/soybeanjs/soybean-admin/commit/345aa293) + +###    ๐Ÿž Bug Fixes + +- **hooks**: + - correct chart rendering logic in useEcharts  -  by @soybeanjs [(8a7cd)](https://github.com/soybeanjs/soybean-admin/commit/8a7cd593) +- **layout**: + - fix getSiderWidth  -  by @soybeanjs [(e471e)](https://github.com/soybeanjs/soybean-admin/commit/e471e914) +- **packages**: + - fix the parsing logic for stored data to ensure correct return of boolean values  -  by @Lruihao [(9ea56)](https://github.com/soybeanjs/soybean-admin/commit/9ea56c9b) + - axios: fix json response. fixed #815  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [(fd087)](https://github.com/soybeanjs/soybean-admin/commit/fd087f59) + - axios: fix json response. fixed #815  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [(5be86)](https://github.com/soybeanjs/soybean-admin/commit/5be864a8) +- **projects**: + - Fix i18n-ally not working when setting moduleResolution to bundler. fixed #780  -  by @xiaobao0505 in https://github.com/soybeanjs/soybean-admin/issues/780 [(41191)](https://github.com/soybeanjs/soybean-admin/commit/41191d54) + - adjust legend position in line chart options.  -  by **Azir-11** [(0b998)](https://github.com/soybeanjs/soybean-admin/commit/0b9982bd) +- **readme**: + - update GitHub stars and forks links for gitee  -  by @soybeanjs [(923eb)](https://github.com/soybeanjs/soybean-admin/commit/923eb98a) +- **scripts**: + - update command to use 'npm-check-updates' instead of 'ncu'  -  by @soybeanjs [(8dc17)](https://github.com/soybeanjs/soybean-admin/commit/8dc17e62) +- **styles**: + - show light color scrollbar while dark mode is on  -  by **whyang** [(dac50)](https://github.com/soybeanjs/soybean-admin/commit/dac5075b) +- **table**: + - add type annotations for records in useTable hook  -  by @soybeanjs [(32b8f)](https://github.com/soybeanjs/soybean-admin/commit/32b8f990) +- **types**: + - fix proxy types  -  by @soybeanjs [(3d72f)](https://github.com/soybeanjs/soybean-admin/commit/3d72f954) + - fix proxy types  -  by @soybeanjs [(12b25)](https://github.com/soybeanjs/soybean-admin/commit/12b25e0d) + - fix ts type error  -  by @soybeanjs [(d5a3a)](https://github.com/soybeanjs/soybean-admin/commit/d5a3a25d) + +###    ๐Ÿ›  Optimizations + +- **hooks**: + - optimize useEcharts  -  by @soybeanjs [(936b8)](https://github.com/soybeanjs/soybean-admin/commit/936b834e) +- **packages**: + - remove ofetch package  -  by @soybeanjs [(abaaa)](https://github.com/soybeanjs/soybean-admin/commit/abaaa4a0) +- **projects**: + - improve theme drawer responsive width for mobile devices  -  by @wenyuanw [(8439a)](https://github.com/soybeanjs/soybean-admin/commit/8439a600) + - improve robustness of second-level menu key logic  -  by @wenyuanw [(8b8a2)](https://github.com/soybeanjs/soybean-admin/commit/8b8a2083) + - optimize theme drawer width  -  by @soybeanjs [(81468)](https://github.com/soybeanjs/soybean-admin/commit/8146858b) + - optimize api type file  -  by @soybeanjs [(3a343)](https://github.com/soybeanjs/soybean-admin/commit/3a343eea) + - optimize radius settings  -  by @soybeanjs [(87a66)](https://github.com/soybeanjs/soybean-admin/commit/87a66a42) +- **request**: + - enhance request options and response handling with generic types  -  by @soybeanjs [(50a5c)](https://github.com/soybeanjs/soybean-admin/commit/50a5cba0) +- **typings**: + - update component typings  -  by @soybeanjs [(1d142)](https://github.com/soybeanjs/soybean-admin/commit/1d142695) + +###    ๐Ÿ’… Refactors + +- **hooks**: + - optimize useContext and update useMixMenuContext  -  by @soybeanjs [(c9651)](https://github.com/soybeanjs/soybean-admin/commit/c965140b) + - streamline column visibility handling in useTable and table components  -  by @soybeanjs [(ee434)](https://github.com/soybeanjs/soybean-admin/commit/ee434145) + - remove useSignal hook and update exports  -  by @soybeanjs [(87adc)](https://github.com/soybeanjs/soybean-admin/commit/87adc35f) +- **menu**: + - optimize the margin on the menu  -  by **NicholasLD** [(d7311)](https://github.com/soybeanjs/soybean-admin/commit/d7311111) +- **projects**: + - remove unnecessary logic in onRouteSwitchWhenLoggedIn  -  by @wenyuanw [(d6c81)](https://github.com/soybeanjs/soybean-admin/commit/d6c8142b) +- **request**: + - unify response transformation methods and deprecate transformBackendResponse  -  by @soybeanjs [(f83ee)](https://github.com/soybeanjs/soybean-admin/commit/f83eefbc) +- **types**: + - move Auth and Route namespaces to separate files and clean up api.d.ts  -  by **Azir** [(d37ce)](https://github.com/soybeanjs/soybean-admin/commit/d37ce046) + +###    ๐Ÿ“– Documentation + +- **projects**: + - add github trendshift info.  -  by **ๆ•็‘ž็Ž›็š„็š‡ๅธ** [(e18d3)](https://github.com/soybeanjs/soybean-admin/commit/e18d3972) + - add github trendshift info.  -  by **ๆ•็‘ž็Ž›็š„็š‡ๅธ** [(2a0c9)](https://github.com/soybeanjs/soybean-admin/commit/2a0c9f1b) + - add contribution leaderboard  -  by @wenyuanw [(01744)](https://github.com/soybeanjs/soybean-admin/commit/017440c1) + +###    ๐Ÿก Chore + +- **deps**: + - update NodeJS and pnpm version requirements in package.json and documentation  -  by **Junior25306** [(a5c4b)](https://github.com/soybeanjs/soybean-admin/commit/a5c4b4e3) + - update deps  -  by @soybeanjs [(5cb1c)](https://github.com/soybeanjs/soybean-admin/commit/5cb1cebd) + - update deps  -  by @soybeanjs [(aeb63)](https://github.com/soybeanjs/soybean-admin/commit/aeb63690) + - update deps  -  by @soybeanjs [(e89b8)](https://github.com/soybeanjs/soybean-admin/commit/e89b86ce) + - update deps  -  by @soybeanjs [(c962f)](https://github.com/soybeanjs/soybean-admin/commit/c962f7b2) + - update deps  -  by @soybeanjs [(12135)](https://github.com/soybeanjs/soybean-admin/commit/1213531b) + - update deps  -  by @soybeanjs [(e33f9)](https://github.com/soybeanjs/soybean-admin/commit/e33f944a) + - update deps  -  by @soybeanjs [(9fa95)](https://github.com/soybeanjs/soybean-admin/commit/9fa951aa) + - update deps  -  by @soybeanjs [(b041f)](https://github.com/soybeanjs/soybean-admin/commit/b041fdd8) + - update deps  -  by @soybeanjs [(d567c)](https://github.com/soybeanjs/soybean-admin/commit/d567c057) + - update deps  -  by @soybeanjs [(6cbf5)](https://github.com/soybeanjs/soybean-admin/commit/6cbf5705) + - update deps  -  by @soybeanjs [(6010f)](https://github.com/soybeanjs/soybean-admin/commit/6010f518) + - update deps  -  by @soybeanjs [(232f5)](https://github.com/soybeanjs/soybean-admin/commit/232f56fd) +- **other**: + - update the ESLint validation configuration to support more file types.  -  by **Azir-11** [(8d7f9)](https://github.com/soybeanjs/soybean-admin/commit/8d7f91dc) + - update the ESLint validation configuration to support more file types.  -  by **Azir-11** [(be8f9)](https://github.com/soybeanjs/soybean-admin/commit/be8f915a) +- **packages**: + - update Vite version to 7 in package.json and documentation.  -  by **Azir** [(03dd6)](https://github.com/soybeanjs/soybean-admin/commit/03dd64c5) + - add picomatch to fix scripts  -  by @soybeanjs [(805c3)](https://github.com/soybeanjs/soybean-admin/commit/805c3381) +- **readme**: + - remove DartNode sponsorship badge from README files  -  by @soybeanjs [(33ade)](https://github.com/soybeanjs/soybean-admin/commit/33ade539) +- **vscode**: + - remove unused vue.server.hybridMode setting from .vscode/settings.json  -  by @soybeanjs [(13319)](https://github.com/soybeanjs/soybean-admin/commit/133196f3) + +###    ๐ŸŽจ Styles + +- **projects**: format code.  -  by **Azir-11** [(100e0)](https://github.com/soybeanjs/soybean-admin/commit/100e0ea5) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)  [![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)  [![CyberShen](https://github.com/CyberShen.png?size=48)](https://github.com/CyberShen)  [![Lruihao](https://github.com/Lruihao.png?size=48)](https://github.com/Lruihao)  [![xiaobao0505](https://github.com/xiaobao0505.png?size=48)](https://github.com/xiaobao0505)   +[Azir-11](mailto:2075125282@qq.com), [CyberShen123](mailto:s.lijun@qq.com), [whyang](mailto:whyang9701@gmail.com), [HongxuanG](mailto:1359774872@qq.com), [NicholasLD](mailto:878639947@qq.com), [Junior25306](mailto:dayu429@qq.com) + +## [v2.0.0-beta.2](https://github.com/soybeanjs/soybean-admin/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2025-10-27) + +###    ๐Ÿš€ Features + +- **global-tab**: add support for switching tabs with right mouse button click  -  by @soybeanjs [(b2c91)](https://github.com/soybeanjs/soybean-admin/commit/b2c919b6) + +###    ๐Ÿž Bug Fixes + +- **layout**: fix getSiderWidth  -  by @soybeanjs [(e471e)](https://github.com/soybeanjs/soybean-admin/commit/e471e914) +- **packages**: axios: fix json response. fixed #815  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [(fd087)](https://github.com/soybeanjs/soybean-admin/commit/fd087f59) +- **readme**: update GitHub stars and forks links for gitee  -  by @soybeanjs [(923eb)](https://github.com/soybeanjs/soybean-admin/commit/923eb98a) +- **scripts**: update command to use 'npm-check-updates' instead of 'ncu'  -  by @soybeanjs [(8dc17)](https://github.com/soybeanjs/soybean-admin/commit/8dc17e62) +- **types**: fix proxy types  -  by @soybeanjs [(12b25)](https://github.com/soybeanjs/soybean-admin/commit/12b25e0d) + +###    ๐Ÿ“– Documentation + +- **projects**: + - add github trendshift info.  -  by **ๆ•็‘ž็Ž›็š„็š‡ๅธ** [(e18d3)](https://github.com/soybeanjs/soybean-admin/commit/e18d3972) + - add contribution leaderboard  -  by @wenyuanw [(01744)](https://github.com/soybeanjs/soybean-admin/commit/017440c1) + +###    ๐Ÿก Chore + +- **deps**: + - update deps  -  by @soybeanjs [(e33f9)](https://github.com/soybeanjs/soybean-admin/commit/e33f944a) + - update deps  -  by @soybeanjs [(9fa95)](https://github.com/soybeanjs/soybean-admin/commit/9fa951aa) +- **other**: + - update the ESLint validation configuration to support more file types.  -  by **Azir-11** [(8d7f9)](https://github.com/soybeanjs/soybean-admin/commit/8d7f91dc) +- **readme**: + - remove DartNode sponsorship badge from README files  -  by @soybeanjs [(33ade)](https://github.com/soybeanjs/soybean-admin/commit/33ade539) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)  [![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)   +[ๆ•็‘ž็Ž›็š„็š‡ๅธ](mailto:2075125282@qq.com) + +## [v2.0.0-beta.1](https://github.com/soybeanjs/soybean-admin/compare/v1.3.15...v2.0.0-beta.1) (2025-10-25) + +###    ๐Ÿšจ Breaking Changes + +- **hooks**: refactor useTable and enhance type definitions  -  by @soybeanjs [(8cc51)](https://github.com/soybeanjs/soybean-admin/commit/8cc5177) +- **projects**: optimize layout mode, split horizontal mix component into two layouts, and rename the component.  -  by **Azir** [(b6ac3)](https://github.com/soybeanjs/soybean-admin/commit/b6ac310) +- **request**: remove cancelRequest method and related logic from request instances  -  by @soybeanjs [(b4e12)](https://github.com/soybeanjs/soybean-admin/commit/b4e1253) + +###    ๐Ÿš€ Features + +- **components**: + - add the IconTooltip component.  -  by **Azir-11** [(a55b4)](https://github.com/soybeanjs/soybean-admin/commit/a55b4dc) + - replace NTooltip with IconTooltip and optimize the layout of related components.  -  by **Azir-11** [(40057)](https://github.com/soybeanjs/soybean-admin/commit/4005763) +- **hooks**: + - add scrollX computation for total table width in useNaiveTable  -  by @Lruihao [(358e1)](https://github.com/soybeanjs/soybean-admin/commit/358e129) +- **packages**: + - materials support slider-tab. closed #823  -  by @CyberShen in https://github.com/soybeanjs/soybean-admin/issues/823 [(61fa4)](https://github.com/soybeanjs/soybean-admin/commit/61fa4b7) +- **projects**: + - refactor theme drawer with tabbed layout for better UX.  -  by **Azir** [(8ba71)](https://github.com/soybeanjs/soybean-admin/commit/8ba71a0) + - Add current time display option for watermark  -  by @wenyuanw in https://github.com/soybeanjs/soybean-admin/issues/772 [(f238f)](https://github.com/soybeanjs/soybean-admin/commit/f238fcb) + - add 'vertical-hybrid-header-first' layout mode  -  by @wenyuanw [(b4e5c)](https://github.com/soybeanjs/soybean-admin/commit/b4e5c6d) + - add prompt information for scrolling mode and tab bar caching.  -  by **Azir-11** [(29a2a)](https://github.com/soybeanjs/soybean-admin/commit/29a2a5c) + - support theme preset function.  -  by **Azir-11** [(257f1)](https://github.com/soybeanjs/soybean-admin/commit/257f118) + - modify the default value of the reset cache policy to 'refresh'.  -  by **Azir-11** [(3c0a5)](https://github.com/soybeanjs/soybean-admin/commit/3c0a528) + - optimize tabs cache cleaning strategy. close #820.  -  by **Azir-11** in https://github.com/soybeanjs/soybean-admin/issues/820 [(ef7ac)](https://github.com/soybeanjs/soybean-admin/commit/ef7acc6) + - support closing tabs with middle mouse button click  -  by @wenyuanw [(a8d1e)](https://github.com/soybeanjs/soybean-admin/commit/a8d1e5d) + - support set global redius  -  by **CyberShen123** [(24c6d)](https://github.com/soybeanjs/soybean-admin/commit/24c6df5) + - support set global redius  -  by **CyberShen123** [(3549c)](https://github.com/soybeanjs/soybean-admin/commit/3549c4d) +- **styles**: + - add text-autospace property to improve text layout  -  by @wenyuanw [(345aa)](https://github.com/soybeanjs/soybean-admin/commit/345aa29) + +###    ๐Ÿž Bug Fixes + +- **hooks**: + - correct chart rendering logic in useEcharts  -  by @soybeanjs [(8a7cd)](https://github.com/soybeanjs/soybean-admin/commit/8a7cd59) +- **packages**: + - fix the parsing logic for stored data to ensure correct return of boolean values  -  by @Lruihao [(9ea56)](https://github.com/soybeanjs/soybean-admin/commit/9ea56c9) + - axios: fix json response. fixed #815  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [(5be86)](https://github.com/soybeanjs/soybean-admin/commit/5be864a) +- **projects**: + - Fix i18n-ally not working when setting moduleResolution to bundler. fixed #780  -  by @xiaobao0505 in https://github.com/soybeanjs/soybean-admin/issues/780 [(41191)](https://github.com/soybeanjs/soybean-admin/commit/41191d5) +- **styles**: + - show light color scrollbar while dark mode is on  -  by **whyang** [(dac50)](https://github.com/soybeanjs/soybean-admin/commit/dac5075) +- **table**: + - add type annotations for records in useTable hook  -  by @soybeanjs [(32b8f)](https://github.com/soybeanjs/soybean-admin/commit/32b8f99) +- **types**: + - fix proxy types  -  by @soybeanjs [(3d72f)](https://github.com/soybeanjs/soybean-admin/commit/3d72f95) + - fix ts type error  -  by @soybeanjs [(d5a3a)](https://github.com/soybeanjs/soybean-admin/commit/d5a3a25) + +###    ๐Ÿ›  Optimizations + +- **hooks**: + - optimize useEcharts  -  by @soybeanjs [(936b8)](https://github.com/soybeanjs/soybean-admin/commit/936b834) +- **packages**: + - remove ofetch package  -  by @soybeanjs [(abaaa)](https://github.com/soybeanjs/soybean-admin/commit/abaaa4a) +- **projects**: + - improve theme drawer responsive width for mobile devices  -  by @wenyuanw [(8439a)](https://github.com/soybeanjs/soybean-admin/commit/8439a60) + - improve robustness of second-level menu key logic  -  by @wenyuanw [(8b8a2)](https://github.com/soybeanjs/soybean-admin/commit/8b8a208) + - optimize theme drawer width  -  by @soybeanjs [(81468)](https://github.com/soybeanjs/soybean-admin/commit/8146858) + - optimize api type file  -  by @soybeanjs [(3a343)](https://github.com/soybeanjs/soybean-admin/commit/3a343ee) + - optimize radius settings  -  by @soybeanjs [(87a66)](https://github.com/soybeanjs/soybean-admin/commit/87a66a4) +- **request**: + - enhance request options and response handling with generic types  -  by @soybeanjs [(50a5c)](https://github.com/soybeanjs/soybean-admin/commit/50a5cba) + +###    ๐Ÿ’… Refactors + +- **hooks**: + - optimize useContext and update useMixMenuContext  -  by @soybeanjs [(c9651)](https://github.com/soybeanjs/soybean-admin/commit/c965140) + - streamline column visibility handling in useTable and table components  -  by @soybeanjs [(ee434)](https://github.com/soybeanjs/soybean-admin/commit/ee43414) + - remove useSignal hook and update exports  -  by @soybeanjs [(87adc)](https://github.com/soybeanjs/soybean-admin/commit/87adc35) +- **menu**: + - optimize the margin on the menu  -  by **NicholasLD** [(d7311)](https://github.com/soybeanjs/soybean-admin/commit/d731111) +- **projects**: + - remove unnecessary logic in onRouteSwitchWhenLoggedIn  -  by @wenyuanw [(d6c81)](https://github.com/soybeanjs/soybean-admin/commit/d6c8142) +- **request**: + - unify response transformation methods and deprecate transformBackendResponse  -  by @soybeanjs [(f83ee)](https://github.com/soybeanjs/soybean-admin/commit/f83eefb) +- **types**: + - move Auth and Route namespaces to separate files and clean up api.d.ts  -  by **Azir** [(d37ce)](https://github.com/soybeanjs/soybean-admin/commit/d37ce04) + +###    ๐Ÿ“– Documentation + +- **projects**: add github trendshift info.  -  by **ๆ•็‘ž็Ž›็š„็š‡ๅธ** [(2a0c9)](https://github.com/soybeanjs/soybean-admin/commit/2a0c9f1) + +###    ๐Ÿก Chore + +- **deps**: + - update NodeJS and pnpm version requirements in package.json and documentation  -  by **Junior25306** [(a5c4b)](https://github.com/soybeanjs/soybean-admin/commit/a5c4b4e) + - update deps  -  by @soybeanjs [(5cb1c)](https://github.com/soybeanjs/soybean-admin/commit/5cb1ceb) + - update deps  -  by @soybeanjs [(aeb63)](https://github.com/soybeanjs/soybean-admin/commit/aeb6369) + - update deps  -  by @soybeanjs [(e89b8)](https://github.com/soybeanjs/soybean-admin/commit/e89b86c) + - update deps  -  by @soybeanjs [(c962f)](https://github.com/soybeanjs/soybean-admin/commit/c962f7b) + - update deps  -  by @soybeanjs [(12135)](https://github.com/soybeanjs/soybean-admin/commit/1213531) + - update deps  -  by @soybeanjs [(b041f)](https://github.com/soybeanjs/soybean-admin/commit/b041fdd) + - update deps  -  by @soybeanjs [(d567c)](https://github.com/soybeanjs/soybean-admin/commit/d567c05) + - update deps  -  by @soybeanjs [(6cbf5)](https://github.com/soybeanjs/soybean-admin/commit/6cbf570) +- **other**: + - update the ESLint validation configuration to support more file types.  -  by **Azir-11** [(be8f9)](https://github.com/soybeanjs/soybean-admin/commit/be8f915) +- **packages**: + - update Vite version to 7 in package.json and documentation.  -  by **Azir** [(03dd6)](https://github.com/soybeanjs/soybean-admin/commit/03dd64c) + - add picomatch to fix scripts  -  by @soybeanjs [(805c3)](https://github.com/soybeanjs/soybean-admin/commit/805c338) +- **vscode**: + - remove unused vue.server.hybridMode setting from .vscode/settings.json  -  by @soybeanjs [(13319)](https://github.com/soybeanjs/soybean-admin/commit/133196f) + +###    ๐ŸŽจ Styles + +- **projects**: format code.  -  by **Azir-11** [(100e0)](https://github.com/soybeanjs/soybean-admin/commit/100e0ea) + +###    โค๏ธ Contributors + +[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)  [![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)  [![CyberShen](https://github.com/CyberShen.png?size=48)](https://github.com/CyberShen)  [![Lruihao](https://github.com/Lruihao.png?size=48)](https://github.com/Lruihao)  [![xiaobao0505](https://github.com/xiaobao0505.png?size=48)](https://github.com/xiaobao0505)   +[CyberShen123](mailto:s.lijun@qq.com), [whyang](mailto:whyang9701@gmail.com), [HongxuanG](mailto:1359774872@qq.com), [Azir-11](mailto:2075125282@qq.com), [NicholasLD](mailto:878639947@qq.com), [Junior25306](mailto:dayu429@qq.com) ## [v1.3.15](https://github.com/soybeanjs/soybean-admin/compare/v1.3.14...v1.3.15) (2025-06-24) @@ -84,13 +542,13 @@ ###    ๐Ÿก Chore - **deps**: - - add vscode recommend plugin close #738  -  by @tu6ge in https://github.com/soybeanjs/soybean-admin/issues/739 and https://github.com/soybeanjs/soybean-admin/issues/738 [(61244)](https://github.com/soybeanjs/soybean-admin/commit/61244f0f) + - add vscode recommend plugin close #738  -  by @tu6ge in https://github.com/soybeanjs/soybean-admin/issues/739 and https://github.com/soybeanjs/soybean-admin/issues/738 [(61244)](https://github.com/soybeanjs/soybean-admin/commit/61244f0f) - update deps  -  by @soybeanjs [(41b5f)](https://github.com/soybeanjs/soybean-admin/commit/41b5f493) - update deps  -  by @soybeanjs [(3e4e1)](https://github.com/soybeanjs/soybean-admin/commit/3e4e17ab) ###    ๐Ÿค– CI -- **hooks**: remove lint-staged in git hook. close #724  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/743 and https://github.com/soybeanjs/soybean-admin/issues/724 [(c3abc)](https://github.com/soybeanjs/soybean-admin/commit/c3abc3df) +- **hooks**: remove lint-staged in git hook. close #724  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/743 and https://github.com/soybeanjs/soybean-admin/issues/724 [(c3abc)](https://github.com/soybeanjs/soybean-admin/commit/c3abc3df) ###    โค๏ธ Contributors @@ -210,7 +668,7 @@ ###    ๐Ÿ“– Documentation -- **projects**: โœ๏ธ add element-plus version link  -  by **ไธ€ๅฏธ็ฐ** in https://github.com/honghuangdc/soybean-admin/issues/679 [(5c6ab)](https://github.com/honghuangdc/soybean-admin/commit/5c6ab0b) +- **projects**: โœ๏ธ add element-plus version link  -  by **ไธ€ๅฏธ็ฐ** in https://github.com/honghuangdc/soybean-admin/issues/679 [(5c6ab)](https://github.com/honghuangdc/soybean-admin/commit/5c6ab0b) ###    ๐Ÿก Chore @@ -252,7 +710,6 @@ ###    โค๏ธ Contributors - [Soybean](mailto:soybeanjs@outlook.com), [้’่œ็™ฝ็މๆฑค](mailto:79054161+Azir-11@users.noreply.github.com) ## [v1.3.8](https://github.com/soybeanjs/soybean-admin/compare/v1.3.7...v1.3.8) (2024-10-25) @@ -1299,7 +1756,7 @@ - Optimize internationalized menu search code  -  by **็‡•ๅšๆ–‡** [(8c1ef)](https://github.com/soybeanjs/soybean-admin/commit/8c1ef4b) - Optimize menu search code  -  by **็‡•ๅšๆ–‡** [(296a2)](https://github.com/soybeanjs/soybean-admin/commit/296a2d2) - perf count-to  -  by @honghuangdc [(b2c61)](https://github.com/soybeanjs/soybean-admin/commit/b2c61f0) - - components name is converted to uppercase  -  by **็‡•ๅšๆ–‡** [(04aa1)](https://github.com/soybeanjs/soybean-admin/commit/04aa10b) + - components name is converted to uppercase  -  by **็‡•ๅšๆ–‡** [(04aa1)](https://github.com/soybeanjs/soybean-admin/commit/04aa10b) - perf global-search  -  by @honghuangdc [(72745)](https://github.com/soybeanjs/soybean-admin/commit/7274522) - **projects**: - perf code  -  by @honghuangdc [(8081e)](https://github.com/soybeanjs/soybean-admin/commit/8081e19) @@ -1576,7 +2033,7 @@ - Optimize internationalized menu search code  -  by **็‡•ๅšๆ–‡** [(8c1ef)](https://github.com/soybeanjs/soybean-admin/commit/8c1ef4b) - Optimize menu search code  -  by **็‡•ๅšๆ–‡** [(296a2)](https://github.com/soybeanjs/soybean-admin/commit/296a2d2) - perf count-to  -  by @honghuangdc [(b2c61)](https://github.com/soybeanjs/soybean-admin/commit/b2c61f0) - - components name is converted to uppercase  -  by **็‡•ๅšๆ–‡** [(04aa1)](https://github.com/soybeanjs/soybean-admin/commit/04aa10b) + - components name is converted to uppercase  -  by **็‡•ๅšๆ–‡** [(04aa1)](https://github.com/soybeanjs/soybean-admin/commit/04aa10b) - perf global-search  -  by @honghuangdc [(72745)](https://github.com/soybeanjs/soybean-admin/commit/7274522) - **projects**: - perf code  -  by @honghuangdc [(8081e)](https://github.com/soybeanjs/soybean-admin/commit/8081e19) @@ -2971,6 +3428,3 @@ ###    โค๏ธ Contributors [![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)  [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   - - - diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index 79587cd715..ec87c122b3 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -143,7 +143,6 @@ [![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)  [![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)   [Azir](mailto:2075125282@qq.com),  - ## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22) ###    ๐Ÿšจ ็ ดๅๆ€งๅ˜ๆ›ด @@ -182,7 +181,6 @@ [![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)  [![mmdapl](https://github.com/mmdapl.png?size=48)](https://github.com/mmdapl)  [![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)   - ## [v1.2.8](https://github.com/soybeanjs/soybean-admin/compare/v1.2.7...v1.2.8) (2024-07-20) ###    ๐Ÿž ไฟฎๅค @@ -210,7 +208,6 @@ [![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)   [dodu2014](mailto:dodu@live.cn) - ## [v1.2.7](https://github.com/honghuangdc/soybean-admin/compare/v1.2.6...v1.2.7) (2024-07-12) ###    ๐Ÿ›  ไผ˜ๅŒ– @@ -294,7 +291,6 @@ [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)  [![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)   [CHENZL](mailto:zlong5568863@qq.com) - ## [v1.2.4](https://github.com/soybeanjs/soybean-admin/compare/v1.2.3...v1.2.4) (2024-06-14) ###    ๐Ÿ›  ไผ˜ๅŒ– @@ -372,7 +368,6 @@ [![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)   - ## [v1.2.0](https://github.com/soybeanjs/soybean-admin/compare/v1.1.5...v1.2.0) (2024-06-06) ###    ๐Ÿš€ ๅŠŸ่ƒฝ @@ -593,7 +588,6 @@ [![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)   [paynezhuang](mailto:paynezhuang@gmail.com) - ## [v1.1.0-beta.1](https://github.com/soybeanjs/soybean-admin/compare/v1.0.9...v1.1.0-beta.1) (2024-05-07) ###    ๐Ÿš€ ๅŠŸ่ƒฝ diff --git a/README.en_US.md b/README.en_US.md index e1fd000d6d..ffaad7edd6 100644 --- a/README.en_US.md +++ b/README.en_US.md @@ -12,8 +12,10 @@ [![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin) [![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin) -Featured๏ฝœHelloGitHub - +
+ soybeanjs%2Fsoybean-admin | Trendshift + Featured๏ฝœHelloGitHub +
> [!NOTE] > If you think `SoybeanAdmin` is helpful to you, or you like our project, please give us a โญ๏ธ on GitHub. Your support is the driving force for us to continue to improve and add new features! Thank you for your support! @@ -21,17 +23,53 @@ > [!NOTE] > The `SoybeanAdmin` quick start series videos have been uploaded to [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) Go online [click here](https://www.bilibili.com/video/BV1YKdRYXELC) Go check it out -> [!WARNING] -> `SoybeanAdmin` is planning to develop a `V2` version, see [plan list](https://github.com/soybeanjs/soybean-admin/issues/767) +
+ + + + + +
+ + + + + +
+ + SoybeanUI + + + + SoybeanUI + +
+ A powerful and elegant component system for Vue 3, delivering headless interaction capabilities and ready-to-use style encapsulation.
+ Built on a two-layer Headless + UI architecture to reuse interaction capabilities and unify interface expression. +

+ Vue 3 + Headless + Admin +
+ + Visit SoybeanUI + +
+
## Introduction -[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite7, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly. - +[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite8, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly. ## Features -- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite7, TypeScript, Pinia and UnoCSS. +- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite8, TypeScript, Pinia and UnoCSS. - **Clear project architecture**: using pnpm monorepo architecture, clear structure, elegant and easy to understand. - **Strict code specifications**: follow the [SoybeanJS specification](https://docs.soybeanjs.cn/standard), integrate eslint, prettier and simple-git-hooks to ensure the code is standardized. - **TypeScript**: support strict type checking to improve code maintainability. @@ -43,7 +81,6 @@ - **Command line tool**: built-in efficient command line tool, git commit, delete file, release, etc. - **Mobile adaptation**: perfectly support mobile terminal to realize adaptive layout. - ## Version - **NaiveUI Version:** @@ -70,7 +107,6 @@ - [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin/tree/legacy) - [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin/tree/legacy) - ## Documentation - [Link](https://docs.soybeanjs.cn) @@ -90,7 +126,6 @@ ![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-10.png) ![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-mobile.png) - ## Usage **Environment Preparation** @@ -117,6 +152,7 @@ git clone https://gitcode.com/soybeanjs/soybean-admin.git ```bash pnpm i ``` + > Since this project uses the pnpm monorepo management method, please do not use npm or yarn to install dependencies. **Start Project** @@ -137,7 +173,7 @@ Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) docume ## Ecosystem -- [react-soybean-admin](https://github.com/mufeng889/react-soybean-admin): SoybeanAdmin based version of React. +- [skyroc-admin](https://github.com/Ohh-889/skyroc-admin): SoybeanAdmin's React version implementation. - [electron-mock-admin](https://github.com/lixin59/electron-mock-api): A Mock Api management system that helps front-end developers quickly implement interface mocks. - [T-Shell](https://github.com/TheBlindM/T-Shell): A terminal emulator and SSH client with configurable command prompts. - [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin). @@ -149,6 +185,7 @@ Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) docume - [ba](https://github.com/xiatianYa/Ba-Server): Backend service docking with soybean admin based on goFrame framework, adapted to dynamic routing, and interface authentication permissions. - [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):A Go backend service developed based on the Gin and GORM frameworks, integrated with the example branch of Soybean Admin. It supports dynamic routing and API permission authentication. +More ecosystem please refer to [Ecosystem](https://docs.soybeanjs.cn/awesome) document. ## How to Contribute @@ -163,8 +200,8 @@ This project has built-in `commit` command, you can execute `pnpm commit` to gen It is recommended to use the latest version of Chrome in development for a better experience. | [IE](http://godban.github.io/browsers-support-badges/) | [ Edge](http://godban.github.io/browsers-support-badges/) | [Firefox](http://godban.github.io/browsers-support-badges/) | [Chrome](http://godban.github.io/browsers-support-badges/) | [Safari](http://godban.github.io/browsers-support-badges/) | -| --- | --- | --- | --- | --- | -| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | ## OpenSource Author @@ -178,26 +215,26 @@ Thanks the following people for their contributions. If you want to contribute t +--- + +Here are the most active contributors from the past year. Thank you all for your support, which has enabled the project's continued development. + + + Contribution Leaderboard + + ## Communication -`SoybeanAdmin` is a completely open source and free project, helping developers to develop medium and large-scale management systems more conveniently. It also provides WeChat and QQ communication groups. If you have any questions, please feel free to ask in the group. +`SoybeanAdmin` is a completely open source and free project, helping developers to develop medium and large-scale management systems more conveniently. It provides FeiShu communication groups. If you have any questions, please feel free to ask in the group.
-

QQ Group

- +

FeiShu Group

+
- -
-

Add the following WeChat to invite to the WeChat group

- -
## Star Trend -[![Star History Chart](https://api.star-history.com/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.com/#soybeanjs/soybean-admin&Date) +[![Star History Chart](https://star-history.dera.page/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.dera.page/#soybeanjs/soybean-admin&Date) ## License diff --git a/README.md b/README.md index c581412bf2..89f73477ec 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,10 @@ [![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin) [![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin) -Featured๏ฝœHelloGitHub +
+ soybeanjs%2Fsoybean-admin | Trendshift + Featured๏ฝœHelloGitHub +
> [!NOTE] > ๅฆ‚ๆžœๆ‚จ่ง‰ๅพ— `SoybeanAdmin`ๅฏนๆ‚จๆœ‰ๆ‰€ๅธฎๅŠฉ๏ผŒๆˆ–่€…ๆ‚จๅ–œๆฌขๆˆ‘ไปฌ็š„้กน็›ฎ๏ผŒ่ฏทๅœจ GitHub ไธŠ็ป™ๆˆ‘ไปฌไธ€ไธช โญ๏ธใ€‚ๆ‚จ็š„ๆ”ฏๆŒๆ˜ฏๆˆ‘ไปฌๆŒ็ปญๆ”น่ฟ›ๅ’ŒๅขžๅŠ ๆ–ฐๅŠŸ่ƒฝ็š„ๅŠจๅŠ›๏ผๆ„Ÿ่ฐขๆ‚จ็š„ๆ”ฏๆŒ๏ผ @@ -20,16 +23,53 @@ > [!NOTE] > `SoybeanAdmin` ๅฟซ้€ŸไธŠๆ‰‹็ณปๅˆ—่ง†้ข‘ๅทฒๅœจ [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) ไธŠ็บฟ [็‚นๅ‡ป่ฟ™้‡Œ](https://www.bilibili.com/video/BV1YKdRYXELC) ๅ‰ๅพ€ๆŸฅ็œ‹ -> [!WARNING] -> `SoybeanAdmin` ๆญฃๅœจ่ฎกๅˆ’ๅผ€ๅ‘ `V2` ็‰ˆๆœฌ๏ผŒ่ฏฆๆƒ…่ง[่ฎกๅˆ’ๆธ…ๅ•](https://github.com/soybeanjs/soybean-admin/issues/767) +
+ + + + + +
+ + + + + +
+ + SoybeanUI + + + + SoybeanUI + +
+ ้ขๅ‘ Vue 3 ็š„ๅผบๅคง่€Œไผ˜้›…็š„็ป„ไปถ็ณป็ปŸ๏ผŒๆไพ›ๆ— ๅคดไบคไบ’่ƒฝๅŠ›ไธŽๅผ€็ฎฑๅณ็”จ็š„ๆ ทๅผๅฐ่ฃ…ใ€‚
+ ้‡‡็”จ Headless + UI ไธคๅฑ‚ๆžถๆž„๏ผŒๅค็”จๅบ•ๅฑ‚ไบคไบ’่ƒฝๅŠ›๏ผŒ็ปŸไธ€ไธŠๅฑ‚็•Œ้ข่กจ่พพใ€‚ +

+ Vue 3 + Headless + Admin +
+ + Visit SoybeanUI + +
+
## ็ฎ€ไป‹ -[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) ๆ˜ฏไธ€ไธชๆธ…ๆ–ฐไผ˜้›…ใ€้ซ˜้ขœๅ€ผไธ”ๅŠŸ่ƒฝๅผบๅคง็š„ๅŽๅฐ็ฎก็†ๆจกๆฟ๏ผŒๅŸบไบŽๆœ€ๆ–ฐ็š„ๅ‰็ซฏๆŠ€ๆœฏๆ ˆ๏ผŒๅŒ…ๆ‹ฌ Vue3, Vite7, TypeScript, Pinia ๅ’Œ UnoCSSใ€‚ๅฎƒๅ†…็ฝฎไบ†ไธฐๅฏŒ็š„ไธป้ข˜้…็ฝฎๅ’Œ็ป„ไปถ๏ผŒไปฃ็ ่ง„่Œƒไธฅ่ฐจ๏ผŒๅฎž็Žฐไบ†่‡ชๅŠจๅŒ–็š„ๆ–‡ไปถ่ทฏ็”ฑ็ณป็ปŸใ€‚ๆญคๅค–๏ผŒๅฎƒ่ฟ˜้‡‡็”จไบ†ๅŸบไบŽ ApiFox ็š„ๅœจ็บฟMockๆ•ฐๆฎๆ–นๆกˆใ€‚`SoybeanAdmin` ไธบๆ‚จๆไพ›ไบ†ไธ€็ซ™ๅผ็š„ๅŽๅฐ็ฎก็†่งฃๅ†ณๆ–นๆกˆ๏ผŒๆ— ้œ€้ขๅค–้…็ฝฎ๏ผŒๅผ€็ฎฑๅณ็”จใ€‚ๅŒๆ ทๆ˜ฏไธ€ไธชๅฟซ้€Ÿๅญฆไน ๅ‰ๆฒฟๆŠ€ๆœฏ็š„ๆœ€ไฝณๅฎž่ทตใ€‚ +[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) ๆ˜ฏไธ€ไธชๆธ…ๆ–ฐไผ˜้›…ใ€้ซ˜้ขœๅ€ผไธ”ๅŠŸ่ƒฝๅผบๅคง็š„ๅŽๅฐ็ฎก็†ๆจกๆฟ๏ผŒๅŸบไบŽๆœ€ๆ–ฐ็š„ๅ‰็ซฏๆŠ€ๆœฏๆ ˆ๏ผŒๅŒ…ๆ‹ฌ Vue3, Vite8, TypeScript, Pinia ๅ’Œ UnoCSSใ€‚ๅฎƒๅ†…็ฝฎไบ†ไธฐๅฏŒ็š„ไธป้ข˜้…็ฝฎๅ’Œ็ป„ไปถ๏ผŒไปฃ็ ่ง„่Œƒไธฅ่ฐจ๏ผŒๅฎž็Žฐไบ†่‡ชๅŠจๅŒ–็š„ๆ–‡ไปถ่ทฏ็”ฑ็ณป็ปŸใ€‚ๆญคๅค–๏ผŒๅฎƒ่ฟ˜้‡‡็”จไบ†ๅŸบไบŽ ApiFox ็š„ๅœจ็บฟMockๆ•ฐๆฎๆ–นๆกˆใ€‚`SoybeanAdmin` ไธบๆ‚จๆไพ›ไบ†ไธ€็ซ™ๅผ็š„ๅŽๅฐ็ฎก็†่งฃๅ†ณๆ–นๆกˆ๏ผŒๆ— ้œ€้ขๅค–้…็ฝฎ๏ผŒๅผ€็ฎฑๅณ็”จใ€‚ๅŒๆ ทๆ˜ฏไธ€ไธชๅฟซ้€Ÿๅญฆไน ๅ‰ๆฒฟๆŠ€ๆœฏ็š„ๆœ€ไฝณๅฎž่ทตใ€‚ ## ็‰นๆ€ง -- **ๅ‰ๆฒฟๆŠ€ๆœฏๅบ”็”จ**๏ผš้‡‡็”จ Vue3, Vite7, TypeScript, Pinia ๅ’Œ UnoCSS ็ญ‰ๆœ€ๆ–ฐๆต่กŒ็š„ๆŠ€ๆœฏๆ ˆใ€‚ +- **ๅ‰ๆฒฟๆŠ€ๆœฏๅบ”็”จ**๏ผš้‡‡็”จ Vue3, Vite8, TypeScript, Pinia ๅ’Œ UnoCSS ็ญ‰ๆœ€ๆ–ฐๆต่กŒ็š„ๆŠ€ๆœฏๆ ˆใ€‚ - **ๆธ…ๆ™ฐ็š„้กน็›ฎๆžถๆž„**๏ผš้‡‡็”จ pnpm monorepo ๆžถๆž„๏ผŒ็ป“ๆž„ๆธ…ๆ™ฐ๏ผŒไผ˜้›…ๆ˜“ๆ‡‚ใ€‚ - **ไธฅๆ ผ็š„ไปฃ็ ่ง„่Œƒ**๏ผš้ตๅพช [SoybeanJS ่ง„่Œƒ](https://docs.soybeanjs.cn/zh/standard)๏ผŒ้›†ๆˆไบ†eslint, prettier ๅ’Œ simple-git-hooks๏ผŒไฟ่ฏไปฃ็ ็š„่ง„่Œƒๆ€งใ€‚ - **TypeScript**๏ผš ๆ”ฏๆŒไธฅๆ ผ็š„็ฑปๅž‹ๆฃ€ๆŸฅ๏ผŒๆ้ซ˜ไปฃ็ ็š„ๅฏ็ปดๆŠคๆ€งใ€‚ @@ -41,7 +81,6 @@ - **ๅ‘ฝไปค่กŒๅทฅๅ…ท**๏ผšๅ†…็ฝฎ้ซ˜ๆ•ˆ็š„ๅ‘ฝไปค่กŒๅทฅๅ…ท๏ผŒgitๆไบคใ€ๅˆ ้™คๆ–‡ไปถใ€ๅ‘ๅธƒ็ญ‰ใ€‚ - **็งปๅŠจ็ซฏ้€‚้…**๏ผšๅฎŒ็พŽๆ”ฏๆŒ็งปๅŠจ็ซฏ๏ผŒๅฎž็Žฐ่‡ช้€‚ๅบ”ๅธƒๅฑ€ใ€‚ - ## ็‰ˆๆœฌ - **NaiveUI ็‰ˆๆœฌ:** @@ -65,13 +104,11 @@ - [Gitee ไป“ๅบ“](https://gitee.com/honghuangdc/soybean-admin/tree/legacy) - [Gitcode ไป“ๅบ“](https://gitcode.com/soybeanjs/soybean-admin/tree/legacy) - ## ๆ–‡ๆกฃ - [ๅœฐๅ€](https://docs.soybeanjs.cn) - [ๆ—ง็‰ˆๆ–‡ๆกฃ](https://legacy-docs.soybeanjs.cn) - ## ๅˆไฝœไบ‹้กน ๆˆ‘ไปฌ้žๅธธๆ„Ÿ่ฐขๅคงๅฎถๅฏน [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) ็š„ๆ”ฏๆŒ๏ผไธบไบ†่ฟ›ไธ€ๆญฅๅ›ž้ฆˆ็คพๅŒบ๏ผŒๅนถๅŠฉๅŠ›ไผไธšๅ’Œๅผ€ๅ‘่€…ๅฎž็Žฐไธชๆ€งๅŒ–้œ€ๆฑ‚๏ผŒๆˆ‘ไปฌ็Žฐๆไพ›ๅคš็งๅˆไฝœๆœๅŠก๏ผŒๆœŸๅพ…ไธŽๆ‚จๆบๆ‰‹ๅ…ฑ่ตขใ€‚ @@ -96,11 +133,10 @@ - **Email**: [soybeanjs@outlook.com](mailto:soybeanjs@outlook.com) - **GitHub Issues**: ๆฌข่ฟŽ้€š่ฟ‡ [GitHub Issues](https://github.com/soybeanjs/soybean-admin/issues/new) ่”็ณปๆˆ‘ไปฌ๏ผŒ่ฟ›่กŒๅˆๆญฅ็š„ๅˆไฝœๆดฝ่ฐˆใ€‚ -- **ๅ•†ๅŠกๅˆไฝœๅพฎไฟก**: honghuangdc +- **ๅ•†ๅŠกๅˆไฝœๅพฎไฟก**: honghuangdc ๆœŸๅพ…ไธŽๆ‚จๅผ€ๅฑ•ๆทฑๅ…ฅๅˆไฝœ๏ผŒๅ…ฑๅŒๆŽจๅŠจ SoybeanAdmin ้กน็›ฎๅŠๅ…ถๅœจๆ›ดๅคš้ข†ๅŸŸ็š„ๆˆๅŠŸๅบ”็”จ๏ผ - ## ็คบไพ‹ๅ›พ็‰‡ ![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-01.png) @@ -115,7 +151,6 @@ ![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-10.png) ![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-mobile.png) - ## ไฝฟ็”จ **็Žฏๅขƒๅ‡†ๅค‡** @@ -142,6 +177,7 @@ git clone https://gitcode.com/soybeanjs/soybean-admin.git ```bash pnpm i ``` + > ็”ฑไบŽๆœฌ้กน็›ฎ้‡‡็”จไบ† pnpm monorepo ็š„็ฎก็†ๆ–นๅผ๏ผŒๅ› ๆญค่ฏทไธ่ฆไฝฟ็”จ npm ๆˆ– yarn ๆฅๅฎ‰่ฃ…ไพ่ต–ใ€‚ **ๅฏๅŠจ้กน็›ฎ** @@ -162,7 +198,7 @@ pnpm build ## ๅ‘จ่พน็”Ÿๆ€ -- [react-soybean-admin](https://github.com/mufeng889/react-soybean-admin): ๅŸบไบŽSoybeanAdmin็š„React็‰ˆๆœฌ. +- [skyroc-admin](https://github.com/Ohh-889/skyroc-admin): SoybeanAdmin็š„React็‰ˆๆœฌๅฎž็Žฐ. - [electron-mock-admin](https://github.com/lixin59/electron-mock-api): ไธ€ไธช Mock Api ็ฎก็†็ณป็ปŸ๏ผŒๅธฎๅŠฉๅ‰็ซฏๅผ€ๅ‘ไผ™ไผดๅฟซ้€Ÿๅฎž็ŽฐๆŽฅๅฃ็š„ mockใ€‚ - [T-Shell](https://github.com/TheBlindM/T-Shell): ๆ˜ฏไธ€ไธชๅฏ้…็ฝฎๅ‘ฝไปคๆ็คบ็š„็ปˆ็ซฏๆจกๆ‹Ÿๅ™จๅ’Œ SSH ๅฎขๆˆท็ซฏใ€‚ - [pea](https://github.com/haitang1894/pea) : ้‡‡็”จSpringBoot3.2 + JDK21ใ€MyBatis-Plusใ€SpringSecurityๅฎ‰ๅ…จๆก†ๆžถ็ญ‰๏ผŒ้€‚้… [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) ๅผ€ๅ‘็š„็ฎ€ๅ•ๆƒ้™็ณป็ปŸใ€‚ @@ -174,6 +210,7 @@ pnpm build - [ba](https://github.com/xiatianYa/Ba-Server): ๅŸบไบŽgoFrameๆก†ๆžถๅผ€ๅ‘็š„ๅŽ็ซฏๆœๅŠกๅฏนๆŽฅsoybean-admin,้€‚้…ๅŠจๆ€่ทฏ็”ฑ,ๆŽฅๅฃ้‰ดๆƒ้™ใ€‚ - [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):ๅŸบไบŽgin+gormๆก†ๆžถๅผ€ๅ‘็š„go่ฏญ่จ€ๅŽ็ซฏๆœๅŠกๅฏนๆŽฅsoybean-admin็š„exampleๅˆ†ๆ”ฏ,้€‚้…ๅŠจๆ€่ทฏ็”ฑ,ๆŽฅๅฃ้‰ดๆƒ้™ใ€‚ +ๆ›ดๅคšๅ‘จ่พน็”Ÿๆ€่ฏท็ฟป้˜… [ๅ‘จ่พน็”Ÿๆ€](https://docs.soybeanjs.cn/zh/awesome) ๆ–‡ๆกฃใ€‚ ## ๅฆ‚ไฝ•่ดก็Œฎ @@ -181,22 +218,20 @@ pnpm build ## Git ๆไบค่ง„่Œƒ -ๆœฌ้กน็›ฎๅทฒๅ†…็ฝฎ `commit` ๅ‘ฝไปค๏ผŒๆ‚จๅฏไปฅ้€š่ฟ‡ๆ‰ง่กŒ `pnpm commit` ๆฅ็”Ÿๆˆ็ฌฆๅˆ [Conventional Commits]([conventionalcommits](https://www.conventionalcommits.org/)) ่ง„่Œƒ็š„ๆไบคไฟกๆฏใ€‚ๅœจๆไบคPRๆ—ถ๏ผŒ่ฏทๅŠกๅฟ…ไฝฟ็”จ `commit` ๅ‘ฝไปคๆฅๅˆ›ๅปบๆไบคไฟกๆฏ๏ผŒไปฅ็กฎไฟไฟกๆฏ็š„่ง„่Œƒๆ€งใ€‚ - +ๆœฌ้กน็›ฎๅทฒๅ†…็ฝฎ `commit` ๅ‘ฝไปค๏ผŒๆ‚จๅฏไปฅ้€š่ฟ‡ๆ‰ง่กŒ `pnpm commit` ๆฅ็”Ÿๆˆ็ฌฆๅˆ [Conventional Commits](<[conventionalcommits](https://www.conventionalcommits.org/)>) ่ง„่Œƒ็š„ๆไบคไฟกๆฏใ€‚ๅœจๆไบคPRๆ—ถ๏ผŒ่ฏทๅŠกๅฟ…ไฝฟ็”จ `commit` ๅ‘ฝไปคๆฅๅˆ›ๅปบๆไบคไฟกๆฏ๏ผŒไปฅ็กฎไฟไฟกๆฏ็š„่ง„่Œƒๆ€งใ€‚ ## ๆต่งˆๅ™จๆ”ฏๆŒ ๆŽจ่ไฝฟ็”จๆœ€ๆ–ฐ็‰ˆ็š„ Chrome ๆต่งˆๅ™จ่ฟ›่กŒๅผ€ๅ‘๏ผŒไปฅ่Žทๅพ—ๆ›ดๅฅฝ็š„ไฝ“้ชŒใ€‚ | [IE](http://godban.github.io/browsers-support-badges/) | [ Edge](http://godban.github.io/browsers-support-badges/) | [Firefox](http://godban.github.io/browsers-support-badges/) | [Chrome](http://godban.github.io/browsers-support-badges/) | [Safari](http://godban.github.io/browsers-support-badges/) | -| --- | --- | --- | --- | --- | -| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | ## ๅผ€ๆบไฝœ่€… [Soybean](https://github.com/honghuangdc) - ## ่ดก็Œฎ่€… ๆ„Ÿ่ฐขไปฅไธ‹่ดก็Œฎ่€…็š„่ดก็Œฎใ€‚ๅฆ‚ๆžœๆ‚จๆƒณไธบๆœฌ้กน็›ฎๅšๅ‡บ่ดก็Œฎ๏ผŒ่ฏทๅ‚่€ƒ [ๅฆ‚ไฝ•่ดก็Œฎ](#ๅฆ‚ไฝ•่ดก็Œฎ)ใ€‚ @@ -205,26 +240,26 @@ pnpm build +--- + +ไปฅไธ‹ๆ˜ฏ่ฟ‘ไธ€ๅนดไธญๆดป่ทƒๅบฆ่พƒ้ซ˜็š„่ดก็Œฎ่€…๏ผŒๆ„Ÿ่ฐขๅ„ไฝ็š„ๆ”ฏๆŒ๏ผŒ่ฎฉ้กน็›ฎๅพ—ไปฅๆŒ็ปญๅ‘ๅฑ•ใ€‚ + + + Contribution Leaderboard + + ## ไบคๆต -`SoybeanAdmin` ๆ˜ฏๅฎŒๅ…จๅผ€ๆบๅ…่ดน็š„้กน็›ฎ๏ผŒๅœจๅธฎๅŠฉๅผ€ๅ‘่€…ๆ›ดๆ–นไพฟๅœฐ่ฟ›่กŒไธญๅคงๅž‹็ฎก็†็ณป็ปŸๅผ€ๅ‘๏ผŒๅŒๆ—ถไนŸๆไพ›ๅพฎไฟกๅ’Œ QQ ไบคๆต็พค๏ผŒไฝฟ็”จ้—ฎ้ข˜ๆฌข่ฟŽๅœจ็พคๅ†…ๆ้—ฎใ€‚ - -
-

QQไบคๆต็พค

- -
- -
-

ๆทปๅŠ ไธ‹้ขๅพฎไฟก้‚€่ฏท่ฟ›ๅพฎไฟก็พค

- -
+`SoybeanAdmin` ๆ˜ฏๅฎŒๅ…จๅผ€ๆบๅ…่ดน็š„้กน็›ฎ๏ผŒๅœจๅธฎๅŠฉๅผ€ๅ‘่€…ๆ›ดๆ–นไพฟๅœฐ่ฟ›่กŒไธญๅคงๅž‹็ฎก็†็ณป็ปŸๅผ€ๅ‘๏ผŒๅŒๆ—ถๆไพ›้ฃžไนฆ็พคไพ›ๅคงๅฎถไบคๆต๏ผŒไฝฟ็”จ้—ฎ้ข˜ๆฌข่ฟŽๅœจ็พคๅ†…ๆ้—ฎใ€‚ + +
+

้ฃžไนฆ็พค

+ +
## Star ่ถ‹ๅŠฟ -[![Star History Chart](https://api.star-history.com/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.com/#soybeanjs/soybean-admin&Date) +[![Star History Chart](https://star-history.dera.page/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.dera.page/#soybeanjs/soybean-admin&Date) ## ๅผ€ๆบๅ่ฎฎ diff --git a/build/plugins/index.ts b/build/plugins/index.ts index 1243fc2f8b..5cefcd5cd9 100644 --- a/build/plugins/index.ts +++ b/build/plugins/index.ts @@ -2,6 +2,7 @@ import type { PluginOption } from 'vite'; import vue from '@vitejs/plugin-vue'; import vueJsx from '@vitejs/plugin-vue-jsx'; import progress from 'vite-plugin-progress'; +import vueRootValidator from 'vite-plugin-vue-transition-root-validator'; import { setupElegantRouter } from './router'; import { setupUnocss } from './unocss'; import { setupUnplugin } from './unplugin'; @@ -17,7 +18,8 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) { setupUnocss(viteEnv), ...setupUnplugin(viteEnv), progress(), - setupHtmlPlugin(buildTime) + setupHtmlPlugin(buildTime), + vueRootValidator() ]; return plugins; diff --git a/build/plugins/unocss.ts b/build/plugins/unocss.ts index 06b41d377a..fb250aff8a 100644 --- a/build/plugins/unocss.ts +++ b/build/plugins/unocss.ts @@ -1,7 +1,7 @@ import process from 'node:process'; import path from 'node:path'; -import unocss from '@unocss/vite'; -import presetIcons from '@unocss/preset-icons'; +import { presetIcons } from 'unocss'; +import unocss from 'unocss/vite'; import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'; export function setupUnocss(viteEnv: Env.ImportMeta) { diff --git a/docs/v3.md b/docs/v3.md new file mode 100644 index 0000000000..6f6e4bc16a --- /dev/null +++ b/docs/v3.md @@ -0,0 +1,564 @@ +# soybean-admin v3.0 ๅฎž็Žฐๆ–นๆกˆ + +> ๅŸบไบŽ ubean + vite-plus + soybean-ui ็š„ๅ…จๆ ˆ้‡ๆž„ๆ–นๆกˆ +> +> ็Šถๆ€๏ผš่‰ๆกˆ๏ผˆไพ›่ฏ„ๅฎก๏ผ‰ +> ๅŸบ็บฟ๏ผšsoybean-admin v2.x๏ผˆmain = ่„šๆ‰‹ๆžถๅ†…ๆ ธ๏ผŒexample = ๅ…จๅŠŸ่ƒฝๆผ”็คบ๏ผ‰ +> ๅ‚่€ƒๅฎž็Žฐ๏ผšsoybean-unify๏ผˆ็ฎ€็บฆๅ…จๆ ˆๅ‚่€ƒ๏ผ‰ใ€ubean examples/ubean-test๏ผˆๅ…จๆ ˆ้ชจๆžถๅ‚่€ƒ๏ผ‰ + +--- + +## 1. ่ƒŒๆ™ฏไธŽ็›ฎๆ ‡ + +soybean-admin v2.x ๆ˜ฏ็บฏๅ‰็ซฏ SPA ๆจกๆฟ๏ผšVue 3 + Vite + Naive UI + Pinia + UnoCSS + elegant-router๏ผŒๅŽ็ซฏ่ƒฝๅŠ›ๅฎŒๅ…จไพ่ต– Apifox ๅœจ็บฟ mock ๆˆ–็”จๆˆท่‡ชๅค‡ๆœๅŠกใ€‚v3.0 ็š„็›ฎๆ ‡๏ผš + +1. **ๆ•ดไฝ“ๆŠ€ๆœฏๆ ˆๅ‡็บงไธบๅ…จๆ ˆๆก†ๆžถ**๏ผšไปฅ [ubean](https://github.com/soybeanjs/ubean)๏ผˆv0.4.1๏ผŒVue ๅ…จๆ ˆๆก†ๆžถ๏ผŒHono ๅ†…ๆ ธ๏ผ‰ไธบๅŸบๅบง๏ผŒๆž„ๅปบๅทฅๅ…ท้“พๅˆ‡ๆขไธบ vite-plus๏ผˆ`vp`๏ผ‰๏ผŒUI ๅฑ‚ๅˆ‡ๆขไธบ [SoybeanUI](https://ui.soybeanjs.cn)๏ผˆ`@soybeanjs/ui` + `@soybeanjs/headless`๏ผ‰ใ€‚ +2. **่ƒฝๅŠ›ๅฏน้ฝ**๏ผšๅฎŒๆ•ดๅฎž็Žฐ v2.x๏ผˆmain + example๏ผ‰็š„ๅ…จ้ƒจ่ƒฝๅŠ›๏ผŒไธไธข"้šๅฝข่ƒฝๅŠ›"๏ผˆ่ง ยง5.9๏ผ‰ใ€‚ +3. **็ฎ€็บฆไผ˜ๅ…ˆ**๏ผšไปฅ soybean-unify ็š„็ฎ€็บฆๅฎž็Žฐไธบๅ‚่€ƒ่Œƒๅผโ€”โ€”ๅ•ไธ€ๅฅ‘็บฆๆบ๏ผˆ`src/schema/` Valibot๏ผ‰ใ€OpenAPI ็ฑปๅž‹ๅŒ–ๅฎขๆˆท็ซฏใ€่œๅ•้ฉฑๅŠจ่ทฏ็”ฑ/้กต็ญพ/ๆ ‡้ข˜็š„ไธ€ๅ…ƒๆ•ฐๆฎๆจกๅž‹ใ€้›ถ่‡ช็ ”ๅธƒๅฑ€ใ€ๅ•ๅŒ…ๅนณ้“บๅทฅ็จ‹็ป“ๆž„ใ€‚ +4. **ๆžถๅญ + ๆ ทๅผ้€‚้…**๏ผš็”จ soybean-ui ๆญๅฅฝๅฎŒๆ•ดๆžถๅญ๏ผˆๅธƒๅฑ€/ๅฏผ่ˆช/้กต็ญพ/ไธป้ข˜๏ผ‰๏ผ›็ฌฌไธ‰ๆ–น UI ๅบ“๏ผˆnaive-ui / element-plus / ant-design-vue๏ผ‰็š„**ๆ ทๅผ้€‚้…ๅ™จ**๏ผˆ่ฎฉ็ฌฌไธ‰ๆ–น็ป„ไปถๅฏน้ฝ soybean-ui ็š„้ขœ่‰ฒใ€ๅœ†่ง’็ญ‰่ฎพ่ฎก token๏ผ‰ๅœจ็‹ฌ็ซ‹ไป“ๅบ“ๅผ€ๅ‘๏ผŒไฝฟ v2 ๅ„ๅ‘่กŒ็‰ˆๅนณๆป‘่ฟ‡ๆธกๅˆฐ v3ใ€‚ +5. **ๅŒ็‰ˆๆœฌไบคไป˜**๏ผš + - `main` ๅˆ†ๆ”ฏ๏ผš**ๅ…จๆ ˆ็‰ˆ**๏ผˆubean ๅฎŒๆ•ด่ƒฝๅŠ›๏ผšๆ–‡ไปถ่ทฏ็”ฑ API + DB + ้‰ดๆƒ + OpenAPI ็ฑปๅž‹ๅ…ฑไบซ๏ผ‰๏ผ› + - `lite` ๅˆ†ๆ”ฏ๏ผˆๆ–ฐๅˆ†ๆ”ฏๆ‰ฟ่ฝฝ๏ผ‰๏ผš**ๅ‰็ซฏ็‰ˆ**๏ผˆไป… `@ubean/vue` ๅฎขๆˆท็ซฏๅ†…ๆ ธ + ่‡ชๅฎšไน‰ๅŽ็ซฏ๏ผŒไปปๆ„่ฏญ่จ€ๅŽ็ซฏๅฏๆŽฅๅ…ฅ๏ผ‰ใ€‚ + +### ้ž็›ฎๆ ‡ + +- ไธๅฎž็Žฐ v2.x ไปŽๆœชๅญ˜ๅœจ่ฟ‡็š„่ƒฝๅŠ›๏ผˆ้”ๅฑใ€cropperใ€ripple ็ญ‰ไธๅœจ example ๅŸบ็บฟๅ†…๏ผŒไธๅˆ—ๅ…ฅ๏ผ‰ใ€‚ +- v3.0 ้ฆ–็‰ˆไธๅš SSR/SSG๏ผˆadmin ้ขๆฟๆจกๅผๅฎ˜ๆ–นๆŽจ่ `mode: 'fullstack' + ssr: false`๏ผ‰ใ€‚ +- ไธๅš naive-ui ็ญ‰ๆ—ง็‰ˆ็ป„ไปถ็บง 1:1 ่ฝฌๅ‘้€‚้…๏ผˆๅŽŸๅ› ่ง ยง7.1๏ผ‰๏ผŒ้€‚้…ๅ™จๅชๅšๆ ทๅผ token ๅฏน้ฝ๏ผ›้€‚้…ๅ™จๅฎž็Žฐไธ่ฟ›ๆœฌไป“ๅบ“๏ผˆยง7.2๏ผ‰ใ€‚ + +--- + +## 2. ๆŠ€ๆœฏ้€‰ๅž‹ๆ€ป่งˆ + +| ๅฑ‚ | v2.x ็Žฐ็Šถ | v3.0 ้€‰ๅž‹ | ้€‰ๅž‹ไพๆฎ | +| --------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| ๅบ”็”จๆก†ๆžถ | ็บฏๅ‰็ซฏ SPA๏ผˆVite ๆ‰‹ๅทฅ่ฃ…้…๏ผ‰ | **ubean 0.4.x**๏ผˆ`mode: 'fullstack'`, `ssr: false`๏ผ‰ | README ๆ˜Ž็กฎ admin panel ๆ˜ฏ fullstack ๆจกๅผ็š„ๅ…ธๅž‹็”จไพ‹๏ผ›client+server ๅŒๆž„ๅปบใ€ๆ—  SSR ๅผ€้”€ | +| ๆž„ๅปบ/ๅทฅ็จ‹ๅทฅๅ…ท้“พ | Vite + ESLint + Prettier + Turbo๏ผˆๆ— ๏ผ‰+ ๆ‰‹ๅ†™่„šๆœฌ | **vite-plus๏ผˆ`vp`๏ผ‰**๏ผšdev/build/test/lint/fmt/pack ไธ€ไฝ“๏ผ›catalog ไธญ `vite: npm:@voidzero-dev/vite-plus-core` | ubean ๆทฑๅบฆ็ป‘ๅฎš vite-plus๏ผ›soybean-unify ๅทฒ้ชŒ่ฏ่ฏฅๅทฅไฝœๅŒบ็ผ–ๆŽ’๏ผˆ`vp check --fix` stagedใ€oxlint/oxfmt๏ผ‰ | +| ่ทฏ็”ฑ | elegant-router ๆ’ไปถ็”Ÿๆˆ + ๆ‰‹ๅ†™ๅฎˆๅซ | **ubean ๆ–‡ไปถๅผ้กต้ข่ทฏ็”ฑ**๏ผˆ`src/pages/**` + `definePage` ๅฎ + `.ubean/routes.d.ts` ็ฑปๅž‹ๅŒ–่ทฏๅพ„๏ผ‰๏ผŒๅฎˆๅซๆณจๅ…ฅ `defineApp({ router: { setup } })` | ไธŽ elegant-router ๅŒๆ—็š„ๆ–‡ไปถ็บฆๅฎš๏ผŒไธ”้ขๅค–่Žทๅพ—ๅŠจๆ€ๆฎต matcherใ€keep-aliveใ€ๅค็”จ่ทฏ็”ฑใ€ๅนถ่กŒ/ๆ‹ฆๆˆช่ทฏ็”ฑ | +| ็Šถๆ€็ฎก็† | Pinia + `@sa/*` ็งๆœ‰ๅŒ… | **Pinia**๏ผˆ็ป `@ubean/integrations/pinia`๏ผŒ`pinia: true`๏ผ‰ | ๆก†ๆžถไธๅ†…็ฝฎ store๏ผ›unify ้ชŒ่ฏ 6 ไธช store ่ถณๅคŸ | +| UI ็ป„ไปถ | Naive UI๏ผˆๆžถๆž„็บง่€ฆๅˆ๏ผšNConfigProvider/ๅ››ๅคง Provider/TableColumn๏ผ‰ | **SoybeanUI 0.30+**๏ผˆ`@soybeanjs/ui` styled + `@soybeanjs/headless`๏ผ‰+ `@soybeanjs/theme` + `@soybeanjs/ui-uno` | `SLayout`/`SSplitNav`/`SPageTabs`/`STreeMenu` ๅทฒ่ฆ†็›–็Žฐๆœ‰ๅธƒๅฑ€ๅ…จ้ƒจ่ƒฝๅŠ›๏ผ›ubean `ui: true` ่‡ชๅŠจๆŽฅ UiResolver | +| HTTP ๅฎขๆˆท็ซฏ | `@sa/axios`๏ผˆaxios ๅ†…ๆ ธ flat request๏ผ‰ | **`@soybeanjs/fetch`**๏ผˆ`createRequest`/`toFlatRequest` + `/openapi` ๅญ่ทฏๅพ„ `createTypedClient`/`toFlatTypedClient`๏ผŒๅซ `createUploadProgressAdapter`/`downloadFile`๏ผ‰ | ubean ๅฎ˜ๆ–นๆŒ‡ๅฎšๅฎขๆˆท็ซฏ๏ผˆ`examples/ubean-test/src/request/client.ts` ๅณๆญค็”จๆณ•๏ผ‰๏ผ›fetch ๅ†…ๆ ธๅ… axios ไพ่ต– | +| ๅทฅ็จ‹ CLI | `@sa/scripts`๏ผˆ`sa git-commit`/`release`/`changelog`/`cleanup`๏ผ‰ | **`@soybeanjs/cli`**๏ผˆ1.8.x๏ผŒbin `soy`๏ผš`git-commit`/`git-commit-verify`/`changelog`/`release`/`cleanup`/`ncu`/`config`๏ผ‰ | ๅฎ˜ๆ–น็ปดๆŠค้€š็”จ CLI๏ผŒ่ƒฝๅŠ›่ฆ†็›– `@sa/scripts`๏ผ›`gen-route` ็”ฑๆ–‡ไปถๅผ่ทฏ็”ฑๅคฉ็„ถๆ›ฟไปฃ | +| ๅŽ็ซฏ | Apifox ๅœจ็บฟ mock๏ผˆๆ— ็œŸๅฎžๆœๅŠก๏ผ‰ | **ubean ๅŽ็ซฏ**๏ผš`src/routes/api/**` ๆ–‡ไปถ่ทฏ็”ฑ๏ผˆHono handler ้“พ๏ผ‰+ valibot ๆ ก้ชŒ + `@ubean/server` ๅŽŸ่ฏญ๏ผˆdb/cache/sessionโ€ฆ๏ผ‰ | unify ็š„ server๏ผˆHono + Drizzle + RBAC๏ผ‰ๅณไธบ่ฏฅๅฝขๆ€็š„ๆ‰‹ๅ†™็‰ˆ๏ผŒubean ๅฐ†ๅ…ถๆก†ๆžถๅŒ– | +| ๆ•ฐๆฎๅฑ‚ | ๆ—  | **Drizzle ORM + db0 connector**๏ผˆdev ้ป˜่ฎค SQLite/ๅ†…ๅญ˜๏ผŒprod ๅฏๅˆ‡ Postgres๏ผ‰+ drizzle ่ฟ็งป | `defineDatabase`/`useDatabase` ๅŽŸ่ฏญ๏ผ›unify ็š„ 13 ่กจ schema ๅฏ็›ดๆŽฅ็งปๆค | +| ้‰ดๆƒ | mock ๅ‡็™ปๅฝ• + ๅ‰็ซฏ roles ่ฟ‡ๆปค | **JWT ๅŒ token + Redis/ๅ†…ๅญ˜้ป‘ๅๅ•**๏ผˆunify ๆ–นๆกˆ๏ผ‰ๆˆ– `@ubean/auth`๏ผˆBetter Auth๏ผ‰๏ผ›RBAC ๆƒ้™็  `api:${method}:${template}` | ่ง ยง6.4๏ผ›`@ubean/auth` ๆˆ็†Ÿๅบฆๅพ…่ง‚ๅฏŸ๏ผŒ้ฆ–็‰ˆๅปบ่ฎฎๆฒฟ็”จ unify ่‡ช็ ” JWT ๆ–นๆกˆไปฅๅฏๆŽง | +| ็ฑปๅž‹ๅ…ฑไบซ | ๆ‰‹ๅ†™ `typings/api/*.d.ts` | **ๅ•ไธ€ๅฅ‘็บฆๆบ**๏ผš`src/schema/`๏ผˆValibot๏ผŒๅ‰ๅŽ็ซฏๆบ็ ็›ดๆถˆ่ดน๏ผ‰+ OpenAPI codegen๏ผˆ`.ubean/openapi.d.ts`๏ผ‰โ†’ `toFlatTypedClient` | Hono ่ทฏ็”ฑ โ†’ `/_openapi.json` โ†’ openapi-typescript ้—ญ็Žฏ๏ผŒubean ๅ†…็ฝฎ๏ผ›ๅบ”็”จๅ•ๅŒ…ๅŒ–ๅŽ schema ๆ— ้œ€็‹ฌ็ซ‹ๅŒ… | +| ไธป้ข˜ | naive themeOverrides + 4 ไธช JSON ้ข„่ฎพ | **`@soybeanjs/theme`**๏ผˆ`createTheme` CSS ๅ˜้‡ๅผ•ๆ“Ž๏ผ‰+ `SConfigProvider`/`SThemeCustomizer` | shadcn ้ฃŽๆ ผๅ…จ้‡่ฏญไน‰ token๏ผŒlight/dark ๅŒไปฝ๏ผŒ`SThemeCustomizer` ๅณ็Žฐๆˆไธป้ข˜ๆŠฝๅฑ‰ | +| i18n | vue-i18n ๆ‰‹ๅทฅ่ฃ…้… | **`@ubean/i18n`**๏ผˆvue-i18n 11 + ็บฆๆŸๅ‰็ผ€่ฏญ่จ€่ทฏ็”ฑ + `src/locales/` ๆ‰ซๆ๏ผ‰ | ้…็ฝฎๅŒ–๏ผˆ`i18n: { locales, defaultLocale }`๏ผ‰๏ผŒ`.ubean/i18n.d.ts` ็ฑปๅž‹็”Ÿๆˆ | +| ๅ›พๆ ‡ | iconify + unplugin-icons + svg-icons ไธ‰ๅฅ— | **`@ubean/icon`**๏ผˆ`icon: true`๏ผŒIconify๏ผ‰+ `SIcon`๏ผˆ`IconValue`๏ผ‰+ ๆœฌๅœฐ svg ็บฆๅฎš | soybean-ui ๆ•ฐๆฎๆจกๅž‹๏ผˆmenu/table ็š„ `icon` ๅญ—ๆฎต๏ผ‰็›ดๆŽฅๅƒ iconify ๅญ—็ฌฆไธฒ | + +**็‰ˆๆœฌ้ฃŽ้™ฉๅฃฐๆ˜Ž**๏ผšubean ๅฝ“ๅ‰ 0.4.1ใ€README ๆ ‡ๆณจ"ไธๅฏ็”Ÿไบง"๏ผŒ0.3โ†’0.4 ๅˆšๅš่ฟ‡ๅ…ฅๅฃๅคง้‡ๆž„ใ€‚v3.0 ๅ‘ๅธƒ่Š‚ๅฅๅฟ…้กปไธŽ ubean ็‰ˆๆœฌ้”ๅฎš็ญ–็•ฅ็ป‘ๅฎš๏ผˆ่ง ยง10๏ผ‰ใ€‚ + +--- + +## 3. ไป“ๅบ“ไธŽๅทฅ็จ‹็ป“ๆž„ + +### 3.1 ้กน็›ฎ็ป“ๆž„๏ผˆmain ๅˆ†ๆ”ฏ = ๅ…จๆ ˆ็‰ˆ๏ผŒๅ•ๅŒ…ๅนณ้“บ๏ผ‰ + +v3 ไธๅ†ไฝฟ็”จ monorepo๏ผšๅบ”็”จ็›ดๆŽฅ่ฝๅœจไป“ๅบ“ๆ น็›ฎๅฝ•๏ผˆ็ญ‰ไปทไบŽๆŠŠ v2 ่ฎพๆƒณไธญ `apps/admin/` ็š„ๅ†…ๅฎนๅนณ้“บ๏ผ‰๏ผŒๅ…จๆ ˆๅบ”็”จ็š„ๅคฉ็„ถๅฑžๆ€ง๏ผˆๅ‰็ซฏ + API + ๆ•ฐๆฎๅบ“ๅŒไป“ๅŒ่ฟ›็จ‹๏ผ‰ไฝฟ workspace ๅˆ†ๅฑ‚ๅคฑๅŽปๆ„ไน‰ใ€‚ + +``` +soybean-admin/ +โ”œโ”€โ”€ ubean.config.ts # defineConfig: mode/preset/i18n/ui/icon/pinia/devtools/routing +โ”œโ”€โ”€ vite.config.ts # import { defineConfig } from 'vite-plus'; plugins: [ubeanPlugin()] +โ”œโ”€โ”€ .env / .env.prod # ็ฒพ็ฎ€ๅŽ็š„ env ๅ˜้‡๏ผˆ่ง ยง5.10๏ผ‰ +โ”œโ”€โ”€ package.json # ๅ•ๅŒ…๏ผšscripts ็”จ vp + soy๏ผˆ@soybeanjs/cli๏ผ‰ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ pages/ # ๆ–‡ไปถๅผ้กต้ข่ทฏ็”ฑ๏ผˆๆ›ฟไปฃ v2 src/views๏ผ‰ +โ”‚ โ”‚ โ”œโ”€โ”€ (builtin)/ # login / 403 / 404 / 500 / iframe-page +โ”‚ โ”‚ โ”œโ”€โ”€ home/ # dashboard +โ”‚ โ”‚ โ”œโ”€โ”€ manage/ # user / role / menu / permission / dict ... +โ”‚ โ”‚ โ”œโ”€โ”€ function/ # ๅŠŸ่ƒฝๆผ”็คบ๏ผˆtab / multi-tab / request / access-control ...๏ผ‰ +โ”‚ โ”‚ โ”œโ”€โ”€ multi-menu/ # ๅคš็บง่œๅ•ๆผ”็คบ +โ”‚ โ”‚ โ”œโ”€โ”€ plugin/ # ๆ’ไปถๆผ”็คบ๏ผˆeditor / excel / pdf / charts ...๏ผ‰ +โ”‚ โ”‚ โ””โ”€โ”€ about/ +โ”‚ โ”œโ”€โ”€ routes/ # API ่ทฏ็”ฑ๏ผˆๆ›ฟไปฃ Apifox mock๏ผ‰ +โ”‚ โ”‚ โ””โ”€โ”€ api/ +โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # login / refresh / logout / user-info +โ”‚ โ”‚ โ”œโ”€โ”€ menu/ # public / user / exist-path +โ”‚ โ”‚ โ””โ”€โ”€ user/ role/ permission/ org/ dict/ ... +โ”‚ โ”œโ”€โ”€ schema/ # ๅ‰ๅŽ็ซฏๅ…ฑไบซ Valibot schema + DTO ็ฑปๅž‹๏ผˆๅŽŸ unify packages/schema ไธ‹ๆฒ‰่‡ณๆญค๏ผ‰ +โ”‚ โ”œโ”€โ”€ layouts/ # default.vue๏ผˆ= SLayout ๆžถๅญ๏ผ‰/ blank.vue +โ”‚ โ”œโ”€โ”€ middleware/ # ๆœๅŠก็ซฏ้‰ดๆƒไธญ้—ดไปถ๏ผˆ01.auth.ts๏ผ‰ +โ”‚ โ”œโ”€โ”€ services/ # ๆœๅŠก็ซฏไธšๅŠกๅฑ‚๏ผˆๆฏ่ต„ๆบ *.service.ts๏ผ‰ +โ”‚ โ”œโ”€โ”€ db/ # Drizzle schema + ่ฟ็งป + seed +โ”‚ โ”œโ”€โ”€ locales/ # zh-CN.json / en-US.json +โ”‚ โ”œโ”€โ”€ components/ # ๅ…จๅฑ€ๅฐ็ป„ไปถ +โ”‚ โ”œโ”€โ”€ composables/ # useTableOperate / useAuth / useCaptcha ... +โ”‚ โ”œโ”€โ”€ constants/ # ๆŒ‚่ฝฝ็‚น IDใ€ๆญฃๅˆ™ใ€i18n key ๆ˜ ๅฐ„ +โ”‚ โ”œโ”€โ”€ directives/ # v-auth๏ผˆๆŒ‰้’ฎ็บงๆƒ้™๏ผ‰็ญ‰ +โ”‚ โ”œโ”€โ”€ shared/ # storage / table / request ้€‚้… +โ”‚ โ”œโ”€โ”€ store/ # pinia: app / auth / menu / route / tab / theme +โ”‚ โ”œโ”€โ”€ styles/ # ๅ…จๅฑ€ css๏ผˆ่ตฐ @soybeanjs/ui-uno๏ผ‰ +โ”‚ โ”œโ”€โ”€ theme/ # ไธป้ข˜้ข„่ฎพ JSON๏ผˆshadcn-theme ๅ…ผๅฎน๏ผ‰ +โ”‚ โ”œโ”€โ”€ request/ # typed client๏ผˆclient.ts / internal.ts๏ผ‰ +โ”‚ โ”œโ”€โ”€ typings/ # ็Žฏๅขƒ/ๅ…จๅฑ€็ฑปๅž‹่กฅๅ…… +โ”‚ โ”œโ”€โ”€ server.ts # defineServer๏ผˆๅ…จๅฑ€ hooks๏ผ‰ +โ”‚ โ”œโ”€โ”€ app.ts # defineApp๏ผˆrouter.setup ๅฎˆๅซใ€appRoot๏ผ‰ +โ”‚ โ””โ”€โ”€ app.vue # appRoot ๅŒ…่ฃ…๏ผšSConfigProvider + Provider ๆŒ‚่ฝฝ +โ”œโ”€โ”€ scripts/ # ่พ…ๅŠฉ่„šๆœฌ๏ผšopenapi/seed/remove-i18n๏ผˆdev/build ็”ฑ vp + ubean CLI ๆ‰ฟๆ‹…๏ผ‰ +โ”œโ”€โ”€ docs/ # v3.md๏ผˆๆœฌๆ–‡ๆกฃ๏ผ‰็ญ‰ +โ””โ”€โ”€ .github/workflows/ # lint + typecheck + build + release +``` + +่ฆ็‚น๏ผš + +- **ๅˆ ้™ค v2 ็š„ `packages/` ็งๆœ‰ๅŒ…ไฝ“็ณป**๏ผˆ`@sa/axios`ใ€`@sa/materials`ใ€`@sa/hooks`ใ€`@sa/color`ใ€`@sa/scripts`ใ€`@sa/uno-preset`ใ€`@sa/alova`ใ€`@sa/ofetch`๏ผ‰ไธŽๆ น `pnpm-workspace.yaml`๏ผšๅ…ถ่Œ่ดฃๅˆ†ๅˆซ็”ฑ `@soybeanjs/fetch`ใ€soybean-uiใ€`@soybeanjs/utils`ใ€`@soybeanjs/theme`ใ€`@soybeanjs/cli`ใ€`vp` ๆ‰ฟๆ‹…๏ผŒๅ…จ้ƒจไปฅๅทฒๅ‘ๅธƒ npm ๅŒ…ๆถˆ่ดน๏ผŒไป“ๅบ“ๅ†…ไธๅ†็ปดๆŠคๆœฌๅœฐๅŒ…ใ€‚ +- **schema ไธ‹ๆฒ‰ `src/schema/`**๏ผšๅ…จๆ ˆๅบ”็”จๅ‰ๅŽ็ซฏๅŒ่ฟ›็จ‹ใ€ๅŒ tsconfig๏ผŒValibot schema ็›ดๆŽฅไปฅ็›ธๅฏน่ทฏๅพ„ import๏ผˆ`#/schema/auth`๏ผ‰๏ผŒๆ— ้œ€ workspace ๅŒ…๏ผ›DTO ็ฑปๅž‹ๅณ schema ๆŽจๅฏผ็ฑปๅž‹๏ผŒๅ•ไธ€ๅฅ‘็บฆๆบไธๅ˜ใ€‚ +- **็ฌฌไธ‰ๆ–น UI ๆ ทๅผ้€‚้…ไธๆ”พๆœฌไป“ๅบ“**๏ผš็”ฑ็‹ฌ็ซ‹ไป“ๅบ“ๅผ€ๅ‘๏ผˆ่ง ยง7.2๏ผ‰๏ผŒๆœฌ้กน็›ฎๅชไฟ็•™ๅฏนๆŽฅ็บฆๅฎšใ€‚ +- **mock ๆœๅŠก้€€ๅฝน**๏ผš`src/service/api/*` ๆ‰‹ๅ†™ API ๅฑ‚ไธŽ Apifox mock ๅ…จ้ƒจๅˆ ้™ค๏ผŒๆŽฅๅฃ็”ฑ `src/routes/api/**` ็œŸๅฎžๅฎž็Žฐ๏ผŒdev ๅณ่ท‘็œŸๅŽ็ซฏ๏ผˆๅŒ่ฟ›็จ‹๏ผŒVite middlewareMode + Hono๏ผ‰ใ€‚ +- **v2 `src/views` โ†’ `src/pages`**๏ผšๆ–‡ไปถ็บฆๅฎšๅŸบๆœฌๅŒๆž„๏ผˆ`[id].vue` ๅŠจๆ€ๆฎตใ€`(group)` ่ทฏ็”ฑ็ป„ใ€`_builtin` โ†’ `(builtin)`๏ผ‰๏ผŒ่ฟ็งปไปฅ็›ฎๅฝ•้‡ๅ‘ฝๅ + meta ๆ”นๅ†™ไธบไธปใ€‚ +- ่ทฏๅพ„ๅˆซๅ็ปŸไธ€ `#/` โ†’ `src/`๏ผˆvite-plus `tsconfigPaths` + ubean ้ป˜่ฎค็บฆๅฎš๏ผ‰ใ€‚ + +### 3.2 ๅŒ็‰ˆๆœฌๅˆ†ๆ”ฏ็ญ–็•ฅ๏ผˆ่ฆๆฑ‚ 8๏ผ‰ + +| | `main`๏ผˆๅ…จๆ ˆ็‰ˆ๏ผŒ้ป˜่ฎค๏ผ‰ | `lite`๏ผˆๅ‰็ซฏ็‰ˆ๏ผŒๆ–ฐๅˆ†ๆ”ฏ๏ผ‰ | +| -------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ๆก†ๆžถๅ…ฅๅฃ | `ubean`๏ผˆ`ubean/client`ใ€`ubean/server` ๅ…จ้‡๏ผ‰ | ไป… `@ubean/vue` + `@ubean/client`๏ผˆๆˆ–็บฏ `@ubean/vue` ๅ†…ๆ ธ๏ผŒๅ‚็…ง `examples/client-only-spa`๏ผ‰ | +| ๅŽ็ซฏ | `src/routes/api/**` + `@ubean/server` ๅŽŸ่ฏญ + Drizzle | **ๆ— ๅŽ็ซฏไปฃ็ **๏ผ›`src/request/` ๆŒ‡ๅ‘็”จๆˆท่‡ชๅค‡ API๏ผˆไปปๆ„่ฏญ่จ€๏ผ‰๏ผŒๅฅ‘็บฆ็”จ OpenAPI ๆ–‡ๆกฃ + `openapi-typescript` ๆ‰‹ๅทฅ็”Ÿๆˆ | +| ๆž„ๅปบ | `ubean dev/build`๏ผˆclient+server ๅŒๆž„ๅปบ๏ผ‰ | `vp build`๏ผˆ็บฏ Vite SPA๏ผ‰๏ผŒ`vite.config.ts` ็”จ `ubeanVueVite()` | +| ่ทฏ็”ฑ | ๆ–‡ไปถๅผ `src/pages` + virtual routes | ๅŒๆ ทๆ–‡ไปถๅผ๏ผˆ`@ubean/vue` ๆ˜ฏๅคš็›ฎๅฝ•้กต้ข่ทฏ็”ฑๅ”ฏไธ€ๆ‰€ๆœ‰่€…๏ผ‰๏ผŒๆ—  loader/SSR ๆ•ฐๆฎๅฑ‚ | +| ้‰ดๆƒ | JWT ๅŒ token + RBAC ไธญ้—ดไปถ | ๅ‰็ซฏๆต็จ‹ไฟ็•™๏ผˆ็™ปๅฝ•้กต/ๅฎˆๅซ/token ๅญ˜ๅ‚จ/ๆƒ้™่ฟ‡ๆปค๏ผ‰๏ผŒๆŽฅๅฃ่ทฏๅพ„ไธŽๅ“ๅบ”็  env ๅฏ้… | +| mock | ไธ้œ€่ฆ๏ผˆ็œŸๅŽ็ซฏ๏ผ‰ | ๅฏ้€‰ไฟ็•™่ฝป้‡ๆœฌๅœฐ mock๏ผˆ`routeRules.proxy` ๆˆ– MSW๏ผ‰๏ผŒ้ป˜่ฎคๆไพ› unify ้ฃŽๆ ผ `docs/api-contract.md` ๆŽฅๅฃๅฅ‘็บฆ่ฏดๆ˜Ž | +| ๅ…ฑไบซไปฃ็  | `src/schema`ใ€`src/store`ใ€`src/layouts`ใ€`src/components` ็ญ‰็ปๅคง้ƒจๅˆ† | ไธŽ main ๅฐฝ้‡ไฟๆŒ็›ฎๅฝ•ไธŽไปฃ็ ไธ€่‡ด๏ผŒๅทฎๅผ‚้›†ไธญๅœจ๏ผš`ubean.config.ts`ใ€`vite.config.ts`ใ€`src/routes/`+`src/services/`+`src/db/`๏ผˆๅˆ ้™ค๏ผ‰ใ€`src/server.ts`๏ผˆๅˆ ้™ค๏ผ‰ใ€`src/request/`๏ผˆๆ”น baseURL ๆฅๆบ๏ผ‰ใ€`src/schema/`๏ผˆไฟ็•™ไฝ†ไป…ๅ‰็ซฏๆถˆ่ดน๏ผ‰ | + +**ๅŒๆญฅๆœบๅˆถ**๏ผš`lite` ๅˆ†ๆ”ฏๅฎšๆœŸไปŽ `main` rebase/merge๏ผŒๅ†ฒ็ช้ขๆŽงๅˆถๅœจไธŠ่ฟฐๅทฎๅผ‚็‚น๏ผˆ้›†ไธญๅœจ 5 ไธชๆ–‡ไปถ/็›ฎๅฝ•๏ผ‰ใ€‚CI ๅฏนไธคๅˆ†ๆ”ฏ้ƒฝ่ท‘ typecheck + buildใ€‚ๆ–‡ๆกฃ็ซ™ๅˆ†ๅˆซ่ฏดๆ˜Žไธค็ง่ตทๆญฅๆ–นๅผ๏ผš`pnpm create` ๆจกๆฟๅฏนๅบ” main๏ผ›lite ไฝœไธบ"ๆŽฅๅ…ฅ่‡ชๅฎšไน‰ๅŽ็ซฏ"็š„ๅฎ˜ๆ–น็คบไพ‹ใ€‚ + +--- + +## 4. ็Žฐๆœ‰่ƒฝๅŠ› vs v3.0 ่ƒฝๅŠ›ๅฎž็Žฐๅฏนๆฏ”๏ผˆๆ ธๅฟƒ๏ผ‰ + +### 4.1 ๅธƒๅฑ€ไธŽๅฏผ่ˆช + +| v2.x ่ƒฝๅŠ› | v2.x ๅฎž็Žฐ | v3.0 ๅฎž็Žฐ | ็Šถๆ€ | +| ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| 6 ็งๅธƒๅฑ€ๆจกๅผ๏ผˆvertical / horizontal / vertical-mix / vertical-hybrid-header-first / top-hybrid-sidebar-first / top-hybrid-header-first๏ผ‰ | ่‡ช็ ” AdminLayout + 6 ๅฅ— global-menu ็ป„ไปถ | **`SLayout` + `SSplitNav`**๏ผš`SplitNavMode` 4 ็ง๏ผˆdual-vertical / vertical-horizontal / horizontal-vertical / horizontal-dual-vertical๏ผ‰+ `SLayout` ็š„ `orientation`/`side`/`variant`/`collapsible` + Teleport ๆŒ‚่ฝฝ็‚น๏ผˆ`horizontalMountedId`/`verticalMountedId` โ†” v2 `GLOBAL_HEADER_MENU_ID`/`GLOBAL_SIDER_MENU_ID`๏ผ‰็ป„ๅˆ่ฆ†็›–ๅ…จ้ƒจ 6 ๆจกๅผ | โœ… ็ป„ไปถๅŒ–ๆ‰ฟๆŽฅ | +| ไธป้ข˜้…็ฝฎๆŠฝๅฑ‰๏ผˆๅค–่ง‚/ๅธƒๅฑ€/้€š็”จ/ๆ“ไฝœ 4 Tab๏ผŒ็บฆ 40 ้กน้…็ฝฎ๏ผ‰ | ่‡ช็ ” theme-drawer | **`SThemeCustomizer`** ๅ†…็ฝฎ้ขๆฟ + ๅบ”็”จๅฑ‚่กฅๅ…… admin ไธ“ๅฑž้กน๏ผˆๅธƒๅฑ€ๆจกๅผๅก็‰‡ใ€้กต็ญพ้ฃŽๆ ผใ€ๆฐดๅฐใ€ๆœ็ดขๅผ€ๅ…ณ็ญ‰๏ผ‰๏ผŒ้…็ฝฎๆจกๅž‹ = theme store โ†’ `layoutProps` ไธ€ๅฑ‚ๆ˜ ๅฐ„๏ผˆunify ่Œƒๅผ๏ผ‰ | โœ… ๅคง้ƒจๅˆ†ๅ†…็ฝฎ๏ผŒadmin ้กน้œ€ๆ‰ฉๅฑ• | +| ้กต็ญพ๏ผš3 ้ฃŽๆ ผ๏ผˆchrome/button/slider๏ผ‰ใ€ๆ‹–ๆ‹ฝๆŽ’ๅบใ€ๅณ้”ฎ่œๅ•ใ€ไธญ้”ฎๅ…ณ้—ญใ€ๅ›บๅฎšใ€็ผ“ๅญ˜ใ€่‡ชๅฎšไน‰ๆ ‡้ข˜ใ€ๅคšๆ ‡็ญพ | ่‡ช็ ” GlobalTab + @better-scroll | **`SPageTabs`**๏ผšv-model:itemsใ€beforeCloseใ€ไธญ้”ฎๅ…ณ้—ญใ€pin ่‡ชๅŠจๆŽ’ๅบใ€draggableใ€contextMenu ๅ…จๅ†…็ฝฎ๏ผ›ๅคšๆ ‡็ญพ๏ผˆquery ๅŒบๅˆ†๏ผ‰ใ€้กต็ญพ็ผ“ๅญ˜ใ€setTabLabel ๅœจ tab store ๆ‰ฟๆŽฅ | โœ… ็ป„ไปถๅŒ–ๆ‰ฟๆŽฅ | +| ไพงๆ ๆ ‘ๅฝข่œๅ•๏ผˆๆŠ˜ๅ /ๅ›พๆ ‡ๆŠ˜ๅ /ๆทฑ่‰ฒไพงๆ /mix ไธ‰ๅฎฝๅบฆ๏ผ‰ | NMenu 6 ๅฝขๆ€ | **`STreeMenu`**๏ผˆWAI-ARIA ้”ฎ็›˜๏ผ‰/ `SSplitNav` ๅญ้ขๆฟ๏ผ›`collapsible: 'offcanvas' \| 'icon'`ใ€`collapsedWidth`ใ€`--sidebar-*` ไธ“็”จ่‰ฒๆฟ | โœ… | +| ้ขๅŒ…ๅฑ‘๏ผˆๅ›พๆ ‡ใ€ไธ‹ๆ‹‰ๅˆ‡ๆขๅ…„ๅผŸ่œๅ•๏ผ‰ | global-breadcrumb | **`SBreadcrumb`**๏ผˆ+Ellipsis/Page๏ผ‰+ ่œๅ•ๆ ‘ๆดพ็”Ÿ๏ผˆmenu store breadcrumbs๏ผ‰ | โš ๏ธ ไธ‹ๆ‹‰ๅˆ‡ๆขๅ…„ๅผŸ่œๅ•้œ€ๅบ”็”จๅฑ‚ๅŒ…่ฃ… | +| ๅคด้ƒจ๏ผšlogo/ๆŠ˜ๅ /ๆœ็ดข/ๅ…จๅฑ/่ฏญ่จ€/ๆ˜Žๆš—/ไธป้ข˜/ๅคดๅƒ | global-header ๆ‰‹ๅทฅ็ป„่ฃ… | `SLayout` Header slot + soybean-ui ๅฐ็ป„ไปถๆ‹ผ่ฃ…๏ผ›ๆ˜Žๆš— = `SThemeModeSwitch`๏ผˆlight/dark/auto๏ผ‰ | โœ… | +| ็งปๅŠจ็ซฏ้€‚้…๏ผˆๆ–ญ็‚นๅˆ‡ vertical+ๆŠฝๅฑ‰ใ€่ฎพ็ฝฎๅค‡ไปฝๆขๅค๏ผ‰ | useBreakpoints + app store | `SLayout` `isMobile` ๅฃฐๆ˜Žๅผๅˆ‡ Dialog ๆŠฝๅฑ‰ + `useMediaQuery`๏ผ›ๅค‡ไปฝๆขๅค้€ป่พ‘ไฟ็•™ๅœจ app store | โœ… | +| ๆฐดๅฐ๏ผˆๆ–‡ๆœฌ/็”จๆˆทๅ/ๅฎžๆ—ถๆ—ถ้—ด 7 ๆ ผๅผ๏ผ‰ | NWatermark | **`SWatermark`**๏ผˆheadless ๅทฒๆœ‰ watermark ็ป„ไปถ๏ผ‰+ watermarkContent ๆดพ็”Ÿ้€ป่พ‘ไฟ็•™ | โœ… | +| ๅ…จๅฑ€ๆœ็ดข๏ผˆ่œๅ•ๆจก็ณŠๆœ็ดขใ€้”ฎ็›˜ๅฏผ่ˆชใ€้ซ˜ไบฎ๏ผ‰ | ่‡ช็ ” search-modal | **`SCommand`**๏ผˆๅ‘ฝไปค้ขๆฟๅŽŸ่ฏญ๏ผ‰+ ่œๅ•็ดขๅผ•๏ผ›`routeRules` ๆ— ๅ…ณ๏ผŒ็บฏๅ‰็ซฏ | โœ… ็ป„ไปถๅŒ–้‡ๅ†™ | +| ้กต้ขๅˆ‡ๆขๅŠจ็”ป 7 ็ง | transition.css | `SLayout` ้กต้ข่ฟ‡ๆธก props + `@ubean/vue` ้กต้ข่ฟ‡ๆธก่ƒฝๅŠ› + view transitions | โœ… | +| ๆปšๅŠจๆจกๅผ wrapper/contentใ€ๅ›บๅฎšๅคด้ƒจ/้กต็ญพ/ๅบ•้ƒจ | AdminLayout props | `SLayout` `scrollBehavior`/`fixedTop`/`fixedFooter` ๅŽŸ็”Ÿ | โœ… | +| Footer๏ผˆๆ˜พ้š/ๅ›บๅฎš/้ซ˜ๅบฆ/ๅฑ…ๅณ๏ผ‰ | global-footer | `SLayout` Footer๏ผˆvisible/height๏ผ‰+ slot | โœ… | +| ็ฐ่‰ฒ/่‰ฒๅผฑๆจกๅผ | documentElement filter | ๅบ”็”จๅฑ‚ไฟ็•™๏ผˆcss filter๏ผ‰๏ผŒtheme store ้ฉฑๅŠจ | โœ… ไฟ็•™ | +| ๅ…จๅฑๅ†…ๅฎนๅˆ‡ๆข | app store fullContent | `SLayout` `fullContent` prop | โœ… | + +### 4.2 ่ทฏ็”ฑไธŽๆƒ้™ + +| v2.x ่ƒฝๅŠ› | v3.0 ๅฎž็Žฐ | ็Šถๆ€ | +| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | +| elegant-router ่‡ชๅŠจ็”Ÿๆˆ + `RouteKey`/`RouteMap` ็ฑปๅž‹ | `.ubean/routes.d.ts` + `typed-router.d.ts`๏ผˆ`useRoute('RouteName')` ๅ‚ๆ•ฐๆŽจๆ–ญ๏ผ‰+ ๅŠจๆ€ๆฎต matcher๏ผˆ`[id=numeric].vue`๏ผ‰ | โœ… ็ญ‰ไปทไธ”ๆ›ดๅผบ๏ผˆmatcher ๆ ก้ชŒใ€ๅค็”จ่ทฏ็”ฑใ€ๆ‹ฆๆˆช่ทฏ็”ฑ๏ผ‰ | +| ้™ๆ€/ๅŠจๆ€ๅŒๆƒ้™ๆจกๅผ๏ผˆ`VITE_AUTH_ROUTE_MODE`๏ผ‰ | ไฟ็•™ๅŒๆจกๅผ๏ผšstatic = ๆœฌๅœฐ่œๅ•ๆ•ฐๆฎ + roles ่ฟ‡ๆปค๏ผ›dynamic = `GET /api/menu/user` ่ฟ”ๅ›ž่œๅ•ๆ ‘ โ†’ `transformMenusToAutoRoutes`๏ผˆunify ่Œƒๅผ๏ผŒ่œๅ•้ฉฑๅŠจ่ทฏ็”ฑ/้กต็ญพ/ๆ ‡้ข˜ไธ€ๅ…ƒๆจกๅž‹๏ผ‰ | โœ… | +| ่ทฏ็”ฑๅฎˆๅซ้“พ๏ผˆprogress โ†’ route โ†’ title๏ผ‰ | `defineApp({ router: { setup } })` ๆณจๅ†Œ beforeEach๏ผšauth ๅฎˆๅซ + ้ฆ–ๅฑ loading + ่ฟ›ๅบฆๆก๏ผˆsoybean-ui `progress`๏ผ‰๏ผ›ๆ ‡้ข˜็”ฑ่œๅ• `i18nKey` ้ฉฑๅŠจ | โœ… ๆณจๅ…ฅ็‚นๅ˜ๅŒ–๏ผŒ้€ป่พ‘ไฟ็•™ | +| 15 ้กน meta ๅญ—ๆฎต | `definePage({ meta })`๏ผšrequiresAuth/roles/keepAlive/multiTab/pinned/tabbable/href/activeMenu/order/icon/hideInMenu/query/isDev ๅ…จ้ƒจไฟ็•™๏ผ›title/i18nKey ๆ”น็”ฑ่œๅ•ๆ•ฐๆฎๆ‰ฟ่ฝฝ๏ผˆunify ็ฎ€ๅŒ–๏ผ‰๏ผŒ็บฏ้™ๆ€่ทฏ็”ฑๆไพ› `routeTitle` ๅ…œๅบ• | โœ…๏ผˆtitle ่ฏญไน‰่ฟ็งป๏ผŒ่ง ยง5.3 ่ฏดๆ˜Ž๏ผ‰ | +| ๆŒ‰้’ฎ็บงๆƒ้™๏ผˆuserInfo.buttons + hasAuth + ๆผ”็คบ้กต๏ผ‰ | `useAuth().hasAuth(code)` + **`v-auth` ๆŒ‡ไปค**๏ผˆv2 ๆ— ๆŒ‡ไปคใ€v3 ่กฅไธŠ๏ผ‰+ ่ง’่‰ฒ็ฎก็†ๅˆ†้…ๆŒ‰้’ฎๆƒ้™ๅผน็ช— | โœ… ๅขžๅผบ | +| 403/404 ๅˆคๅฎš๏ผˆ่ทฏๅพ„ๅญ˜ๅœจไฝ†ๆ— ๆƒ้™ โ†’ 403๏ผ‰ | ไฟ็•™๏ผšNotFound ๆ•่Žทๆ—ถ `checkIsRouteExist`๏ผˆstatic ๆŸฅ่กจ / dynamic `POST /api/menu/exist-path`๏ผ‰ | โœ… | +| keep-alive ๆŒ‰ meta ๆ”ถ้›† + exclude ็ฒพ็กฎ้‡็ฝฎ | `@ubean/vue` ้กต้ข็ผ“ๅญ˜๏ผˆ`useCacheViews`/`enablePageCache`/`invalidatePageCache`๏ผ‰+ route store cachedRoutes | โœ… | +| iframe ๅค–้“พ้กตใ€`href` ๅค–้“พๆ–ฐ็ช—ๅฃ | `(builtin)/iframe/[url].vue` ไฟ็•™๏ผ›meta.href ่กŒไธบไฟ็•™ | โœ… | +| root redirect = ๅŠจๆ€ home | ่œๅ•ๆŽฅๅฃ่ฟ”ๅ›ž home โ†’ ๆ”นๅ†™ ROOT redirect๏ผˆunify ๅŒๆฌพ๏ผ‰ | โœ… | + +### 4.3 ่ฏทๆฑ‚ไธŽๅŽ็ซฏ + +| v2.x ่ƒฝๅŠ› | v3.0 ๅฎž็Žฐ | ็Šถๆ€ | +| --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| flat request๏ผˆ`{data,error}` ๅ… try/catch๏ผ‰ | `toFlatRequest`๏ผˆ`@soybeanjs/fetch`๏ผ‰ | โœ… | +| Bearer ๆณจๅ…ฅใ€ๅ“ๅบ”่งฃๅŒ…ใ€ไธšๅŠก็ ๅˆคๅฎš | onRequest/transform/isBackendSuccess ็บฆ 80 ่กŒ่Œƒๅผ๏ผˆunify๏ผ‰ | โœ… | +| token ่ฟ‡ๆœŸๅˆทๆ–ฐ + ๅนถๅ‘ๅŽป้‡ + ๅŽŸ่ฏทๆฑ‚้‡ๆ”พ | **v2 ๆœ‰ใ€unify ็ผบ**โ€”โ€”v3 ๅฟ…้กปๆŽฅ็บฟ๏ผš401/่ฟ‡ๆœŸ็  โ†’ `refreshTokenPromise` ๅŽป้‡ โ†’ ้‡ๆ”พ้˜Ÿๅˆ—๏ผˆไฟ็•™ v2 shared.ts ้€ป่พ‘๏ผ‰ | โœ… ไฟ็•™๏ผˆๅธๅ– unify ๆœชๅฎŒๆˆๆ•™่ฎญ๏ผ‰ | +| LOGOUT_CODES / MODAL_LOGOUT_CODES ๅผน็ช—็™ปๅ‡บ | env ๅฏ้… + `SDialog`/`useToast` ๅฎž็Žฐ | โœ… | +| ้”™่ฏฏๆถˆๆฏๅŽป้‡ๆ ˆ | errMsgStack ้€ป่พ‘ไฟ็•™ | โœ… | +| ๅคš baseURL๏ผˆdemoRequest / `VITE_OTHER_SERVICE_BASE_URL`๏ผ‰ | `routeRules.proxy`๏ผˆ`/_p/{key}` ๅ‰็ผ€ไปฃ็†๏ผ‰+ ๅคš client ๅฎžไพ‹ | โœ… | +| dev ไปฃ็† + ็ปˆ็ซฏๅฝฉ่‰ฒๆ—ฅๅฟ— | vite-plus proxy / routeRules proxy๏ผ›ๆ—ฅๅฟ—็”จ `@ubean/server` logger | โœ… | +| ็‰ˆๆœฌๆ›ดๆ–ฐๆฃ€ๆต‹๏ผˆ่ฝฎ่ฏข buildTime meta๏ผ‰ | ไฟ็•™๏ผˆprod ็‰นๆ€งๅผ€ๅ…ณ `VITE_AUTOMATICALLY_DETECT_UPDATE`๏ผ‰๏ผ›ๆˆ–ๆ”น `/_health` ็‰ˆๆœฌ็ซฏ็‚น | โœ… | +| Apifox mock | **ๅˆ ้™ค**๏ผŒ็”ฑ็œŸๅฎžๅŽ็ซฏ + seed ๆ•ฐๆฎๆ›ฟไปฃ๏ผ›lite ๅˆ†ๆ”ฏๆไพ›ๆŽฅๅฃๅฅ‘็บฆๆ–‡ๆกฃ + ๅฏ้€‰ MSW | ๐Ÿ”„ ๆžถๆž„ๆ›ฟๆข | +| ๆ‰‹ๅ†™ `service/api/*.ts` + `typings/api/*.d.ts` | **ๅˆ ้™ค**๏ผŒ`createTypedClient` ็›ดๆŽฅๆŒ‰่ทฏๅพ„่ฐƒ็”จ๏ผ›ๆœๅŠก็ซฏ่ฟ›็จ‹ๅ†… `createInternalAdapter` ้›ถ็ฝ‘็ปœๅค็”จ | โœ… ็ฎ€ๅŒ– | + +### 4.4 ็Šถๆ€็ฎก็† + +v2 ็š„ 5 ไธช store๏ผˆapp/auth/route/tab/theme๏ผ‰+ v3 ๆ–ฐๅขž menu๏ผŒๅ…จ้ƒจไฟ็•™ไธบ Pinia setup store๏ผˆunify ้ชŒ่ฏ 6 store ็ป“ๆž„๏ผ‰๏ผš + +| store | v3.0 ่Œ่ดฃๅ˜ๅŒ– | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| app | ไธๅ˜๏ผšisMobileใ€reloadSignalใ€fullContentใ€drawer ๅผ€ๅ…ณใ€็งปๅŠจ็ซฏๅธƒๅฑ€ๅค‡ไปฝๆขๅค | +| auth | ๆŽฅ็œŸๅฎž็™ปๅฝ•ๆŽฅๅฃ๏ผ›่ทจ็”จๆˆทๅˆ‡ๆขๆธ…้กต็ญพใ€lastLoginUserId ไฟ็•™ | +| theme | settings โ†’ `ThemeOptions`๏ผˆ`@soybeanjs/theme`๏ผ‰+ `layoutProps` ๆดพ็”Ÿ๏ผ›naive themeOverrides ็”Ÿๆˆ้€ป่พ‘ๅˆ ้™ค๏ผ›็”Ÿไบง็ผ“ๅญ˜ + `overrideThemeSettings` ็‰ˆๆœฌ่ฆ†็›–ไฟ็•™ | +| route | ๅŠจๆ€่ทฏ็”ฑๆณจๅ†Œ๏ผˆ`addRoute` ไปๅฏ็”จ๏ผŒvue-router 5 ๅฎžไพ‹็”ฑๆก†ๆžถๅˆ›ๅปบ๏ผ‰+ cacheRoutes | +| tab | ไธๅ˜๏ผˆๅฏนๆŽฅ `SPageTabs` items ๆจกๅž‹๏ผ‰ | +| menu๏ผˆๆ–ฐๅขž๏ผŒ่‡ช route ๆ‹†ๅˆ†๏ผ‰ | ่œๅ•ๆ ‘ + map + ้ขๅŒ…ๅฑ‘ + routeMenu๏ผ›็›‘ๅฌ่ฏญ่จ€ๅ˜ๅŒ–้‡ๅปบ๏ผˆunify ่Œƒๅผ๏ผ‰ | + +### 4.5 i18n / ไธป้ข˜ / ๅ›พๆ ‡ + +| v2.x | v3.0 | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| vue-i18n ๆ‰‹ๅทฅ่ฃ…้…ใ€zh-cn/en-us ไธคๅฅ— Schema ็ฑปๅž‹ | `@ubean/i18n` ้…็ฝฎๅŒ– + `.ubean/i18n.d.ts` ็ฑปๅž‹็”Ÿๆˆ๏ผ›`src/locales/` JSON ็ป„็ป‡๏ผ›ไฟ็•™ `remove-i18n` ไธ€้”ฎ่ฃๅ‰ช่„šๆœฌ๏ผˆunify๏ผ‰ | +| naive locale/dateLocale ่”ๅŠจ | `SConfigProvider` locale + `@soybeanjs/headless/locale`๏ผˆ13 ่ฏญ่จ€ๅŒ…๏ผ‰๏ผ›dayjs locale ่”ๅŠจไฟ็•™ | +| ่ฏญ่จ€ๅˆ‡ๆข่”ๅŠจ title/menu/tab/dayjs | ๅ…จ้ƒจไฟ็•™๏ผˆmenu store + tab store ็›‘ๅฌ๏ผ‰ | +| 4 ไธชไธป้ข˜้ข„่ฎพ JSON๏ผˆdefault/dark/compact/azir๏ผ‰ | `@soybeanjs/shadcn-theme` ๅ†…็ฝฎ้ข„่ฎพ + ่‡ชๅฎšไน‰ preset/overrides๏ผ›`VITE_THEME_PRESET` env ้€‰ๆ‹ฉ | +| iconify ็ฆป็บฟ provider + ๆœฌๅœฐ svg ๅŒๆจก | `icon: true`๏ผˆ`@ubean/icon`๏ผ‰+ ๆœฌๅœฐ svg collection ็บฆๅฎšไฟ็•™๏ผ›`SIcon` ็š„ `IconValue` ็ปŸไธ€ๆ•ฐๆฎๆจกๅž‹ | + +### 4.6 ้กต้ขๅŠŸ่ƒฝๅฏน้ฝๆธ…ๅ• + +| v2.x ้กต้ข | v3.0 ๅค„็† | +| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ็™ปๅฝ•๏ผˆpwd/้ชŒ่ฏ็ /ๆณจๅ†Œ/้‡็ฝฎๅฏ†็ /็ป‘ๅฎšๅพฎไฟก 5 ๆจกๅ—๏ผ‰ | ๅ…จ้ƒจๅฎž็Žฐ๏ผ›ๅŽ็ซฏ็œŸๅฎž็ญพๅ‘ token๏ผ›ๅ›พๅฝข้ชŒ่ฏ็ ็”จ `@ubean/server` cache ๅญ˜ๅ‚จ็ญ”ๆกˆ | +| ้ฆ–้กต dashboard๏ผˆ้—ฎๅ€™ๆจชๅน…/4 ็ปŸ่ฎกๅก/ๆŠ˜็บฟ/้ฅผๅ›พ/้กน็›ฎๅŠจๆ€๏ผ‰ | ๅ…จ้ƒจๅฎž็Žฐ๏ผ›ๅ›พ่กจ็”จ `@soybeanjs/chart` + echarts๏ผˆ`useEcharts` hook ไฟ็•™๏ผ‰๏ผ›CountTo ็”จ `useTransition` | +| manage๏ผš็”จๆˆท/่ง’่‰ฒ/่œๅ•/ๆŒ‰้’ฎๆƒ้™็ฎก็†๏ผˆexample๏ผ‰ | ๅ‡็บงไธบ unify ็š„ 8 ๅฅ— CRUD๏ผšmenu/user/role/permission/org/api/dict/dict-item๏ผŒ็ปŸไธ€"ๆœ็ดขๅก + ๅˆ†้กต่กจๆ ผ + operate ๅผน็ช—"่Œƒๅผ๏ผˆ`usePaginatedTable` + `useTableOperate`๏ผ‰ | +| function๏ผšๆ ‡็ญพ้กต/ๅคšๆ ‡็ญพ/่ฏทๆฑ‚ๆผ”็คบ/ๅˆ‡ๆขๆƒ้™/้š่—ๅญ่œๅ•/่ถ…็ฎก้กต | ๅ…จ้ƒจไฟ็•™๏ผŒ่ฏทๆฑ‚ๆผ”็คบๅฏนๆŽฅ็œŸๅฎž้”™่ฏฏ็ ็ซฏ็‚น `POST /api/auth/error` | +| multi-menu ไธ‰็บงๅตŒๅฅ— | ไฟ็•™๏ผˆๆ–‡ไปถ็›ฎๅฝ•ๅคฉ็„ถ่กจ่พพ๏ผ‰ | +| plugin๏ผšcopy/icon/echarts/antv/vchart/wangEditor/vditor/excel/pdf/print/barcode/pinyin/video/map/swiper/typeit/vtable/gantt | ๅ…จ้ƒจไฟ็•™ไธบ็ฌฌไธ‰ๆ–น้›†ๆˆ็คบไพ‹้กต๏ผ›ไพ่ต–ๆธ…ๅ•่ง ยง5.8 | +| about๏ผˆไพ่ต–็‰ˆๆœฌ่กจ/ๆž„ๅปบๆ—ถ้—ด/ๆŠ€ๆœฏๆ ˆๅก็‰‡๏ผ‰ | ไฟ็•™๏ผ›็‰ˆๆœฌ่กจๆ”น่ฏป catalog | +| user-center ๅ ไฝ | ๅ‡็บงไธบๅฎŒๆ•ด profile + settings ้กต๏ผˆunify ๅ ไฝๆ•™่ฎญ๏ผŒv3 ่กฅ้ฝ๏ผ‰ | +| 403/404/500 | ไฟ็•™๏ผˆ`exception-base` โ†’ soybean-ui `SEmpty` + ๆœฌๅœฐ svg๏ผ‰ | +| alova ็คบไพ‹ / pro-naive-ui ็คบไพ‹ | **ไธ่ฟ็งป**๏ผˆalova ไธŽๆก†ๆžถ่ฏทๆฑ‚ๅฑ‚้‡ๅค๏ผ›pro-naive-ui ๅฑž naive ็”Ÿๆ€๏ผ‰ | + +### 4.7 ๅทฅ็จ‹่ƒฝๅŠ› + +| v2.x | v3.0 | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ESLint + oxlint + oxfmt + simple-git-hooks | `vp lint` / `vp fmt`๏ผˆoxc ็ณป๏ผ‰+ `vp check --fix` staged๏ผ›ๅ…ฑไบซ `@soybeanjs/oxc-config` | +| `sa git-commit` ไบคไบ’ๅผๆไบค๏ผˆ@sa/scripts๏ผ‰ | **`soy git-commit`**๏ผˆ`@soybeanjs/cli`๏ผŒdevDependency ๅผ•ๅ…ฅ๏ผ‰๏ผŒcommit-msg ้’ฉๅญๆข `soy git-commit-verify`๏ผ›`soy release`/`soy changelog`/`soy cleanup`/`soy ncu` ่ฆ†็›– v2 `@sa/scripts` ๅ…จ้ƒจ่ƒฝๅŠ› | +| vue-tsc typecheck | ไฟ็•™๏ผˆubean `prepare` ็”Ÿๆˆ `.ubean/` ็ฑปๅž‹ๅŽ่ท‘ vue-tsc๏ผ‰ | +| bundle ไฝ“็งฏๆ— ็บฆๆŸ | ๅผ•ๅ…ฅ ubean `pnpm analyze` ๅŸบ็บฟๅ›žๅฝ’๏ผˆgzip ๅขž้•ฟ >5% CI ๅคฑ่ดฅ๏ผ‰ | +| GitHub Actions lint/release | ไฟ็•™๏ผ›release ่ตฐ `soy release`๏ผˆๅ†…้ƒจ bumpp + @soybeanjs/changelog๏ผ‰ | +| env ็บฆ 25 ไธชๅ˜้‡ | ็ฒพ็ฎ€่‡ณ็บฆ 15 ไธช๏ผˆ่ง ยง5.10๏ผ‰๏ผšๅˆ ้™ค naive ไธ“ๅฑžใ€mock ไธ“ๅฑž๏ผ›ๆ–ฐๅขž `VITE_API_ENV`/`VITE_API_PROXY`/`VITE_THEME_PRESET` | + +--- + +## 5. ๅ‰็ซฏ่ฏฆ็ป†่ฎพ่ฎก + +### 5.1 ๅบ”็”จๅ…ฅๅฃไธŽ็”Ÿๅ‘ฝๅ‘จๆœŸ + +```ts +// src/app.ts +import { defineApp } from 'ubean/client'; +import { setupStore } from './store'; + +export default defineApp({ + appRoot: () => import('./app.vue'), // SConfigProvider ๅŒ…่ฃ…ๅฑ‚ + plugins: [setupStore], // pinia ็ป @ubean/integrations/pinia + router: { + setup(router) { + router.beforeEach(authGuard); // ็™ปๅฝ•/ๆƒ้™/ๅŠจๆ€่ทฏ็”ฑๅˆๅง‹ๅŒ– + router.beforeEach(progressGuard); // soybean-ui progress + router.afterEach(titleGuard); // document.title๏ผˆ่œๅ• i18nKey ้ฉฑๅŠจ๏ผ‰ + } + }, + onClientReady() { + setupLoading(); // ้ฆ–ๅฑ HTML loading๏ผˆไธป้ข˜่‰ฒ่”ๅŠจ๏ผ‰ + } +}); +``` + +`app.vue`๏ผˆappRoot๏ผ‰๏ผš`SConfigProvider`๏ผˆtheme/persistTheme/dir/locale๏ผ‰+ Toast/Dialog Provider ๆŒ‚่ฝฝ + ๅ…จๅฑ€ๆฐดๅฐ `SWatermark`ใ€‚ + +### 5.2 ๅธƒๅฑ€ๆžถๅญ๏ผˆlayouts/default.vue๏ผ‰ + +```vue + + +``` + +ๅธƒๅฑ€ๆจกๅผๆ˜ ๅฐ„่กจ๏ผˆtheme settings โ†’ ็ป„ไปถ props๏ผ‰๏ผš + +| v2 layout.mode | SLayout | SplitNav.mode / ่œๅ•็ป„ไปถ | +| ---------------------------- | -------------------------------------- | -------------------------- | +| vertical | orientation=vertical, collapsible=icon | `STreeMenu`๏ผˆๅ•ๆ ๏ผ‰ | +| horizontal | header ๅ†…ๆจชๆŽ’ | `STreeNav` ๆจชๆŽ’ | +| vertical-mix | sidebar + ๅญ้ขๆฟๅŒๆ  | `dual-vertical` | +| top-hybrid-sidebar-first | orientation=vertical + Teleport | `vertical-horizontal` | +| top-hybrid-header-first | header ไผ˜ๅ…ˆ | `horizontal-vertical` | +| vertical-hybrid-header-first | ๆททๅˆ | `horizontal-dual-vertical` | + +> ่‹ฅ soybean-ui `@soybeanjs/admin` ๅˆ†ๆ”ฏ๏ผˆ`origin/admin`๏ผŒ`SAppLayout` ๅŒๅผ•ๆ“Ž 6 ๆจกๅผ๏ผŒๅญ—ๆฎตๅˆปๆ„ๅฏน้ฝ v2๏ผ‰ๅœจ v3 ๅผ€ๅ‘ๆœŸๅฎŒๆˆ้ชŒๆ”ถ๏ผŒๅˆ™็›ดๆŽฅๅˆ‡ๆขๅˆฐ่ฏฅๅŒ…ๆ›ฟๆขๆ‰‹ๅ†™ๆ˜ ๅฐ„ๅฑ‚โ€”โ€”ๆ–นๆกˆ้ข„็•™ๆญคๅ‡็บง่ทฏๅพ„๏ผŒๆžถๅญ API ไปฅ `SAppLayout` ๅ‘ฝๅไธบ็›ฎๆ ‡ใ€‚ + +### 5.3 ่ทฏ็”ฑไธŽ่œๅ•ไธ€ๅ…ƒๆจกๅž‹ + +- ้กต้ขๅณๆ–‡ไปถ๏ผš`src/pages/**` ่‡ชๅŠจ็”Ÿๆˆ่ทฏ็”ฑ่กจ๏ผˆ`routing: { mode: 'virtual' }`๏ผŒadmin ๆ— ้œ€็”Ÿๆˆๆ–‡ไปถ๏ผ›lite ๅˆ†ๆ”ฏๅŒๆ ทไฝฟ็”จ๏ผ‰ใ€‚ +- `definePage({ requiresAuth: true, meta: { keepAlive, multiTab, pinned, ... } })` ็ผ–่ฏ‘ๆœŸๅฎ๏ผŒ้›ถ่ฟ่กŒๆ—ถๅผ€้”€ใ€‚ +- **ๅŠจๆ€่ทฏ็”ฑ**๏ผšdynamic ๆจกๅผไธ‹ๅŽ็ซฏ่œๅ•๏ผˆmenuType `menu|page`๏ผ‰โ†’ `transformMenusToAutoRoutes` โ†’ `router.addRoute`๏ผ›routeName/routePath ไธŽ้กต้ข่ทฏ็”ฑ่กจๅšไธ€่‡ดๆ€งๆ ก้ชŒ๏ผˆๅฏๅŠจๆ–ญ่จ€๏ผŒ้˜ฒๆผ‚็งป๏ผ‰ใ€‚ +- **ๆ ‡้ข˜**๏ผš่œๅ• `i18nKey` ๅ•ไธ€ๆฅๆบ๏ผ›ๆ— ่œๅ•ๆก็›ฎ็š„้™ๆ€้กต๏ผˆๅผ‚ๅธธ้กต็ญ‰๏ผ‰็”จ `definePage({ meta: { title } })` ๅ…œๅบ•ใ€‚ๆญคไธบๅฏน v2 "meta.i18nKey ๅ…จๅฑ€ๅ”ฏไธ€ๆฅๆบ"็š„็ฎ€ๅŒ–็ปŸไธ€ใ€‚ + +### 5.4 ่ฏทๆฑ‚ๅฑ‚ + +```ts +// src/request/client.ts +import { createRequest } from '@soybeanjs/fetch'; +import { toFlatTypedClient } from '@soybeanjs/fetch/openapi'; +import type { paths } from '../../.ubean/openapi'; + +const request = createRequest( + { baseURL: '/api' }, + { + transform: response => response.data.data, + isBackendSuccess: response => response.data.code === import.meta.env.VITE_SERVICE_SUCCESS_CODE, + onRequest: config => { + /* ๆณจๅ…ฅ Bearer token */ + }, + onError: error => { + /* ็™ปๅ‡บ็  / ๅผน็ช—็™ปๅ‡บ / ่ฟ‡ๆœŸๅˆทๆ–ฐ้‡ๆ”พ / ้”™่ฏฏๅŽป้‡ */ + } + } +); + +export const api = toFlatTypedClient(request, '/api'); +// ็”จๆณ•๏ผšconst { data, error } = await api.get('/user/list', { params }); +``` + +- ๅ†…ๆ ธไธบ fetch๏ผˆๅ… axios ไพ่ต–๏ผ‰๏ผŒไธŠไผ ่ฟ›ๅบฆ็”จ `createUploadProgressAdapter`๏ผŒๆ–‡ไปถไธ‹่ฝฝ็”จๅ†…็ฝฎ `downloadFile`ใ€‚ +- `baseRequest` ๆ‰ฟๆ‹…๏ผšBearer ๆณจๅ…ฅใ€`{ code, message, data }` ่งฃๅŒ…ใ€ไธšๅŠก็ ๅˆ†ๆ”ฏ๏ผˆ็™ปๅ‡บ/ๅผน็ช—็™ปๅ‡บ/่ฟ‡ๆœŸๅˆทๆ–ฐ้‡ๆ”พ๏ผ‰ใ€้”™่ฏฏๅŽป้‡ใ€‚ๅ…จ้ƒจ้€ป่พ‘ โ‰ค150 ่กŒใ€‚ +- ๆœๅŠก็ซฏ่ฟ›็จ‹ๅ†…ๅค็”จ๏ผš`createInternalAdapter(c)` ๆณจๅ…ฅ adapter๏ผŒ`internal.ts` ้›ถ็ฝ‘็ปœ็›ด่ฐƒ handler๏ผˆunify/ubean-test ๅŒๆฌพ๏ผ‰ใ€‚ + +### 5.5 ้‰ดๆƒๆต็จ‹ + +1. `POST /api/auth/login`๏ผˆvalibot ๆ ก้ชŒ + ้ชŒ่ฏ็ ๏ผ‰โ†’ ่ฟ”ๅ›ž `{ accessToken, refreshToken }` โ†’ `localStg` ๅญ˜ๅ‚จใ€‚ +2. ๅฎˆๅซ้ฆ–ๆฌก่ฟ›ๅ…ฅ๏ผš`GET /api/auth/user-info` + `GET /api/role/user` + `GET /api/menu/user` โ†’ ๅˆๅง‹ๅŒ–่ทฏ็”ฑ/่œๅ•/้กต็ญพใ€‚ +3. ็™ปๅ‡บ๏ผš่ฐƒ `POST /api/auth/logout`๏ผˆๆ‹‰้ป‘ refreshToken๏ผ‰+ ๅ‰็ซฏ resetStore๏ผˆไฟฎๅค unify ๆœช่ฐƒๅŽ็ซฏ็š„้—ฎ้ข˜๏ผ‰ใ€‚ +4. ๅˆทๆ–ฐ้“พ่ทฏ๏ผš่ฟ‡ๆœŸ็  โ†’ ๅ•้ฃž `refreshTokenPromise` โ†’ ้‡ๆ”พ้˜Ÿๅˆ— โ†’ ๅคฑ่ดฅๅˆ™็™ปๅ‡บใ€‚ + +### 5.6 ไธป้ข˜็ณป็ปŸ + +- theme store `settings`๏ผˆไฟ็•™ v2 ๅ…จ้‡้…็ฝฎ้กน่ฏญไน‰๏ผ‰โ†’ ๅŒ่ทฏ่พ“ๅ‡บ๏ผšโ‘  `ThemeOptions`๏ผˆbase/primary/radius/preset/overrides๏ผ‰โ†’ `createTheme` CSS ๅ˜้‡๏ผ›โ‘ก `layoutProps` โ†’ SLayout/SSplitNavใ€‚ +- ๆš—่‰ฒ๏ผš`useColorMode` โ†’ `SConfigProvider` dark selector `.dark`๏ผ›SSR ้˜ฒ้—ช็ƒ่„šๆœฌๆš‚ไธ้œ€่ฆ๏ผˆๆ—  SSR๏ผ‰๏ผŒ้ฆ–ๅธง้˜ฒ้—ช็ƒๆฒฟ็”จ `plugins/loading.ts` ๆ€่ทฏใ€‚ +- ไธป้ข˜ๆŠฝๅฑ‰๏ผš`SThemeCustomizer` ๆ‰ฟๆ‹…ๅค–่ง‚ Tab๏ผ›ๅธƒๅฑ€/้€š็”จ/ๆ“ไฝœไธ‰ไธช Tab ๅบ”็”จๅฑ‚ๅฎž็Žฐ๏ผˆ้…็ฝฎ้กนๆธ…ๅ•็…งๆฌ v2 theme-drawer๏ผ‰ใ€‚ + +### 5.7 ่กจๆ ผไธŽ CRUD ่Œƒๅผ + +`usePaginatedTable`๏ผˆsoybean-ui ๅ†…็ฝฎ๏ผ‰+ `useTableOperate`๏ผˆๅบ”็”จ composable๏ผ‰+ `defaultTableTransform`๏ผš + +- `STable` ๅˆ—ๆจกๅž‹๏ผˆ`TableColumnType: 'index'|'selection'|'expand'` + sortable + virtual๏ผ‰ๆ›ฟไปฃ `NaiveUI.TableColumn`ใ€‚ +- v2 `table-column-setting`๏ผˆๅˆ—ๅ‹พ้€‰/ๅ›บๅฎš/ๆ‹–ๆ‹ฝๆŽ’ๅบ๏ผ‰ใ€`table-header-operation` ็”จ `SDropdownMenu` + `vue-draggable-plus` ้‡ๅ†™๏ผŒไฟ็•™ไธบๅ…จๅฑ€็ป„ไปถใ€‚ +- ๆ ก้ชŒ๏ผš`SForm` + StandardSchema๏ผˆ็›ดๆŽฅๅค็”จ `src/schema/` ็š„ valibot schema๏ผŒๅ‰ๅŽ็ซฏๅŒไธ€ไปฝๆ ก้ชŒ่ง„ๅˆ™๏ผ‰ใ€‚ + +### 5.8 ็ฌฌไธ‰ๆ–น้›†ๆˆ้กตไพ่ต– + +ไฟ็•™ v2 example ๅ…จ้ƒจๆผ”็คบ้กต๏ผŒไพ่ต–ๆ˜ ๅฐ„๏ผšechartsใ€@antv/g2ใ€@antv/g6ใ€vchartใ€wangeditorใ€vditorใ€xlsxใ€vue-pdf-embedใ€print-jsใ€jsbarcodeใ€pinyin-proใ€xgplayerใ€@fawmi/vue-google-maps๏ผˆๆˆ–ๅ›ฝๅ†…ไธ‰ๅ›พ JS SDK ็›ด่ฟž๏ผ‰ใ€swiperใ€typeitใ€vtableใ€dhtmlx-ganttใ€‚ๅ…จ้ƒจ็ป `definePage` ๆ‡’ๅŠ ่ฝฝ้š”็ฆปๅœจ `pages/plugin/**`๏ผŒไธ่ฟ›ไธปๅŒ…ไฝ“็งฏ๏ผˆ้…ๅˆ bundle ๅŸบ็บฟๅ›žๅฝ’๏ผ‰ใ€‚ + +### 5.9 ๅฟ…้กปไฟ็•™็š„"้šๅฝข่ƒฝๅŠ›"ๆธ…ๅ•๏ผˆๅ›žๅฝ’ๆต‹่ฏ•็”จไพ‹ๆฅๆบ๏ผ‰ + +็‰ˆๆœฌๆ›ดๆ–ฐๆฃ€ๆต‹ใ€่ทจ็”จๆˆท็™ปๅฝ•ๆธ…้กต็ญพใ€ไธป้ข˜่ฎพ็ฝฎ็”Ÿไบง็ผ“ๅญ˜ + BUILD_TIME ็‰ˆๆœฌ่ฆ†็›–ใ€้”™่ฏฏๆถˆๆฏๅŽป้‡ใ€token ๅˆทๆ–ฐๅนถๅ‘ๅŽป้‡ใ€็งปๅŠจ็ซฏๅธƒๅฑ€ๅค‡ไปฝๆขๅคใ€ๆฐดๅฐๅฎšๆ—ถๅ™จๆŒ‰้œ€่ฟ่กŒใ€้กต็ญพๆปš่ฝฎๆจชๆปš/ไธญ้”ฎๅ…ณ้—ญใ€`isDev` ่ทฏ็”ฑใ€`activeMenu`/`multiTab`/`fixedIndexInTab`/`href` meta ่ฏญไน‰ใ€้ฆ–ๅฑ loading ไธป้ข˜่‰ฒ่”ๅŠจใ€็™ปๅฝ•ๆˆๅŠŸ notificationใ€‚ + +### 5.10 env ๅ˜้‡็ฒพ็ฎ€ + +``` +VITE_APP_TITLE / VITE_APP_DESC +VITE_AUTH_ROUTE_MODE=static|dynamic VITE_ROUTE_HOME +VITE_ROUTER_HISTORY_MODE=hash|history +VITE_SERVICE_SUCCESS_CODE / VITE_SERVICE_LOGOUT_CODES / VITE_SERVICE_MODAL_LOGOUT_CODES / VITE_SERVICE_EXPIRED_TOKEN_CODES +VITE_STATIC_SUPER_ROLE +VITE_STORAGE_PREFIX +VITE_API_ENV / VITE_API_PROXY VITE_THEME_PRESET +VITE_AUTOMATICALLY_DETECT_UPDATE +``` + +ๅˆ ้™ค๏ผšnaive ็›ธๅ…ณใ€mock baseURLใ€ICONIFY_URL๏ผˆๆ”น `@ubean/icon` ้…็ฝฎ๏ผ‰ใ€SOURCE_MAP๏ผˆubean build ้…็ฝฎๆ‰ฟ่ฝฝ๏ผ‰ใ€PROXY_LOG๏ผˆrouteRules ๆ‰ฟ่ฝฝ๏ผ‰ใ€‚ + +--- + +## 6. ๅŽ็ซฏ่ฏฆ็ป†่ฎพ่ฎก๏ผˆไป… main ๅˆ†ๆ”ฏ๏ผ‰ + +### 6.1 ๅˆ†ๅฑ‚ + +``` +src/routes/api/** ๆ–‡ไปถๅผ API ่ทฏ็”ฑ๏ผšdefineHandler(defineHandlerMeta, describeRoute, validator('json', schema), handler) +src/server.ts defineServer๏ผšๅ…จๅฑ€ hooks๏ผˆrequestIdใ€ๆ—ฅๅฟ—ใ€้”™่ฏฏๆ˜ ๅฐ„๏ผ‰ +src/middleware/ 01.auth.ts๏ผšBearer โ†’ verifyToken๏ผˆ้ป‘ๅๅ•+็”จๆˆท็Šถๆ€๏ผ‰โ†’ ๆƒ้™็ ๅŒน้… โ†’ checkUserHasPermission +src/services/ ็บฏๅ‡ฝๆ•ฐ/composable ไธšๅŠกๅฑ‚๏ผˆๆฏ่ต„ๆบ *.service.ts๏ผ‰ +src/db/ Drizzle schema๏ผˆ็งปๆค unify 13 ่กจ๏ผšuser/role/permission/api/menu/org/dict/dict-item + 4 ๅ…ณ็ณป่กจ๏ผ‰+ ่ฟ็งป + seed +src/schema/ Valibot DTO๏ผˆๅ‰ๅŽ็ซฏๆบ็ ็›ดๆถˆ่ดน๏ผŒไธๆž„ๅปบ๏ผ‰ +``` + +- ็ปŸไธ€ๅ“ๅบ”๏ผš`response` ไธญ้—ดไปถ้‡ๅ†™ `c.json` โ†’ `{ code:'0000', message, data }`๏ผ›้”™่ฏฏ `AppError` + error ไธญ้—ดไปถๅŒๆž„่พ“ๅ‡บ๏ผˆๅซไธšๅŠก็ ๏ผš8888 ็™ปๅ‡บ / 7777 ๅผน็ช—็™ปๅ‡บ / 9999 ่ฟ‡ๆœŸ๏ผŒๆ”ฏๆ’‘ๅ‰็ซฏๆผ”็คบ้กต๏ผ‰ใ€‚ +- OpenAPI๏ผšdev ่‡ชๅŠจๆŒ‚ `/_openapi.json` + `/_scalar`๏ผŒๆ›ฟไปฃ v2 "ๆ‰‹ๅ†™ๆŽฅๅฃๆ–‡ๆกฃ็ป™ๅŽ็ซฏๅŒๅญฆ"็š„็—›็‚นใ€‚ +- ๆ•ฐๆฎๆจกๅž‹็บฆๅฎšๆฒฟ็”จ unify๏ผšUUID v7 ๅญ—็ฌฆไธฒไธป้”ฎใ€`enabled: 'Y'|'N'|'D'` ่ฝฏๅˆ ใ€ๅฎก่ฎกๅ››ๅญ—ๆฎตใ€‚ + +### 6.2 ๆ•ฐๆฎๅบ“ไธŽ้ฉฑๅŠจ + +- dev๏ผš`defineDatabase` ้ป˜่ฎค SQLite ๆ–‡ไปถ๏ผˆbetter-sqlite3 connector๏ผ‰๏ผŒๅผ€็ฎฑ่ท‘้€š + seed ๆผ”็คบๆ•ฐๆฎใ€‚ +- prod๏ผšPostgres๏ผˆpostgres-js๏ผ‰๏ผ›cloudflare ้€‚้…็‰ˆ๏ผˆD1๏ผ‰ไฝœไธบๅฏ้€‰็คบไพ‹็›ฎๅฝ•๏ผˆๅ‚่€ƒ unify `apps/cloudflare`๏ผŒv3.2 ๅ†่ฏ„ไผฐ๏ผ‰ใ€‚ +- ้ฃŽ้™ฉ๏ผšubean DB ๅŽŸ่ฏญ้ป˜่ฎคๅ†…ๅญ˜ๅฎž็Žฐ๏ผŒๅฟ…้กปๆ˜พๅผๆŽฅ็บฟ db0 connector ๅนถๅœจๆ–‡ๆกฃๅผบ่ฐƒ๏ผˆroadmap ่‡ช่ฟฐ้ฃŽ้™ฉ๏ผ‰ใ€‚ + +### 6.3 ๆŽฅๅฃๆธ…ๅ•๏ผˆๅฏน้ฝ v2 mock + unify๏ผ‰ + +| ๆจกๅ— | ็ซฏ็‚น | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| auth | `POST /login`๏ผˆpwd/captcha ๅŒ็ฑปๅž‹๏ผ‰ใ€`POST /register`ใ€`POST /token/refresh`ใ€`POST /logout`ใ€`GET /user-info`ใ€`POST /error`๏ผˆๆผ”็คบ็ ๏ผ‰ใ€`GET /captcha` | +| menu | `GET /public`ใ€`GET /user`ใ€`POST /exist-path`ใ€CRUD + tree | +| role | CRUD + `GET /user`๏ผˆ่ง’่‰ฒ็ ๏ผ‰+ ่œๅ•/ๆŒ‰้’ฎๆƒ้™ๅˆ†้… | +| user | CRUD + ็Šถๆ€ + ๅˆ†้…่ง’่‰ฒ | +| permission / api / org / dict / dict-item | CRUD๏ผˆunify 8 ๅฅ—่Œƒๅผ๏ผ‰ | + +### 6.4 ้‰ดๆƒๆ–นๆกˆๅ–่ˆ + +- **้ฆ–็‰ˆ**๏ผšunify ๅผ่‡ช็ ” JWT๏ผˆhono/jwt + bcryptjs + ้ป‘ๅๅ•็ป `useCacheStore`๏ผŒdev ๅ†…ๅญ˜ / prod Redis driver๏ผ‰โ€”โ€”่กŒไธบๅฎŒๅ…จๅฏๆŽง๏ผŒไธŽ v2 ไธšๅŠก็ ่ฏญไน‰ๆ— ็ผใ€‚ +- **ๆผ”่ฟ›**๏ผš`@ubean/auth`๏ผˆBetter Auth๏ผ‰ไฝœไธบ v3.x ๅฏ้€‰ๆจกๅ—่ฏ„ไผฐๅˆ‡ๆข๏ผˆsession/ๅคš provider ่ƒฝๅŠ›ๆ›ดๅผบ๏ผ‰๏ผŒ้€š่ฟ‡ `defineHandlerMeta({ requiresAuth })` + `useAuth()` ๅ‰็ซฏไฝ“้ชŒ็ปŸไธ€๏ผŒๅˆ‡ๆขๆˆๆœฌๆ”ถๆ•›ๅœจ auth ๆจกๅ—ใ€‚ + +### 6.5 ๆœๅŠก็ซฏ่ƒฝๅŠ›ๅˆฉ็”จ๏ผˆubean ๅทฎๅผ‚ๅŒ–ไปทๅ€ผ๏ผ‰ + +`defineQueue`๏ผˆๅฏผๅ‡บไปปๅŠกๅผ‚ๆญฅๅŒ–๏ผ‰ใ€`defineScheduled`๏ผˆๅญ—ๅ…ธ็ผ“ๅญ˜ๅˆทๆ–ฐ๏ผ‰ใ€`defineWebSocket`/`createSSEStream`๏ผˆ้€š็Ÿฅไธญๅฟƒๆผ”็คบ้กต๏ผ‰ใ€`useRateLimit`๏ผˆ็™ปๅฝ•้˜ฒ็ˆ†็ ด๏ผ‰ใ€`useCacheStore`๏ผˆ้ชŒ่ฏ็ /ๅญ—ๅ…ธ็ผ“ๅญ˜๏ผ‰ใ€`routeRules`๏ผˆdev ไปฃ็†ใ€prod CDN headers๏ผ‰ใ€‚่ฟ™ไบ›ไฝœไธบ v3.1+ ๅขžๅ€ผๆผ”็คบ๏ผŒไธ่ฟ›้ฆ–็‰ˆๅ…ณ้”ฎ่ทฏๅพ„ใ€‚ + +--- + +## 7. ๆžถๅญ + ็ฌฌไธ‰ๆ–น UI ๆ ทๅผ้€‚้…๏ผˆ่ฆๆฑ‚ 7๏ผ‰ + +### 7.1 ๆžถๆž„ๅŽŸๅˆ™๏ผšๆžถๅญ 100% ๅŸบไบŽ soybean-ui๏ผŒ้€‚้…ๅชๅš"ๆ ทๅผๅฏน้ฝ" + +v3 ็š„ๆžถๅญๅฑ‚๏ผˆlayout/menu/tabs/theme store/router/store/request๏ผ‰ๅ…จ้ƒจๆž„ๅปบๅœจ `@soybeanjs/ui` + `@soybeanjs/headless` ไน‹ไธŠ๏ผŒ**ไธๅญ˜ๅœจ"ๆข UI ๅบ“่ท‘ๅŒไธ€ๅฅ—ๆžถๅญ"็š„่ฝฌๅ‘ๅฑ‚**ใ€‚ๅฝ“ไธšๅŠก้กต้œ€่ฆๅผ•ๅ…ฅ็ฌฌไธ‰ๆ–น็ป„ไปถๆ—ถ๏ผˆnaive-ui / element-plus / ant-design-vueโ€”โ€”ๆ— ่ฎบๅ›  soybean-ui ็ป„ไปถ็ผบๅฃ๏ผŒ่ฟ˜ๆ˜ฏๅ›  v2 ่ฟ็งป็”จๆˆทๅญ˜้‡ไปฃ็ ๏ผ‰๏ผŒ้€‚้…ๅ™จ็š„่Œ่ดฃๆ˜ฏ๏ผš**่ฎฉ็ฌฌไธ‰ๆ–น็ป„ไปถ้€‚้… soybean-ui ็š„ๆ ทๅผไฝ“็ณป๏ผˆ้ขœ่‰ฒใ€ๅœ†่ง’ใ€ๅญ—ๅทใ€ๆ˜Žๆš—็ญ‰่ฎพ่ฎก token๏ผ‰๏ผŒไฟ่ฏๅŒไธ€้กต้ข่ง†่ง‰ไธ€่‡ด**ใ€‚ + +### 7.2 ้€‚้…ๅ™จๅœจ็‹ฌ็ซ‹ไป“ๅบ“ๅผ€ๅ‘๏ผŒไธ่ฟ›ๆœฌไป“ๅบ“ + +- ็‹ฌ็ซ‹ไป“ๅบ“๏ผˆๅปบ่ฎฎๅ‘ฝๅ `soybean-ui-adapters`๏ผ‰ๆ‰ฟ่ฝฝๅ…จ้ƒจ้€‚้…ๅฎž็Žฐ๏ผŒsoybean-admin ๅชไฟ็•™ไธคไธชๅฏนๆŽฅ็‚น๏ผš + 1. **token ่พ“ๅ‡บ**๏ผštheme store ๅฐ†็”Ÿๆ•ˆ็š„ `@soybeanjs/theme` ๅ˜้‡๏ผˆ`--primary`/`--radius`/`--background`/`--sidebar-*`โ€ฆ๏ผŒlight/dark ๅŒไปฝ๏ผ‰ไปฅ็ป“ๆž„ๅŒ–ๅฏน่ฑกๆšด้œฒ๏ผˆ`useThemeTokens()`๏ผ‰๏ผŒๅนถๆดพๅ‘ `soybean:theme-change` ไบ‹ไปถ๏ผ› + 2. **ๆŒ‚่ฝฝ็บฆๅฎš**๏ผš็”จๆˆทๅœจ `app.vue`๏ผˆappRoot๏ผ‰ๆŒ‰้œ€ๆณจๅ†Œ้€‚้…ๅ™จ๏ผŒๅฆ‚ `setupNaiveAdapter()`ใ€‚ +- ้€‚้…ๅŽŸ็†๏ผˆๆŒ‰็›ฎๆ ‡ๅบ“ไธป้ข˜ๆœบๅˆถ้€‰ๆ‹ฉ๏ผ‰๏ผš + - **naive-ui**๏ผš`ThemeSettings โ†’ GlobalThemeOverrides`๏ผˆcommon.primaryColor/borderRadius/้ซ˜ๅบฆๅญ—ๅทโ€ฆ๏ผ‰๏ผŒ็ป `NConfigProvider` ๆณจๅ…ฅโ€”โ€”ๅณ v2 ๅๅ‘่ƒฝๅŠ›๏ผˆnaive overrides ่ทŸ soybean token ่ตฐ๏ผ‰๏ผ› + - **element-plus**๏ผštoken โ†’ `--el-color-primary`๏ผˆๅซ light-3/5/7/8/9ใ€dark-2 ๆดพ็”Ÿๆขฏๅบฆ๏ผ‰ใ€`--el-border-radius-base`ใ€`--el-bg-color` ็ณปๅˆ— CSS ๅ˜้‡็›ดๅ†™๏ผ› + - **ant-design-vue**๏ผštoken โ†’ `ConfigProvider :theme="{ token: { colorPrimary, borderRadius, colorBgContainer... }, algorithm: dark ? theme.darkAlgorithm : defaultAlgorithm }"`ใ€‚ +- ๆดพ็”Ÿ็ฎ—ๆณ•ๅฏน้ฝ๏ผšprimary ๆขฏๅบฆ๏ผˆhover/active/disabled๏ผ‰็ปŸไธ€็”จ `@soybeanjs/colord` ๆŒ‰ soybean-ui ๅŒไธ€่ง„ๅˆ™็”Ÿๆˆ๏ผŒ้ฟๅ…ไธ‰ๅบ“ๅ„่‡ชๆดพ็”Ÿๅฏผ่‡ด่‰ฒๅทฎใ€‚ +- ไบคไป˜ๅฝขๆ€๏ผšๆฏๅบ“ไธ€ไธชๅŒ…๏ผˆ`@soybeanjs/adapter-naive` ็ญ‰๏ผ‰+ ไธ€ไธชๅ…ฑไบซๆ ธๅฟƒ๏ผˆtoken ๆ˜ ๅฐ„่กจ + ่ฎข้˜… `theme-change` ็š„ runtime๏ผ‰๏ผŒ็”ฑ้€‚้…ๅ™จไป“ๅบ“็‹ฌ็ซ‹ๅ‘ๅธƒใ€็‹ฌ็ซ‹็‰ˆๆœฌใ€‚ + +### 7.3 soybean-ui ๅฝ“ๅ‰็ผบๅฃไธŽๅฏน็ญ– + +| ็ผบๅฃ๏ผˆdocs/roadmap P0/P1๏ผ‰ | v3 ๅฏน็ญ– | +| ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Upload | ไผ˜ๅ…ˆ่‡ชๅปบ๏ผˆheadless ๆ— ่ฏฅๅŽŸ่ฏญ๏ผ‰๏ผšๆ–‡ไปถๅˆ—่กจ + ๆ‹–ๆ‹ฝ + ่ฟ›ๅบฆ๏ผŒๆ ทๅผ็”จ S\* ๆ‹ผ่ฃ…๏ผŒไธŠไผ ่ตฐ `@soybeanjs/fetch` ็š„ `createUploadProgressAdapter`๏ผ›่‹ฅ็›ดๆŽฅ็”จ็›ฎๆ ‡ๅบ“ Upload๏ผŒๅˆ™็”ฑ ยง7.2 ้€‚้…ๅ™จๅฏน้ฝๆ ทๅผ | +| Descriptions / TreeSelect / Transfer / Timeline / Statistic / Result / Typography / Image / Space | ๅบ”็”จๅฑ‚ๅฐ็ป„ไปถๅ…ˆ่กŒ๏ผˆ`components/custom/`๏ผ‰๏ผŒๅŒๆญฅๅ‘ soybean-ui ๆ issue/PR๏ผŒๆˆ็†ŸๅŽๆ›ฟๆข | +| ่กจๆ ผ่™šๆ‹ŸๆปšๅŠจๅทฒๆœ‰๏ผˆvirtualizer๏ผ‰๏ผ›ๅ›พ่กจไป… 4 ไปถ | dashboard ๅ›พ่กจ็ปง็ปญ็”จ echarts + `@soybeanjs/chart` container | + +### 7.4 v2 naive/element/antd ็‰ˆ็”จๆˆท่ฟ็งป่ทฏๅพ„ + +1. `pnpm create soybean-admin@v3`๏ผˆๅ…จๆ ˆ็‰ˆ๏ผ‰ๆˆ– clone `lite` ๅˆ†ๆ”ฏ๏ผ› +2. ไธšๅŠก้กตไปŽ `views/` ่ฟๅˆฐ `pages/`๏ผŒ`useNaiveTable` โ†’ `usePaginatedTable`๏ผˆๅˆ—ๆจกๅž‹ๅญ—ๆฎตๆ˜ ๅฐ„่กจ้š่ฟ็งปๆ–‡ๆกฃๆไพ›๏ผ‰๏ผ› +3. ๅญ˜้‡็ฌฌไธ‰ๆ–น็ป„ไปถ้กตๆš‚ไธๆ”นๅ†™ โ†’ ๅฎ‰่ฃ…ๅฏนๅบ”้€‚้…ๅ™จๅŒ…๏ผˆๅฆ‚ `@soybeanjs/adapter-naive`๏ผ‰๏ผŒ็ฌฌไธ‰ๆ–น็ป„ไปถๅณ่‡ชๅŠจ่ทŸ้š soybean-ui ไธป้ข˜๏ผˆๅซๆš—่‰ฒๅˆ‡ๆขไธŽ่‡ชๅฎšไน‰ไธป่‰ฒ/ๅœ†่ง’๏ผ‰๏ผŒ้กต้ข่ง†่ง‰ไธ€่‡ด๏ผ› +4. ้š soybean-ui ็ป„ไปถ่ฆ†็›–ๅบฆๆๅ‡้€้กตๆ›ฟๆขไธบ S\* ็ป„ไปถ๏ผŒๆœ€็ปˆ็งป้™ค้€‚้…ๅ™จโ€”โ€”**ไธ€ๅฅ—ๆžถๅญๆธ่ฟ›ๆ”ถๆ•›**๏ผŒๆ›ฟไปฃ v2 ๆ—ถไปฃ"ๆฏไธช UI ๅบ“ fork ไธ€ไธชไป“ๅบ“"๏ผˆsoybean-admin-element-plus ็ญ‰๏ผ‰็š„็ปดๆŠคๆจกๅผใ€‚ + +--- + +## 8. lite ๅˆ†ๆ”ฏ๏ผˆ@ubean/vue ๅ‰็ซฏ็‰ˆ๏ผ‰่ฏฆ็ป†่ฎพ่ฎก๏ผˆ่ฆๆฑ‚ 8๏ผ‰ + +ๅŸบไบŽ `examples/client-only-spa` ๅฝขๆ€๏ผš + +```ts +// vite.config.ts +import { defineConfig } from 'vite-plus'; +import { ubeanVueVite } from '@ubean/vue/vite'; +export default defineConfig({ plugins: [ubeanVueVite({ markdown: false, head: true })] }); + +// main.ts +import { createApp } from 'vue'; +import { ubeanVue } from '@ubean/vue'; +import routes from 'virtual:ubean-vue-routes'; +createApp(App).use(ubeanVue, { routes }).mount('#app'); +``` + +- ไฟ็•™๏ผšๆ–‡ไปถๅผ่ทฏ็”ฑ + `definePage`ใ€ๅŠจๆ€ๆฎต matcherใ€keep-aliveใ€้กต้ข่ฟ‡ๆธกใ€`/`ใ€‚ +- ็งป้™ค๏ผš`src/routes/`ใ€`src/server.ts`ใ€`ubean.config.ts`ใ€loader/usePage ๆ•ฐๆฎๅฑ‚ใ€`@ubean/i18n`๏ผˆๆ”นๆ‰‹ๅทฅ vue-i18n ่ฃ…้…๏ผŒไธŽ v2 ไธ€่‡ด๏ผ‰ใ€SSR ็›ธๅ…ณไธ€ๅˆ‡ใ€‚ +- ่ฏทๆฑ‚ๅฑ‚๏ผš`toFlatTypedClient` ็š„ `paths` ๆฅ่‡ช็”จๆˆท `openapi-typescript <็”จๆˆทๅŽ็ซฏ openapi.json>` ็”Ÿๆˆ่„šๆœฌ๏ผˆ`pnpm gen:api`๏ผ‰๏ผŒๆ–‡ๆกฃๆไพ› Java/Go/Python/Node ๅ„ๅŽ็ซฏ็š„ๅฅ‘็บฆ็คบไพ‹ใ€‚ +- ้‰ดๆƒ๏ผšๅ‰็ซฏๆต็จ‹ๅŒ main๏ผˆๅฎˆๅซ/ๅญ˜ๅ‚จ/ๆƒ้™่ฟ‡ๆปค๏ผ‰๏ผŒ็™ปๅฝ•ๆŽฅๅฃ่ทฏๅพ„ env ๅฏ้…๏ผˆ`VITE_API_LOGIN_PATH` ็ญ‰๏ผ‰๏ผŒๅ…ผๅฎน้žๆ ‡ๅ‡†ๅ“ๅบ”็ ใ€‚ +- ็ปดๆŠค็บฆๅฎš๏ผšlite ไธๅผ•ๅ…ฅๆก†ๆžถๆœชๆไพ›็š„"ไผชๅ…จๆ ˆ"่ƒฝๅŠ›๏ผ›ไธคๅˆ†ๆ”ฏๅทฎๅผ‚ๆธ…ๅ•ๅ›บๅŒ–ๅœจ `docs/lite-delta.md`๏ผŒCI ๆ ก้ชŒ mainโ†’lite merge ๅช่งฆ็ขฐ 5 ไธชๅทฎๅผ‚ๆ–‡ไปถใ€‚ + +--- + +## 9. ๅฎžๆ–ฝ่ทฏ็บฟๅ›พ + +### Phase 0 โ€” ๅœฐๅŸบ๏ผˆ่„šๆ‰‹ๆžถไธŽๅทฅๅ…ท้“พ๏ผ‰ + +- ๅ•ๅŒ…ๅทฅ็จ‹้‡ๅปบ๏ผšๆ น็›ฎๅฝ•ๅนณ้“บ๏ผˆ`ubean.config.ts` + `vite.config.ts` + `src/`๏ผ‰๏ผŒvite-plus ๆŽฅ็ฎก dev/lint/fmt/test๏ผŒ`@soybeanjs/cli`๏ผˆ`soy`๏ผ‰ๆŽฅ็ฎก commit/changelog/release/cleanup +- ubean ้ชจๆžถ่ท‘้€š๏ผš`ubean.config.ts`๏ผˆfullstack/ssr:false/ui/icon/pinia/i18n๏ผ‰+ `pages/` ่ทฏ็”ฑ + `app.ts` ๅฎˆๅซๆณจๅ…ฅ +- `src/schema/` ๅปบ็ซ‹๏ผ›`.ubean/openapi.d.ts` codegen ้“พ่ทฏ + `@soybeanjs/fetch` typed client ๆ‰“้€š +- CI๏ผštypecheck + build + bundle ๅŸบ็บฟ + +### Phase 1 โ€” ๅŽ็ซฏๅ†…ๆ ธ + +- Drizzle 13 ่กจ + ่ฟ็งป + seed๏ผˆunify ็งปๆค๏ผ‰ +- auth ๆจกๅ—๏ผˆๅŒ token + ้ป‘ๅๅ• + ้ชŒ่ฏ็ ๏ผ‰+ ็ปŸไธ€ๅ“ๅบ”/้”™่ฏฏ/ไธšๅŠก็  +- menu/role/user/permission/api/org/dict/dict-item 8 ๅฅ— CRUD + RBAC ไธญ้—ดไปถ +- Scalar ๆ–‡ๆกฃ + ้›†ๆˆๆต‹่ฏ•๏ผˆubean-test 38 ไพ‹่Œƒๅผ๏ผŒ็›ฎๆ ‡ โ‰ฅ40 ไพ‹๏ผ‰ + +### Phase 2 โ€” ๅ‰็ซฏๆžถๅญ + +- SLayout + SSplitNav 6 ๅธƒๅฑ€ๆจกๅผๆ˜ ๅฐ„ + theme store/layoutProps +- ่œๅ•้ฉฑๅŠจ่ทฏ็”ฑ/้กต็ญพ/ๆ ‡้ข˜ไธ€ๅ…ƒๆจกๅž‹๏ผˆstatic + dynamic ๅŒๆจกๅผ๏ผ‰ +- SPageTabs ๅ…จ้‡้กต็ญพ่ƒฝๅŠ› + keep-alive + reload + ๅคšๆ ‡็ญพ +- ่ฏทๆฑ‚ๅฑ‚๏ผˆๅซ token ๅˆทๆ–ฐ้‡ๆ”พ๏ผ‰+ ็™ปๅฝ•/ๆณจๅ†Œ/้ชŒ่ฏ็ ๅ…จๆต็จ‹ +- i18n ๅŒ่ฏญ + ๆš—่‰ฒ + ไธป้ข˜ๆŠฝๅฑ‰ + ๅ…จๅฑ€ๆœ็ดข + ๆฐดๅฐ + ็งปๅŠจ็ซฏ้€‚้… + +### Phase 3 โ€” ไธšๅŠก้กต้ข + +- dashboard ้ฆ–้กต๏ผˆ็ปŸ่ฎกๅก/ๅ›พ่กจ/ๅŠจๆ€๏ผ‰ +- manage 8 ๅฅ— CRUD ้กต๏ผˆ่กจๆ ผ่Œƒๅผ + ๅˆ—่ฎพ็ฝฎ + ๅผน็ช—่กจๅ•๏ผ‰ +- function ๆผ”็คบ้กตๅ…จ้‡๏ผˆๅซๆŒ‰้’ฎ็บงๆƒ้™ v-auth + ่ง’่‰ฒๅˆ†้…ๅผน็ช—๏ผ‰ +- 403/404/500/iframe/about/profile/settings + +### Phase 4 โ€” ๆ’ไปถๆผ”็คบไธŽๅขžๅ€ผ + +- plugin ๆผ”็คบ้กต๏ผˆeditor/excel/pdf/charts/video/map/gantt...๏ผ‰ +- ็‰ˆๆœฌๆ›ดๆ–ฐๆฃ€ๆต‹ใ€้”™่ฏฏๅŽป้‡็ญ‰้šๅฝข่ƒฝๅŠ›ๅ›žๅฝ’ๆต‹่ฏ• +- lite ๅˆ†ๆ”ฏๅˆ‡ๅ‡บไธŽๅทฎๅผ‚ๅ›บๅŒ– + +### Phase 5 โ€” ้€‚้…ๅฏนๆŽฅไธŽๅ‘ๅธƒ + +- ไธŽ็‹ฌ็ซ‹้€‚้…ๅ™จไป“ๅบ“๏ผˆsoybean-ui-adapters๏ผ‰่”่ฐƒ๏ผš`useThemeTokens()` + `soybean:theme-change` ๅฏนๆŽฅ็บฆๅฎš่ฝๅœฐ๏ผŒnaive/element/antd ๆ ทๅผ้€‚้…้ชŒ่ฏ +- ็ผบๅฃ็ป„ไปถ๏ผˆUpload ็ญ‰๏ผ‰่กฅ้ฝๆˆ–ๆกฅๆŽฅ +- ๆ–‡ๆกฃ็ซ™ๆ›ดๆ–ฐ๏ผˆv3 ๆŒ‡ๅ— + ่ฟ็งปๆŒ‡ๅ— + API ๅฅ‘็บฆๆ–‡ๆกฃ๏ผ‰+ ไธญ่‹ฑ README ้‡ๅ†™ +- beta๏ผˆv3.0.0-beta.x ๅฏน้ฝ ubean 0.4.x ้”ๅฎš็‰ˆๆœฌ๏ผ‰โ†’ rc โ†’ stable + +### ๅˆ†ๆ”ฏไธŽ็‰ˆๆœฌ่ฎกๅˆ’ + +``` +main v3.0.0-beta โ†’ rc โ†’ stable๏ผˆๅ…จๆ ˆ็‰ˆ๏ผ‰ +lite ไปŽ main Phase 2 ๅฎŒๆˆๅŽๅˆ‡ๅ‡บ๏ผŒ่ทŸ้š main ้‡Œ็จ‹็ข‘ๅŒๆญฅ +v2.x ็ปดๆŠคๅˆ†ๆ”ฏไป…ๆ”ถๅฎ‰ๅ…จ/ไธฅ้‡ bug๏ผŒREADME ็ฝฎ้กถ่ฟ็งปๆŒ‡ๅผ• +``` + +--- + +## 10. ้ฃŽ้™ฉไธŽๅฏน็ญ– + +| ้ฃŽ้™ฉ | ็ญ‰็บง | ๅฏน็ญ– | +| --------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------- | +| ubean 0.4.x API ็ ดๅๆ€งๅ˜ๆ›ด๏ผˆ0.3โ†’0.4 ๅˆš้‡ๆž„ๅ…ฅๅฃ๏ผ‰ | ้ซ˜ | lock ็ฒพ็กฎ็‰ˆๆœฌ๏ผ›ๅฐ่ฃ… `src/shared/ubean-compat.ts` ้š”็ฆป้ซ˜้ข‘ API๏ผ›ๆฏๅ‡็บงไธ€ไธช ubean minor ่ท‘ๅ…จ้‡ e2e๏ผ›v3.0 stable ๅ‰ไธ่ฟฝๆ–ฐ | +| ubean "ๅฃฐๆ˜ŽๅฎฝไบŽ้ป˜่ฎค"๏ผˆDB/queue ้ป˜่ฎคๅ†…ๅญ˜ๅฎž็Žฐ๏ผ‰ | ้ซ˜ | ้ฆ–็‰ˆๅŽ็ซฏๅชไพ่ต–ๅทฒ้ชŒ่ฏๅญ้›†๏ผˆHono ่ทฏ็”ฑ/ไธญ้—ดไปถ/cache/db0 ๆ˜พๅผๆŽฅ็บฟ๏ผ‰๏ผ›็”Ÿไบง้ƒจ็ฝฒๆ–‡ๆกฃๅผบๅˆถ connector ้…็ฝฎๆฃ€ๆŸฅ | +| soybean-ui ็ผบๅฃ็ป„ไปถ๏ผˆUpload/Descriptions ็ญ‰ 22 ไธช P0/P1๏ผ‰ | ไธญ | ยง7.3 ๆธ…ๅ•ๅŒ–๏ผ›็ผบๅฃ็ป„ไปถๅบ”็”จๅฑ‚ๅ…ˆ่กŒ + ๅ‘ ui ไป“ๅบ“ๆ PR ๅŒๅ‘ๆ”ถๆ•› | +| `@soybeanjs/admin`๏ผˆorigin/admin ๅˆ†ๆ”ฏ๏ผ‰ไธŽๆ‰‹ๅ†™ๆžถๅญไบŒ้€‰ไธ€ | ไธญ | ๆžถๅญ API ้ขๅ‘ `SAppLayout` ๅ‘ฝๅ่ฎพ่ฎก๏ผ›่ฏฅๅˆ†ๆ”ฏ M2 ้ชŒๆ”ถ้€š่ฟ‡ๅˆ™็›ดๆŽฅๆ›ฟๆข๏ผŒๅฆๅˆ™ๅบ”็”จๅฑ‚็ป„ๅˆ SLayout+SSplitNav ไบคไป˜ | +| vite-plus ๅทฅๅ…ท้“พ๏ผˆoxlint/oxfmt๏ผ‰ๅ›ข้˜ŸไธŽ็คพๅŒบๆŽฅๅ—ๅบฆ | ไธญ | ไฟ็•™ eslint ๅ…ผๅฎน้…็ฝฎๅฏผๅ‡บ๏ผ›CI ๅŒ่ท‘ไธ€ไธช Phase ๅŽๆ”ถๆ•› | +| 6 ๅธƒๅฑ€ๆจกๅผๆ˜ ๅฐ„ SplitNav 4 mode ๅญ˜ๅœจ็ป„ๅˆ็›ฒๅŒบ | ไธญ | Phase 2 ็ฌฌไธ€ๅ‘จไบงๅ‡บ"ๆจกๅผ ร— ้…็ฝฎ้กน"็Ÿฉ้˜ต PoC๏ผŒ็›ฒๅŒบ้กนๆๅ‰็”จ Teleport + props ็ป„ๅˆ่งฃๅ†ณๆˆ–้™็บงไธบ 5 ๆจกๅผ | +| lite/main ๅŒๅˆ†ๆ”ฏๆผ‚็งป | ไธญ | ๅทฎๅผ‚ๆ–‡ไปถๆธ…ๅ• CI ๆ ก้ชŒ๏ผ›ๆฏๅญฃๅบฆ mainโ†’lite ๅ•ๅ‘ๅŒๆญฅ | +| ๅŠŸ่ƒฝๅ›žๅฝ’้—ๆผ๏ผˆ้šๅฝข่ƒฝๅŠ›๏ผ‰ | ไธญ | ยง5.9 ๆธ…ๅ•่ฝฌ e2e ็”จไพ‹๏ผˆPlaywright๏ผŒubean vitest 5 ๆต่งˆๅ™จๆจกๅผ๏ผ‰ | +| ไธŽ v2 ็”จๆˆท่ฟ็งปๆ–ญๅฑ‚ | ไฝŽ | ็ฌฌไธ‰ๆ–น็ป„ไปถๆ ทๅผ้€‚้…ๅ™จ๏ผˆ็‹ฌ็ซ‹ไป“ๅบ“๏ผ‰+ ่ฟ็งปๆŒ‡ๅ— + ๅญ—ๆฎตๆ˜ ๅฐ„่กจ๏ผˆยง7.4๏ผ‰ | + +--- + +## 11. ้™„ๅฝ• + +### 11.1 ๅ…ณ้”ฎๅ‚่€ƒ่ทฏๅพ„ + +- ubean ๅ…จๆ ˆ้ชจๆžถ๏ผš`/Users/soybean/Web/Projects/SoybeanJS/ubean/examples/ubean-test/`๏ผˆ38 ้›†ๆˆๆต‹่ฏ•๏ผ‰ +- ็บฏๅ‰็ซฏ SPA ๅฝขๆ€๏ผš`ubean/examples/client-only-spa/` +- ๅŽ็ซฏ่Œƒๅผ๏ผš`/Users/soybean/Web/Projects/SoybeanJS/soybean-unify/apps/server/src/`๏ผˆroutes/services/db/middleware๏ผ‰ +- ๅ‰็ซฏ็ฎ€็บฆ่Œƒๅผ๏ผš`soybean-unify/apps/admin/src/`๏ผˆstore 6 ไธชใ€service ~80 ่กŒใ€layouts/base ็”จ SLayout๏ผ‰ +- ๅธƒๅฑ€็ป„ไปถๆบ็ ๏ผš`soybean-ui/packages/headless/src/components/{layout,split-nav,page-tabs,tree-menu}/` +- ไธป้ข˜ๅผ•ๆ“Ž๏ผš`soybean-ui/packages/theme/src/{variables.ts,index.ts}` +- admin ๅฃณ็ป„ไปถ๏ผˆๅพ…้ชŒๆ”ถ๏ผ‰๏ผš`soybean-ui` ่ฟœ็จ‹ๅˆ†ๆ”ฏ `origin/admin`๏ผˆ`packages/admin/`๏ผŒ`SAppLayout` 6 ๆจกๅผ๏ผ‰ +- ubean API ้€ŸๆŸฅไธŽ้™ท้˜ฑๆธ…ๅ•๏ผš`ubean/AGENTS.md`๏ผˆ18 ๆกๆณจๆ„ไบ‹้กน๏ผ‰ใ€`ubean/skills/ubean/SKILL.md` + +### 11.2 ๆœฏ่ฏญ + +- **ไธ€ๅ…ƒๆ•ฐๆฎๆจกๅž‹**๏ผš่œๅ•่ฎฐๅฝ• = ๅฏผ่ˆชๆก็›ฎ + ่ทฏ็”ฑๅฎšไน‰ + ้กต็ญพๆ ‡้ข˜ๆฅๆบ๏ผˆ`App.Menu = MenuDTO + RouteKey ๆ”ถ็ช„`๏ผ‰๏ผŒๆบ่‡ช unifyใ€‚ +- **ๆ ทๅผ้€‚้…๏ผˆadapter๏ผ‰**๏ผš็‹ฌ็ซ‹ไป“ๅบ“ๅฎž็Žฐ๏ผŒๆŠŠ soybean-ui ็š„่ฎพ่ฎก token๏ผˆ้ขœ่‰ฒ/ๅœ†่ง’/ๆ˜Žๆš—โ€ฆ๏ผ‰ๆ˜ ๅฐ„ๅˆฐ naive๏ผˆthemeOverrides๏ผ‰/ element๏ผˆ`--el-*` ๅ˜้‡๏ผ‰/ antd๏ผˆConfigProvider theme token๏ผ‰๏ผŒไฝฟ็ฌฌไธ‰ๆ–น็ป„ไปถไธŽ S\* ็ป„ไปถๅŒ้กต่ง†่ง‰ไธ€่‡ดใ€‚ +- **ๅฅ‘็บฆๆบ**๏ผš`src/schema/`๏ผˆValibot๏ผ‰ไธบ DTO/ๆ ก้ชŒๅ”ฏไธ€ๆฅๆบ๏ผ›OpenAPI ็”Ÿๆˆไธบ็ฑปๅž‹ๅฎขๆˆท็ซฏๆฅๆบใ€‚ diff --git a/eslint.config.js b/eslint.config.js index 00537e3f36..c47fe5afe3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,24 +1,12 @@ -import { defineConfig } from '@soybeanjs/eslint-config'; +import { defineConfig } from '@soybeanjs/eslint-config-vue'; -export default defineConfig( - { vue: true, unocss: true }, - { - rules: { - 'vue/multi-word-component-names': [ - 'warn', - { - ignores: ['index', 'App', 'Register', '[id]', '[url]'] - } - ], - 'vue/component-name-in-template-casing': [ - 'warn', - 'PascalCase', - { - registeredComponentsOnly: false, - ignores: ['/^icon-/'] - } - ], - 'unocss/order-attributify': 'off' +export default defineConfig({ + 'vue/component-name-in-template-casing': [ + 'warn', + 'PascalCase', + { + registeredComponentsOnly: false, + ignores: ['/^icon-/'] } - } -); + ] +}); diff --git a/index.html b/index.html index b04ac55c10..0ec4a3196b 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/package.json b/package.json index 0b09c785ef..59c060f9cd 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,31 @@ { "name": "soybean-admin", - "type": "module", - "version": "1.3.15", - "description": "A fresh and elegant admin template, based on Vue3ใ€Vite7ใ€TypeScriptใ€NaiveUI and UnoCSS. ไธ€ไธชๅŸบไบŽVue3ใ€Vite7ใ€TypeScriptใ€NaiveUI and UnoCSS็š„ๆธ…ๆ–ฐไผ˜้›…็š„ไธญๅŽๅฐๆจก็‰ˆใ€‚", + "version": "2.2.0", + "description": "A fresh and elegant admin template, based on Vue3ใ€Vite8ใ€TypeScriptใ€NaiveUI and UnoCSS. ไธ€ไธชๅŸบไบŽVue3ใ€Vite8ใ€TypeScriptใ€NaiveUI and UnoCSS็š„ๆธ…ๆ–ฐไผ˜้›…็š„ไธญๅŽๅฐๆจก็‰ˆใ€‚", + "keywords": [ + "TypeScript", + "UnoCSS", + "Vite8", + "Vue3 admin ", + "ant-design-vue v4", + "naive-ui", + "naive-ui-admin", + "vue-admin-template" + ], + "homepage": "https://github.com/soybeanjs/soybean-admin", + "bugs": { + "url": "https://github.com/soybeanjs/soybean-admin/issues" + }, + "license": "MIT", "author": { "name": "Soybean", "email": "soybeanjs@outlook.com", "url": "https://github.com/soybeanjs" }, - "license": "MIT", - "homepage": "https://github.com/soybeanjs/soybean-admin", "repository": { "url": "https://github.com/soybeanjs/soybean-admin.git" }, - "bugs": { - "url": "https://github.com/soybeanjs/soybean-admin/issues" - }, - "keywords": [ - "Vue3 admin ", - "vue-admin-template", - "Vite7", - "TypeScript", - "naive-ui", - "naive-ui-admin", - "ant-design-vue v4", - "UnoCSS" - ], - "engines": { - "node": ">=20.19.0", - "pnpm": ">=10.5.0" - }, + "type": "module", "scripts": { "build": "vite build --mode prod", "build:test": "vite build --mode test", @@ -38,8 +34,9 @@ "commit:zh": "sa git-commit -l=zh-cn", "dev": "vite --mode test", "dev:prod": "vite --mode prod", + "fmt": "oxfmt", "gen-route": "sa gen-route", - "lint": "eslint . --fix", + "lint": "oxlint --fix && eslint --fix .", "prepare": "simple-git-hooks", "preview": "vite preview", "release": "sa release", @@ -48,63 +45,63 @@ }, "dependencies": { "@better-scroll/core": "2.5.1", - "@iconify/vue": "5.0.0", + "@iconify/vue": "5.0.1", "@sa/axios": "workspace:*", "@sa/color": "workspace:*", "@sa/hooks": "workspace:*", "@sa/materials": "workspace:*", "@sa/utils": "workspace:*", - "@vueuse/core": "13.9.0", + "@vueuse/core": "14.3.0", "clipboard": "2.0.11", - "dayjs": "1.11.18", - "defu": "6.1.4", + "dayjs": "1.11.20", + "defu": "6.1.7", "echarts": "6.0.0", "json5": "2.2.3", - "naive-ui": "2.43.1", + "naive-ui": "2.44.1", "nprogress": "0.2.0", - "pinia": "3.0.3", - "tailwind-merge": "3.3.1", - "vue": "3.5.21", - "vue-draggable-plus": "0.6.0", - "vue-i18n": "11.1.12", - "vue-router": "4.5.1" + "pinia": "3.0.4", + "tailwind-merge": "3.6.0", + "vue": "3.5.34", + "vue-draggable-plus": "0.6.1", + "vue-i18n": "11.4.2", + "vue-router": "5.0.7" }, "devDependencies": { "@elegant-router/vue": "0.3.8", - "@iconify/json": "2.2.385", + "@iconify/json": "2.2.472", "@sa/scripts": "workspace:*", "@sa/uno-preset": "workspace:*", - "@soybeanjs/eslint-config": "1.7.1", - "@types/node": "24.5.1", + "@soybeanjs/eslint-config-vue": "^0.0.2", + "@types/node": "25.7.0", "@types/nprogress": "0.2.3", - "@unocss/eslint-config": "66.5.1", - "@unocss/preset-icons": "66.5.1", - "@unocss/preset-uno": "66.5.1", - "@unocss/transformer-directives": "66.5.1", - "@unocss/transformer-variant-group": "66.5.1", - "@unocss/vite": "66.5.1", - "@vitejs/plugin-vue": "6.0.1", - "@vitejs/plugin-vue-jsx": "5.1.1", + "@vitejs/plugin-vue": "6.0.6", + "@vitejs/plugin-vue-jsx": "5.1.5", "consola": "3.4.2", - "eslint": "9.35.0", - "eslint-plugin-vue": "10.4.0", + "eslint": "10.3.0", "kolorist": "1.8.0", - "sass": "1.92.1", + "oxfmt": "^0.49.0", + "oxlint": "^1.64.0", + "sass": "1.99.0", "simple-git-hooks": "2.13.1", - "tsx": "4.20.5", - "typescript": "5.9.2", - "unplugin-icons": "22.3.0", - "unplugin-vue-components": "29.0.0", - "vite": "7.1.5", + "tsx": "4.21.0", + "typescript": "6.0.3", + "unocss": "^66.6.8", + "unplugin-icons": "23.0.1", + "unplugin-vue-components": "32.0.0", + "vite": "8.0.12", "vite-plugin-progress": "0.0.7", "vite-plugin-svg-icons": "2.0.1", - "vite-plugin-vue-devtools": "8.0.2", - "vue-eslint-parser": "10.2.0", - "vue-tsc": "3.0.7" + "vite-plugin-vue-devtools": "8.1.2", + "vite-plugin-vue-transition-root-validator": "^0.1.0", + "vue-tsc": "3.2.8" }, "simple-git-hooks": { "commit-msg": "pnpm sa git-commit-verify", - "pre-commit": "pnpm typecheck && pnpm lint && git diff --exit-code" + "pre-commit": "pnpm typecheck && pnpm lint && pnpm fmt && git diff --exit-code" + }, + "engines": { + "node": ">=20.19.0", + "pnpm": ">=10.5.0" }, "website": "https://admin.soybeanjs.cn" } diff --git a/packages/alova/package.json b/packages/alova/package.json index 1e3bb5f8e2..0a9eb1cd76 100644 --- a/packages/alova/package.json +++ b/packages/alova/package.json @@ -1,20 +1,22 @@ { "name": "@sa/alova", - "version": "1.3.15", + "version": "2.2.0", + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, "exports": { ".": "./src/index.ts", "./fetch": "./src/fetch.ts", "./client": "./src/client.ts", "./mock": "./src/mock.ts" }, - "typesVersions": { - "*": { - "*": ["./src/*"] - } - }, "dependencies": { - "@alova/mock": "2.0.17", + "@alova/mock": "2.0.20", "@sa/utils": "workspace:*", - "alova": "3.3.4" + "alova": "3.5.1" } } diff --git a/packages/alova/tsconfig.json b/packages/alova/tsconfig.json index 5823ed54e6..97b95afd43 100644 --- a/packages/alova/tsconfig.json +++ b/packages/alova/tsconfig.json @@ -3,9 +3,8 @@ "target": "ESNext", "jsx": "preserve", "lib": ["DOM", "ESNext"], - "baseUrl": ".", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "types": ["node"], "strict": true, diff --git a/packages/axios/package.json b/packages/axios/package.json index c173a016d8..f2e7555a6d 100644 --- a/packages/axios/package.json +++ b/packages/axios/package.json @@ -1,21 +1,23 @@ { "name": "@sa/axios", - "version": "1.3.15", - "exports": { - ".": "./src/index.ts" - }, + "version": "2.2.0", "typesVersions": { "*": { - "*": ["./src/*"] + "*": [ + "./src/*" + ] } }, + "exports": { + ".": "./src/index.ts" + }, "dependencies": { "@sa/utils": "workspace:*", - "axios": "1.12.2", + "axios": "1.16.0", "axios-retry": "4.5.0", - "qs": "6.14.0" + "qs": "6.15.1" }, "devDependencies": { - "@types/qs": "6.14.0" + "@types/qs": "6.15.1" } } diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index c29d9d2bd3..92421daf90 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -1,8 +1,9 @@ -import axios, { AxiosError } from 'axios'; +import { create, AxiosError } from 'axios'; import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios'; import axiosRetry from 'axios-retry'; import { nanoid } from '@sa/utils'; import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options'; +import { transformResponse } from './shared'; import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant'; import type { CustomAxiosRequestConfig, @@ -13,14 +14,15 @@ import type { ResponseType } from './type'; -function createCommonRequest( - axiosConfig?: CreateAxiosDefaults, - options?: Partial> -) { - const opts = createDefaultOptions(options); +function createCommonRequest< + ResponseData, + ApiData = ResponseData, + State extends Record = Record +>(axiosConfig?: CreateAxiosDefaults, options?: Partial>) { + const opts = createDefaultOptions(options); const axiosConf = createAxiosConfig(axiosConfig); - const instance = axios.create(axiosConf); + const instance = create(axiosConf); const abortControllerMap = new Map(); @@ -52,6 +54,8 @@ function createCommonRequest( async response => { const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json'; + await transformResponse(response); + if (responseType !== 'json' || opts.isBackendSuccess(response)) { return Promise.resolve(response); } @@ -80,14 +84,6 @@ function createCommonRequest( } ); - function cancelRequest(requestId: string) { - const abortController = abortControllerMap.get(requestId); - if (abortController) { - abortController.abort(); - abortControllerMap.delete(requestId); - } - } - function cancelAllRequest() { abortControllerMap.forEach(abortController => { abortController.abort(); @@ -98,7 +94,6 @@ function createCommonRequest( return { instance, opts, - cancelRequest, cancelAllRequest }; } @@ -109,27 +104,27 @@ function createCommonRequest( * @param axiosConfig axios config * @param options request options */ -export function createRequest>( +export function createRequest>( axiosConfig?: CreateAxiosDefaults, - options?: Partial> + options?: Partial> ) { - const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest(axiosConfig, options); + const { instance, opts, cancelAllRequest } = createCommonRequest(axiosConfig, options); - const request: RequestInstance = async function request( - config: CustomAxiosRequestConfig - ) { + const request: RequestInstance = async function request< + T extends ApiData = ApiData, + R extends ResponseType = 'json' + >(config: CustomAxiosRequestConfig) { const response: AxiosResponse = await instance(config); const responseType = response.config?.responseType || 'json'; if (responseType === 'json') { - return opts.transformBackendResponse(response); + return opts.transform(response); } return response.data as MappedType; - } as RequestInstance; + } as RequestInstance; - request.cancelRequest = cancelRequest; request.cancelAllRequest = cancelAllRequest; request.state = {} as State; @@ -144,14 +139,14 @@ export function createRequest>( +export function createFlatRequest>( axiosConfig?: CreateAxiosDefaults, - options?: Partial> + options?: Partial> ) { - const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest(axiosConfig, options); + const { instance, opts, cancelAllRequest } = createCommonRequest(axiosConfig, options); - const flatRequest: FlatRequestInstance = async function flatRequest< - T = any, + const flatRequest: FlatRequestInstance = async function flatRequest< + T extends ApiData = ApiData, R extends ResponseType = 'json' >(config: CustomAxiosRequestConfig) { try { @@ -160,20 +155,21 @@ export function createFlatRequest, error: null }; + return { data: response.data as MappedType, error: null, response }; } catch (error) { return { data: null, error, response: (error as AxiosError).response }; } - } as FlatRequestInstance; + } as FlatRequestInstance; - flatRequest.cancelRequest = cancelRequest; flatRequest.cancelAllRequest = cancelAllRequest; - flatRequest.state = {} as State; + flatRequest.state = { + ...opts.defaultState + } as State; return flatRequest; } diff --git a/packages/axios/src/options.ts b/packages/axios/src/options.ts index 8b2b116a22..e7866397b4 100644 --- a/packages/axios/src/options.ts +++ b/packages/axios/src/options.ts @@ -4,15 +4,27 @@ import { stringify } from 'qs'; import { isHttpSuccess } from './shared'; import type { RequestOption } from './type'; -export function createDefaultOptions(options?: Partial>) { - const opts: RequestOption = { +export function createDefaultOptions< + ResponseData, + ApiData = ResponseData, + State extends Record = Record +>(options?: Partial>) { + const opts: RequestOption = { + defaultState: {} as State, + transform: async response => response.data as unknown as ApiData, + transformBackendResponse: async response => response.data as unknown as ApiData, onRequest: async config => config, isBackendSuccess: _response => true, onBackendFail: async () => {}, - transformBackendResponse: async response => response.data, onError: async () => {} }; + if (options?.transform) { + opts.transform = options.transform; + } else { + opts.transform = options?.transformBackendResponse || opts.transform; + } + Object.assign(opts, options); return opts; diff --git a/packages/axios/src/shared.ts b/packages/axios/src/shared.ts index 7afd32b1e0..74fd79ad11 100644 --- a/packages/axios/src/shared.ts +++ b/packages/axios/src/shared.ts @@ -1,4 +1,5 @@ import type { AxiosHeaderValue, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import type { ResponseType } from './type'; export function getContentType(config: InternalAxiosRequestConfig) { const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json'; @@ -26,3 +27,53 @@ export function isResponseJson(response: AxiosResponse) { return responseType === 'json' || responseType === undefined; } + +export async function transformResponse(response: AxiosResponse) { + const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json'; + if (responseType === 'json') return; + + const isJson = (response.headers['content-type'] as string)?.includes('application/json'); + if (!isJson) return; + + if (responseType === 'blob') { + await transformBlobToJson(response); + } + + if (responseType === 'arrayBuffer') { + await transformArrayBufferToJson(response); + } +} + +export async function transformBlobToJson(response: AxiosResponse) { + try { + let data = response.data; + + if (typeof data === 'string') { + data = JSON.parse(data); + } + + if (Object.prototype.toString.call(data) === '[object Blob]') { + const json = await data.text(); + data = JSON.parse(json); + } + + response.data = data; + } catch {} +} + +export async function transformArrayBufferToJson(response: AxiosResponse) { + try { + let data = response.data; + + if (typeof data === 'string') { + data = JSON.parse(data); + } + + if (Object.prototype.toString.call(data) === '[object ArrayBuffer]') { + const json = new TextDecoder().decode(data); + data = JSON.parse(json); + } + + response.data = data; + } catch {} +} diff --git a/packages/axios/src/type.ts b/packages/axios/src/type.ts index 644847ff89..0fa6caa5e5 100644 --- a/packages/axios/src/type.ts +++ b/packages/axios/src/type.ts @@ -8,7 +8,30 @@ export type ContentType = | 'application/x-www-form-urlencoded' | 'application/octet-stream'; -export interface RequestOption { +export type ResponseTransform = (input: Input) => Output | Promise; + +export interface RequestOption< + ResponseData, + ApiData = ResponseData, + State extends Record = Record +> { + /** + * The default state + */ + defaultState?: State; + /** + * transform the response data to the api data + * + * @param response Axios response + */ + transform: ResponseTransform, ApiData>; + /** + * transform the response data to the api data + * + * @deprecated use `transform` instead, will be removed in the next major version v3 + * @param response Axios response + */ + transformBackendResponse: ResponseTransform, ApiData>; /** * The hook before request * @@ -35,12 +58,6 @@ export interface RequestOption { response: AxiosResponse, instance: AxiosInstance ) => Promise | Promise; - /** - * transform backend response when the responseType is json - * - * @param response Axios response - */ - transformBackendResponse(response: AxiosResponse): any | Promise; /** * The hook to handle error * @@ -68,15 +85,7 @@ export type CustomAxiosRequestConfig = Omit { - /** - * cancel the request by request id - * - * if the request provide abort controller sign from config, it will not collect in the abort controller map - * - * @param requestId - */ - cancelRequest: (requestId: string) => void; +export interface RequestInstanceCommon> { /** * cancel all request * @@ -84,32 +93,38 @@ export interface RequestInstanceCommon { */ cancelAllRequest: () => void; /** you can set custom state in the request instance */ - state: T; + state: State; } /** The request instance */ -export interface RequestInstance> extends RequestInstanceCommon { - (config: CustomAxiosRequestConfig): Promise>; +export interface RequestInstance> extends RequestInstanceCommon { + ( + config: CustomAxiosRequestConfig + ): Promise>; } -export type FlatResponseSuccessData = { - data: T; +export type FlatResponseSuccessData = { + data: ApiData; error: null; response: AxiosResponse; }; -export type FlatResponseFailData = { +export type FlatResponseFailData = { data: null; error: AxiosError; response: AxiosResponse; }; -export type FlatResponseData = - | FlatResponseSuccessData +export type FlatResponseData = + | FlatResponseSuccessData | FlatResponseFailData; -export interface FlatRequestInstance, ResponseData = any> extends RequestInstanceCommon { - ( +export interface FlatRequestInstance< + ResponseData, + ApiData, + State extends Record +> extends RequestInstanceCommon { + ( config: CustomAxiosRequestConfig - ): Promise, ResponseData>>; + ): Promise>>; } diff --git a/packages/axios/tsconfig.json b/packages/axios/tsconfig.json index 5823ed54e6..97b95afd43 100644 --- a/packages/axios/tsconfig.json +++ b/packages/axios/tsconfig.json @@ -3,9 +3,8 @@ "target": "ESNext", "jsx": "preserve", "lib": ["DOM", "ESNext"], - "baseUrl": ".", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "types": ["node"], "strict": true, diff --git a/packages/color/package.json b/packages/color/package.json index 151c79ceda..323654c38d 100644 --- a/packages/color/package.json +++ b/packages/color/package.json @@ -1,14 +1,16 @@ { "name": "@sa/color", - "version": "1.3.15", - "exports": { - ".": "./src/index.ts" - }, + "version": "2.2.0", "typesVersions": { "*": { - "*": ["./src/*"] + "*": [ + "./src/*" + ] } }, + "exports": { + ".": "./src/index.ts" + }, "dependencies": { "@sa/utils": "workspace:*", "colord": "2.9.3" diff --git a/packages/color/tsconfig.json b/packages/color/tsconfig.json index 5823ed54e6..97b95afd43 100644 --- a/packages/color/tsconfig.json +++ b/packages/color/tsconfig.json @@ -3,9 +3,8 @@ "target": "ESNext", "jsx": "preserve", "lib": ["DOM", "ESNext"], - "baseUrl": ".", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "types": ["node"], "strict": true, diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 598a09362e..28dce29e66 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,14 +1,16 @@ { "name": "@sa/hooks", - "version": "1.3.15", - "exports": { - ".": "./src/index.ts" - }, + "version": "2.2.0", "typesVersions": { "*": { - "*": ["./src/*"] + "*": [ + "./src/*" + ] } }, + "exports": { + ".": "./src/index.ts" + }, "dependencies": { "@sa/axios": "workspace:*", "@sa/utils": "workspace:*" diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index a6a330bd9e..3a73bbc788 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -3,9 +3,7 @@ import useLoading from './use-loading'; import useCountDown from './use-count-down'; import useContext from './use-context'; import useSvgIconRender from './use-svg-icon-render'; -import useHookTable from './use-table'; +import useTable from './use-table'; -export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable }; - -export * from './use-signal'; -export * from './use-table'; +export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useTable }; +export type * from './use-table'; diff --git a/packages/hooks/src/use-context.ts b/packages/hooks/src/use-context.ts index 001d8aa684..08a6a519c1 100644 --- a/packages/hooks/src/use-context.ts +++ b/packages/hooks/src/use-context.ts @@ -1,96 +1,57 @@ import { inject, provide } from 'vue'; -import type { InjectionKey } from 'vue'; -/** - * Use context - * - * @example - * ```ts - * // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue - * - * // context.ts - * import { ref } from 'vue'; - * import { useContext } from '@sa/hooks'; - * - * export const { setupStore, useStore } = useContext('demo', () => { - * const count = ref(0); - * - * function increment() { - * count.value++; - * } - * - * function decrement() { - * count.value--; - * } - * - * return { - * count, - * increment, - * decrement - * }; - * }) - * ``` // A.vue - * ```vue - * - * - * ``` // B.vue - * ```vue - * - * - * ```; - * - * // C.vue is same as B.vue - * - * @param contextName Context name - * @param fn Context function - */ -export default function useContext any>(contextName: string, fn: T) { - type Context = ReturnType; - - const { useProvide, useInject: useStore } = createContext(contextName); +type ContextName = string | { name: string; key: string | symbol }; - function setupStore(...args: Parameters) { - const context: Context = fn(...args); - return useProvide(context); - } +type ContextValue = T extends (...args: any[]) => any ? ReturnType : T; - return { - /** Setup store in the parent component */ - setupStore, - /** Use store in the child component */ - useStore - }; -} +type ContextProvider = T extends (...args: any[]) => any ? T : (arg: T) => T; -/** Create context */ -function createContext(contextName: string) { - const injectKey: InjectionKey = Symbol(contextName); +type ContextConsumer = ( + consumerName?: N, + defaultValue?: Context +) => N extends null | undefined ? Context | null : Context; - function useProvide(context: T) { - provide(injectKey, context); +/** + * Creates a context provider and consumer pair. + * + * @param contextName - The name of the context. This can be a string or an object with a `name` and `key` property. + * @param composable - An optional composable function that returns the context value. If not provided, the context value will be the first argument passed to the provider. + */ +export default function useContext( + contextName: ContextName, + composable?: T extends (...args: any[]) => any ? T : never +) { + type Context = ContextValue; + + const name = typeof contextName === 'string' ? contextName : contextName.name; + + const key = typeof contextName === 'string' ? Symbol(contextName) : contextName.key; + + /** + * Injects the context value. + * + * @param consumerName - The name of the component that is consuming the context. If provided, the component must be + * used within the context provider. + * @param defaultValue - The default value to return if the context is not provided. + * @returns The context value. + */ + const useInject = (consumerName?: string | null, defaultValue?: any) => { + const value = inject(key, defaultValue) ?? null; + + if (consumerName != null && value === null) { + throw new Error(`\`${consumerName}\` must be used within \`${name}\``); + } + + return value; + }; - return context; - } + const useProvide = (...args: any[]) => { + const value = composable?.(...args) ?? args[0]; - function useInject() { - return inject(injectKey) as T; - } + provide(key, value); - return { - useProvide, - useInject + return value; }; + + return [useProvide, useInject] as [ContextProvider, ContextConsumer]; } diff --git a/packages/hooks/src/use-request.ts b/packages/hooks/src/use-request.ts index a0a40e639a..51784aff54 100644 --- a/packages/hooks/src/use-request.ts +++ b/packages/hooks/src/use-request.ts @@ -6,31 +6,34 @@ import type { CreateAxiosDefaults, CustomAxiosRequestConfig, MappedType, + RequestInstanceCommon, RequestOption, ResponseType } from '@sa/axios'; import useLoading from './use-loading'; -export type HookRequestInstanceResponseSuccessData = { - data: Ref; +export type HookRequestInstanceResponseSuccessData = { + data: Ref; error: Ref; }; -export type HookRequestInstanceResponseFailData = { +export type HookRequestInstanceResponseFailData = { data: Ref; error: Ref>; }; -export type HookRequestInstanceResponseData = { +export type HookRequestInstanceResponseData = { loading: Ref; -} & (HookRequestInstanceResponseSuccessData | HookRequestInstanceResponseFailData); +} & (HookRequestInstanceResponseSuccessData | HookRequestInstanceResponseFailData); -export interface HookRequestInstance { - ( +export interface HookRequestInstance< + ResponseData, + ApiData, + State extends Record +> extends RequestInstanceCommon { + ( config: CustomAxiosRequestConfig - ): HookRequestInstanceResponseData, ResponseData>; - cancelRequest: (requestId: string) => void; - cancelAllRequest: () => void; + ): HookRequestInstanceResponseData>; } /** @@ -39,25 +42,26 @@ export interface HookRequestInstance { * @param axiosConfig * @param options */ -export default function createHookRequest( +export default function createHookRequest>( axiosConfig?: CreateAxiosDefaults, - options?: Partial> + options?: Partial> ) { - const request = createFlatRequest(axiosConfig, options); + const request = createFlatRequest(axiosConfig, options); - const hookRequest: HookRequestInstance = function hookRequest( - config: CustomAxiosRequestConfig - ) { + const hookRequest: HookRequestInstance = function hookRequest< + T extends ApiData = ApiData, + R extends ResponseType = 'json' + >(config: CustomAxiosRequestConfig) { const { loading, startLoading, endLoading } = useLoading(); - const data = ref | null>(null) as Ref>; - const error = ref | null>(null) as Ref | null>; + const data = ref(null) as Ref>; + const error = ref(null) as Ref | null>; startLoading(); request(config).then(res => { if (res.data) { - data.value = res.data; + data.value = res.data as MappedType; } else { error.value = res.error; } @@ -70,9 +74,8 @@ export default function createHookRequest( data, error }; - } as HookRequestInstance; + } as HookRequestInstance; - hookRequest.cancelRequest = request.cancelRequest; hookRequest.cancelAllRequest = request.cancelAllRequest; return hookRequest; diff --git a/packages/hooks/src/use-signal.ts b/packages/hooks/src/use-signal.ts deleted file mode 100644 index fa9d626504..0000000000 --- a/packages/hooks/src/use-signal.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { computed, ref, shallowRef, triggerRef } from 'vue'; -import type { - ComputedGetter, - DebuggerOptions, - Ref, - ShallowRef, - WritableComputedOptions, - WritableComputedRef -} from 'vue'; - -type Updater = (value: T) => T; -type Mutator = (value: T) => void; - -/** - * Signal is a reactive value that can be set, updated or mutated - * - * @example - * ```ts - * const count = useSignal(0); - * - * // `watchEffect` - * watchEffect(() => { - * console.log(count()); - * }); - * - * // watch - * watch(count, value => { - * console.log(value); - * }); - * - * // useComputed - * const double = useComputed(() => count() * 2); - * const writeableDouble = useComputed({ - * get: () => count() * 2, - * set: value => count.set(value / 2) - * }); - * ``` - */ -export interface Signal { - (): Readonly; - /** - * Set the value of the signal - * - * It recommend use `set` for primitive values - * - * @param value - */ - set(value: T): void; - /** - * Update the value of the signal using an updater function - * - * It recommend use `update` for non-primitive values, only the first level of the object will be reactive. - * - * @param updater - */ - update(updater: Updater): void; - /** - * Mutate the value of the signal using a mutator function - * - * this action will call `triggerRef`, so the value will be tracked on `watchEffect`. - * - * It recommend use `mutate` for non-primitive values, all levels of the object will be reactive. - * - * @param mutator - */ - mutate(mutator: Mutator): void; - /** - * Get the reference of the signal - * - * Sometimes it can be useful to make `v-model` work with the signal - * - * ```vue - * ; - * - * - * ``` - */ - getRef(): Readonly>>; -} - -export interface ReadonlySignal { - (): Readonly; -} - -export interface SignalOptions { - /** - * Whether to use `ref` to store the value - * - * @default false use `sharedRef` to store the value - */ - useRef?: boolean; -} - -export function useSignal(initialValue: T, options?: SignalOptions): Signal { - const { useRef } = options || {}; - - const state = useRef ? (ref(initialValue) as Ref) : shallowRef(initialValue); - - return createSignal(state); -} - -export function useComputed(getter: ComputedGetter, debugOptions?: DebuggerOptions): ReadonlySignal; -export function useComputed(options: WritableComputedOptions, debugOptions?: DebuggerOptions): Signal; -export function useComputed( - getterOrOptions: ComputedGetter | WritableComputedOptions, - debugOptions?: DebuggerOptions -) { - const isGetter = typeof getterOrOptions === 'function'; - - const computedValue = computed(getterOrOptions as any, debugOptions); - - if (isGetter) { - return () => computedValue.value as ReadonlySignal; - } - - return createSignal(computedValue); -} - -function createSignal(state: ShallowRef | WritableComputedRef): Signal { - const signal = () => state.value; - - signal.set = (value: T) => { - state.value = value; - }; - - signal.update = (updater: Updater) => { - state.value = updater(state.value); - }; - - signal.mutate = (mutator: Mutator) => { - mutator(state.value); - triggerRef(state); - }; - - signal.getRef = () => state as Readonly>>; - - return signal; -} diff --git a/packages/hooks/src/use-table.ts b/packages/hooks/src/use-table.ts index 46bcf5200f..3a119df38f 100644 --- a/packages/hooks/src/use-table.ts +++ b/packages/hooks/src/use-table.ts @@ -1,12 +1,20 @@ -import { computed, reactive, ref } from 'vue'; +import { computed, ref } from 'vue'; import type { Ref, VNodeChild } from 'vue'; -import { jsonClone } from '@sa/utils'; import useBoolean from './use-boolean'; import useLoading from './use-loading'; -export type MaybePromise = T | Promise; +export interface PaginationData { + data: T[]; + pageNum: number; + pageSize: number; + total: number; +} + +type GetApiData = Pagination extends true ? PaginationData : ApiData[]; -export type ApiFn = (args: any) => Promise; +type Transform = ( + response: ResponseData +) => GetApiData; export type TableColumnCheckTitle = string | ((...args: any) => VNodeChild); @@ -14,125 +22,90 @@ export type TableColumnCheck = { key: string; title: TableColumnCheckTitle; checked: boolean; + visible: boolean; + fixed: 'left' | 'right' | 'unFixed'; }; -export type TableDataWithIndex = T & { index: number }; - -export type TransformedData = { - data: TableDataWithIndex[]; - pageNum: number; - pageSize: number; - total: number; -}; - -export type Transformer = (response: Response) => TransformedData; - -export type TableConfig = { - /** api function to get table data */ - apiFn: A; - /** api params */ - apiParams?: Parameters[0]; - /** transform api response to table data */ - transformer: Transformer>>; - /** columns factory */ - columns: () => C[]; +export interface UseTableOptions { + /** + * api function to get table data + */ + api: () => Promise; + /** + * whether to enable pagination + */ + pagination?: Pagination; + /** + * transform api response to table data + */ + transform: Transform; + /** + * columns factory + */ + columns: () => Column[]; /** * get column checks - * - * @param columns */ - getColumnChecks: (columns: C[]) => TableColumnCheck[]; + getColumnChecks: (columns: Column[]) => TableColumnCheck[]; /** * get columns - * - * @param columns */ - getColumns: (columns: C[], checks: TableColumnCheck[]) => C[]; + getColumns: (columns: Column[], checks: TableColumnCheck[]) => Column[]; /** * callback when response fetched - * - * @param transformed transformed data */ - onFetched?: (transformed: TransformedData) => MaybePromise; + onFetched?: (data: GetApiData) => void | Promise; /** * whether to get data immediately * * @default true */ immediate?: boolean; -}; +} -export default function useHookTable(config: TableConfig) { +export default function useTable( + options: UseTableOptions +) { const { loading, startLoading, endLoading } = useLoading(); const { bool: empty, setBool: setEmpty } = useBoolean(); - const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config; - - const searchParams: NonNullable[0]> = reactive(jsonClone({ ...apiParams })); + const { api, pagination, transform, columns, getColumnChecks, getColumns, onFetched, immediate = true } = options; - const allColumns = ref(config.columns()) as Ref; + const data = ref([]) as Ref; - const data: Ref[]> = ref([]); + const columnChecks = ref(getColumnChecks(columns())) as Ref; - const columnChecks: Ref = ref(getColumnChecks(config.columns())); - - const columns = computed(() => getColumns(allColumns.value, columnChecks.value)); + const $columns = computed(() => getColumns(columns(), columnChecks.value)); function reloadColumns() { - allColumns.value = config.columns(); - const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked])); + const fixedMap = new Map(columnChecks.value.map(col => [col.key, col.fixed])); - const defaultChecks = getColumnChecks(allColumns.value); + const defaultChecks = getColumnChecks(columns()); columnChecks.value = defaultChecks.map(col => ({ ...col, - checked: checkMap.get(col.key) ?? col.checked + checked: checkMap.get(col.key) ?? col.checked, + fixed: (fixedMap.get(col.key) !== 'unFixed' ? fixedMap.get(col.key) : undefined) ?? col.fixed })); } async function getData() { - startLoading(); - - const formattedParams = formatSearchParams(searchParams); - - const response = await apiFn(formattedParams); - - const transformed = transformer(response as Awaited>); - - data.value = transformed.data; - - setEmpty(transformed.data.length === 0); - - await config.onFetched?.(transformed); + try { + startLoading(); - endLoading(); - } + const response = await api(); - function formatSearchParams(params: Record) { - const formattedParams: Record = {}; + const transformed = transform(response); - Object.entries(params).forEach(([key, value]) => { - if (value !== null && value !== undefined) { - formattedParams[key] = value; - } - }); + data.value = getTableData(transformed, pagination); - return formattedParams; - } + setEmpty(data.value.length === 0); - /** - * update search params - * - * @param params - */ - function updateSearchParams(params: Partial[0]>) { - Object.assign(searchParams, params); - } - - /** reset search params */ - function resetSearchParams() { - Object.assign(searchParams, jsonClone(apiParams)); + await onFetched?.(transformed); + } finally { + endLoading(); + } } if (immediate) { @@ -143,12 +116,20 @@ export default function useHookTable(config: TableConfig< loading, empty, data, - columns, + columns: $columns, columnChecks, reloadColumns, - getData, - searchParams, - updateSearchParams, - resetSearchParams + getData }; } + +function getTableData( + data: GetApiData, + pagination?: Pagination +) { + if (pagination) { + return (data as PaginationData).data; + } + + return data as ApiData[]; +} diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json index 5823ed54e6..97b95afd43 100644 --- a/packages/hooks/tsconfig.json +++ b/packages/hooks/tsconfig.json @@ -3,9 +3,8 @@ "target": "ESNext", "jsx": "preserve", "lib": ["DOM", "ESNext"], - "baseUrl": ".", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "types": ["node"], "strict": true, diff --git a/packages/materials/package.json b/packages/materials/package.json index 35000f8b6f..a10d2889d9 100644 --- a/packages/materials/package.json +++ b/packages/materials/package.json @@ -1,14 +1,16 @@ { "name": "@sa/materials", - "version": "1.3.15", - "exports": { - ".": "./src/index.ts" - }, + "version": "2.2.0", "typesVersions": { "*": { - "*": ["./src/*"] + "*": [ + "./src/*" + ] } }, + "exports": { + ".": "./src/index.ts" + }, "dependencies": { "@sa/utils": "workspace:*", "simplebar-vue": "2.4.2" diff --git a/packages/materials/src/libs/admin-layout/index.vue b/packages/materials/src/libs/admin-layout/index.vue index 543f7dc05b..773421fd2a 100644 --- a/packages/materials/src/libs/admin-layout/index.vue +++ b/packages/materials/src/libs/admin-layout/index.vue @@ -127,7 +127,6 @@ function handleClickMask() { :class="[ style['layout-header'], commonClass, - headerClass, headerLeftGapClass, { 'absolute top-0 left-0 w-full': fixedHeaderAndTab } ]" @@ -182,12 +181,12 @@ function handleClickMask() { diff --git a/src/components/common/icon-tooltip.vue b/src/components/common/icon-tooltip.vue new file mode 100644 index 0000000000..d98db322b9 --- /dev/null +++ b/src/components/common/icon-tooltip.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/components/common/system-logo.vue b/src/components/common/system-logo.vue index 7f23cc1a12..7777825899 100644 --- a/src/components/common/system-logo.vue +++ b/src/components/common/system-logo.vue @@ -1,9 +1,160 @@ - - - + diff --git a/src/constants/app.ts b/src/constants/app.ts index 4767c4799b..046893ead1 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -4,10 +4,13 @@ export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__'; export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__'; +/** Wheel speed ratio for horizontal scrolling on the global tab bar. <1 slower, >1 faster */ +export const GLOBAL_TAB_WHEEL_SPEED_RATIO = 0.3; + export const themeSchemaRecord: Record = { - light: 'theme.themeSchema.light', - dark: 'theme.themeSchema.dark', - auto: 'theme.themeSchema.auto' + light: 'theme.appearance.themeSchema.light', + dark: 'theme.appearance.themeSchema.dark', + auto: 'theme.appearance.themeSchema.auto' }; export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord); @@ -21,45 +24,51 @@ export const loginModuleRecord: Record = }; export const themeLayoutModeRecord: Record = { - vertical: 'theme.layoutMode.vertical', - 'vertical-mix': 'theme.layoutMode.vertical-mix', - horizontal: 'theme.layoutMode.horizontal', - 'horizontal-mix': 'theme.layoutMode.horizontal-mix' + vertical: 'theme.layout.layoutMode.vertical', + 'vertical-mix': 'theme.layout.layoutMode.vertical-mix', + 'vertical-hybrid-header-first': 'theme.layout.layoutMode.vertical-hybrid-header-first', + horizontal: 'theme.layout.layoutMode.horizontal', + 'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first', + 'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first' }; export const themeLayoutModeOptions = transformRecordToOption(themeLayoutModeRecord); export const themeScrollModeRecord: Record = { - wrapper: 'theme.scrollMode.wrapper', - content: 'theme.scrollMode.content' + wrapper: 'theme.layout.content.scrollMode.wrapper', + content: 'theme.layout.content.scrollMode.content' }; export const themeScrollModeOptions = transformRecordToOption(themeScrollModeRecord); export const themeTabModeRecord: Record = { - chrome: 'theme.tab.mode.chrome', - button: 'theme.tab.mode.button' + chrome: 'theme.layout.tab.mode.chrome', + button: 'theme.layout.tab.mode.button', + slider: 'theme.layout.tab.mode.slider' }; export const themeTabModeOptions = transformRecordToOption(themeTabModeRecord); export const themePageAnimationModeRecord: Record = { - 'fade-slide': 'theme.page.mode.fade-slide', - fade: 'theme.page.mode.fade', - 'fade-bottom': 'theme.page.mode.fade-bottom', - 'fade-scale': 'theme.page.mode.fade-scale', - 'zoom-fade': 'theme.page.mode.zoom-fade', - 'zoom-out': 'theme.page.mode.zoom-out', - none: 'theme.page.mode.none' + 'fade-slide': 'theme.layout.content.page.mode.fade-slide', + fade: 'theme.layout.content.page.mode.fade', + 'fade-bottom': 'theme.layout.content.page.mode.fade-bottom', + 'fade-scale': 'theme.layout.content.page.mode.fade-scale', + 'zoom-fade': 'theme.layout.content.page.mode.zoom-fade', + 'zoom-out': 'theme.layout.content.page.mode.zoom-out', + none: 'theme.layout.content.page.mode.none' }; export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord); -export const resetCacheStrategyRecord: Record = { - close: 'theme.resetCacheStrategy.close', - refresh: 'theme.resetCacheStrategy.refresh' -}; - -export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord); - export const DARK_CLASS = 'dark'; + +export const watermarkTimeFormatOptions = [ + { label: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm' }, + { label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' }, + { label: 'YYYY/MM/DD HH:mm', value: 'YYYY/MM/DD HH:mm' }, + { label: 'YYYY/MM/DD HH:mm:ss', value: 'YYYY/MM/DD HH:mm:ss' }, + { label: 'HH:mm', value: 'HH:mm' }, + { label: 'HH:mm:ss', value: 'HH:mm:ss' }, + { label: 'MM-DD HH:mm', value: 'MM-DD HH:mm' } +]; diff --git a/src/constants/env.ts b/src/constants/env.ts new file mode 100644 index 0000000000..02ff4e41d5 --- /dev/null +++ b/src/constants/env.ts @@ -0,0 +1,2 @@ +// Whether the app is running in the development environment +export const isDev = import.meta.env.DEV; diff --git a/src/hooks/common/echarts.ts b/src/hooks/common/echarts.ts index 733068348c..8e7c72818d 100644 --- a/src/hooks/common/echarts.ts +++ b/src/hooks/common/echarts.ts @@ -1,4 +1,4 @@ -import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue'; +import { computed, effectScope, nextTick, onScopeDispose, shallowRef, watch } from 'vue'; import { useElementSize } from '@vueuse/core'; import * as echarts from 'echarts/core'; import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts'; @@ -86,11 +86,11 @@ export function useEcharts(optionsFactory: () => T, hooks: C const themeStore = useThemeStore(); const darkMode = computed(() => themeStore.darkMode); - const domRef = ref(null); + const domRef = shallowRef(null); const initialSize = { width: 0, height: 0 }; const { width, height } = useElementSize(domRef, initialSize); - let chart: echarts.ECharts | null = null; + const chart = shallowRef(null); const chartOptions: T = optionsFactory(); const { @@ -111,18 +111,9 @@ export function useEcharts(optionsFactory: () => T, hooks: C onDestroy } = hooks; - /** - * whether can render chart - * - * when domRef is ready and initialSize is valid - */ - function canRender() { - return domRef.value && initialSize.width > 0 && initialSize.height > 0; - } - /** is chart rendered */ function isRendered() { - return Boolean(domRef.value && chart); + return Boolean(domRef.value && chart.value); } /** @@ -131,59 +122,59 @@ export function useEcharts(optionsFactory: () => T, hooks: C * @param callback callback function */ async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) { - if (!isRendered()) return; - const updatedOpts = callback(chartOptions, optionsFactory); Object.assign(chartOptions, updatedOpts); + await nextTick(); + + if (!isRendered()) return; + if (isRendered()) { - chart?.clear(); + chart.value?.clear(); } - chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' }); + chart.value?.setOption({ ...updatedOpts, backgroundColor: 'transparent' }); - await onUpdated?.(chart!); + await onUpdated?.(chart.value!); } function setOptions(options: T) { - chart?.setOption(options); + chart.value?.setOption(options); } /** render chart */ async function render() { - if (!isRendered()) { - const chartTheme = darkMode.value ? 'dark' : 'light'; + if (isRendered()) return; - await nextTick(); + const chartTheme = darkMode.value ? 'dark' : 'light'; - chart = echarts.init(domRef.value, chartTheme); + chart.value = echarts.init(domRef.value, chartTheme); - chart.setOption({ ...chartOptions, backgroundColor: 'transparent' }); + chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' }); - await onRender?.(chart); - } + await onRender?.(chart.value!); } /** resize chart */ function resize() { - chart?.resize(); + chart.value?.resize(); } /** destroy chart */ async function destroy() { - if (!chart) return; + if (!chart.value) return; - await onDestroy?.(chart); - chart?.dispose(); - chart = null; + await onDestroy?.(chart.value); + chart.value?.dispose(); + chart.value = null; } /** change chart theme */ async function changeTheme() { await destroy(); await render(); - await onUpdated?.(chart!); + await onUpdated?.(chart.value!); } /** @@ -196,30 +187,29 @@ export function useEcharts(optionsFactory: () => T, hooks: C initialSize.width = w; initialSize.height = h; - // size is abnormal, destroy chart - if (!canRender()) { - await destroy(); - - return; - } - // resize chart if (isRendered()) { resize(); + + return; } // render chart await render(); - if (chart) { - await onUpdated?.(chart); + if (chart.value) { + await onUpdated?.(chart.value); } } scope.run(() => { - watch([width, height], ([newWidth, newHeight]) => { - renderChartBySize(newWidth, newHeight); - }); + watch( + [width, height], + ([newWidth, newHeight]) => { + renderChartBySize(newWidth, newHeight); + }, + { flush: 'post' } + ); watch(darkMode, () => { changeTheme(); @@ -233,6 +223,7 @@ export function useEcharts(optionsFactory: () => T, hooks: C return { domRef, + chart, updateOptions, setOptions }; diff --git a/src/hooks/common/table.ts b/src/hooks/common/table.ts index 5484bf36a8..2b9202a05b 100644 --- a/src/hooks/common/table.ts +++ b/src/hooks/common/table.ts @@ -1,194 +1,163 @@ -import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue'; +import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue'; import type { Ref } from 'vue'; import type { PaginationProps } from 'naive-ui'; +import { useBoolean, useTable } from '@sa/hooks'; +import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks'; +import type { FlatResponseData } from '@sa/axios'; import { jsonClone } from '@sa/utils'; -import { useBoolean, useHookTable } from '@sa/hooks'; import { useAppStore } from '@/store/modules/app'; import { $t } from '@/locales'; -type TableData = NaiveUI.TableData; -type GetTableData = NaiveUI.GetTableData; -type TableColumn = NaiveUI.TableColumn; +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; +}; + +const SELECTION_KEY = '__selection__'; -export function useTable(config: NaiveUI.NaiveTableConfig) { +const EXPAND_KEY = '__expand__'; + +export function useNaiveTable(options: UseNaiveTableOptions) { const scope = effectScope(); const appStore = useAppStore(); - const isMobile = computed(() => appStore.isMobile); + const result = useTable, false>({ + ...options, + getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible), + getColumns + }); - const { apiFn, apiParams, immediate, showTotal } = config; - - const SELECTION_KEY = '__selection__'; - - const EXPAND_KEY = '__expand__'; - - const { - loading, - empty, - data, - columns, - columnChecks, - reloadColumns, - getData, - searchParams, - updateSearchParams, - resetSearchParams - } = useHookTable, TableColumn>>>({ - apiFn, - apiParams, - columns: config.columns, - transformer: res => { - const { records = [], current = 1, size = 10, total = 0 } = res.data || {}; - - // Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors. - const pageSize = size <= 0 ? 10 : size; - - const recordsWithIndex = records.map((item, index) => { - return { - ...item, - index: (current - 1) * pageSize + index + 1 - }; - }); + // calculate the total width of the table this is used for horizontal scrolling + const scrollX = computed(() => { + return result.columns.value.reduce((acc, column) => { + return acc + Number(column.width ?? column.minWidth ?? 120); + }, 0); + }); - return { - data: recordsWithIndex, - pageNum: current, - pageSize, - total - }; - }, - getColumnChecks: cols => { - const checks: NaiveUI.TableColumnCheck[] = []; - - cols.forEach(column => { - if (isTableColumnHasKey(column)) { - checks.push({ - key: column.key as string, - title: column.title!, - checked: true - }); - } else if (column.type === 'selection') { - checks.push({ - key: SELECTION_KEY, - title: $t('common.check'), - checked: true - }); - } else if (column.type === 'expand') { - checks.push({ - key: EXPAND_KEY, - title: $t('common.expandColumn'), - checked: true - }); - } - }); + scope.run(() => { + watch( + () => appStore.locale, + () => { + result.reloadColumns(); + } + ); + }); - return checks; - }, - getColumns: (cols, checks) => { - const columnMap = new Map>>(); - - cols.forEach(column => { - if (isTableColumnHasKey(column)) { - columnMap.set(column.key as string, column); - } else if (column.type === 'selection') { - columnMap.set(SELECTION_KEY, column); - } else if (column.type === 'expand') { - columnMap.set(EXPAND_KEY, column); - } - }); + onScopeDispose(() => { + scope.stop(); + }); - const filteredColumns = checks - .filter(item => item.checked) - .map(check => columnMap.get(check.key) as TableColumn>); + return { + ...result, + scrollX + }; +} - return filteredColumns; - }, - onFetched: async transformed => { - const { pageNum, pageSize, total } = transformed; +type PaginationParams = Pick; - updatePagination({ - page: pageNum, - pageSize, - itemCount: total - }); - }, - immediate - }); +type UseNaivePaginatedTableOptions = UseNaiveTableOptions & { + paginationProps?: Omit; + /** + * whether to show the total count of the table + * + * @default true + */ + showTotal?: boolean; + onPaginationParamsChange?: (params: PaginationParams) => void | Promise; +}; - const pagination: PaginationProps = reactive({ +export function useNaivePaginatedTable( + options: UseNaivePaginatedTableOptions +) { + const scope = effectScope(); + const appStore = useAppStore(); + + const isMobile = computed(() => appStore.isMobile); + + const showTotal = computed(() => options.showTotal ?? true); + + const pagination = reactive({ page: 1, pageSize: 10, - showSizePicker: true, itemCount: 0, + showSizePicker: true, pageSizes: [10, 15, 20, 25, 30], - onUpdatePage: async (page: number) => { + prefix: showTotal.value ? page => $t('datatable.itemCount', { total: page.itemCount }) : undefined, + onUpdatePage(page) { pagination.page = page; - - updateSearchParams({ - current: page, - size: pagination.pageSize! - }); - - getData(); }, - onUpdatePageSize: async (pageSize: number) => { + onUpdatePageSize(pageSize) { pagination.pageSize = pageSize; pagination.page = 1; - - updateSearchParams({ - current: pagination.page, - size: pageSize - }); - - getData(); }, - ...(showTotal - ? { - prefix: page => $t('datatable.itemCount', { total: page.itemCount }) - } - : {}) - }); + ...options.paginationProps + }) as PaginationProps; // this is for mobile, if the system does not support mobile, you can use `pagination` directly const mobilePagination = computed(() => { const p: PaginationProps = { ...pagination, pageSlot: isMobile.value ? 3 : 9, - prefix: !isMobile.value && showTotal ? pagination.prefix : undefined + prefix: !isMobile.value && showTotal.value ? pagination.prefix : undefined }; return p; }); - function updatePagination(update: Partial) { - Object.assign(pagination, update); - } + const paginationParams = computed(() => { + const { page, pageSize } = pagination; - /** - * get data by page number - * - * @param pageNum the page number. default is 1 - */ - async function getDataByPage(pageNum: number = 1) { - updatePagination({ - page: pageNum - }); + return { + page, + pageSize + }; + }); - updateSearchParams({ - current: pageNum, - size: pagination.pageSize! - }); + const result = useTable, true>({ + ...options, + pagination: true, + getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible), + getColumns, + onFetched: data => { + pagination.itemCount = data.total; + pagination.pageSize = data.pageSize; + } + }); - await getData(); + async function getDataByPage(page: number = 1) { + if (page !== pagination.page) { + pagination.page = page; + + return; + } + + await result.getData(); } scope.run(() => { watch( () => appStore.locale, () => { - reloadColumns(); + result.reloadColumns(); } ); + + watch(paginationParams, async newVal => { + await options.onPaginationParamsChange?.(newVal); + + await result.getData(); + }); }); onScopeDispose(() => { @@ -196,27 +165,21 @@ export function useTable(config: NaiveUI.NaiveTabl }); return { - loading, - empty, - data, - columns, - columnChecks, - reloadColumns, - pagination, - mobilePagination, - updatePagination, - getData, + ...result, getDataByPage, - searchParams, - updateSearchParams, - resetSearchParams + pagination, + mobilePagination }; } -export function useTableOperate(data: Ref, getData: () => Promise) { +export function useTableOperate( + data: Ref, + idKey: keyof TableData, + getData: () => Promise +) { const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean(); - const operateType = ref('add'); + const operateType = shallowRef('add'); function handleAdd() { operateType.value = 'add'; @@ -224,18 +187,18 @@ export function useTableOperate(data: Ref, } /** the editing row data */ - const editingData: Ref = ref(null); + const editingData = shallowRef(null); - function handleEdit(id: T['id']) { + function handleEdit(id: TableData[keyof TableData]) { operateType.value = 'edit'; - const findItem = data.value.find(item => item.id === id) || null; + const findItem = data.value.find(item => item[idKey] === id) || null; editingData.value = jsonClone(findItem); openDrawer(); } /** the checked row keys of table */ - const checkedRowKeys = ref([]); + const checkedRowKeys = shallowRef([]); /** the hook after the batch delete operation is completed */ async function onBatchDeleted() { @@ -267,6 +230,92 @@ export function useTableOperate(data: Ref, }; } -function isTableColumnHasKey(column: TableColumn): column is NaiveUI.TableColumnWithKey { +export function defaultTransform( + response: FlatResponseData> +): PaginationData { + const { data, error } = response; + + if (!error) { + const { records, current, size, total } = data; + + return { + data: records, + pageNum: current, + pageSize: size, + total + }; + } + + return { + data: [], + pageNum: 1, + pageSize: 10, + total: 0 + }; +} + +function getColumnChecks>( + cols: Column[], + getColumnVisible?: (column: Column) => boolean +) { + const checks: TableColumnCheck[] = []; + + cols.forEach(column => { + if (isTableColumnHasKey(column)) { + checks.push({ + key: column.key as string, + title: column.title!, + checked: true, + fixed: column.fixed ?? 'unFixed', + visible: getColumnVisible?.(column) ?? true + }); + } else if (column.type === 'selection') { + checks.push({ + key: SELECTION_KEY, + title: $t('common.check'), + checked: true, + fixed: column.fixed ?? 'unFixed', + visible: getColumnVisible?.(column) ?? false + }); + } else if (column.type === 'expand') { + checks.push({ + key: EXPAND_KEY, + title: $t('common.expandColumn'), + checked: true, + fixed: column.fixed ?? 'unFixed', + visible: getColumnVisible?.(column) ?? false + }); + } + }); + + return checks; +} + +function getColumns>(cols: Column[], checks: TableColumnCheck[]) { + const columnMap = new Map(); + + cols.forEach(column => { + if (isTableColumnHasKey(column)) { + columnMap.set(column.key as string, column); + } else if (column.type === 'selection') { + columnMap.set(SELECTION_KEY, column); + } else if (column.type === 'expand') { + columnMap.set(EXPAND_KEY, column); + } + }); + + const filteredColumns = checks + .filter(item => item.checked) + .map(check => { + return { + ...columnMap.get(check.key), + fixed: check.fixed + } as Column; + }); + + return filteredColumns; +} + +export function isTableColumnHasKey(column: NaiveUI.TableColumn): column is NaiveUI.TableColumnWithKey { return Boolean((column as NaiveUI.TableColumnWithKey).key); } diff --git a/src/layouts/base-layout/index.vue b/src/layouts/base-layout/index.vue index 7debe5d1f5..c552792d74 100644 --- a/src/layouts/base-layout/index.vue +++ b/src/layouts/base-layout/index.vue @@ -10,7 +10,7 @@ import GlobalTab from '../modules/global-tab/index.vue'; import GlobalContent from '../modules/global-content/index.vue'; import GlobalFooter from '../modules/global-footer/index.vue'; import ThemeDrawer from '../modules/theme-drawer/index.vue'; -import { setupMixMenuContext } from '../context'; +import { provideMixMenuContext } from '../modules/global-menu/context'; defineOptions({ name: 'BaseLayout' @@ -18,7 +18,7 @@ defineOptions({ const appStore = useAppStore(); const themeStore = useThemeStore(); -const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext(); +const { secondLevelMenus, childLevelMenus, isActiveFirstLevelMenuHasChildren } = provideMixMenuContext(); const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue')); @@ -29,7 +29,7 @@ const layoutMode = computed(() => { }); const headerProps = computed(() => { - const { mode, reverseHorizontalMix } = themeStore.layout; + const { mode } = themeStore.layout; const headerPropsConfig: Record = { vertical: { @@ -42,15 +42,25 @@ const headerProps = computed(() => { showMenu: false, showMenuToggler: false }, + 'vertical-hybrid-header-first': { + showLogo: !isActiveFirstLevelMenuHasChildren.value, + showMenu: true, + showMenuToggler: false + }, horizontal: { showLogo: true, showMenu: true, showMenuToggler: false }, - 'horizontal-mix': { + 'top-hybrid-sidebar-first': { + showLogo: true, + showMenu: true, + showMenuToggler: false + }, + 'top-hybrid-header-first': { showLogo: true, showMenu: true, - showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value + showMenuToggler: isActiveFirstLevelMenuHasChildren.value } }; @@ -61,44 +71,48 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal'); const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix'); -const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix'); +const isVerticalHybridHeaderFirst = computed(() => themeStore.layout.mode === 'vertical-hybrid-header-first'); -const siderWidth = computed(() => getSiderWidth()); +const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first'); -const siderCollapsedWidth = computed(() => getSiderCollapsedWidth()); +const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first'); -function getSiderWidth() { - const { reverseHorizontalMix } = themeStore.layout; - const { width, mixWidth, mixChildMenuWidth } = themeStore.sider; +const siderWidth = computed(() => getSiderAndCollapsedWidth(false)); - if (isHorizontalMix.value && reverseHorizontalMix) { - return isActiveFirstLevelMenuHasChildren.value ? width : 0; - } +const siderCollapsedWidth = computed(() => getSiderAndCollapsedWidth(true)); - let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width; +function getSiderAndCollapsedWidth(isCollapsed: boolean) { + const { + mixChildMenuWidth, + collapsedWidth, + width: themeWidth, + mixCollapsedWidth, + mixWidth: themeMixWidth + } = themeStore.sider; - if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { - w += mixChildMenuWidth; + const width = isCollapsed ? collapsedWidth : themeWidth; + const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth; + + if (isTopHybridHeaderFirst.value) { + return isActiveFirstLevelMenuHasChildren.value ? width : 0; } - return w; -} + if (isVerticalHybridHeaderFirst.value && !isActiveFirstLevelMenuHasChildren.value) { + return 0; + } -function getSiderCollapsedWidth() { - const { reverseHorizontalMix } = themeStore.layout; - const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider; + const isMixMode = isVerticalMix.value || isTopHybridSidebarFirst.value || isVerticalHybridHeaderFirst.value; + let finalWidth = isMixMode ? mixWidth : width; - if (isHorizontalMix.value && reverseHorizontalMix) { - return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0; + if (isVerticalMix.value && appStore.mixSiderFixed && secondLevelMenus.value.length) { + finalWidth += mixChildMenuWidth; } - let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth; - - if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { - w += mixChildMenuWidth; + if (isVerticalHybridHeaderFirst.value && appStore.mixSiderFixed && childLevelMenus.value.length) { + finalWidth += mixChildMenuWidth; } - return w; + return finalWidth; } diff --git a/src/layouts/context/index.ts b/src/layouts/context/index.ts deleted file mode 100644 index 8b542644a2..0000000000 --- a/src/layouts/context/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { computed, ref, watch } from 'vue'; -import { useRoute } from 'vue-router'; -import { useContext } from '@sa/hooks'; -import { useRouteStore } from '@/store/modules/route'; - -export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu); - -function useMixMenu() { - const route = useRoute(); - const routeStore = useRouteStore(); - const { selectedKey } = useMenu(); - - const activeFirstLevelMenuKey = ref(''); - - function setActiveFirstLevelMenuKey(key: string) { - activeFirstLevelMenuKey.value = key; - } - - function getActiveFirstLevelMenuKey() { - const [firstLevelRouteName] = selectedKey.value.split('_'); - - setActiveFirstLevelMenuKey(firstLevelRouteName); - } - - const allMenus = computed(() => routeStore.menus); - - const firstLevelMenus = computed(() => - routeStore.menus.map(menu => { - const { children: _, ...rest } = menu; - - return rest; - }) - ); - - const childLevelMenus = computed( - () => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || [] - ); - - const isActiveFirstLevelMenuHasChildren = computed(() => { - if (!activeFirstLevelMenuKey.value) { - return false; - } - - const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value); - - return Boolean(findItem?.children?.length); - }); - - watch( - () => route.name, - () => { - getActiveFirstLevelMenuKey(); - }, - { immediate: true } - ); - - return { - allMenus, - firstLevelMenus, - childLevelMenus, - isActiveFirstLevelMenuHasChildren, - activeFirstLevelMenuKey, - setActiveFirstLevelMenuKey, - getActiveFirstLevelMenuKey - }; -} - -export function useMenu() { - const route = useRoute(); - - const selectedKey = computed(() => { - const { hideInMenu, activeMenu } = route.meta; - const name = route.name as string; - - const routeName = (hideInMenu ? activeMenu : name) || name; - - return routeName; - }); - - return { - selectedKey - }; -} diff --git a/src/layouts/modules/global-logo/index.vue b/src/layouts/modules/global-logo/index.vue index 40ec52b068..82e4a2d3e5 100644 --- a/src/layouts/modules/global-logo/index.vue +++ b/src/layouts/modules/global-logo/index.vue @@ -17,7 +17,7 @@ withDefaults(defineProps(), {