diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..179aec6f1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=2 +max_line_length = 100 +trim_trailing_whitespace = true +quote_type = single + +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 000000000..4b28a69cd --- /dev/null +++ b/.gitconfig @@ -0,0 +1,2 @@ +[core] + ignorecase = false diff --git a/.gitignore b/.gitignore index 72c0b1a1f..64ce78b31 100644 --- a/.gitignore +++ b/.gitignore @@ -42,10 +42,24 @@ vite.config.ts.* # Editor directories and files .idea -.vscode *.suo *.ntvs* *.njsproj *.sln *.sw? .history + +# 升级vben时需要删除的文件 +.vscode +.changeset +.github +backend-mock +web-ele +web-naive +docs +playground +.gitpod.yml +README.*.md +tea.yaml +vben-admin.code-workspace +scripts/deploy diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 000000000..270ebb8c1 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,6 @@ +echo Start running commit-msg hook... + +# Check whether the git commit information is standardized +pnpm exec commitlint --edit "$1" + +echo Run commit-msg hook done. diff --git a/.husky/post-merge b/.husky/post-merge new file mode 100644 index 000000000..83fa775d5 --- /dev/null +++ b/.husky/post-merge @@ -0,0 +1,3 @@ +# 每次 git pull 之后, 安装依赖 + +pnpm install diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..5dccee288 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,7 @@ +# update `.vscode/vben-admin.code-workspace` file +pnpm vsh code-workspace --auto-commit + +# Format and submit code according to lintstagedrc.js configuration +pnpm exec lint-staged + +echo Run pre-commit hook done. diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index e68d8a3e0..94b0192a7 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,4 +1,10 @@ export default { + '*.md': ['prettier --cache --ignore-unknown --write'], + '*.vue': [ + 'prettier --write', + 'eslint --cache --fix', + 'stylelint --fix --allow-empty-input', + ], '*.{js,jsx,ts,tsx}': [ 'prettier --cache --ignore-unknown --write', 'eslint --cache --fix', @@ -7,14 +13,8 @@ export default { 'prettier --cache --ignore-unknown --write', 'stylelint --fix --allow-empty-input', ], - '*.md': ['prettier --cache --ignore-unknown --write'], - '*.vue': [ - 'prettier --write', - 'eslint --cache --fix', - 'stylelint --fix --allow-empty-input', - ], + 'package.json': ['prettier --cache --write'], '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ 'prettier --cache --write--parser json', ], - 'package.json': ['prettier --cache --write'], }; diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..48b14e6b2 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +20.14.0 diff --git a/.npmrc b/.npmrc index f7947b4d0..f4a1ad483 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,5 @@ registry = "https://registry.npmmirror.com" +public-hoist-pattern[]=husky public-hoist-pattern[]=eslint public-hoist-pattern[]=prettier public-hoist-pattern[]=prettier-plugin-tailwindcss diff --git a/apps/fba-admin/vite.config.mts b/apps/fba-admin/vite.config.mts deleted file mode 100644 index a2533c79e..000000000 --- a/apps/fba-admin/vite.config.mts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from '@vben/vite-config'; - -export default defineConfig(async () => { - return { - application: {}, - vite: { - server: {}, - }, - }; -}); diff --git a/apps/fba-admin/.env b/apps/web-antd/.env similarity index 100% rename from apps/fba-admin/.env rename to apps/web-antd/.env diff --git a/apps/fba-admin/.env.analyze b/apps/web-antd/.env.analyze similarity index 100% rename from apps/fba-admin/.env.analyze rename to apps/web-antd/.env.analyze diff --git a/apps/fba-admin/.env.development b/apps/web-antd/.env.development similarity index 100% rename from apps/fba-admin/.env.development rename to apps/web-antd/.env.development diff --git a/apps/fba-admin/.env.production b/apps/web-antd/.env.production similarity index 100% rename from apps/fba-admin/.env.production rename to apps/web-antd/.env.production diff --git a/apps/fba-admin/index.html b/apps/web-antd/index.html similarity index 100% rename from apps/fba-admin/index.html rename to apps/web-antd/index.html diff --git a/apps/fba-admin/package.json b/apps/web-antd/package.json similarity index 98% rename from apps/fba-admin/package.json rename to apps/web-antd/package.json index 375ef0ba7..ec47c14d3 100644 --- a/apps/fba-admin/package.json +++ b/apps/web-antd/package.json @@ -8,6 +8,7 @@ "preview": "vite preview", "typecheck": "vue-tsc --noEmit --skipLibCheck" }, + "type": "module", "imports": { "#/*": "./src/*" }, diff --git a/apps/fba-admin/postcss.config.mjs b/apps/web-antd/postcss.config.mjs similarity index 100% rename from apps/fba-admin/postcss.config.mjs rename to apps/web-antd/postcss.config.mjs diff --git a/apps/web-antd/public/favicon.ico b/apps/web-antd/public/favicon.ico new file mode 100644 index 000000000..fcf9818e2 Binary files /dev/null and b/apps/web-antd/public/favicon.ico differ diff --git a/apps/fba-admin/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts similarity index 96% rename from apps/fba-admin/src/adapter/component/index.ts rename to apps/web-antd/src/adapter/component/index.ts index a43a8280a..a541a845d 100644 --- a/apps/fba-admin/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -3,9 +3,10 @@ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, */ +import type { Component, SetupContext } from 'vue'; + import type { BaseFormComponentType } from '@vben/common-ui'; -import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; @@ -124,7 +125,13 @@ async function initComponentAdapter() { IconPicker: (props, { attrs, slots }) => { return h( IconPicker, - { iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs }, + { + iconSlot: 'addonAfter', + inputComponent: Input, + modelValueProp: 'value', + ...props, + ...attrs, + }, slots, ); }, diff --git a/apps/fba-admin/src/adapter/form.ts b/apps/web-antd/src/adapter/form.ts similarity index 100% rename from apps/fba-admin/src/adapter/form.ts rename to apps/web-antd/src/adapter/form.ts diff --git a/apps/fba-admin/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts similarity index 100% rename from apps/fba-admin/src/adapter/vxe-table.ts rename to apps/web-antd/src/adapter/vxe-table.ts diff --git a/apps/fba-admin/src/api/auth.ts b/apps/web-antd/src/api/auth.ts similarity index 100% rename from apps/fba-admin/src/api/auth.ts rename to apps/web-antd/src/api/auth.ts diff --git a/apps/web-antd/src/api/core/auth.ts b/apps/web-antd/src/api/core/auth.ts new file mode 100644 index 000000000..71d9f9943 --- /dev/null +++ b/apps/web-antd/src/api/core/auth.ts @@ -0,0 +1,51 @@ +import { baseRequestClient, requestClient } from '#/api/request'; + +export namespace AuthApi { + /** 登录接口参数 */ + export interface LoginParams { + password?: string; + username?: string; + } + + /** 登录接口返回值 */ + export interface LoginResult { + accessToken: string; + } + + export interface RefreshTokenResult { + data: string; + status: number; + } +} + +/** + * 登录 + */ +export async function loginApi(data: AuthApi.LoginParams) { + return requestClient.post('/auth/login', data); +} + +/** + * 刷新accessToken + */ +export async function refreshTokenApi() { + return baseRequestClient.post('/auth/refresh', { + withCredentials: true, + }); +} + +/** + * 退出登录 + */ +export async function logoutApi() { + return baseRequestClient.post('/auth/logout', { + withCredentials: true, + }); +} + +/** + * 获取用户权限码 + */ +export async function getAccessCodesApi() { + return requestClient.get('/auth/codes'); +} diff --git a/apps/web-antd/src/api/core/index.ts b/apps/web-antd/src/api/core/index.ts new file mode 100644 index 000000000..28a5aef47 --- /dev/null +++ b/apps/web-antd/src/api/core/index.ts @@ -0,0 +1,3 @@ +export * from './auth'; +export * from './menu'; +export * from './user'; diff --git a/apps/web-antd/src/api/core/menu.ts b/apps/web-antd/src/api/core/menu.ts new file mode 100644 index 000000000..9ef60b11c --- /dev/null +++ b/apps/web-antd/src/api/core/menu.ts @@ -0,0 +1,10 @@ +import type { RouteRecordStringComponent } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户所有菜单 + */ +export async function getAllMenusApi() { + return requestClient.get('/menu/all'); +} diff --git a/apps/web-antd/src/api/core/user.ts b/apps/web-antd/src/api/core/user.ts new file mode 100644 index 000000000..7e28ea848 --- /dev/null +++ b/apps/web-antd/src/api/core/user.ts @@ -0,0 +1,10 @@ +import type { UserInfo } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户信息 + */ +export async function getUserInfoApi() { + return requestClient.get('/user/info'); +} diff --git a/apps/web-antd/src/api/index.ts b/apps/web-antd/src/api/index.ts new file mode 100644 index 000000000..4b0e04137 --- /dev/null +++ b/apps/web-antd/src/api/index.ts @@ -0,0 +1 @@ +export * from './core'; diff --git a/apps/fba-admin/src/api/menu.ts b/apps/web-antd/src/api/menu.ts similarity index 100% rename from apps/fba-admin/src/api/menu.ts rename to apps/web-antd/src/api/menu.ts diff --git a/apps/fba-admin/src/api/request.ts b/apps/web-antd/src/api/request.ts similarity index 74% rename from apps/fba-admin/src/api/request.ts rename to apps/web-antd/src/api/request.ts index 827188d03..288dddd09 100644 --- a/apps/fba-admin/src/api/request.ts +++ b/apps/web-antd/src/api/request.ts @@ -1,12 +1,13 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { HttpResponse } from '@vben/request'; +import type { RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; import { authenticateResponseInterceptor, + defaultResponseInterceptor, errorMessageResponseInterceptor, RequestClient, } from '@vben/request'; @@ -14,13 +15,15 @@ import { useAccessStore } from '@vben/stores'; import { message } from 'ant-design-vue'; -import { refreshTokenApi } from '#/api/auth'; import { useAuthStore } from '#/store'; +import { refreshTokenApi } from './core'; + const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -function createRequestClient(baseURL: string) { +function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ + ...options, baseURL, }); @@ -68,19 +71,14 @@ function createRequestClient(baseURL: string) { }, }); - // response数据解构 - client.addResponseInterceptor({ - fulfilled: (response) => { - const { data: responseData, status } = response; - - const { code, data } = responseData; - if (status >= 200 && status < 400 && code === 200) { - return data; - } - - throw Object.assign({}, response, { response }); - }, - }); + // 处理返回的响应数据格式 + client.addResponseInterceptor( + defaultResponseInterceptor({ + codeField: 'code', + dataField: 'data', + successCode: 0, + }), + ); // token过期的处理 client.addResponseInterceptor( @@ -96,8 +94,11 @@ function createRequestClient(baseURL: string) { // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 client.addResponseInterceptor( errorMessageResponseInterceptor((msg: string, error) => { + // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg + // 当前mock接口返回的错误字段是 error 或者 message const responseData = error?.response?.data ?? {}; const errorMessage = responseData?.error ?? responseData?.message ?? ''; + // 如果没有错误信息,则会根据状态码进行提示 message.error(errorMessage || msg); }), ); @@ -105,6 +106,8 @@ function createRequestClient(baseURL: string) { return client; } -export const requestClient = createRequestClient(apiURL); +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/fba-admin/src/api/user.ts b/apps/web-antd/src/api/user.ts similarity index 100% rename from apps/fba-admin/src/api/user.ts rename to apps/web-antd/src/api/user.ts diff --git a/apps/fba-admin/src/app.vue b/apps/web-antd/src/app.vue similarity index 100% rename from apps/fba-admin/src/app.vue rename to apps/web-antd/src/app.vue diff --git a/apps/fba-admin/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts similarity index 64% rename from apps/fba-admin/src/bootstrap.ts rename to apps/web-antd/src/bootstrap.ts index 963d1c7f7..fe7d96504 100644 --- a/apps/fba-admin/src/bootstrap.ts +++ b/apps/web-antd/src/bootstrap.ts @@ -1,6 +1,8 @@ import { createApp, watchEffect } from 'vue'; import { registerAccessDirective } from '@vben/access'; +import { initTippy, registerLoadingDirective } from '@vben/common-ui'; +import { MotionPlugin } from '@vben/plugins/motion'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; import '@vben/styles'; @@ -18,8 +20,23 @@ async function bootstrap(namespace: string) { // 初始化组件适配器 await initComponentAdapter(); + // // 设置弹窗的默认配置 + // setDefaultModalProps({ + // fullscreenButton: false, + // }); + // // 设置抽屉的默认配置 + // setDefaultDrawerProps({ + // zIndex: 1020, + // }); + const app = createApp(App); + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + // 国际化 i18n 配置 await setupI18n(app); @@ -29,9 +46,15 @@ async function bootstrap(namespace: string) { // 安装权限指令 registerAccessDirective(app); + // 初始化 tippy + initTippy(app); + // 配置路由及路由守卫 app.use(router); + // 配置Motion插件 + app.use(MotionPlugin); + // 动态更新标题 watchEffect(() => { if (preferences.app.dynamicTitle) { diff --git a/apps/fba-admin/src/layouts/auth.vue b/apps/web-antd/src/layouts/auth.vue similarity index 100% rename from apps/fba-admin/src/layouts/auth.vue rename to apps/web-antd/src/layouts/auth.vue diff --git a/apps/fba-admin/src/layouts/basic.vue b/apps/web-antd/src/layouts/basic.vue similarity index 100% rename from apps/fba-admin/src/layouts/basic.vue rename to apps/web-antd/src/layouts/basic.vue diff --git a/apps/fba-admin/src/layouts/index.ts b/apps/web-antd/src/layouts/index.ts similarity index 100% rename from apps/fba-admin/src/layouts/index.ts rename to apps/web-antd/src/layouts/index.ts diff --git a/apps/web-antd/src/locales/README.md b/apps/web-antd/src/locales/README.md new file mode 100644 index 000000000..7b451032e --- /dev/null +++ b/apps/web-antd/src/locales/README.md @@ -0,0 +1,3 @@ +# locale + +每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/apps/fba-admin/src/locales/index.ts b/apps/web-antd/src/locales/index.ts similarity index 99% rename from apps/fba-admin/src/locales/index.ts rename to apps/web-antd/src/locales/index.ts index 1972e06ee..7f32bd18e 100644 --- a/apps/fba-admin/src/locales/index.ts +++ b/apps/web-antd/src/locales/index.ts @@ -1,7 +1,9 @@ -import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; import type { Locale } from 'ant-design-vue/es/locale'; import type { App } from 'vue'; + +import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; + import { ref } from 'vue'; import { diff --git a/apps/web-antd/src/locales/langs/en-US/demos.json b/apps/web-antd/src/locales/langs/en-US/demos.json new file mode 100644 index 000000000..071564349 --- /dev/null +++ b/apps/web-antd/src/locales/langs/en-US/demos.json @@ -0,0 +1,12 @@ +{ + "title": "Demos", + "antd": "Ant Design Vue", + "vben": { + "title": "Project", + "about": "About", + "document": "Document", + "antdv": "Ant Design Vue Version", + "naive-ui": "Naive UI Version", + "element-plus": "Element Plus Version" + } +} diff --git a/apps/fba-admin/src/locales/langs/en-US/page.json b/apps/web-antd/src/locales/langs/en-US/page.json similarity index 92% rename from apps/fba-admin/src/locales/langs/en-US/page.json rename to apps/web-antd/src/locales/langs/en-US/page.json index 460ae3b43..a3859e50c 100644 --- a/apps/fba-admin/src/locales/langs/en-US/page.json +++ b/apps/web-antd/src/locales/langs/en-US/page.json @@ -10,6 +10,7 @@ }, "dashboard": { "title": "Dashboard", + "analytics": "Analytics", "workspace": "Workspace" } } diff --git a/apps/web-antd/src/locales/langs/zh-CN/demos.json b/apps/web-antd/src/locales/langs/zh-CN/demos.json new file mode 100644 index 000000000..93ee722f5 --- /dev/null +++ b/apps/web-antd/src/locales/langs/zh-CN/demos.json @@ -0,0 +1,12 @@ +{ + "title": "演示", + "antd": "Ant Design Vue", + "vben": { + "title": "项目", + "about": "关于", + "document": "文档", + "antdv": "Ant Design Vue 版本", + "naive-ui": "Naive UI 版本", + "element-plus": "Element Plus 版本" + } +} diff --git a/apps/fba-admin/src/locales/langs/zh-CN/page.json b/apps/web-antd/src/locales/langs/zh-CN/page.json similarity index 92% rename from apps/fba-admin/src/locales/langs/zh-CN/page.json rename to apps/web-antd/src/locales/langs/zh-CN/page.json index cb32c8fa4..1bdcd09f6 100644 --- a/apps/fba-admin/src/locales/langs/zh-CN/page.json +++ b/apps/web-antd/src/locales/langs/zh-CN/page.json @@ -10,6 +10,7 @@ }, "dashboard": { "title": "概览", + "analytics": "分析页", "workspace": "工作台" } } diff --git a/apps/fba-admin/src/main.ts b/apps/web-antd/src/main.ts similarity index 100% rename from apps/fba-admin/src/main.ts rename to apps/web-antd/src/main.ts diff --git a/apps/fba-admin/src/preferences.ts b/apps/web-antd/src/preferences.ts similarity index 100% rename from apps/fba-admin/src/preferences.ts rename to apps/web-antd/src/preferences.ts diff --git a/apps/fba-admin/src/router/access.ts b/apps/web-antd/src/router/access.ts similarity index 96% rename from apps/fba-admin/src/router/access.ts rename to apps/web-antd/src/router/access.ts index ad1234a93..3a48be237 100644 --- a/apps/fba-admin/src/router/access.ts +++ b/apps/web-antd/src/router/access.ts @@ -8,7 +8,7 @@ import { preferences } from '@vben/preferences'; import { message } from 'ant-design-vue'; -import { getAllMenusApi } from '#/api/menu'; +import { getAllMenusApi } from '#/api'; import { BasicLayout, IFrameView } from '#/layouts'; import { $t } from '#/locales'; diff --git a/apps/fba-admin/src/router/guard.ts b/apps/web-antd/src/router/guard.ts similarity index 99% rename from apps/fba-admin/src/router/guard.ts rename to apps/web-antd/src/router/guard.ts index 2a5cf70d7..cbb5235ec 100644 --- a/apps/fba-admin/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -23,7 +23,7 @@ function setupCommonGuard(router: Router) { // 页面加载进度条 if (!to.meta.loaded && preferences.transition.progress) { - await startProgress(); + startProgress(); } return true; }); diff --git a/apps/fba-admin/src/router/index.ts b/apps/web-antd/src/router/index.ts similarity index 100% rename from apps/fba-admin/src/router/index.ts rename to apps/web-antd/src/router/index.ts diff --git a/apps/fba-admin/src/router/routes/core.ts b/apps/web-antd/src/router/routes/core.ts similarity index 87% rename from apps/fba-admin/src/router/routes/core.ts rename to apps/web-antd/src/router/routes/core.ts index fe030a9a2..7218da228 100644 --- a/apps/fba-admin/src/router/routes/core.ts +++ b/apps/web-antd/src/router/routes/core.ts @@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router'; import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants'; -import { AuthPageLayout } from '#/layouts'; +import { AuthPageLayout, BasicLayout } from '#/layouts'; import { $t } from '#/locales'; import Login from '#/views/_core/authentication/login.vue'; @@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = { /** 基本路由,这些路由是必须存在的 */ const coreRoutes: RouteRecordRaw[] = [ + /** + * 根路由 + * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 + * 此路由必须存在,且不应修改 + */ { + component: BasicLayout, meta: { + hideInBreadcrumb: true, title: 'Root', }, name: 'Root', path: '/', redirect: DEFAULT_HOME_PATH, + children: [], }, { component: AuthPageLayout, diff --git a/apps/fba-admin/src/router/routes/index.ts b/apps/web-antd/src/router/routes/index.ts similarity index 100% rename from apps/fba-admin/src/router/routes/index.ts rename to apps/web-antd/src/router/routes/index.ts diff --git a/apps/fba-admin/src/router/routes/modules/dashboard.ts b/apps/web-antd/src/router/routes/modules/dashboard.ts similarity index 67% rename from apps/fba-admin/src/router/routes/modules/dashboard.ts rename to apps/web-antd/src/router/routes/modules/dashboard.ts index 739bba53e..feeb2acbb 100644 --- a/apps/fba-admin/src/router/routes/modules/dashboard.ts +++ b/apps/web-antd/src/router/routes/modules/dashboard.ts @@ -12,8 +12,18 @@ const routes: RouteRecordRaw[] = [ title: $t('page.dashboard.title'), }, name: 'Dashboard', - path: '/', + path: '/dashboard', children: [ + { + name: 'Analytics', + path: '/analytics', + component: () => import('#/views/dashboard/analytics/index.vue'), + meta: { + affixTab: true, + icon: 'lucide:area-chart', + title: $t('page.dashboard.analytics'), + }, + }, { name: 'Workspace', path: '/workspace', diff --git a/apps/web-antd/src/router/routes/modules/demos.ts b/apps/web-antd/src/router/routes/modules/demos.ts new file mode 100644 index 000000000..55ade09c9 --- /dev/null +++ b/apps/web-antd/src/router/routes/modules/demos.ts @@ -0,0 +1,28 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ic:baseline-view-in-ar', + keepAlive: true, + order: 1000, + title: $t('demos.title'), + }, + name: 'Demos', + path: '/demos', + children: [ + { + meta: { + title: $t('demos.antd'), + }, + name: 'AntDesignDemos', + path: '/demos/ant-design', + component: () => import('#/views/demos/antd/index.vue'), + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-antd/src/router/routes/modules/vben.ts b/apps/web-antd/src/router/routes/modules/vben.ts new file mode 100644 index 000000000..98acf5821 --- /dev/null +++ b/apps/web-antd/src/router/routes/modules/vben.ts @@ -0,0 +1,81 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { + VBEN_DOC_URL, + VBEN_ELE_PREVIEW_URL, + VBEN_GITHUB_URL, + VBEN_LOGO_URL, + VBEN_NAIVE_PREVIEW_URL, +} from '@vben/constants'; + +import { IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + badgeType: 'dot', + icon: VBEN_LOGO_URL, + order: 9998, + title: $t('demos.vben.title'), + }, + name: 'VbenProject', + path: '/vben-admin', + children: [ + { + name: 'VbenDocument', + path: '/vben-admin/document', + component: IFrameView, + meta: { + icon: 'lucide:book-open-text', + link: VBEN_DOC_URL, + title: $t('demos.vben.document'), + }, + }, + { + name: 'VbenGithub', + path: '/vben-admin/github', + component: IFrameView, + meta: { + icon: 'mdi:github', + link: VBEN_GITHUB_URL, + title: 'Github', + }, + }, + { + name: 'VbenNaive', + path: '/vben-admin/naive', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: 'logos:naiveui', + link: VBEN_NAIVE_PREVIEW_URL, + title: $t('demos.vben.naive-ui'), + }, + }, + { + name: 'VbenElementPlus', + path: '/vben-admin/ele', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: 'logos:element', + link: VBEN_ELE_PREVIEW_URL, + title: $t('demos.vben.element-plus'), + }, + }, + ], + }, + { + name: 'VbenAbout', + path: '/vben-admin/about', + component: () => import('#/views/_core/about/index.vue'), + meta: { + icon: 'lucide:copyright', + title: $t('demos.vben.about'), + order: 9999, + }, + }, +]; + +export default routes; diff --git a/apps/fba-admin/src/store/auth.ts b/apps/web-antd/src/store/auth.ts similarity index 93% rename from apps/fba-admin/src/store/auth.ts rename to apps/web-antd/src/store/auth.ts index f9494fcaf..833574f94 100644 --- a/apps/fba-admin/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -9,14 +9,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { notification } from 'ant-design-vue'; import { defineStore } from 'pinia'; -import { - type CaptchaResult, - getAccessCodesApi, - getCaptchaApi, - loginApi, - logoutApi, -} from '#/api/auth'; -import { getUserInfoApi } from '#/api/user'; +import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; import { $t } from '#/locales'; export const useAuthStore = defineStore('auth', () => { @@ -108,7 +101,8 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - const userInfo = await getUserInfoApi(); + let userInfo: null | UserInfo = null; + userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/apps/fba-admin/src/store/index.ts b/apps/web-antd/src/store/index.ts similarity index 100% rename from apps/fba-admin/src/store/index.ts rename to apps/web-antd/src/store/index.ts diff --git a/apps/web-antd/src/views/_core/README.md b/apps/web-antd/src/views/_core/README.md new file mode 100644 index 000000000..8248afe6c --- /dev/null +++ b/apps/web-antd/src/views/_core/README.md @@ -0,0 +1,3 @@ +# \_core + +此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/apps/web-antd/src/views/_core/about/index.vue b/apps/web-antd/src/views/_core/about/index.vue new file mode 100644 index 000000000..0ee524335 --- /dev/null +++ b/apps/web-antd/src/views/_core/about/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/fba-admin/src/views/_core/authentication/code-login.vue b/apps/web-antd/src/views/_core/authentication/code-login.vue similarity index 100% rename from apps/fba-admin/src/views/_core/authentication/code-login.vue rename to apps/web-antd/src/views/_core/authentication/code-login.vue diff --git a/apps/fba-admin/src/views/_core/authentication/forget-password.vue b/apps/web-antd/src/views/_core/authentication/forget-password.vue similarity index 100% rename from apps/fba-admin/src/views/_core/authentication/forget-password.vue rename to apps/web-antd/src/views/_core/authentication/forget-password.vue diff --git a/apps/fba-admin/src/views/_core/authentication/login.vue b/apps/web-antd/src/views/_core/authentication/login.vue similarity index 100% rename from apps/fba-admin/src/views/_core/authentication/login.vue rename to apps/web-antd/src/views/_core/authentication/login.vue diff --git a/apps/fba-admin/src/views/_core/authentication/qrcode-login.vue b/apps/web-antd/src/views/_core/authentication/qrcode-login.vue similarity index 100% rename from apps/fba-admin/src/views/_core/authentication/qrcode-login.vue rename to apps/web-antd/src/views/_core/authentication/qrcode-login.vue diff --git a/apps/fba-admin/src/views/_core/authentication/register.vue b/apps/web-antd/src/views/_core/authentication/register.vue similarity index 97% rename from apps/fba-admin/src/views/_core/authentication/register.vue rename to apps/web-antd/src/views/_core/authentication/register.vue index 731627b15..b1a5de726 100644 --- a/apps/fba-admin/src/views/_core/authentication/register.vue +++ b/apps/web-antd/src/views/_core/authentication/register.vue @@ -74,7 +74,7 @@ const formSchema = computed((): VbenFormSchema[] => { ), ]), }), - rules: z.boolean().refine((value) => value, { + rules: z.boolean().refine((value) => !!value, { message: $t('authentication.agreeTip'), }), }, diff --git a/apps/fba-admin/src/views/_core/fallback/coming-soon.vue b/apps/web-antd/src/views/_core/fallback/coming-soon.vue similarity index 100% rename from apps/fba-admin/src/views/_core/fallback/coming-soon.vue rename to apps/web-antd/src/views/_core/fallback/coming-soon.vue diff --git a/apps/fba-admin/src/views/_core/fallback/forbidden.vue b/apps/web-antd/src/views/_core/fallback/forbidden.vue similarity index 100% rename from apps/fba-admin/src/views/_core/fallback/forbidden.vue rename to apps/web-antd/src/views/_core/fallback/forbidden.vue diff --git a/apps/fba-admin/src/views/_core/fallback/internal-error.vue b/apps/web-antd/src/views/_core/fallback/internal-error.vue similarity index 100% rename from apps/fba-admin/src/views/_core/fallback/internal-error.vue rename to apps/web-antd/src/views/_core/fallback/internal-error.vue diff --git a/apps/fba-admin/src/views/_core/fallback/not-found.vue b/apps/web-antd/src/views/_core/fallback/not-found.vue similarity index 100% rename from apps/fba-admin/src/views/_core/fallback/not-found.vue rename to apps/web-antd/src/views/_core/fallback/not-found.vue diff --git a/apps/fba-admin/src/views/_core/fallback/offline.vue b/apps/web-antd/src/views/_core/fallback/offline.vue similarity index 100% rename from apps/fba-admin/src/views/_core/fallback/offline.vue rename to apps/web-antd/src/views/_core/fallback/offline.vue diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue new file mode 100644 index 000000000..f1f0b232a --- /dev/null +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue new file mode 100644 index 000000000..190fb41f4 --- /dev/null +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue new file mode 100644 index 000000000..02f509123 --- /dev/null +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -0,0 +1,46 @@ + + + diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue new file mode 100644 index 000000000..0915c7af7 --- /dev/null +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue new file mode 100644 index 000000000..7e0f10133 --- /dev/null +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-antd/src/views/dashboard/analytics/index.vue b/apps/web-antd/src/views/dashboard/analytics/index.vue new file mode 100644 index 000000000..5e3d6d285 --- /dev/null +++ b/apps/web-antd/src/views/dashboard/analytics/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/fba-admin/src/views/dashboard/workspace/index.vue b/apps/web-antd/src/views/dashboard/workspace/index.vue similarity index 96% rename from apps/fba-admin/src/views/dashboard/workspace/index.vue rename to apps/web-antd/src/views/dashboard/workspace/index.vue index 8ae008eac..b95d61381 100644 --- a/apps/fba-admin/src/views/dashboard/workspace/index.vue +++ b/apps/web-antd/src/views/dashboard/workspace/index.vue @@ -10,6 +10,7 @@ import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { + AnalysisChartCard, WorkbenchHeader, WorkbenchProject, WorkbenchQuickNav, @@ -20,6 +21,8 @@ import { preferences } from '@vben/preferences'; import { useUserStore } from '@vben/stores'; import { openWindow } from '@vben/utils'; +import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue'; + const userStore = useUserStore(); // 这是一个示例数据,实际项目中需要根据实际情况进行调整 @@ -254,6 +257,9 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) { @click="navTo" /> + + + diff --git a/apps/web-antd/src/views/demos/antd/index.vue b/apps/web-antd/src/views/demos/antd/index.vue new file mode 100644 index 000000000..b3b05cc15 --- /dev/null +++ b/apps/web-antd/src/views/demos/antd/index.vue @@ -0,0 +1,66 @@ + + + diff --git a/apps/fba-admin/tailwind.config.mjs b/apps/web-antd/tailwind.config.mjs similarity index 100% rename from apps/fba-admin/tailwind.config.mjs rename to apps/web-antd/tailwind.config.mjs diff --git a/apps/fba-admin/tsconfig.json b/apps/web-antd/tsconfig.json similarity index 100% rename from apps/fba-admin/tsconfig.json rename to apps/web-antd/tsconfig.json diff --git a/apps/fba-admin/tsconfig.node.json b/apps/web-antd/tsconfig.node.json similarity index 100% rename from apps/fba-admin/tsconfig.node.json rename to apps/web-antd/tsconfig.node.json diff --git a/apps/web-antd/vite.config.mts b/apps/web-antd/vite.config.mts new file mode 100644 index 000000000..b6360f1d4 --- /dev/null +++ b/apps/web-antd/vite.config.mts @@ -0,0 +1,20 @@ +import { defineConfig } from '@vben/vite-config'; + +export default defineConfig(async () => { + return { + application: {}, + vite: { + server: { + proxy: { + '/api': { + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + // mock代理目标地址 + target: 'http://localhost:5320/api', + ws: true, + }, + }, + }, + }, + }; +}); diff --git a/cspell.json b/cspell.json new file mode 100644 index 000000000..89545b432 --- /dev/null +++ b/cspell.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en,en-US", + "allowCompoundWords": true, + "words": [ + "acmr", + "antd", + "antdv", + "astro", + "brotli", + "clsx", + "defu", + "demi", + "echarts", + "ependencies", + "esno", + "etag", + "execa", + "iconify", + "iconoir", + "intlify", + "lockb", + "lucide", + "minh", + "minw", + "mkdist", + "mockjs", + "naiveui", + "nocheck", + "noopener", + "noreferrer", + "nprogress", + "nuxt", + "pinia", + "prefixs", + "publint", + "qrcode", + "shadcn", + "sonner", + "sortablejs", + "styl", + "taze", + "ui-kit", + "uicons", + "unplugin", + "unref", + "vben", + "vbenjs", + "vite", + "vitejs", + "vitepress", + "vnode", + "vueuse", + "yxxx" + ], + "ignorePaths": [ + "**/node_modules/**", + "**/dist/**", + "**/*-dist/**", + "**/icons/**", + "pnpm-lock.yaml", + "**/*.log", + "**/*.test.ts", + "**/*.spec.ts", + "**/__tests__/**" + ] +} diff --git a/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json index d7e4c5180..a1645bb77 100644 --- a/internal/lint-configs/commitlint-config/package.json +++ b/internal/lint-configs/commitlint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/commitlint-config", - "version": "5.5.2", + "version": "5.5.3", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/lint-configs/eslint-config/src/configs/import.ts b/internal/lint-configs/eslint-config/src/configs/import.ts index 67a08feaf..ce6cf65d9 100644 --- a/internal/lint-configs/eslint-config/src/configs/import.ts +++ b/internal/lint-configs/eslint-config/src/configs/import.ts @@ -10,6 +10,7 @@ export async function importPluginConfig(): Promise { import: pluginImport, }, rules: { + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/first': 'error', 'import/newline-after-import': 'error', 'import/no-duplicates': 'error', diff --git a/internal/lint-configs/eslint-config/src/configs/javascript.ts b/internal/lint-configs/eslint-config/src/configs/javascript.ts index 0d87c1ba1..44cf5b6e6 100644 --- a/internal/lint-configs/eslint-config/src/configs/javascript.ts +++ b/internal/lint-configs/eslint-config/src/configs/javascript.ts @@ -1,6 +1,5 @@ import type { Linter } from 'eslint'; -// @ts-expect-error - no types import js from '@eslint/js'; import pluginUnusedImports from 'eslint-plugin-unused-imports'; import globals from 'globals'; @@ -175,7 +174,7 @@ export async function javascript(): Promise { ], 'no-use-before-define': [ 'error', - { classes: false, functions: false, variables: true }, + { classes: false, functions: false, variables: false }, ], 'no-useless-backreference': 'error', 'no-useless-call': 'error', diff --git a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts index 1b17b30f3..d4fd0bb8c 100644 --- a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts +++ b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts @@ -1,8 +1,13 @@ import type { Linter } from 'eslint'; -import perfectionistPlugin from 'eslint-plugin-perfectionist'; +import { interopDefault } from '../util'; export async function perfectionist(): Promise { + const perfectionistPlugin = await interopDefault( + // @ts-expect-error - no types + import('eslint-plugin-perfectionist'), + ); + return [ perfectionistPlugin.configs['recommended-natural'], { @@ -19,21 +24,28 @@ export async function perfectionist(): Promise { { customGroups: { type: { - vben: 'vben', - vue: 'vue', + 'vben-core-type': ['^@vben-core/.+'], + 'vben-type': ['^@vben/.+'], + 'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'], }, value: { - vben: ['@vben*', '@vben/**/**', '@vben-core/**/**'], - vue: ['vue', 'vue-*', '@vue*'], + vben: ['^@vben/.+'], + 'vben-core': ['^@vben-core/.+'], + vue: ['^vue$', '^vue-.+', '^@vue/.+'], }, }, + environment: 'node', groups: [ ['external-type', 'builtin-type', 'type'], + 'vue-type', + 'vben-type', + 'vben-core-type', ['parent-type', 'sibling-type', 'index-type'], ['internal-type'], 'builtin', 'vue', 'vben', + 'vben-core', 'external', 'internal', ['parent', 'sibling', 'index'], @@ -43,12 +55,13 @@ export async function perfectionist(): Promise { 'object', 'unknown', ], - internalPattern: ['#*', '#*/**'], + internalPattern: ['^#/.+'], newlinesBetween: 'always', order: 'asc', type: 'natural', }, ], + 'perfectionist/sort-modules': 'off', 'perfectionist/sort-named-exports': [ 'error', { @@ -67,42 +80,6 @@ export async function perfectionist(): Promise { groups: ['unknown', 'items', 'list', 'children'], ignorePattern: ['children'], order: 'asc', - partitionByComment: 'Part:**', - type: 'natural', - }, - ], - 'perfectionist/sort-vue-attributes': [ - 'error', - { - // Based on: https://vuejs.org/style-guide/rules-recommended.html#element-attribute-order - customGroups: { - /* eslint-disable perfectionist/sort-objects */ - DEFINITION: '*(is|:is|v-is)', - LIST_RENDERING: 'v-for', - CONDITIONALS: 'v-*(else-if|if|else|show|cloak)', - RENDER_MODIFIERS: 'v-*(pre|once)', - GLOBAL: '*(:id|id)', - UNIQUE: '*(ref|key|:ref|:key)', - SLOT: '*(v-slot|slot)', - TWO_WAY_BINDING: '*(v-model|v-model:*)', - // OTHER_DIRECTIVES e.g. 'v-custom-directive' - EVENTS: '*(v-on|@*)', - CONTENT: 'v-*(html|text)', - /* eslint-enable perfectionist/sort-objects */ - }, - groups: [ - 'DEFINITION', - 'LIST_RENDERING', - 'CONDITIONALS', - 'RENDER_MODIFIERS', - 'GLOBAL', - 'UNIQUE', - 'SLOT', - 'TWO_WAY_BINDING', - 'unknown', - 'EVENTS', - 'CONTENT', - ], type: 'natural', }, ], diff --git a/internal/lint-configs/eslint-config/src/custom-config.ts b/internal/lint-configs/eslint-config/src/custom-config.ts index 25d22f6be..973df9221 100644 --- a/internal/lint-configs/eslint-config/src/custom-config.ts +++ b/internal/lint-configs/eslint-config/src/custom-config.ts @@ -130,6 +130,26 @@ const customConfig: Linter.Config[] = [ ], }, }, + // 后端模拟代码,不需要太多规则 + { + files: ['apps/backend-mock/**/**', 'docs/**/**'], + rules: { + '@typescript-eslint/no-extraneous-class': 'off', + 'n/no-extraneous-import': 'off', + 'n/prefer-global/buffer': 'off', + 'n/prefer-global/process': 'off', + 'no-console': 'off', + 'unicorn/prefer-module': 'off', + }, + }, + { + files: ['**/**/playwright.config.ts'], + rules: { + 'n/prefer-global/buffer': 'off', + 'n/prefer-global/process': 'off', + 'no-console': 'off', + }, + }, { files: ['internal/**/**', 'scripts/**/**'], rules: { diff --git a/internal/lint-configs/stylelint-config/package.json b/internal/lint-configs/stylelint-config/package.json index 02491dd0f..da7f4e8d1 100644 --- a/internal/lint-configs/stylelint-config/package.json +++ b/internal/lint-configs/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/stylelint-config", - "version": "5.5.2", + "version": "5.5.3", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json index b2a60da8c..2ad95772b 100644 --- a/internal/node-utils/package.json +++ b/internal/node-utils/package.json @@ -1,6 +1,6 @@ { "name": "@vben/node-utils", - "version": "5.5.2", + "version": "5.5.3", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/node-utils/src/index.ts b/internal/node-utils/src/index.ts index 2e39ccff6..963cb8756 100644 --- a/internal/node-utils/src/index.ts +++ b/internal/node-utils/src/index.ts @@ -2,7 +2,7 @@ export * from './constants'; export * from './date'; export * from './fs'; export * from './git'; -export { add as gitAdd, getStagedFiles } from './git'; +export { getStagedFiles, add as gitAdd } from './git'; export { generatorContentHash } from './hash'; export * from './monorepo'; export { toPosixPath } from './path'; diff --git a/internal/node-utils/src/spinner.ts b/internal/node-utils/src/spinner.ts index f07cc2567..13ad6a426 100644 --- a/internal/node-utils/src/spinner.ts +++ b/internal/node-utils/src/spinner.ts @@ -1,4 +1,6 @@ -import ora, { type Ora } from 'ora'; +import type { Ora } from 'ora'; + +import ora from 'ora'; interface SpinnerOptions { failedText?: string; diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json index 0b64d1d53..9737ab338 100644 --- a/internal/tailwind-config/package.json +++ b/internal/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tailwind-config", - "version": "5.5.2", + "version": "5.5.3", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts index dafaaf91e..93332a3f4 100644 --- a/internal/tailwind-config/src/index.ts +++ b/internal/tailwind-config/src/index.ts @@ -130,7 +130,6 @@ export default { enterAnimationPlugin, ], prefix: '', - safelist: ['dark'], theme: { container: { center: true, @@ -202,6 +201,7 @@ export default { }, }, }, + safelist: ['dark'], } as Config; function createColorsPalette(name: string) { diff --git a/internal/tsconfig/base.json b/internal/tsconfig/base.json index 2b6270d6e..1e45a7843 100644 --- a/internal/tsconfig/base.json +++ b/internal/tsconfig/base.json @@ -4,12 +4,16 @@ "compilerOptions": { "composite": false, "target": "ESNext", + "moduleDetection": "force", "experimentalDecorators": true, + "baseUrl": ".", "module": "ESNext", + "moduleResolution": "node", "resolveJsonModule": true, + "strict": true, "strictNullChecks": true, "noFallthroughCasesInSwitch": true, @@ -19,6 +23,7 @@ "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, + "inlineSources": false, "noEmit": true, "removeComments": true, diff --git a/internal/tsconfig/package.json b/internal/tsconfig/package.json index 8ebfeebfd..efeef619c 100644 --- a/internal/tsconfig/package.json +++ b/internal/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tsconfig", - "version": "5.5.2", + "version": "5.5.3", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json index 6c589a1f1..708d519cd 100644 --- a/internal/vite-config/package.json +++ b/internal/vite-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/vite-config", - "version": "5.5.2", + "version": "5.5.3", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts index 93f654e46..f9808cc74 100644 --- a/internal/vite-config/src/config/application.ts +++ b/internal/vite-config/src/config/application.ts @@ -40,6 +40,8 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) { isBuild, license: true, mode, + nitroMock: !isBuild, + nitroMockOptions: {}, print: !isBuild, printInfoMap: { 'Vben Admin Docs': 'https://doc.vben.pro', diff --git a/internal/vite-config/src/plugins/importmap.ts b/internal/vite-config/src/plugins/importmap.ts index c6154c9ea..0ccda99f8 100644 --- a/internal/vite-config/src/plugins/importmap.ts +++ b/internal/vite-config/src/plugins/importmap.ts @@ -10,11 +10,11 @@ import { minify } from 'html-minifier-terser'; const DEFAULT_PROVIDER = 'jspm.io'; -type pluginOptions = { +type pluginOptions = GeneratorOptions & { debug?: boolean; defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io'; importmap?: Array<{ name: string; range?: string }>; -} & GeneratorOptions; +}; // async function getLatestVersionOfShims() { // const result = await fetch('https://ga.jspm.io/npm:es-module-shims'); diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts index 222903e00..da08db4b8 100644 --- a/internal/vite-config/src/plugins/index.ts +++ b/internal/vite-config/src/plugins/index.ts @@ -23,6 +23,7 @@ import { viteImportMapPlugin } from './importmap'; import { viteInjectAppLoadingPlugin } from './inject-app-loading'; import { viteMetadataPlugin } from './inject-metadata'; import { viteLicensePlugin } from './license'; +import { viteNitroMockPlugin } from './nitro-mock'; import { vitePrintPlugin } from './print'; import { viteVxeTableImportsPlugin } from './vxe-table'; @@ -103,6 +104,8 @@ async function loadApplicationPlugins( importmapOptions, injectAppLoading, license, + nitroMock, + nitroMockOptions, print, printInfoMap, pwa, @@ -139,6 +142,12 @@ async function loadApplicationPlugins( return [await viteVxeTableImportsPlugin()]; }, }, + { + condition: nitroMock, + plugins: async () => { + return [await viteNitroMockPlugin(nitroMockOptions)]; + }, + }, { condition: injectAppLoading, diff --git a/internal/vite-config/src/plugins/inject-app-loading/index.ts b/internal/vite-config/src/plugins/inject-app-loading/index.ts index 9f6e2a5c8..c6a79830d 100644 --- a/internal/vite-config/src/plugins/inject-app-loading/index.ts +++ b/internal/vite-config/src/plugins/inject-app-loading/index.ts @@ -1,3 +1,5 @@ +import type { PluginOption } from 'vite'; + import fs from 'node:fs'; import fsp from 'node:fs/promises'; import { join } from 'node:path'; @@ -5,8 +7,6 @@ import { fileURLToPath } from 'node:url'; import { readPackageJSON } from '@vben/node-utils'; -import { type PluginOption } from 'vite'; - /** * 用于生成将loading样式注入到项目中 * 为多app提供loading样式,无需在每个 app -> index.html单独引入 diff --git a/internal/vite-config/src/plugins/nitro-mock.ts b/internal/vite-config/src/plugins/nitro-mock.ts new file mode 100644 index 000000000..60d7327d9 --- /dev/null +++ b/internal/vite-config/src/plugins/nitro-mock.ts @@ -0,0 +1,98 @@ +import type { PluginOption } from 'vite'; + +import type { NitroMockPluginOptions } from '../typing'; + +import { colors, consola, getPackage } from '@vben/node-utils'; + +import getPort from 'get-port'; +import { build, createDevServer, createNitro, prepare } from 'nitropack'; + +const hmrKeyRe = /^runtimeConfig\.|routeRules\./; + +export const viteNitroMockPlugin = ({ + mockServerPackage = '@vben/backend-mock', + port = 5320, + verbose = true, +}: NitroMockPluginOptions = {}): PluginOption => { + return { + async configureServer(server) { + const availablePort = await getPort({ port }); + if (availablePort !== port) { + return; + } + + const pkg = await getPackage(mockServerPackage); + if (!pkg) { + consola.log( + `Package ${mockServerPackage} not found. Skip mock server.`, + ); + return; + } + + runNitroServer(pkg.dir, port, verbose); + + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + consola.log( + ` ${colors.green('➜')} ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`, + ); + }; + }, + enforce: 'pre', + name: 'vite:mock-server', + }; +}; + +async function runNitroServer(rootDir: string, port: number, verbose: boolean) { + let nitro: any; + const reload = async () => { + if (nitro) { + consola.info('Restarting dev server...'); + if ('unwatch' in nitro.options._c12) { + await nitro.options._c12.unwatch(); + } + await nitro.close(); + } + nitro = await createNitro( + { + dev: true, + preset: 'nitro-dev', + rootDir, + }, + { + c12: { + async onUpdate({ getDiff, newConfig }) { + const diff = getDiff(); + if (diff.length === 0) { + return; + } + verbose && + consola.info( + `Nitro config updated:\n${diff + .map((entry) => ` ${entry.toString()}`) + .join('\n')}`, + ); + await (diff.every((e) => hmrKeyRe.test(e.key)) + ? nitro.updateConfig(newConfig.config) + : reload()); + }, + }, + watch: true, + }, + ); + nitro.hooks.hookOnce('restart', reload); + + const server = createDevServer(nitro); + await server.listen(port, { showURL: false }); + await prepare(nitro); + await build(nitro); + + if (verbose) { + console.log(''); + consola.success(colors.bold(colors.green('Nitro Mock Server started.'))); + } + }; + return await reload(); +} diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts index 026381697..31683cc75 100644 --- a/internal/vite-config/src/typing.ts +++ b/internal/vite-config/src/typing.ts @@ -16,6 +16,23 @@ interface PrintPluginOptions { infoMap?: Record; } +interface NitroMockPluginOptions { + /** + * mock server 包名 + */ + mockServerPackage?: string; + + /** + * mock 服务端口 + */ + port?: number; + + /** + * mock 日志是否打印 + */ + verbose?: boolean; +} + interface ArchiverPluginOptions { /** * 输出文件名 @@ -94,6 +111,10 @@ interface ApplicationPluginOptions extends CommonPluginOptions { injectGlobalScss?: boolean; /** 是否注入版权信息 */ license?: boolean; + /** 是否开启nitro mock */ + nitroMock?: boolean; + /** nitro mock 插件配置 */ + nitroMockOptions?: NitroMockPluginOptions; /** 开启控制台自定义打印 */ print?: boolean; /** 打印插件配置 */ @@ -138,5 +159,6 @@ export type { IImportMap, ImportmapPluginOptions, LibraryPluginOptions, + NitroMockPluginOptions, PrintPluginOptions, }; diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts index d9bc4a477..f34221617 100644 --- a/internal/vite-config/src/utils/env.ts +++ b/internal/vite-config/src/utils/env.ts @@ -67,11 +67,11 @@ async function loadAndConvertEnv( match = 'VITE_', confFiles = getConfFiles(), ): Promise< - { + Partial & { appTitle: string; base: string; port: number; - } & Partial + } > { const envConfig = await loadEnv(match, confFiles); @@ -82,6 +82,7 @@ async function loadAndConvertEnv( VITE_COMPRESS, VITE_DEVTOOLS, VITE_INJECT_APP_LOADING, + VITE_NITRO_MOCK, VITE_PORT, VITE_PWA, VITE_VISUALIZER, @@ -99,6 +100,7 @@ async function loadAndConvertEnv( compressTypes, devtools: getBoolean(VITE_DEVTOOLS), injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING), + nitroMock: getBoolean(VITE_NITRO_MOCK), port: getNumber(VITE_PORT, 5173), pwa: getBoolean(VITE_PWA), visualizer: getBoolean(VITE_VISUALIZER), diff --git a/package.json b/package.json index a53538949..ee9e84c01 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,14 @@ "private": false, "license": "MIT", "author": "FastAPI Practices", + "type": "module", "scripts": { "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", "build:analyze": "turbo build:analyze", "build:antd": "pnpm run build --filter=@vben/web-antd", - "check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type", + "check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell", "check:circular": "vsh check-circular", + "check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress", "check:dep": "vsh check-dep", "check:type": "turbo run typecheck", "clean": "node ./scripts/clean.mjs", @@ -21,12 +23,14 @@ "lint": "vsh lint", "postinstall": "pnpm -r run stub --if-present", "preinstall": "npx only-allow pnpm", + "prepare": "is-ci || husky", "preview": "turbo-run preview", "publint": "vsh publint", "reinstall": "pnpm clean --del-lock && pnpm install", "test:unit": "vitest run --dom", "test:e2e": "turbo run test:e2e", - "update:deps": "npx taze -r -w" + "update:deps": "npx taze -r -w", + "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile" }, "devDependencies": { "@types/node": "catalog:", @@ -44,7 +48,9 @@ "@vue/test-utils": "catalog:", "autoprefixer": "catalog:", "cross-env": "catalog:", + "cspell": "catalog:", "happy-dom": "catalog:", + "husky": "catalog:", "is-ci": "catalog:", "lint-staged": "catalog:", "playwright": "catalog:", @@ -62,7 +68,7 @@ "node": ">=20.10.0", "pnpm": ">=9.12.0" }, - "packageManager": "pnpm@9.15.1", + "packageManager": "pnpm@9.15.6", "pnpm": { "peerDependencyRules": { "allowedVersions": { diff --git a/packages/@core/base/design/package.json b/packages/@core/base/design/package.json index 1d9888ca7..ee1968114 100644 --- a/packages/@core/base/design/package.json +++ b/packages/@core/base/design/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/design", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/design/src/css/ui.css b/packages/@core/base/design/src/css/ui.css index 3a002f2af..f7119c8ba 100644 --- a/packages/@core/base/design/src/css/ui.css +++ b/packages/@core/base/design/src/css/ui.css @@ -81,3 +81,7 @@ transform: translateY(0); } } + +.z-popup { + z-index: var(--popup-z-index); +} diff --git a/packages/@core/base/design/src/design-tokens/dark.css b/packages/@core/base/design/src/design-tokens/dark.css index 2a1d052f6..d34541e94 100644 --- a/packages/@core/base/design/src/design-tokens/dark.css +++ b/packages/@core/base/design/src/design-tokens/dark.css @@ -15,7 +15,11 @@ --card-foreground: 210 40% 98%; /* Background color for popovers such as , , */ - --popover: 222.82deg 8.43% 12.27%; + + /* --popover: 222.82deg 8.43% 12.27%; */ + + /* 弹出层的背景色与主题区域背景色太过接近 */ + --popover: 0 0 14.2%; --popover-foreground: 210 40% 98%; /* Muted backgrounds such as , and */ diff --git a/packages/@core/base/design/src/design-tokens/default.css b/packages/@core/base/design/src/design-tokens/default.css index c81ace7ea..64679f854 100644 --- a/packages/@core/base/design/src/design-tokens/default.css +++ b/packages/@core/base/design/src/design-tokens/default.css @@ -1,7 +1,10 @@ :root { - --font-family: -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, - 'Helvetica Neue', arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + /** 弹出层的基础层级 **/ + --popup-z-index: 2000; + --font-family: + -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue', + arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; /* Default background color of ...etc */ --background: 0 0% 100%; diff --git a/packages/@core/base/icons/package.json b/packages/@core/base/icons/package.json index 144814a04..cd8b38962 100644 --- a/packages/@core/base/icons/package.json +++ b/packages/@core/base/icons/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/icons", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index 21a1beffd..751a80ad7 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -1,9 +1,7 @@ export { ArrowDown, ArrowLeft, - ArrowLeftFromLine as MdiMenuOpen, ArrowLeftToLine, - ArrowRightFromLine as MdiMenuClose, ArrowRightLeft, ArrowRightToLine, ArrowUp, @@ -16,6 +14,8 @@ export { ChevronRight, ChevronsLeft, ChevronsRight, + Circle, + CircleCheckBig, CircleHelp, Copy, CornerDownLeft, @@ -29,6 +29,7 @@ export { Github, Grip, GripVertical, + Menu as IconDefault, Info, InspectionPanel, Languages, @@ -37,7 +38,8 @@ export { LogOut, MailCheck, Maximize, - Menu as IconDefault, + ArrowRightFromLine as MdiMenuClose, + ArrowLeftFromLine as MdiMenuOpen, Menu, Minimize, Minimize2, @@ -47,6 +49,7 @@ export { PanelRight, Pin, PinOff, + Plus, RotateCw, Search, SearchX, diff --git a/packages/@core/base/shared/package.json b/packages/@core/base/shared/package.json index c52c040cc..bc1c6f3e4 100644 --- a/packages/@core/base/shared/package.json +++ b/packages/@core/base/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/shared", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { @@ -45,7 +45,7 @@ "default": "./dist/store.mjs" }, "./global-state": { - "types": "./dist/global-state.d.ts", + "types": "./src/global-state.ts", "development": "./src/global-state.ts", "default": "./dist/global-state.mjs" } diff --git a/packages/@core/base/shared/src/cache/storage-manager.ts b/packages/@core/base/shared/src/cache/storage-manager.ts index 611cdb834..4360621e4 100644 --- a/packages/@core/base/shared/src/cache/storage-manager.ts +++ b/packages/@core/base/shared/src/cache/storage-manager.ts @@ -25,15 +25,6 @@ class StorageManager { : window.sessionStorage; } - /** - * 获取完整的存储键 - * @param key 原始键 - * @returns 带前缀的完整键 - */ - private getFullKey(key: string): string { - return `${this.prefix}-${key}`; - } - /** * 清除所有带前缀的存储项 */ @@ -113,6 +104,15 @@ class StorageManager { console.error(`Error setting item with key "${fullKey}":`, error); } } + + /** + * 获取完整的存储键 + * @param key 原始键 + * @returns 带前缀的完整键 + */ + private getFullKey(key: string): string { + return `${this.prefix}-${key}`; + } } export { StorageManager }; diff --git a/packages/@core/base/shared/src/constants/vben.ts b/packages/@core/base/shared/src/constants/vben.ts index 1759ca0bc..0f6cbbe1b 100644 --- a/packages/@core/base/shared/src/constants/vben.ts +++ b/packages/@core/base/shared/src/constants/vben.ts @@ -1,15 +1,26 @@ /** * @zh_CN GITHUB 仓库地址 */ -export const FBA_UI_GITHUB_URL = 'https://github.com/fastapi-practices/fba_ui'; +export const VBEN_GITHUB_URL = 'https://github.com/vbenjs/vue-vben-admin'; /** * @zh_CN 文档地址 */ -export const FBA_UI_DOC_URL = - 'https://fastapi-practices.github.io/fastapi_best_architecture_docs/'; +export const VBEN_DOC_URL = 'https://doc.vben.pro'; /** - * @zh_CN 首页地址 + * @zh_CN Vben Logo */ -export const FBA_UI_PREVIEW_URL = ''; +export const VBEN_LOGO_URL = + 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp'; + +/** + * @zh_CN Vben Admin 首页地址 + */ +export const VBEN_PREVIEW_URL = 'https://www.vben.pro'; + +export const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro'; + +export const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro'; + +export const VBEN_ANT_PREVIEW_URL = 'https://ant.vben.pro'; diff --git a/packages/@core/base/shared/src/utils/__tests__/util.test.ts b/packages/@core/base/shared/src/utils/__tests__/util.test.ts index 0d87b3181..dd5846514 100644 --- a/packages/@core/base/shared/src/utils/__tests__/util.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/util.test.ts @@ -56,12 +56,6 @@ describe('bindMethods', () => { it('should not bind getter/setter properties', () => { class TestWithGetterSetter { - private _value: string = 'test'; - - constructor() { - bindMethods(this); - } - get value() { return this._value; } @@ -69,6 +63,12 @@ describe('bindMethods', () => { set value(newValue: string) { this._value = newValue; } + + private _value: string = 'test'; + + constructor() { + bindMethods(this); + } } const instance = new TestWithGetterSetter(); diff --git a/packages/@core/base/shared/src/utils/cn.ts b/packages/@core/base/shared/src/utils/cn.ts index 5503cc580..3a2f9773c 100644 --- a/packages/@core/base/shared/src/utils/cn.ts +++ b/packages/@core/base/shared/src/utils/cn.ts @@ -1,4 +1,6 @@ -import { type ClassValue, clsx } from 'clsx'; +import type { ClassValue } from 'clsx'; + +import { clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; function cn(...inputs: ClassValue[]) { diff --git a/packages/@core/base/shared/src/utils/download.ts b/packages/@core/base/shared/src/utils/download.ts index 3e708d3c5..6f38ee5b2 100644 --- a/packages/@core/base/shared/src/utils/download.ts +++ b/packages/@core/base/shared/src/utils/download.ts @@ -31,6 +31,7 @@ export async function downloadFileFromUrl({ if (isChrome || isSafari) { triggerDownload(source, resolveFileName(source, fileName)); + return; } if (!source.includes('?')) { source += '?download'; diff --git a/packages/@core/base/shared/src/utils/state-handler.ts b/packages/@core/base/shared/src/utils/state-handler.ts index cef1bd8f3..438fcda17 100644 --- a/packages/@core/base/shared/src/utils/state-handler.ts +++ b/packages/@core/base/shared/src/utils/state-handler.ts @@ -3,12 +3,6 @@ export class StateHandler { private rejectCondition: (() => void) | null = null; private resolveCondition: (() => void) | null = null; - // 清理 resolve/reject 函数 - private clearPromises() { - this.resolveCondition = null; - this.rejectCondition = null; - } - isConditionTrue(): boolean { return this.condition; } @@ -47,4 +41,10 @@ export class StateHandler { } }); } + + // 清理 resolve/reject 函数 + private clearPromises() { + this.resolveCondition = null; + this.rejectCondition = null; + } } diff --git a/packages/@core/base/typings/package.json b/packages/@core/base/typings/package.json index c71539395..f453b6ec4 100644 --- a/packages/@core/base/typings/package.json +++ b/packages/@core/base/typings/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/typings", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/typings/src/app.d.ts b/packages/@core/base/typings/src/app.d.ts index f783c8b56..02da2466a 100644 --- a/packages/@core/base/typings/src/app.d.ts +++ b/packages/@core/base/typings/src/app.d.ts @@ -1,6 +1,8 @@ type LayoutType = | 'full-content' + | 'header-mixed-nav' | 'header-nav' + | 'header-sidebar-nav' | 'mixed-nav' | 'sidebar-mixed-nav' | 'sidebar-nav'; diff --git a/packages/@core/base/typings/src/basic.d.ts b/packages/@core/base/typings/src/basic.d.ts index d4133aa1b..2f209f02b 100644 --- a/packages/@core/base/typings/src/basic.d.ts +++ b/packages/@core/base/typings/src/basic.d.ts @@ -12,18 +12,18 @@ interface BasicUserInfo { * 头像 */ avatar: string; - /** - * 用户id - */ - id: string; /** * 用户昵称 */ - nickname: string; + realName: string; /** * 用户角色 */ roles?: string[]; + /** + * 用户id + */ + userId: string; /** * 用户名 */ diff --git a/packages/@core/base/typings/src/helper.d.ts b/packages/@core/base/typings/src/helper.d.ts index 8a6385b41..96d4f37ba 100644 --- a/packages/@core/base/typings/src/helper.d.ts +++ b/packages/@core/base/typings/src/helper.d.ts @@ -1,4 +1,4 @@ -import { type ComputedRef, type MaybeRef } from 'vue'; +import type { ComputedRef, MaybeRef } from 'vue'; /** * 深层递归所有属性为可选 @@ -109,6 +109,8 @@ type MergeAll< type EmitType = (name: Name, ...args: any[]) => void; +type MaybePromise = Promise | T; + export type { AnyFunction, AnyNormalFunction, @@ -118,6 +120,7 @@ export type { EmitType, IntervalHandle, MaybeComputedRef, + MaybePromise, MaybeReadonlyRef, Merge, MergeAll, diff --git a/packages/@core/base/typings/src/menu-record.ts b/packages/@core/base/typings/src/menu-record.ts index 9cb18dcca..21f6ab516 100644 --- a/packages/@core/base/typings/src/menu-record.ts +++ b/packages/@core/base/typings/src/menu-record.ts @@ -1,15 +1,14 @@ -import type { RouteRecordRaw } from 'vue-router'; - import type { Component } from 'vue'; +import type { RouteRecordRaw } from 'vue-router'; /** * 扩展路由原始对象 */ -type ExRouteRecordRaw = { +type ExRouteRecordRaw = RouteRecordRaw & { parent?: string; parents?: string[]; path?: any; -} & RouteRecordRaw; +}; interface MenuRecordBadgeRaw { /** diff --git a/packages/@core/base/typings/src/vue-router.d.ts b/packages/@core/base/typings/src/vue-router.d.ts index b95bb33b1..099e8171a 100644 --- a/packages/@core/base/typings/src/vue-router.d.ts +++ b/packages/@core/base/typings/src/vue-router.d.ts @@ -1,6 +1,5 @@ -import type { Router, RouteRecordRaw } from 'vue-router'; - import type { Component } from 'vue'; +import type { Router, RouteRecordRaw } from 'vue-router'; interface RouteMeta { /** @@ -98,6 +97,10 @@ interface RouteMeta { * 菜单可以看到,但是访问会被重定向到403 */ menuVisibleWithForbidden?: boolean; + /** + * 不使用基础布局(仅在顶级生效) + */ + noBasicLayout?: boolean; /** * 在新窗口打开 */ @@ -117,10 +120,13 @@ interface RouteMeta { } // 定义递归类型以将 RouteRecordRaw 的 component 属性更改为 string -type RouteRecordStringComponent = { +type RouteRecordStringComponent = Omit< + RouteRecordRaw, + 'children' | 'component' +> & { children?: RouteRecordStringComponent[]; component: T; -} & Omit; +}; type ComponentRecordType = Record Promise>; diff --git a/packages/@core/composables/package.json b/packages/@core/composables/package.json index 49f972fe0..1a8df3557 100644 --- a/packages/@core/composables/package.json +++ b/packages/@core/composables/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/composables", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/composables/src/use-layout-style.ts b/packages/@core/composables/src/use-layout-style.ts index ac599c4fa..395e9e5a3 100644 --- a/packages/@core/composables/src/use-layout-style.ts +++ b/packages/@core/composables/src/use-layout-style.ts @@ -1,4 +1,7 @@ import type { CSSProperties } from 'vue'; + +import type { VisibleDomRect } from '@vben-core/shared/utils'; + import { computed, onMounted, onUnmounted, ref } from 'vue'; import { @@ -7,10 +10,7 @@ import { CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT, CSS_VARIABLE_LAYOUT_HEADER_HEIGHT, } from '@vben-core/shared/constants'; -import { - getElementVisibleRect, - type VisibleDomRect, -} from '@vben-core/shared/utils'; +import { getElementVisibleRect } from '@vben-core/shared/utils'; import { useCssVar, useDebounceFn } from '@vueuse/core'; diff --git a/packages/@core/composables/src/use-priority-value.ts b/packages/@core/composables/src/use-priority-value.ts index d29e894e3..74b5b5bfd 100644 --- a/packages/@core/composables/src/use-priority-value.ts +++ b/packages/@core/composables/src/use-priority-value.ts @@ -1,4 +1,5 @@ import type { ComputedRef, Ref } from 'vue'; + import { computed, getCurrentInstance, unref, useAttrs, useSlots } from 'vue'; import { diff --git a/packages/@core/composables/src/use-simple-locale/index.ts b/packages/@core/composables/src/use-simple-locale/index.ts index 1c3015bbb..67b8173e5 100644 --- a/packages/@core/composables/src/use-simple-locale/index.ts +++ b/packages/@core/composables/src/use-simple-locale/index.ts @@ -1,8 +1,10 @@ +import type { Locale } from './messages'; + import { computed, ref } from 'vue'; import { createSharedComposable } from '@vueuse/core'; -import { getMessages, type Locale } from './messages'; +import { getMessages } from './messages'; export const useSimpleLocale = createSharedComposable(() => { const currentLocale = ref('zh-CN'); diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap index 5e4df4ef7..d537d22e9 100644 --- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -19,7 +19,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "layout": "sidebar-nav", "locale": "zh-CN", "loginExpiredMode": "page", - "name": "FBA", + "name": "Vben Admin", "preferencesButtonPosition": "auto", "watermark": false, }, @@ -31,9 +31,9 @@ exports[`defaultPreferences immutability test > should not modify the config obj "styleType": "normal", }, "copyright": { - "companyName": "FastAPI Practices", - "companySiteLink": "https://github.com/fastapi-practices", - "date": "2025", + "companyName": "Vben", + "companySiteLink": "https://www.vben.pro", + "date": "2024", "enable": true, "icp": "", "icpLink": "", @@ -51,7 +51,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj }, "logo": { "enable": true, - "source": "https://wu-clan.github.io/picx-images-hosting/logo/fba.png", + "source": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp", }, "navigation": { "accordion": true, @@ -71,7 +71,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "collapsedShowTitle": false, "enable": true, "expandOnHover": true, - "extraCollapse": true, + "extraCollapse": false, "hidden": false, "width": 224, }, @@ -80,6 +80,8 @@ exports[`defaultPreferences immutability test > should not modify the config obj "enable": true, "height": 38, "keepAlive": true, + "maxCount": 0, + "middleClickToClose": false, "persist": true, "showIcon": true, "showMaximize": true, @@ -88,7 +90,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "wheelable": true, }, "theme": { - "builtinType": "deep-green", + "builtinType": "default", "colorDestructive": "hsl(348 100% 61%)", "colorPrimary": "hsl(212 100% 45%)", "colorSuccess": "hsl(144 57% 58%)", diff --git a/packages/@core/preferences/package.json b/packages/@core/preferences/package.json index 26232bf56..1ab498a8c 100644 --- a/packages/@core/preferences/package.json +++ b/packages/@core/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/preferences", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index 5482692b0..2c3f65868 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -19,7 +19,7 @@ const defaultPreferences: Preferences = { layout: 'sidebar-nav', locale: 'zh-CN', loginExpiredMode: 'page', - name: 'FBA', + name: 'Vben Admin', preferencesButtonPosition: 'auto', watermark: false, }, @@ -31,9 +31,9 @@ const defaultPreferences: Preferences = { styleType: 'normal', }, copyright: { - companyName: 'FastAPI Practices', - companySiteLink: 'https://github.com/fastapi-practices', - date: '2025', + companyName: 'Vben', + companySiteLink: 'https://www.vben.pro', + date: '2024', enable: true, icp: '', icpLink: '', @@ -51,7 +51,7 @@ const defaultPreferences: Preferences = { }, logo: { enable: true, - source: 'https://wu-clan.github.io/picx-images-hosting/logo/fba.png', + source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', }, navigation: { accordion: true, @@ -71,7 +71,7 @@ const defaultPreferences: Preferences = { collapsedShowTitle: false, enable: true, expandOnHover: true, - extraCollapse: true, + extraCollapse: false, hidden: false, width: 224, }, @@ -80,6 +80,8 @@ const defaultPreferences: Preferences = { enable: true, height: 38, keepAlive: true, + maxCount: 0, + middleClickToClose: false, persist: true, showIcon: true, showMaximize: true, @@ -88,7 +90,7 @@ const defaultPreferences: Preferences = { wheelable: true, }, theme: { - builtinType: 'deep-green', + builtinType: 'default', colorDestructive: 'hsl(348 100% 61%)', colorPrimary: 'hsl(212 100% 45%)', colorSuccess: 'hsl(144 57% 58%)', diff --git a/packages/@core/preferences/src/constants.ts b/packages/@core/preferences/src/constants.ts index 8ef87a755..430ee3b00 100644 --- a/packages/@core/preferences/src/constants.ts +++ b/packages/@core/preferences/src/constants.ts @@ -38,10 +38,12 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [ primaryColor: 'hsl(240 5.9% 10%)', type: 'zinc', }, + { color: 'hsl(181 84% 32%)', type: 'deep-green', }, + { color: 'hsl(211 91% 39%)', type: 'deep-blue', @@ -54,6 +56,7 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [ color: 'hsl(0 75% 42%)', type: 'rose', }, + { color: 'hsl(0 0% 25%)', darkPrimaryColor: 'hsl(0 0% 98%)', diff --git a/packages/@core/preferences/src/preferences.ts b/packages/@core/preferences/src/preferences.ts index 7851fdbc7..5c9582675 100644 --- a/packages/@core/preferences/src/preferences.ts +++ b/packages/@core/preferences/src/preferences.ts @@ -39,6 +39,90 @@ class PreferenceManager { ); } + clearCache() { + [STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => { + this.cache?.removeItem(key); + }); + } + + public getInitialPreferences() { + return this.initialPreferences; + } + + public getPreferences() { + return readonly(this.state); + } + + /** + * 覆盖偏好设置 + * overrides 要覆盖的偏好设置 + * namespace 命名空间 + */ + public async initPreferences({ namespace, overrides }: InitialOptions) { + // 是否初始化过 + if (this.isInitialized) { + return; + } + // 初始化存储管理器 + this.cache = new StorageManager({ prefix: namespace }); + // 合并初始偏好设置 + this.initialPreferences = merge({}, overrides, defaultPreferences); + + // 加载并合并当前存储的偏好设置 + const mergedPreference = merge( + {}, + // overrides, + this.loadCachedPreferences() || {}, + this.initialPreferences, + ); + + // 更新偏好设置 + this.updatePreferences(mergedPreference); + + this.setupWatcher(); + + this.initPlatform(); + // 标记为已初始化 + this.isInitialized = true; + } + + /** + * 重置偏好设置 + * 偏好设置将被重置为初始值,并从 localStorage 中移除。 + * + * @example + * 假设 initialPreferences 为 { theme: 'light', language: 'en' } + * 当前 state 为 { theme: 'dark', language: 'fr' } + * this.resetPreferences(); + * 调用后,state 将被重置为 { theme: 'light', language: 'en' } + * 并且 localStorage 中的对应项将被移除 + */ + resetPreferences() { + // 将状态重置为初始偏好设置 + Object.assign(this.state, this.initialPreferences); + // 保存重置后的偏好设置 + this.savePreferences(this.state); + // 从存储中移除偏好设置项 + [STORAGE_KEY, STORAGE_KEY_THEME, STORAGE_KEY_LOCALE].forEach((key) => { + this.cache?.removeItem(key); + }); + this.updatePreferences(this.state); + } + + /** + * 更新偏好设置 + * @param updates - 要更新的偏好设置 + */ + public updatePreferences(updates: DeepPartial) { + const mergedState = merge({}, updates, markRaw(this.state)); + + Object.assign(this.state, mergedState); + + // 根据更新的键值执行相应的操作 + this.handleUpdates(updates); + this.savePreferences(this.state); + } + /** * 保存偏好设置 * @param {Preferences} preference - 需要保存的偏好设置 @@ -138,90 +222,6 @@ class PreferenceManager { : dom.classList.remove(COLOR_GRAY); } } - - clearCache() { - [STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => { - this.cache?.removeItem(key); - }); - } - - public getInitialPreferences() { - return this.initialPreferences; - } - - public getPreferences() { - return readonly(this.state); - } - - /** - * 覆盖偏好设置 - * overrides 要覆盖的偏好设置 - * namespace 命名空间 - */ - public async initPreferences({ namespace, overrides }: InitialOptions) { - // 是否初始化过 - if (this.isInitialized) { - return; - } - // 初始化存储管理器 - this.cache = new StorageManager({ prefix: namespace }); - // 合并初始偏好设置 - this.initialPreferences = merge({}, overrides, defaultPreferences); - - // 加载并合并当前存储的偏好设置 - const mergedPreference = merge( - {}, - // overrides, - this.loadCachedPreferences() || {}, - this.initialPreferences, - ); - - // 更新偏好设置 - this.updatePreferences(mergedPreference); - - this.setupWatcher(); - - this.initPlatform(); - // 标记为已初始化 - this.isInitialized = true; - } - - /** - * 重置偏好设置 - * 偏好设置将被重置为初始值,并从 localStorage 中移除。 - * - * @example - * 假设 initialPreferences 为 { theme: 'light', language: 'en' } - * 当前 state 为 { theme: 'dark', language: 'fr' } - * this.resetPreferences(); - * 调用后,state 将被重置为 { theme: 'light', language: 'en' } - * 并且 localStorage 中的对应项将被移除 - */ - resetPreferences() { - // 将状态重置为初始偏好设置 - Object.assign(this.state, this.initialPreferences); - // 保存重置后的偏好设置 - this.savePreferences(this.state); - // 从存储中移除偏好设置项 - [STORAGE_KEY, STORAGE_KEY_THEME, STORAGE_KEY_LOCALE].forEach((key) => { - this.cache?.removeItem(key); - }); - this.updatePreferences(this.state); - } - - /** - * 更新偏好设置 - * @param updates - 要更新的偏好设置 - */ - public updatePreferences(updates: DeepPartial) { - const mergedState = merge({}, updates, markRaw(this.state)); - - Object.assign(this.state, mergedState); - - // 根据更新的键值执行相应的操作 - this.handleUpdates(updates); - this.savePreferences(this.state); - } } const preferencesManager = new PreferenceManager(); diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index f8b35242c..9c7e87bc4 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -168,6 +168,10 @@ interface TabbarPreferences { height: number; /** 开启标签页缓存功能 */ keepAlive: boolean; + /** 限制最大数量 */ + maxCount: number; + /** 是否点击中键时关闭标签 */ + middleClickToClose: boolean; /** 是否持久化标签 */ persist: boolean; /** 是否开启多标签页图标 */ diff --git a/packages/@core/preferences/src/use-preferences.ts b/packages/@core/preferences/src/use-preferences.ts index fc1e2de5f..d931f7582 100644 --- a/packages/@core/preferences/src/use-preferences.ts +++ b/packages/@core/preferences/src/use-preferences.ts @@ -82,6 +82,20 @@ function usePreferences() { () => appPreferences.value.layout === 'header-nav', ); + /** + * @zh_CN 是否为头部混合导航模式 + */ + const isHeaderMixedNav = computed( + () => appPreferences.value.layout === 'header-mixed-nav', + ); + + /** + * @zh_CN 是否为顶部通栏+侧边导航模式 + */ + const isHeaderSidebarNav = computed( + () => appPreferences.value.layout === 'header-sidebar-nav', + ); + /** * @zh_CN 是否为混合导航模式 */ @@ -93,7 +107,13 @@ function usePreferences() { * @zh_CN 是否包含侧边导航模式 */ const isSideMode = computed(() => { - return isMixedNav.value || isSideMixedNav.value || isSideNav.value; + return ( + isMixedNav.value || + isSideMixedNav.value || + isSideNav.value || + isHeaderMixedNav.value || + isHeaderSidebarNav.value + ); }); const sidebarCollapsed = computed(() => { @@ -214,7 +234,9 @@ function usePreferences() { globalSearchShortcutKey, isDark, isFullContent, + isHeaderMixedNav, isHeaderNav, + isHeaderSidebarNav, isMixedNav, isMobile, isSideMixedNav, diff --git a/packages/@core/ui-kit/form-ui/package.json b/packages/@core/ui-kit/form-ui/package.json index a3f42ded6..36a0596df 100644 --- a/packages/@core/ui-kit/form-ui/package.json +++ b/packages/@core/ui-kit/form-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/form-ui", - "version": "5.5.2", + "version": "5.5.3", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue index b9a878e8a..fb90a5e42 100644 --- a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue +++ b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue @@ -3,12 +3,7 @@ import { computed, toRaw, unref, watch } from 'vue'; import { useSimpleLocale } from '@vben-core/composables'; import { VbenExpandableArrow } from '@vben-core/shadcn-ui'; -import { - cn, - formatDate, - isFunction, - triggerWindowResize, -} from '@vben-core/shared/utils'; +import { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils'; import { COMPONENT_MAP } from '../config'; import { injectFormProps } from '../use-form-context'; @@ -58,7 +53,7 @@ async function handleSubmit(e: Event) { return; } - const values = handleRangeTimeValue(toRaw(form.values)); + const values = toRaw(await unref(rootProps).formApi?.getValues()); await unref(rootProps).handleSubmit?.(values); } @@ -67,13 +62,7 @@ async function handleReset(e: Event) { e?.stopPropagation(); const props = unref(rootProps); - const values = toRaw(form.values); - // 清理时间字段 - props.fieldMappingTime && - props.fieldMappingTime.forEach(([_, [startTimeKey, endTimeKey]]) => { - delete values[startTimeKey]; - delete values[endTimeKey]; - }); + const values = toRaw(props.formApi?.getValues()); if (isFunction(props.handleReset)) { await props.handleReset?.(values); @@ -82,44 +71,6 @@ async function handleReset(e: Event) { } } -function handleRangeTimeValue(values: Record) { - const fieldMappingTime = unref(rootProps).fieldMappingTime; - - if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { - return values; - } - - fieldMappingTime.forEach( - ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => { - if (startTimeKey && endTimeKey && values[field] === null) { - delete values[startTimeKey]; - delete values[endTimeKey]; - } - - if (!values[field]) { - delete values[field]; - return; - } - - const [startTime, endTime] = values[field]; - const [startTimeFormat, endTimeFormat] = Array.isArray(format) - ? format - : [format, format]; - - values[startTimeKey] = startTime - ? formatDate(startTime, startTimeFormat) - : undefined; - values[endTimeKey] = endTime - ? formatDate(endTime, endTimeFormat) - : undefined; - - delete values[field]; - }, - ); - - return values; -} - watch( () => collapsed.value, () => { diff --git a/packages/@core/ui-kit/form-ui/src/config.ts b/packages/@core/ui-kit/form-ui/src/config.ts index 9e82cffe1..645f51f3b 100644 --- a/packages/@core/ui-kit/form-ui/src/config.ts +++ b/packages/@core/ui-kit/form-ui/src/config.ts @@ -1,10 +1,11 @@ +import type { Component } from 'vue'; + import type { BaseFormComponentType, FormCommonConfig, VbenFormAdapterOptions, } from './types'; -import type { Component } from 'vue'; import { h } from 'vue'; import { diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index 8de5c4056..db09d7c71 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -1,4 +1,3 @@ -import type { Recordable } from '@vben-core/typings'; import type { FormState, GenericObject, @@ -6,6 +5,8 @@ import type { ValidationOptions, } from 'vee-validate'; +import type { Recordable } from '@vben-core/typings'; + import type { FormActions, FormSchema, VbenFormProps } from './types'; import { toRaw } from 'vue'; @@ -14,6 +15,7 @@ import { Store } from '@vben-core/shared/store'; import { bindMethods, createMerge, + formatDate, isDate, isDayjsObject, isFunction, @@ -45,20 +47,20 @@ function getDefaultState(): VbenFormProps { } export class FormApi { - // 最后一次点击提交时的表单值 - private latestSubmissionValues: null | Recordable = null; - private prevState: null | VbenFormProps = null; - // private api: Pick; public form = {} as FormActions; isMounted = false; public state: null | VbenFormProps = null; - stateHandler: StateHandler; public store: Store; + // 最后一次点击提交时的表单值 + private latestSubmissionValues: null | Recordable = null; + + private prevState: null | VbenFormProps = null; + constructor(options: VbenFormProps = {}) { const { ...storeState } = options; @@ -83,40 +85,6 @@ export class FormApi { bindMethods(this); } - private async getForm() { - if (!this.isMounted) { - // 等待form挂载 - await this.stateHandler.waitForCondition(); - } - if (!this.form?.meta) { - throw new Error(' is not mounted'); - } - return this.form; - } - - private updateState() { - const currentSchema = this.state?.schema ?? []; - const prevSchema = this.prevState?.schema ?? []; - // 进行了删除schema操作 - if (currentSchema.length < prevSchema.length) { - const currentFields = new Set( - currentSchema.map((item) => item.fieldName), - ); - const deletedSchema = prevSchema.filter( - (item) => !currentFields.has(item.fieldName), - ); - - for (const schema of deletedSchema) { - this.form?.setFieldValue(schema.fieldName, undefined); - } - } - } - - // 如果需要多次更新状态,可以使用 batch 方法 - batchStore(cb: () => void) { - this.store.batch(cb); - } - getLatestSubmissionValues() { return this.latestSubmissionValues || {}; } @@ -125,9 +93,9 @@ export class FormApi { return this.state; } - async getValues() { + async getValues>() { const form = await this.getForm(); - return form.values; + return (form.values ? this.handleRangeTimeValue(form.values) : {}) as T; } async isFieldValid(fieldName: string) { @@ -150,12 +118,11 @@ export class FormApi { try { const results = await Promise.all( chain.map(async (api) => { - const form = await api.getForm(); const validateResult = await api.validate(); if (!validateResult.valid) { return; } - const rawValues = toRaw(form.values || {}); + const rawValues = toRaw((await api.getValues()) || {}); return rawValues; }), ); @@ -180,7 +147,9 @@ export class FormApi { if (!this.isMounted) { Object.assign(this.form, formActions); this.stateHandler.setConditionTrue(); - this.setLatestSubmissionValues({ ...toRaw(this.form.values) }); + this.setLatestSubmissionValues({ + ...toRaw(this.handleRangeTimeValue(this.form.values)), + }); this.isMounted = true; } } @@ -286,7 +255,7 @@ export class FormApi { e?.stopPropagation(); const form = await this.getForm(); await form.submitForm(); - const rawValues = toRaw(form.values || {}); + const rawValues = toRaw(await this.getValues()); await this.state?.handleSubmit?.(rawValues); return rawValues; @@ -363,4 +332,81 @@ export class FormApi { } return validateResult; } + + private async getForm() { + if (!this.isMounted) { + // 等待form挂载 + await this.stateHandler.waitForCondition(); + } + if (!this.form?.meta) { + throw new Error(' is not mounted'); + } + return this.form; + } + + private handleRangeTimeValue = (originValues: Record) => { + const values = { ...originValues }; + const fieldMappingTime = this.state?.fieldMappingTime; + + if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { + return values; + } + + fieldMappingTime.forEach( + ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => { + if (startTimeKey && endTimeKey && values[field] === null) { + Reflect.deleteProperty(values, startTimeKey); + Reflect.deleteProperty(values, endTimeKey); + // delete values[startTimeKey]; + // delete values[endTimeKey]; + } + + if (!values[field]) { + Reflect.deleteProperty(values, field); + // delete values[field]; + return; + } + + const [startTime, endTime] = values[field]; + if (format === null) { + values[startTimeKey] = startTime; + values[endTimeKey] = endTime; + } else if (isFunction(format)) { + values[startTimeKey] = format(startTime, startTimeKey); + values[endTimeKey] = format(endTime, endTimeKey); + } else { + const [startTimeFormat, endTimeFormat] = Array.isArray(format) + ? format + : [format, format]; + + values[startTimeKey] = startTime + ? formatDate(startTime, startTimeFormat) + : undefined; + values[endTimeKey] = endTime + ? formatDate(endTime, endTimeFormat) + : undefined; + } + // delete values[field]; + Reflect.deleteProperty(values, field); + }, + ); + return values; + }; + + private updateState() { + const currentSchema = this.state?.schema ?? []; + const prevSchema = this.prevState?.schema ?? []; + // 进行了删除schema操作 + if (currentSchema.length < prevSchema.length) { + const currentFields = new Set( + currentSchema.map((item) => item.fieldName), + ); + const deletedSchema = prevSchema.filter( + (item) => !currentFields.has(item.fieldName), + ); + for (const schema of deletedSchema) { + this.form?.setFieldValue?.(schema.fieldName, undefined); + } + } + } } diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue index 50c9c17d9..191c30197 100644 --- a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue +++ b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue @@ -41,12 +41,13 @@ const { label, labelClass, labelWidth, + modelPropName, renderComponentContent, rules, } = defineProps< - { + Props & { commonComponentProps: MaybeComponentProps; - } & Props + } >(); const { componentBindEventMap, componentMap, isVertical } = useFormContext(); @@ -192,7 +193,7 @@ const fieldProps = computed(() => { const rules = fieldRules.value; return { keepValue: true, - label, + label: isString(label) ? label : '', ...(rules ? { rules } : {}), ...(formFieldProps as Record), }; @@ -202,9 +203,9 @@ function fieldBindEvent(slotProps: Record) { const modelValue = slotProps.componentField.modelValue; const handler = slotProps.componentField['onUpdate:modelValue']; - const bindEventField = isString(component) - ? componentBindEventMap.value?.[component] - : null; + const bindEventField = + modelPropName || + (isString(component) ? componentBindEventMap.value?.[component] : null); let value = modelValue; // antd design 的一些组件会传递一个 event 对象 @@ -284,7 +285,7 @@ function autofocus() { 'pb-6': !compact, 'pb-2': compact, }" - class="flex" + class="relative flex" v-bind="$attrs" > -
- - - +
+
+ + - - - - - - -
- + }" + > + + + + + + +
+ + +
+ +
+ + +
- - - - - +
diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue b/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue index 459d5ee56..9af4c133d 100644 --- a/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue +++ b/packages/@core/ui-kit/form-ui/src/form-render/form-label.vue @@ -1,10 +1,18 @@ + diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts b/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts index fbf251c20..0080f23be 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts @@ -1,7 +1,7 @@ -import type { BreadcrumbStyleType } from '@vben-core/typings'; - import type { Component } from 'vue'; +import type { BreadcrumbStyleType } from '@vben-core/typings'; + export interface IBreadcrumb { icon?: Component | string; isHome?: boolean; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/button-group.vue b/packages/@core/ui-kit/shadcn-ui/src/components/button/button-group.vue new file mode 100644 index 000000000..035e88c4f --- /dev/null +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/button-group.vue @@ -0,0 +1,98 @@ + + + + diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts b/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts index 883974239..f91e9d05a 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts @@ -1,9 +1,9 @@ import type { AsTag } from 'radix-vue'; -import type { ButtonVariants, ButtonVariantSize } from '../../ui'; - import type { Component } from 'vue'; +import type { ButtonVariants, ButtonVariantSize } from '../../ui'; + export interface VbenButtonProps { /** * The element or component this component should render as. Can be overwrite by `asChild` @@ -22,3 +22,21 @@ export interface VbenButtonProps { size?: ButtonVariantSize; variant?: ButtonVariants; } + +export type CustomRenderType = (() => Component | string) | string; + +export type ValueType = boolean | number | string; + +export interface VbenButtonGroupProps + extends Pick { + beforeChange?: ( + value: ValueType, + isChecked: boolean, + ) => boolean | PromiseLike | undefined; + btnClass?: any; + gap?: number; + multiple?: boolean; + options?: { label: CustomRenderType; value: ValueType }[]; + showIcon?: boolean; + size?: 'large' | 'middle' | 'small'; +} diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue b/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue new file mode 100644 index 000000000..a7477f076 --- /dev/null +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue @@ -0,0 +1,163 @@ + + + diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/index.ts b/packages/@core/ui-kit/shadcn-ui/src/components/button/index.ts index 4afd1c25e..aa3d9ef84 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/button/index.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/index.ts @@ -1,3 +1,5 @@ export type * from './button'; +export { default as VbenButtonGroup } from './button-group.vue'; export { default as VbenButton } from './button.vue'; +export { default as VbenCheckButtonGroup } from './check-button-group.vue'; export { default as VbenIconButton } from './icon-button.vue'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue b/packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue index 4cc4119ae..a9bfed6e3 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue @@ -1,11 +1,12 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/AvatarImage.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/AvatarImage.vue index bac84a5c4..787eeeb7e 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/AvatarImage.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/AvatarImage.vue @@ -1,5 +1,7 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/avatar.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/avatar.ts index b99641556..8aae81f49 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/avatar.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/avatar/avatar.ts @@ -1,4 +1,6 @@ -import { cva, type VariantProps } from 'class-variance-authority'; +import type { VariantProps } from 'class-variance-authority'; + +import { cva } from 'class-variance-authority'; export const avatarVariant = cva( 'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden', diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/badge/Badge.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/badge/Badge.vue index 729dc9dc3..818d40452 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/badge/Badge.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/badge/Badge.vue @@ -1,7 +1,9 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue index 7895d0106..03e67803b 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue @@ -1,9 +1,11 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuItem.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuItem.vue index ed11878d4..aab42115d 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuItem.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuItem.vue @@ -1,17 +1,14 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuRadioGroup.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuRadioGroup.vue index bfed356d5..b757dd516 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuRadioGroup.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuRadioGroup.vue @@ -1,11 +1,11 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue index 9a8d73962..e7ef1b589 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue @@ -1,4 +1,6 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogScrollContent.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogScrollContent.vue index 99ab7b111..95908abe5 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogScrollContent.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogScrollContent.vue @@ -1,4 +1,6 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenu.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenu.vue index 6a4328e63..12c20692b 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenu.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenu.vue @@ -1,10 +1,7 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuItem.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuItem.vue index 3aac66780..c8836d47b 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuItem.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuItem.vue @@ -1,16 +1,14 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/form/index.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/form/index.ts index 3e8467b25..1d45b2b4d 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/form/index.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/form/index.ts @@ -5,7 +5,7 @@ export { default as FormLabel } from './FormLabel.vue'; export { default as FormMessage } from './FormMessage.vue'; export { FORM_ITEM_INJECTION_KEY } from './injectionKeys'; export { + Form, Field as FormField, FieldArray as FormFieldArray, - Form, } from 'vee-validate'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCard.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCard.vue index 6c4ac04fc..f960699b9 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCard.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCard.vue @@ -1,10 +1,7 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue index a4169355d..ba6890a86 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue @@ -1,11 +1,13 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/RadioGroup.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/RadioGroup.vue index 35c48701b..0bdcafc3c 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/RadioGroup.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/RadioGroup.vue @@ -1,16 +1,13 @@ diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectLabel.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectLabel.vue index 598426865..0dd694f80 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectLabel.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectLabel.vue @@ -1,9 +1,11 @@