1193 Commits

Author SHA1 Message Date
李琦
beba5e8feb feat: 订单双视图、工作台日历组件、医生排班管理与挂号语音提醒
- 订单双视图:商品订单/处方/挂号列表新增卡片视图(CardList)、视图切换组件 view-mode-switch、stat-islands 统计岛、constants 字典
- 工作台:新增工作日历 Widget、待办日历、即将到访预约、公告滚动 NoticeTicker、日历面板 CalendarPanel
- 医生排班:新增排班 API、ScheduleDrawer 抽屉、schedule-calendar 组件、门店设置弹窗
- 日志与通知:新增排班变更日志页、排班变更通知视图
- 挂号提醒:新增挂号语音播报资源与 register-notify 工具
- 桌面端:新增 apps/desktop 壳及 desktop 工具方法
- 其他:处方/订单导出、聊天设置与 WebSocket 等小幅优化
2026-08-14 17:46:50 +08:00
李琦
f15bf10553 feat: 优化了若干接口页面,可用性存疑、科室管理、处方发送成功页面 2026-08-12 12:43:30 +08:00
李琦
f622743661 feat: 优化了若干接口页面,可用性存疑 2026-08-12 08:57:15 +08:00
李琦
6db736a6f4 feat:
1. 更新框架
2. 修复框架更新后导致的问题
3. 消息系统升级
2026-08-11 13:30:21 +08:00
李琦
b82754e36b feat:
1. 更新框架
2. 修复框架更新后导致的问题
3. 消息系统升级
2026-08-10 20:08:50 +08:00
李琦
f0a33a628b feat:
1. 更新框架
2. 修复框架更新后导致的问题
2026-08-10 18:44:39 +08:00
李琦
3547c53f89 feat:
1. 更新框架
2026-08-10 17:56:36 +08:00
李琦
82b10bd122 Merge remote-tracking branch 'vben/main' into vben-dev
# Conflicts:
#	.gitignore
#	.husky/pre-commit
#	.vscode/extensions.json
#	.vscode/settings.json
#	README.ja-JP.md
#	README.md
#	README.zh-CN.md
#	apps/backend-mock/api/auth/codes.ts
#	apps/backend-mock/api/auth/login.post.ts
#	apps/backend-mock/api/auth/logout.post.ts
#	apps/backend-mock/api/auth/refresh.post.ts
#	apps/backend-mock/api/menu/all.ts
#	apps/backend-mock/api/status.ts
#	apps/backend-mock/api/table/list.ts
#	apps/backend-mock/api/test.get.ts
#	apps/backend-mock/api/test.post.ts
#	apps/backend-mock/api/user/info.ts
#	apps/backend-mock/middleware/1.api.ts
#	apps/backend-mock/nitro.config.ts
#	apps/backend-mock/package.json
#	apps/backend-mock/routes/[...].ts
#	apps/backend-mock/tsconfig.json
#	apps/backend-mock/utils/cookie-utils.ts
#	apps/backend-mock/utils/jwt-utils.ts
#	apps/backend-mock/utils/mock-data.ts
#	apps/backend-mock/utils/response.ts
#	apps/web-antd/.env
#	apps/web-antd/src/adapter/component/index.ts
#	apps/web-antd/src/adapter/form.ts
#	apps/web-antd/src/api/request.ts
#	apps/web-antd/src/layouts/auth.vue
#	apps/web-antd/src/layouts/basic.vue
#	apps/web-antd/src/preferences.ts
#	apps/web-antd/src/router/guard.ts
#	apps/web-antd/src/router/routes/modules/demos.ts
#	apps/web-antd/src/store/auth.ts
#	apps/web-antd/src/views/demos/antd/index.vue
#	apps/web-antdv-next/vite.config.ts
#	apps/web-ele/.env
#	apps/web-ele/index.html
#	apps/web-ele/package.json
#	apps/web-ele/src/adapter/component/index.ts
#	apps/web-ele/src/adapter/form.ts
#	apps/web-ele/src/adapter/vxe-table.ts
#	apps/web-ele/src/api/request.ts
#	apps/web-ele/src/bootstrap.ts
#	apps/web-ele/src/layouts/auth.vue
#	apps/web-ele/src/layouts/basic.vue
#	apps/web-ele/src/locales/index.ts
#	apps/web-ele/src/locales/langs/en-US/demos.json
#	apps/web-ele/src/locales/langs/en-US/page.json
#	apps/web-ele/src/locales/langs/zh-CN/demos.json
#	apps/web-ele/src/locales/langs/zh-CN/page.json
#	apps/web-ele/src/preferences.ts
#	apps/web-ele/src/router/guard.ts
#	apps/web-ele/src/router/routes/core.ts
#	apps/web-ele/src/router/routes/modules/dashboard.ts
#	apps/web-ele/src/router/routes/modules/demos.ts
#	apps/web-ele/src/router/routes/modules/vben.ts
#	apps/web-ele/src/store/auth.ts
#	apps/web-ele/src/views/_core/authentication/code-login.vue
#	apps/web-ele/src/views/_core/authentication/forget-password.vue
#	apps/web-ele/src/views/_core/authentication/register.vue
#	apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue
#	apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue
#	apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue
#	apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue
#	apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue
#	apps/web-ele/src/views/dashboard/analytics/index.vue
#	apps/web-ele/src/views/demos/element/index.vue
#	apps/web-ele/src/views/demos/form/basic.vue
#	apps/web-ele/tsconfig.json
#	apps/web-ele/tsconfig.node.json
#	apps/web-naive/.env
#	apps/web-naive/index.html
#	apps/web-naive/package.json
#	apps/web-naive/src/adapter/component/index.ts
#	apps/web-naive/src/adapter/form.ts
#	apps/web-naive/src/adapter/vxe-table.ts
#	apps/web-naive/src/api/request.ts
#	apps/web-naive/src/bootstrap.ts
#	apps/web-naive/src/layouts/auth.vue
#	apps/web-naive/src/layouts/basic.vue
#	apps/web-naive/src/locales/index.ts
#	apps/web-naive/src/locales/langs/en-US/demos.json
#	apps/web-naive/src/locales/langs/en-US/page.json
#	apps/web-naive/src/locales/langs/zh-CN/demos.json
#	apps/web-naive/src/locales/langs/zh-CN/page.json
#	apps/web-naive/src/preferences.ts
#	apps/web-naive/src/router/guard.ts
#	apps/web-naive/src/router/routes/core.ts
#	apps/web-naive/src/router/routes/modules/dashboard.ts
#	apps/web-naive/src/router/routes/modules/demos.ts
#	apps/web-naive/src/router/routes/modules/vben.ts
#	apps/web-naive/src/store/auth.ts
#	apps/web-naive/src/views/_core/authentication/code-login.vue
#	apps/web-naive/src/views/_core/authentication/forget-password.vue
#	apps/web-naive/src/views/_core/authentication/register.vue
#	apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue
#	apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue
#	apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue
#	apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue
#	apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue
#	apps/web-naive/src/views/dashboard/analytics/index.vue
#	apps/web-naive/src/views/demos/form/basic.vue
#	apps/web-naive/src/views/demos/naive/index.vue
#	apps/web-naive/tsconfig.json
#	apps/web-naive/tsconfig.node.json
#	apps/web-naive/vite.config.ts
#	docs/.vitepress/components/demo-preview.vue
#	docs/.vitepress/components/preview-group.vue
#	docs/.vitepress/config/en.mts
#	docs/.vitepress/config/index.mts
#	docs/.vitepress/config/plugins/demo-preview.ts
#	docs/.vitepress/config/shared.mts
#	docs/.vitepress/config/zh.mts
#	docs/.vitepress/theme/components/site-layout.vue
#	docs/.vitepress/theme/components/vben-contributors.vue
#	docs/.vitepress/theme/styles/variables.css
#	docs/package.json
#	docs/src/_env/adapter/component.ts
#	docs/src/_env/adapter/form.ts
#	docs/src/_env/adapter/vxe-table.ts
#	docs/src/commercial/community.md
#	docs/src/components/common-ui/vben-count-to-animator.md
#	docs/src/components/common-ui/vben-drawer.md
#	docs/src/components/common-ui/vben-form.md
#	docs/src/components/common-ui/vben-modal.md
#	docs/src/components/common-ui/vben-vxe-table.md
#	docs/src/components/layout-ui/page.md
#	docs/src/demos/vben-drawer/auto-height/drawer.vue
#	docs/src/demos/vben-drawer/basic/index.vue
#	docs/src/demos/vben-drawer/dynamic/index.vue
#	docs/src/demos/vben-drawer/shared-data/drawer.vue
#	docs/src/demos/vben-drawer/shared-data/index.vue
#	docs/src/demos/vben-form/api/index.vue
#	docs/src/demos/vben-form/basic/index.vue
#	docs/src/demos/vben-form/custom/index.vue
#	docs/src/demos/vben-form/dynamic/index.vue
#	docs/src/demos/vben-form/query/index.vue
#	docs/src/demos/vben-form/rules/index.vue
#	docs/src/demos/vben-modal/auto-height/modal.vue
#	docs/src/demos/vben-modal/basic/index.vue
#	docs/src/demos/vben-modal/dynamic/index.vue
#	docs/src/demos/vben-modal/shared-data/index.vue
#	docs/src/demos/vben-modal/shared-data/modal.vue
#	docs/src/demos/vben-vxe-table/basic/index.vue
#	docs/src/demos/vben-vxe-table/custom-cell/index.vue
#	docs/src/demos/vben-vxe-table/edit-row/index.vue
#	docs/src/demos/vben-vxe-table/fixed/index.vue
#	docs/src/demos/vben-vxe-table/form/index.vue
#	docs/src/demos/vben-vxe-table/remote/index.vue
#	docs/src/demos/vben-vxe-table/tree/index.vue
#	docs/src/demos/vben-vxe-table/virtual/index.vue
#	docs/src/en/guide/essentials/development.md
#	docs/src/en/guide/essentials/external-module.md
#	docs/src/en/guide/essentials/route.md
#	docs/src/en/guide/essentials/server.md
#	docs/src/en/guide/essentials/settings.md
#	docs/src/en/guide/in-depth/access.md
#	docs/src/en/guide/in-depth/loading.md
#	docs/src/en/guide/in-depth/locale.md
#	docs/src/en/guide/in-depth/theme.md
#	docs/src/en/guide/introduction/quick-start.md
#	docs/src/en/guide/introduction/thin.md
#	docs/src/en/guide/introduction/vben.md
#	docs/src/en/guide/other/faq.md
#	docs/src/en/guide/project/dir.md
#	docs/src/en/guide/project/standard.md
#	docs/src/en/guide/project/tailwindcss.md
#	docs/src/en/index.md
#	docs/src/friend-links/index.md
#	docs/src/guide/essentials/build.md
#	docs/src/guide/essentials/development.md
#	docs/src/guide/essentials/external-module.md
#	docs/src/guide/essentials/route.md
#	docs/src/guide/essentials/server.md
#	docs/src/guide/essentials/settings.md
#	docs/src/guide/in-depth/access.md
#	docs/src/guide/in-depth/check-updates.md
#	docs/src/guide/in-depth/loading.md
#	docs/src/guide/in-depth/locale.md
#	docs/src/guide/in-depth/theme.md
#	docs/src/guide/introduction/quick-start.md
#	docs/src/guide/introduction/thin.md
#	docs/src/guide/introduction/vben.md
#	docs/src/guide/introduction/why.md
#	docs/src/guide/other/faq.md
#	docs/src/guide/project/dir.md
#	docs/src/guide/project/standard.md
#	docs/src/guide/project/tailwindcss.md
#	docs/src/index.md
#	docs/tsconfig.json
#	package.json
#	packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue
#	packages/constants/src/core.ts
#	packages/effects/common-ui/src/components/index.ts
#	packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue
#	packages/effects/common-ui/src/ui/dashboard/workbench/workbench-header.vue
#	packages/effects/common-ui/src/ui/dashboard/workbench/workbench-project.vue
#	packages/effects/layouts/src/authentication/authentication.vue
#	packages/effects/layouts/src/basic/layout.vue
#	packages/effects/layouts/src/widgets/notification/notification.vue
#	packages/effects/layouts/src/widgets/notification/types.ts
#	packages/icons/src/iconify/index.ts
#	packages/icons/src/svg/index.ts
#	packages/stores/src/modules/user.ts
#	playground/src/adapter/component/index.ts
#	playground/src/store/auth.ts
#	pnpm-lock.yaml
#	vben-admin.code-workspace
2026-08-10 16:55:17 +08:00
李琦
b1f223554b feat:
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
1. 创建就诊人功能增强
2. 优化交互细节
2026-08-10 15:33:08 +08:00
李琦
871982fc07 feat:
1. 金方
2026-08-09 15:27:27 +08:00
李琦
689a0f64a6 feat:
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
1. 医生线下接诊创建就诊人、患者端扫码认领就诊人
2. 生成处方后医生代付码
2026-08-09 14:36:02 +08:00
李琦
335b3535d5 feat: 金方相关
feat: 患者信息身份证改成非必填
2026-08-08 18:36:26 +08:00
李琦
c4e833a1c4 feat: 金方相关 2026-08-08 14:53:02 +08:00
李琦
7697af2619 feat: 修复部分功能(健康信息、VIP、医生端健康信息导入) 2026-08-08 11:24:46 +08:00
李琦
396a6e5b8b feat: 修复部分功能(健康信息、VIP、医生端健康信息导入) 2026-08-08 10:33:49 +08:00
李琦
7a504433da fix: 接诊UI 2026-08-07 20:24:49 +08:00
李琦
33dcd3f5d8 fix: 接诊UI 2026-08-07 14:37:23 +08:00
李琦
efd7e8ffc8 feat: 药品别名 2026-08-07 14:25:19 +08:00
李琦
5b9f3ec3ed feat: 修复配送仓库的报备字段 2026-08-07 13:24:29 +08:00
李琦
e434aa9534 feat: 修复配送仓库的报备字段 2026-08-07 13:22:58 +08:00
李琦
32eb96b4ad feat: 中药导入模块修复、部门财务的优化、队列的优化管理 2026-08-07 13:17:07 +08:00
李琦
9f37802ba7 feat: 中药导入模块修复、部门财务的优化、队列的优化管理 2026-08-07 09:48:07 +08:00
JyQAQ
e8d35a7471 fix(@vben/styles): 修复弹窗内 tooltip 层级被遮挡问题 (#8240)
将 antd-tooltip 的 z-index 提升至 2001,避免在 Modal/弹窗等高 z-index 容器内的 tooltip 被遮挡
2026-08-07 06:08:20 +08:00
yshbyy
f038166681 chore: 更新.gitattributes文件配置 (#8178)
git 不支持 Shell 的花括号展开语法
2026-08-07 05:16:35 +08:00
dependabot[bot]
88cc837f6a build(deps): bump actions/stale from 10 to 11 (#8227)
Bumps [actions/stale](https://github.com/actions/stale) from 10 to 11.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v10...v11)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 05:15:36 +08:00
金毛88
82d790a6ab Merge branch 'fork/dream-weave/fix/popup-route-reentry' 2026-08-07 05:14:52 +08:00
金毛88
9c3ba53990 Merge branch 'fork/ming4762/fix-transition-20260805' 2026-08-07 05:11:42 +08:00
金毛88
cb707c8c78 Merge branch 'fork/LayHuts/fix/lint-format' 2026-08-07 05:09:58 +08:00
李琦
cfc8390573 feat: 中药导入模块修复、部门财务的优化 2026-08-06 20:15:42 +08:00
李琦
174693f6db feat: 病历模块优化、AI辅助问诊、接入deepseek 2026-08-06 17:56:28 +08:00
Dream
98761dce98 fix(@vben-core/popup-ui): stabilize content popups across routes
Mount in-content drawers and modals to the persistent main content element.

Initialize drawer open state when mounted after the API opens.

Fixes vbenjs/vue-vben-admin#8210
2026-08-06 10:17:14 +08:00
李琦
c1bc234509 feat: 病历模块优化、AI辅助问诊 2026-08-06 10:08:02 +08:00
李琦
dc9f9c5c13 feat: 病历模块优化、AI辅助问诊 2026-08-06 08:14:33 +08:00
zhongming4762
d3dc081b5d perf: 优化路由动画切换 1、切换出现双页面问题 2、切换出现横向和纵向滚动条问题 2026-08-05 19:14:12 +08:00
李琦
0331325929 feat: 病历模块优化 2026-08-05 17:54:17 +08:00
李琦
9c629ff8ee feat: 病历模块优化 2026-08-05 17:50:32 +08:00
李琦
bd5b0b2de1 feat: 病历模块优化 2026-08-05 16:22:46 +08:00
layhuts
2c1e643ab9 fix: 调整lint执行顺序
使用核心 no-nested-ternary 规则替代 Unicorn 自动修复,避免嵌套三元表达式被错误加括号
调整 oxlint、oxfmt、eslint 和 stylelint 的执行顺序,保证commit与format流程一致
2026-08-05 11:10:21 +08:00
李琦
029b45d0bb feat: VIP功能绑定、VIP病历模块、字典模块、传方按钮移动到顶部导航栏 2026-08-05 10:45:58 +08:00
Dream
0a47dc8a3d fix(@vben/playground): reset popup form state 2026-08-05 10:24:28 +08:00
Dream
d0c8c887fd docs: document typed popup data contracts 2026-08-05 09:54:01 +08:00
Dream
f16c780c2b refactor(@vben/web-naive): type modal shared data 2026-08-05 09:52:28 +08:00
Dream
ebe0eadee4 feat(@vben/playground): add typed popup data examples 2026-08-05 09:50:55 +08:00
Dream
3cbb1e8411 refactor(@vben/playground): type popup data consumers 2026-08-05 09:48:27 +08:00
Dream
21d9132703 feat(@vben-core/popup-ui)!: add typed popup data contracts
Bind shared data to modal and drawer APIs and infer it from exposed connected components.

BREAKING CHANGE: getData<T>() is replaced by getData().
Shared data defaults to unknown, and an unset payload returns undefined instead
of an empty object.
2026-08-05 09:46:37 +08:00
李琦
212b16a8ec feat: VIP模块 2026-08-04 14:53:11 +08:00
李琦
feebf4b14e feat: 1. 数脉api鉴别身份证。 2. 后台+微信登陆优化 2026-08-04 13:28:43 +08:00
李琦
3bc22d02e6 feat:诊所问题优化,气泡卡片优化,修复了导入药品的时候无法打开开方组件并且切换tab的问题 2026-08-01 08:04:31 +08:00
dream-weave
9f5b1cd9fb fix(@vben-core/form-ui)!: group field slot component props (#8215)
* refactor(@vben-core/form-ui)!: group field slot component props

* fix(@vben-core/form-ui): stabilize grouped field slot bindings

Resolve function-based common component props with field context.

Preserve internal model and event bindings when custom field slots spread componentProps.

Add regression coverage for array contexts, disabled precedence, and stale binding isolation.

* feat(@vben/vite-config): warn about field slot migration

Print the temporary field-slot migration warning when the development server starts.

Inject the same message into the browser console and keep the plugin serve-only.

* docs(@vben/docs): document field slot migration

Place the breaking field-slot notice at the start of both form documents.

Explain old and new bindings, development warnings, and planned notice removal.

* feat(@vben/playground): extend custom form example

Add a switchable input and select field to demonstrate schema component updates.

Update the composite phone field to emit new tuple values so validation observes changes.
2026-07-31 18:13:49 +08:00
dream-weave
32495e40b2 fix(@vben-core/popup-ui): rebind popup api after HMR (#8217)
* fix(@vben-core/popup-ui): rebind popup api after hmr

* fix(@vben-core/popup-ui): preserve options after remount
2026-07-31 06:13:10 +08:00
dream-weave
cb52807b27 fix(@vben-core/form-ui): correct field slot props (#8216) 2026-07-31 06:07:52 +08:00
李琦
371594bfda feat:诊所问题优化,气泡卡片优化,修复了导入药品的时候无法打开开方组件并且切换tab的问题 2026-07-30 07:37:51 +08:00
MistyMoon
418c16e093 fix(@vben/locales): 修复锁屏弹窗按钮文案歧义 (#8211)
锁屏设置弹窗(lock-screen-modal)的确认按钮作用是输入密码后锁定屏幕,
但按钮文案 i18n key `ui.widgets.lockScreen.screenButton` 原值为「解锁」/「Unlock」,
与弹窗标题「锁定屏幕」及实际动作矛盾,存在歧义。

- zh-CN: 「解锁」->「锁定」
- en-US: 「Unlock」->「Lock」

(同段另有 `unlock` key 值「点击解锁」,用于已锁定后的全屏解锁界面,语义正确,保持不变)
2026-07-30 07:12:08 +08:00
李琦
52a1cb1ce5 feat:诊所问题优化,气泡卡片优化,修复了导入药品的时候无法打开开方组件并且切换tab的问题
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
2026-07-28 16:53:31 +08:00
JyQAQ
b04d728249 fix(@vben-core/form-ui): 兼容 zod v4 的 ZodArray,避免误用 unwrap() 丢失数组外壳导致数组校验失败 (#8207) 2026-07-28 15:50:15 +08:00
李琦
5506c8917f feat:诊所问题优化,气泡卡片优化,修复了导入药品的时候无法打开开方组件并且切换tab的问题 2026-07-28 14:23:50 +08:00
李琦
05885ed405 Merge remote-tracking branch 'origin/dev/message-oa' into feature/invoice
# Conflicts:
#	apps/web-antd/src/views/system/system-config/index.vue
2026-07-28 08:25:45 +08:00
李琦
c8045e1da8 feat:优化了模态框的大小
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
2026-07-28 07:59:10 +08:00
JyQAQ
44483db9af fix(project): 修复弹窗中表单上传组件预览图片层级问题 (#8205)
将 PreviewGroupComponent 的 hidden class 替换为通过 classes.popup.root
设置更高 z-index,避免在弹窗内被遮挡
2026-07-27 22:37:33 +08:00
layhuts
fbf0cebf4b style: 优化 vxe-table 类型组织结构,将 viewed-row 功能域相关类型进行模块化拆分,由全局类型声明调整为功能目录内聚管理,避免公共 types 文件持续膨胀。 (#8204) 2026-07-27 22:36:04 +08:00
金毛88
98344dfcec Merge branch 'fork/dream-weave/fix-layout-scroll-container' 2026-07-25 20:37:20 +08:00
Dream
6d5ccc7ac8 fix(@vben/layouts): scope hash targets to layout 2026-07-24 17:32:51 +08:00
Dream
b9c6d56f74 fix(@vben/layouts): restore internal scroll navigation 2026-07-24 16:58:39 +08:00
Dream
aaf0da582c fix(@vben-core/layout-ui): stabilize internal layout scrolling 2026-07-24 16:57:32 +08:00
Dream
9a5d052fa5 fix(@vben-core/composables): lock layout scroll container 2026-07-24 16:56:35 +08:00
Dream
baea741432 feat(@vben-core/shared): add layout scroll helpers 2026-07-24 16:55:36 +08:00
xingyu4j
33f4522bff fix: 补充 i18n 缺失的英文 2026-07-24 15:26:08 +08:00
xingyu4j
cb6b0a8872 fix: constrain popper content to available viewport width 2026-07-24 15:21:07 +08:00
xingyu4j
57e2a54219 fix: add missing Symbol descriptions 2026-07-24 15:17:55 +08:00
xingyu4j
4db254cd21 fix: Tailwindcss v4 new variable shorthand syntax 2026-07-24 15:07:17 +08:00
xingyu4j
255a76d79a feat: canonical tailwind class 2026-07-24 15:04:10 +08:00
dream-weave
dcdbfe5dfc fix(@vben-core/form-ui): restore lock screen form submission (#8195)
* fix(@vben-core/form-ui): avoid stale form context access

* fix(@vben/playground): constrain date range codec fields

* test(@vben-core/form-ui): harden benchmark fixtures

* docs(@vben/docs): document form runtime and benchmarks

* fix(@vben/docs): 明确 getValues/submit 返回值及 submit 参数类型
2026-07-24 10:05:27 +08:00
李琦
14414bf9b0 feat:优化了门店部门财务功能,新增开票功能 2026-07-24 09:54:44 +08:00
xingyu
0542b509ca chore: bump versions (#8192) 2026-07-23 20:41:13 +08:00
金毛88
eaf585d03a Merge branch 'fork/dream-weave/update-zod' 2026-07-23 20:35:00 +08:00
xingyu4j
0a45cb41fb fix: 添加 StyleProvider 以支持 Tailwind 样式覆盖;移除 InputNumber 组件的宽度样式,优化组件适配;antdv 暂不支持 2026-07-23 17:50:22 +08:00
Dream
517a42ec05 perf(@vben-core/form-ui): benchmark form hot paths 2026-07-23 17:47:57 +08:00
Dream
bacfdf5e42 perf(@vben-core/form-ui): optimize array field updates 2026-07-23 17:47:57 +08:00
Dream
2e69c3fefc fix(@vben/playground): address form codec edge cases 2026-07-23 17:47:57 +08:00
Dream
43b78da024 refactor(@vben/playground): share date range codecs 2026-07-23 17:47:57 +08:00
Dream
decbed20bc fix(@vben-core/form-ui): harden codec value boundaries 2026-07-23 17:47:57 +08:00
Dream
1ac5ac3e82 fix(@vben-core/form-ui): preserve array field focus 2026-07-23 17:47:57 +08:00
李琦
b1df6fb4e8 feat:优化了门店部门财务
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
2026-07-23 16:55:26 +08:00
李琦
5fcb65aa0d feat:优化了门店搜索组件、优化了模态框默认的内容 2026-07-23 16:37:55 +08:00
zhb410181
8d5a419c50 Merge branch 'vbenjs:main' into main 2026-07-23 15:25:16 +08:00
xingyu
2d82505330 fix: dropdown menu responsiveness and improve dialog handling (#8187)
* fix: 修复用户下拉框小部件菜单行点击无响应及弹框闪退

- 主题/时区/全屏/通知菜单行补充 @select 处理器,点击行任意位置即可触发
- 时区设置弹框宿主移出 DropdownMenu(showButton 模式),修复弹框随下拉框关闭而闪退
- 通知组件暴露 toggle 供下拉框调用
- 偏好设置小部件文案精简,positionMap 顺序调整

* feat: draggable-list.vue 直接复用 SelectItem,用法和 tabbar.vue 完全一致
2026-07-23 15:14:25 +08:00
dream-weave
98dea92225 feat(@vben-core/form-ui): add typed form value codecs (#8189)
* feat(@vben-core/form-ui): add typed form value codecs

* test(@vben-core/form-ui): cover codec value boundaries

* refactor(project): propagate form codec generics

* feat(@vben/plugins): propagate form codecs through vxe grids

* refactor(@vben/playground): migrate form codec examples

* refactor(@vben/playground): migrate query forms to codecs

* docs(@vben/docs): document form value codecs
2026-07-23 15:09:31 +08:00
hiss
e6a23479ee fix: 修复锁定屏幕和退出登录小部件,位置在 user-dropdown 时,使用快捷键唤出两个模态窗 2026-07-23 14:44:32 +08:00
李琦
bbb133476a feat:优化了门店搜索组件、优化了模态框默认的内容 2026-07-23 14:23:32 +08:00
zhb410181
294e9f2966 Merge branch 'vbenjs:main' into main 2026-07-23 13:44:17 +08:00
Harold Zhang
f43de252b5 fix: fixed the icon defect which cause to langauge button can not be clicked (#8184) 2026-07-23 12:20:38 +08:00
李琦
700db76f16 feat:优化了订单、诊所搜索组件、药品这些交互 2026-07-23 09:24:06 +08:00
zhb410181
ebc0f23e64 Merge branch 'vbenjs:main' into main 2026-07-22 20:35:55 +08:00
dream-weave
28757fb9c8 refactor(@vben-core/form-ui): migrate to Zod 4 and TanStack Form (#8176)
* build(@vben-core/form-ui): update form validation dependencies

* refactor(@vben-core/form-ui): replace vee-validate with TanStack Form

* test(@vben-core/form-ui): cover TanStack Form migration

* docs(@vben/docs): document Zod 4 form migration

* refactor(project): update form adapters

* refactor(project): migrate application form consumers

* refactor(@vben/playground): migrate form examples

* docs(@vben/docs): fix validate return type and required rule empty check in form docs

* fix(@vben-core/form-ui): resolve oxlint and eslint errors

- rewrite nested ternary expressions as if/else in form-field-array.vue and form-runtime.ts
- sort @tanstack/vue-form before @vben-core/composables in package.json

* chore: fix merge artifacts and formatting

- move dependencies before devDependencies in root package.json
- update preferences snapshot for widget positioning fields
- format form-array demo README

* refactor(@vben-core/form-ui): optimize form runtime and value flow

- add fine-grained field selectors and atomic dependency resolution
- expose raw and formatted value snapshots with focused regression coverage

* fix(@vben/layouts): dispose sortable instance on unmount

* docs(@vben/docs): document form runtime API changes

- list added, changed, removed, and deprecated form APIs
- document atomic dependencies and raw versus formatted values
2026-07-22 19:47:07 +08:00
zhb410181
81f69d797d Merge branch 'vbenjs:main' into main 2026-07-22 18:06:57 +08:00
xingyu
6b6708bcf2 fix: 修复点击退出图标确认后不能退出 && 图标排序等数组字段需顺序敏感比较 (#8179)
* chore: 调整 sortablejs 的位置,只在用到的模块引入

* fix: non-null assertion lint error

* fix: 修复点击退出图标确认后不能退出

* fix: 图标排序等数组字段需顺序敏感比较

* feat: 配置中增加 refresh 并统一位置
2026-07-22 17:44:10 +08:00
zhb410181
5c1d31f680 Merge branch 'vbenjs:main' into main 2026-07-22 14:59:16 +08:00
金毛88
899910547f Merge branch 'fork/xingyu4j/deps' 2026-07-22 09:55:03 +08:00
金毛88
70a1d0a2e0 Merge branch 'fork/maoxuner/fix/system-menu-form' 2026-07-21 23:05:33 +08:00
MistyMoon
24eb67224d fix: message/notification demos not auto-dismissing due to wrong duration unit (#8168)
duration was written as 2500 (intended as ms), but ant-design-vue's
message/notification duration is in seconds, so it was interpreted as
2500s (~41min), making the popups appear to never close.
Changed to 2.5 with a comment documenting the unit.

Closes #8144
2026-07-21 22:57:17 +08:00
Jin Mao
e4e25028b5 Merge branch 'main' into fix/system-menu-form 2026-07-21 22:53:06 +08:00
金毛88
ef27b4e349 Merge branch 'fork/maoxuner/fix/lang-doc' 2026-07-21 22:48:41 +08:00
金毛88
b21563a89b feat: 顶栏与用户下拉框按钮样式统一与 tooltip 修复
- 修复 VbenTooltip 的 :content prop 类型错误,改用默认插槽
- 修复 VbenIconButton 的 onClick prop 支持数组合并
- VbenFullScreen 新增 tooltip prop,支持直接设置提示
- 统一顶栏与用户下拉框所有按钮样式,使用 VbenIconButton 包装
- 移除 i18n 中多余的"启用"字样(全局搜索/语言切换)
- 修复 VbenPopover PopoverTrigger 嵌套 button 导致 tooltip 不显示

Co-Authored-By: AtomCode (GLM-5.2) <noreply@atomgit.com>
2026-07-21 22:36:42 +08:00
zhb410181
d06451cdb7 Merge branch 'vbenjs:main' into main 2026-07-21 16:31:51 +08:00
李琦
ee4490a91c feat:优化了订单、药品这些交互 2026-07-21 07:36:52 +08:00
xingyu4j
5318774efe fix: 优化命令执行逻辑,使用串行执行以提高错误可见性 2026-07-20 18:50:49 +08:00
xingyu4j
68a2717a26 fix: test error 2026-07-20 18:48:20 +08:00
xingyu4j
0a2c01e453 fix: 添加 oxlint 禁用注释以避免嵌套三元运算符警告 2026-07-20 18:45:38 +08:00
xingyu4j
b330f08ef4 fix: lint 2026-07-20 18:36:48 +08:00
xingyu4j
6676d962e9 fix: 修复Vben用户信息中的 homePath 属性定义 2026-07-20 18:36:37 +08:00
xingyu4j
dd8307dfe5 chore: update dependencies and refactor command execution 2026-07-20 18:36:09 +08:00
xingyu4j
d0c327e037 chore: update pnpm v11.15.1 2026-07-20 17:40:54 +08:00
xingyu4j
901a147abb Merge branch 'main' of https://github.com/xingyu4j/vue-vben-admin into deps 2026-07-20 17:39:20 +08:00
陆伯言
86e68bdde3 docs: 同步后端接入文档示例菜单地址
提升示例菜单数据的整体一致性,避免多处信息不一致的情况
2026-07-20 01:03:58 +08:00
陆伯言
054deae3bb fix: 菜单组件mock数据与实际保持一致 2026-07-20 01:03:58 +08:00
陆伯言
772286fb73 fix: 路由地址不强制以斜杠开头
兼容新dashboard路由配置
2026-07-20 00:46:27 +08:00
陆伯言
5936ff2e0f fix: 系统菜单管理上级菜单无法显示名称的问题
antdv-next TreeSelect组件没有title插槽
2026-07-20 00:20:00 +08:00
金毛88
f840d235ef feat: 偏好设置小部件支持位置选择与拖拽排序
- 顶栏各小部件按钮支持选择位置(header/user-dropdown/none),偏好设置按钮额外保留 auto/fixed 智能兜底,移动端/全屏内容模式下自动切 fixed 浮动保命,避免偏好入口死锁
- draggable-list.vue 的 listRef 未在模板绑定,Sortable 永不初始化导致小部件无法拖拽;加 ref 绑定与 data-key 让 onEnd 从 DOM 读真实新顺序
- preferences.ts 的 updatePreferences 用 defu(merge) 合并数组,对 widget.order 是 concat 语义,每次拖拽触发更新都会把当前 order 追加一次,localStorage 里膨胀到上百条;改用 mergeWithArrayOverride 整体替换,加载缓存时 sanitizeCachedArray 去重已存在的脏数据
- preferences 按钮之前在 widget.order 里因 labelMap 未登记显示成裸字符串,且 widgetChecks 缺 refresh 导致刷新按钮不按 order 渲染;统一 preferences 与其它 widget 的拖拽排序与位置 select,refresh 补进 rightSlots.widgetChecks 并补模板渲染
- 补 sortablejs 依赖、WidgetPreferences.order 类型字段、preferences-drawer.vue 的 widgetOrder 绑定
2026-07-19 21:34:11 +08:00
李琦
7656c3ac49 feat:优化在线问诊的患者选药显示信息,并且优化了交互,患者挂号卡片可以直接把药品导入至处方 2026-07-19 16:10:55 +08:00
陆伯言
930597c605 docs: 更新新增语言包的说明
closed #8160
2026-07-19 02:27:09 +08:00
陆伯言
41c20a43af docs: 更正语言使用示例,确保可用
使用可用的语言配置
修复computed变量使用语法错误

closed #8159
2026-07-19 01:22:00 +08:00
陆伯言
fe65d548be docs: 修复英文语言文件路径错误
closed #8158
2026-07-19 01:17:22 +08:00
金毛88
0cd87c170f feat: 顶栏按钮支持位置选择(header/user-dropdown/none)
为顶栏所有部件按钮添加独立的位置配置,支持在顶栏、用户下拉菜单、不显示
三种选项间切换,每个按钮可独立设置。

- 新增 7 个位置偏好字段:globalSearchButtonPosition、
  themeToggleButtonPosition、languageToggleButtonPosition、
  fullscreenButtonPosition、notificationButtonPosition、
  refreshButtonPosition、timezoneButtonPosition
- 合并 lockScreenButtonPosition、logoutButtonPosition
- header.vue 按位置条件过滤按钮显示
- user-dropdown.vue 新增对应菜单项,支持在用户下拉菜单中显示
- 偏好设置界面仅保留位置选择器(不再需要独立的开关)
- 更新中英文国际化文案

Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>
2026-07-18 22:46:31 +08:00
金毛88
b71ebbd673 Merge remote-tracking branch 'pr-8019-temp/main' 2026-07-18 19:21:36 +08:00
金毛88
31d3bd18f3 Merge branch 'fork/LayHuts/feature/form-field-arry' 2026-07-18 19:18:14 +08:00
李琦
c1b71142df fix:
1. 优化了审方pc的效果
2. 就诊人信息优化
2026-07-18 17:08:37 +08:00
李琦
92149f43a6 fix:
1. 优化了审方pc的效果
2. 就诊人信息优化
2026-07-18 14:16:25 +08:00
李琦
47a829c1b1 1. 配送仓库 2026-07-18 10:22:36 +08:00
Jin Mao
eae6771554 Merge branch 'main' into feature/fix_defects_router 2026-07-18 03:54:49 +08:00
JyQAQ
8ac66a96bc fix: 修复弹窗中使用裁剪图片会出现层级问题 (#8156)
* fix(@vben/playground): 弹窗中使用裁剪图片会出现层级问题

* fix(project): 修复弹窗中裁剪图片在 web-antd 与 web-antdv-next 的层级问题
2026-07-17 15:17:37 +08:00
zhb410181
822cec0f5c Merge branch 'vbenjs:main' into main 2026-07-16 13:13:38 +08:00
Guillermo Sagar
6910c691f7 修复动画导致的白屏Bug (#8148)
首次页面加载正常,但是当通过页面点击切换路由的时候白屏,无法正常渲染子页面

 Vue Transition + KeepAlive 空白页 bug**: `<Transition mode="out-in">` + 条件渲染会导致路由切换空白页。`mode="out-in"` 要求旧元素完成 leave 动画后新元素才进入,但路由切换时条件渲染可能在动画前就移除了旧元素,导致 `<Transition>` 永远等待一个不存在的元素 → 死锁。**解决方案**: 移除 `mode="out-in"`(保留 Transition 动画),或在 `apps/web-antd/src/preferences.ts` 设置 `transition: { enable: false }` 关闭动画走 `v-else` 分支。
2026-07-16 09:35:25 +08:00
李琦
5ff6f0d837 1. 修复了接诊页面的剂量、天数输入框过窄 2026-07-15 08:46:37 +08:00
李琦
8eca0bd84d 1. 修复了接诊页面的剂量、天数输入框过窄 2026-07-15 08:46:31 +08:00
zhb410181
71c30d3e18 Merge branch 'vbenjs:main' into main 2026-07-14 14:07:09 +08:00
Harold Zhang
263d8e5b52 Merge branch 'main' into feature/fix_defects_router 2026-07-14 09:47:34 +08:00
Harold Zhang
b79b82ecf0 feat: fixed the preference will hide all other button theme/timezone/lanague etc (#8147) 2026-07-13 21:31:06 +08:00
xingyu4j
91a2e80be8 fix: 优化代码格式,合并类型声明 2026-07-13 18:30:19 +08:00
xingyu4j
819ddd4ebb fix(style): 添加no-invalid-position-declaration规则到stylelint配置 2026-07-13 18:29:30 +08:00
xingyu4j
bb6403d513 chore: update deps 2026-07-13 18:29:01 +08:00
xingyu4j
45c3776ffd chore: update pnpm v11.12.0 2026-07-13 18:27:57 +08:00
zhb410181
1d23cd707c Merge branch 'vbenjs:main' into main 2026-07-13 11:22:06 +08:00
Harold Zhang
6eb79b8ef0 Merge branch 'main' into feature/fix_defects_router 2026-07-13 08:59:16 +08:00
zouawen
8b7c245bc7 fix: 修复首选项导航栏宽度问题 (#8136) 2026-07-11 11:43:47 +08:00
Harold Zhang
e416de3290 fix: fixed defects of router 2026-07-10 10:15:40 +08:00
zhb410181
398b445d35 Merge branch 'vbenjs:main' into main 2026-07-09 15:41:03 +08:00
luoqiz
14d03a1c2d fix: 统一BasicUserInfo (#8133) 2026-07-09 13:23:29 +08:00
zhb410181
9769c11de5 fix(style): 修复偏好设置功能tab栏宽度未自适应问题 (#8132) 2026-07-09 13:23:08 +08:00
hiss
cae47e8c7f fix(style): 修复偏好设置功能tab栏宽度未自适应问题 2026-07-08 16:42:04 +08:00
陆伯言
88918aad10 docs: fix typo of env demo (#8129)
closed #8125
2026-07-08 08:38:06 +08:00
陆伯言
75c3a7214b docs: update default home path perferences (#8121)
closed #8117
2026-07-07 21:41:51 +08:00
陆伯言
71bcabd118 docs: update nitro mock server url (#8119)
closed 8113
2026-07-03 17:12:43 +08:00
dream-weave
81619b6090 fix(lint): align eslint and oxlint rules (#8118)
* docs: 路由文档示例代码同步路由组织新规范

* fix(lint): align eslint and oxlint rules

* fix(lint): avoid control character regex
2026-07-03 17:12:13 +08:00
dream-weave
77468ce9a6 docs: 路由文档示例代码同步路由组织新规范 (#8114) 2026-07-02 18:35:54 +08:00
Evan
8779c00670 fix: 修复homePath的路径 (#8110)
* fix: 修复homePath的路径

* fix: 单元测试数据

* chore: 单元测试快照数据

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2026-07-01 17:31:37 +08:00
金毛88
61f1b53325 fix: 修改默认首页,以适配router重构 2026-07-01 17:02:55 +08:00
layhuts
c56bb7d320 docs: 更新文档 2026-07-01 15:07:56 +08:00
金毛88
1490a93f47 Merge branch 'fork/bnuephjx/refactor/router-redirect' 2026-07-01 13:31:27 +08:00
金毛88
e50296e3c5 Merge branch 'fork/zouawen/main' 2026-07-01 13:06:42 +08:00
陆伯言
5da38ccded fix: 调整mock默认路由 (#8108)
* fix: 调整mock默认路由

1. starting改为running 语义更明确
2. API地址修复
3. 列表顺序调整,登录接口靠前

* docs: 替换axios调用示例

原先/api/user接口地址不对,且是受认证保护的接口,响应异常容易造成误解
2026-07-01 13:02:44 +08:00
layhuts
20b69fa480 revert: 撤销修改 2026-07-01 11:49:27 +08:00
layhuts
fdf2abb702 refactor: 修改form-field-array支持dependencies 2026-07-01 11:08:35 +08:00
layhuts
9d36b34c4c docs: 修复错误的注释说明 2026-07-01 09:52:51 +08:00
dependabot[bot]
9b356d731d build(deps): bump actions/checkout from 6 to 7 (#8080)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 22:42:58 +08:00
Evan
18e2bbf969 Merge branch 'main' into refactor/router-redirect 2026-06-30 22:23:54 +08:00
layhuts
14bb94245c feat: 新增logo铺满显示和是否显示text配置 (#8102)
* feat: 新增logo铺满显示和是否显示text配置

* feat: logo.enable=false时隐藏logo区域占位;设置logoMode、showText为必填。

* style: 修复注释问题

* fix: 删除多余属性
2026-06-30 21:50:45 +08:00
Evan
feb0265744 Merge branch 'main' into refactor/router-redirect 2026-06-30 14:26:23 +08:00
xueyitt
7182405d24 fix: 修复弹框自定义宽度 class 被默认 max-w 覆盖的问题 (#8100)
将 modalClass 参数移至 cn() 末尾,确保用户通过 class 传入的宽度样式
优先于内置默认的 max-w-[calc(100vw-20px)] 生效,使 业务使用Modal 上的自定义的max-w-[50%] 等方式能正确控制弹框宽度。
2026-06-30 14:24:18 +08:00
陆伯言
d7d6d2e337 fix: lint issue from a8c3729 (#8098) 2026-06-30 14:23:52 +08:00
Harold Zhang
31c31b914b feat: fix check error (#8094) 2026-06-30 14:23:27 +08:00
Evan
f00a881295 refactor(@vben/access): 重构路由的重定向逻辑
@vben-core/shared
@vben/playground
@vben/backend-mock
@vben/web-antd
@vben/web-antdv-next
@vben/web-ele
@vben/naive
@vben/tdesign
2026-06-30 13:36:37 +08:00
zoumingjun
5ed867eb95 feat: 优化禁用的开关组件也可以查看help帮助信息 2026-06-30 09:39:32 +08:00
zoumingjun
ae5bd3afc5 feat: 首选项导航栏选项标题过长时显示省略号,且增加聚焦时文字提示 2026-06-30 09:37:28 +08:00
vben
a8c3729850 feat: add ICP filing number to copyright across app 2026-06-24 10:04:10 +08:00
gotounix
794edd419e fix: add includes in mock tsconfig (#8085) 2026-06-23 21:47:08 +08:00
layhuts
9a284ee385 style: 处理modal设置class="md:w-5/12"格式时Fullscreen时无法最大化问题 (#8084) 2026-06-23 21:46:43 +08:00
gotounix
e031457271 fix: add missing imports (#8082) 2026-06-23 21:46:19 +08:00
金毛88
0931599f96 chore: update deps 2026-06-21 14:48:51 +08:00
xingyu4j
dc3865f778 fix: 优化 lint 执行策略并修复 pre-commit 依赖解析
- vsh lint 按硬件自适应:CPU<=4 核串行(汇总错误),>4 核并行;
  CPU>4 且空闲内存>8GB 时 oxfmt/oxlint 默认线程数提升至 4,否则维持 2
- lefthook pre-commit 改为串行执行,避免与 check:type 并行导致低配机资源飙升
- pre-commit 仅对暂存文件 lint 并自动修复回填(stage_fixed)
- lint 命令统一加 pnpm exec 前缀,修复 lefthook 经 sh 调用时
  oxlint 找不到 workspace 局部依赖(eslint-plugin-eslint-comments)的问题
2026-06-18 16:35:27 +08:00
yshbyy
45399d0299 fix(@vben-core/shadcn-ui): 补全 ScrollArea onScroll 滚动事件透传 (#8069) 2026-06-18 16:10:21 +08:00
xingyu
dc3466d77a refactor: 将 unicorn / TypeScript 规则迁移至 oxlint,并完善 oxfmt 配置 (#8067)
* chore: update deps

* chore: update archiver v8

* fix: 还原 vue/html-closing-bracket-newline

* feat: 增加默认配置和配置注释

* feat: 增加oxlint默认插件配置和注释

* refactor: remove unicorn ESLint configuration and update dependencies

- Deleted the unicorn ESLint configuration file from the lint-configs package.
- Removed unicorn configuration from the main index file.
- Updated oxlint-config to include a comprehensive set of unicorn rules.
- Adjusted preferences and vxe-table files to disable specific unicorn rules.
- Removed references to eslint-plugin-unicorn from pnpm-lock.yaml and pnpm-workspace.yaml.

* refactor: 移除 eslint 的 @typescript-eslint/eslint-plugin 依赖,将 TypeScript 配置迁移到 oxlint

* feat: open typeAware

* fix: mac 和linux下报错
2026-06-18 16:10:01 +08:00
金毛88
65885ea74b chore: update lockfile 2026-06-16 16:47:33 +08:00
金毛88
769791351f chore: update deps && lint 2026-06-16 16:25:21 +08:00
金毛88
741cbb3794 Merge branch 'fork/xueyitt/feat/feat3' 2026-06-16 16:16:56 +08:00
金毛88
edd110ac4e Merge branch 'fork/xueyitt/feat/feat1' 2026-06-16 16:15:33 +08:00
xueyitt
7824219557 fix: 修正AppPreferences中defaultAvatar、dynamicTitle注释 (#8060) 2026-06-16 14:23:25 +08:00
雪忆天堂
dd4afec7ed feat: workbench公共组件增加支持slot方式进行自定义 2026-06-16 10:01:24 +08:00
雪忆天堂
ebebb42625 feat: 1、FieldMappingTime拆解独立FieldMappingTimeItem+集合形式,方便单独对象定义;
2、表单submitOnChange增加changeDebouncedTime防抖处理,允许自定义防抖周期;
3、handleValuesChange事件set增加cloneDeep,防止对象值污染;
2026-06-16 09:48:27 +08:00
雪忆天堂
d1e6d4a3da Merge remote-tracking branch 'origin/main' 2026-06-16 09:10:50 +08:00
Jefsky Wong
ad3f996bdb fix(@vben/common-ui): prevent footer slot from overlapping content when Page contains Grid (#8056)
The Page component's outer container used min-h-full, which doesn't
provide an explicit height for flex children. Combined with h-full on
the content div, this caused the footer slot to overlap table content
(Grid/vxe-table) because the content area didn't properly account for
footer space in the flex column layout.

Fix:
1. Change outer container from min-h-full to h-full — gives the flex
   container an explicit height so percentage heights resolve correctly.
2. Change content area from static h-full to flex-1 when autoContentHeight
   is false — lets the flex layout naturally distribute remaining space
   between content and footer.

Closes #5494

Co-authored-by: jefsky <jefsky@users.noreply.github.com>
2026-06-16 08:36:39 +08:00
HaroldZhangCode91
68877a7e97 Merge branch 'main' into feature/fix_confirm_regression 2026-06-15 17:46:19 +08:00
雪忆天堂
c5a839f652 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
2026-06-15 17:41:12 +08:00
HaroldZhangCode91
bf8c3d6411 feat: aligns ESLint with oxfmt's behavior (#8054) 2026-06-15 17:25:04 +08:00
HaroldZhangCode91
f753a3b75f Merge branch 'main' into feature/fix_confirm_regression 2026-06-15 16:45:08 +08:00
曾兴顺
fcf726f953 fix: 修复Tree组件不支持半选的问题(子节点全选时父节点选中图标为勾选状态,子节点部分选中时父节点图标为减号,也就是版勾选状态,子节点未被选中时,父节点不被选中) (#8050) 2026-06-15 16:38:21 +08:00
Harold Zhang
16ebc67796 feat: fixed useScrollLock() can not be unlock 2026-06-15 16:34:53 +08:00
Harold Zhang
6b109c9ae6 fix: fixed regression of confirm 2026-06-15 16:24:17 +08:00
xingyu
160114c5e0 fix: select and user-dropdown (#8042)
* fix: 偏好设置中下拉框无法选择的问题,原因: 同步 shadcn-vue 将 z-popup 同步为 shadcn 的 z-50

* fix: 鼠标hover到用户登录头像 下拉来回闪烁 隐藏 显示
2026-06-10 21:26:47 +08:00
金毛88
4b1fada0ab Merge branch 'fork/zhb410181/fix/style' 2026-06-09 11:22:58 +08:00
hiss
5c9daab2ff fix(style): 1.修复ToggleGroupItem边框样式问题 2.修复用户头像下拉框Badge背景颜色 2026-06-09 08:56:07 +08:00
Harold Zhang
c2a85f4ec2 feat: support unlocking scroll when modal dialog closes 2026-06-08 14:15:31 +08:00
abc_abc
e7f541f37e Merge branch 'main' into main 2026-06-08 10:31:39 +08:00
金毛88
f23c9f5e19 chore: update deps 2026-06-07 18:54:43 +08:00
ddfabc
4459389d4a feat: 将锁定屏幕和退出登录图标从用户下拉菜单移至头部栏
将锁定屏幕(LockKeyhole)和退出登录(LogOut)图标按钮从 UserDropdown 下拉菜单
移至 LayoutHeader 头部栏,位于通知图标的右侧。

变更内容:
- header.vue: 新增 lock-screen-btn 和 logout-btn 插槽,位于 notification 之后;
  添加 LockScreenModal/LogoutModal 及快捷键(Alt+L/Alt+Q)支持;
  新增 avatar/text props 用于锁屏弹窗显示
- layout.vue: 透传 avatar/text props 和 logout 事件
- user-dropdown.vue: 移除锁定屏幕和退出登录的菜单项、弹窗及快捷键逻辑
- 所有 5 个应用 basic.vue: 向 BasicLayout 传递 avatar/text,绑定 @logout 事件

头部右侧图标顺序: ... → 全屏 → 通知🔔 → 锁定🔒 → 退出🚪 → 用户头像
2026-06-06 23:24:54 +08:00
xingyu4j
8e0564cc42 chore: node v24 2026-06-06 21:07:55 +08:00
xingyu4j
017b3c3fce ci: bump actions/stale from 9 to 10 2026-06-06 20:49:34 +08:00
xingyu4j
126275b417 ci: bump dessant/lock-threads from 5 to 6 2026-06-06 20:48:51 +08:00
xingyu4j
bc908e29eb ci: bump amannn/action-semantic-pull-request from 5 to 6 2026-06-06 20:48:19 +08:00
xingyu4j
efe3c25310 chore: update deps 2026-06-06 19:36:39 +08:00
xingyu4j
d85ac2c976 feat: form 表单赋值 2026-06-06 19:33:31 +08:00
xingyu4j
c816591b69 fix: modal 下拉等不能选中 2026-06-06 19:32:31 +08:00
xingyu4j
564b349700 fix: vue/custom-event-name-casing 2026-06-06 16:21:25 +08:00
xingyu4j
ce495d67a0 feat: 同步 antdv-next 的组件 2026-06-06 16:20:36 +08:00
xingyu4j
8094d1ffb9 chore: update deps 2026-06-06 16:19:32 +08:00
afe1
748b182e44 fix: workflow (#8013)
* fix: catelog

* fix: system

* chore: sync upstream changes

* feat:  component and workflow error
2026-06-05 05:13:08 +08:00
金毛88
6544340d80 Merge branch 'fork/HaroldZhangCode91/feature/fix_freeze_scrollbar_of_modal' 2026-06-04 13:54:21 +08:00
Harold Zhang
6b7cb7b1fc feat: fixed the regression of changing Menu to MenuUI 2026-06-04 11:46:53 +08:00
Harold Zhang
d90d45a128 fix: fixed modal freeze issue 2026-06-04 11:19:38 +08:00
Harold Zhang
66077b3bcf Merge branch 'main' into feature/fix_freeze_scrollbar_of_modal 2026-06-03 18:07:37 +08:00
Harold Zhang
0328594747 feat: fixed the overlay layer can not work with reka-ui 2026-06-03 17:52:41 +08:00
金毛88
790e114d41 chore: 固定vite 8.0.10,避免编译后异常 2026-06-03 17:32:13 +08:00
Harold Zhang
cc60ca9592 feat: fixed the defects the scrollbar disapear when modal close 2026-06-03 16:10:17 +08:00
HaroldZhangCode91
79707a2ebd feat: fixed regression of modal for element plus (#7999) 2026-06-03 13:27:12 +08:00
金毛88
0eb72ca4fd feat: add useTDesignDesignTokens 2026-06-03 13:25:23 +08:00
金毛88
7fd4b1de04 Merge branch 'fork/SaleriHQ/feat/form-field-array' 2026-06-03 13:22:21 +08:00
Saleri
9ffd42f013 chore: add changeset for form field array 2026-05-31 23:06:00 +08:00
Saleri
40101911f0 style: format naive styles 2026-05-31 16:21:13 +08:00
xingyu
04fbb7a556 chore: 升级 shadcn-vue 组件到v4最新版 (#7972)
* fix: useStore is deprecated

* chore: update deps

* feat: 升级shadcn-ui v4

* fix: workbench style

* feat: 升级shadcn-ui v4 step2

* feat: 升级shadcn-ui v4 step3

* chore: 升级shadcn v4

* fix: pagination

* fix: dark style

* fix: doc import

* feat: 增加详情组件,参考 antdv-next

* docs: descriptions docs

* docs: Browser Support

* feat: add table action

* feat: icon use vbenIcon

* fix: type error

* fix: dropdown popConfirm

* feat: 使用默认的文字交互

* feat: 优化渲染性能
2026-05-31 15:18:46 +08:00
Saleri
e5f9106caa feat(@core/form-ui): 新增 useVbenForm 数组编辑器 VbenFormFieldArray 2026-05-31 15:03:55 +08:00
afe1
108d7ff335 chore: add commitlint config declaration (#7968)
* fix: catelog

* fix: system

* chore: sync upstream changes

* types: add commitlint config declaration
2026-05-28 15:50:47 +08:00
leo
1d6842fa69 build: change runtime config asset naming strategy for PWA compatibility (#7963)
replace query-based config url (_app.config.js?v=...)
with deterministic hashed filename (_app-config-v{version}-{hash}.js)
ensure service worker precache matches runtime injected asset
2026-05-28 12:53:51 +08:00
Saleri
d6a9eaf9e9 fix: 修复 web-naive 表单页 Input 输入 _ 不显示的问题 (#7962) (#7965)
* fix(@vben/web-naive): 修复 naive input 输入 _ 不显示的问题 (#7962)

* fix(@vben/web-naive): 限制 input 字体修复作用域,避免全局影响
2026-05-28 12:49:48 +08:00
boisduval
f7540c119b fix: 修复 useVbenModal useVbenDrawer 传入参数失效的问题 (#7964)
- 把provide的key值修改为与inject一致的options
2026-05-28 12:49:23 +08:00
过冬
acf289381e fix(@vben-core/popup-ui): modal 响应式适配,支持视口自适应 (#7951) 2026-05-25 13:27:17 +08:00
PanFu
f813245827 feat: 偏好设置的快捷键列表追加ESC快捷键的控制(关闭当前窗口) (#7947)
* feat: 快捷键追加ESC控制,关闭当前窗口

* feat: 偏好设置中,页面切换动画的颜色看不清的问题(使用当前主题色)

* feat: 三种弹出框支持快捷键ESC动作

* feat: 代码自动格式化(3个框架改动)

* feat: 代码自动格式化(3个框架改动)

* fix: 修正locale数据获取方式

* 单元测试问题修改

* 单元测试问题修改

* fix: 解决代码评论的问题

* fix: 解决代码评论的问题

* fix: 解决代码评论的问题

* fix: 解决代码评论的问题

* 单元测试问题修改

* fix: 解决评论问题

* fix: 解决代码格式导致pnpm run lint报错的问题

---------

Co-authored-by: PanFu <panfu@zhihuaai.com>
2026-05-23 09:50:35 +08:00
过冬
e98f0b7558 fix: 移动端禁用混合布局菜单拆分 (#7949) 2026-05-23 04:21:19 +08:00
Reese Wellin
cd86de54e1 fix(preferences): 修复主题等个性化信息持久化被初始化配置覆盖 (#7948)
调整 initPreferences 中 merge 顺序,使用户缓存优先于 initialPreferences。

Fixes #7943
2026-05-23 04:17:46 +08:00
Jin Mao
60eb1335f8 chore: vite 降级到8.0.10 2026-05-22 13:20:25 +08:00
xingyu
f2b3b12553 chore: pnpm v11 (#7942)
* chore: remove rolldown dep

* fix: issues-helper and maintain-one-comment are temporarily disabled due to security issues

* chore: update deps

* fix: change error type to TypeError for non-browser environment check in LocalStorageDriver

* chore: update pnpm v11
2026-05-22 12:02:14 +08:00
layhuts
c066889c37 Fix: 在isMobile=true时无法自定义Drawer的class问题 (#7941)
* fix: 在isMobile=true时无法自定义Drawer的class问题

* fix: 在ResizableHandle两边增加边距
2026-05-22 11:56:09 +08:00
928646789
6b81d1b0d5 fix: preferences-button click to show setting drawer (#7938) 2026-05-22 07:26:12 +08:00
afe1
126b33687a chore: vite config tsconfig include (#7936)
* fix: catelog

* fix: system

* fix(vite-config): include tsdown config in tsconfig
2026-05-22 07:25:50 +08:00
Jin Mao
5b67c2e740 chore: 更新pnpm-lock 2026-05-21 20:44:38 +08:00
Jin Mao
2d7c3c3072 chore: 更新pnpm-lock 2026-05-21 20:40:49 +08:00
Jin Mao
5cc68c9dd0 chore: 更新 vite 依赖版本
- 将 vite 版本从 ^8.0.13 降级到 ^8.0.10
- 保持其他依赖包版本不变
2026-05-21 20:22:28 +08:00
Jin Mao
63a38dce49 refactor(workflow): 优化发布工作流配置
- 简化触发条件,移除冗余的推送事件配置
- 更新标签创建输入参数描述,明确格式要求
- 移除 Husky 环境变量配置
- 重命名构建作业为发布作业,提高语义清晰度
- 移除 Node.js 版本矩阵策略配置
- 优化版本提取逻辑,支持工作流调度和推送事件
- 统一标签前缀处理,确保版本格式一致性
- 移除注释掉的冗余步骤配置
- 简化发布配置,移除不必要的发布操作注释块
2026-05-21 09:07:59 +08:00
Jin Mao
629f747795 feat(workflow): 添加手动触发release标签工作流
- 添加 workflow_dispatch 触发器支持手动运行
- 新增 tag 输入参数用于指定创建的标签
- 设置默认标签值为 staging
- 保留原有的 push tags 自动触发功能
2026-05-21 08:54:51 +08:00
Jin Mao
8585008f79 chore: release v5.7.0 2026-05-20 17:45:44 +08:00
xingyu
00990d9453 Merge branch 'main' into fix/lint 2026-05-20 17:38:19 +08:00
PanFu
2a84b590b5 feat: 偏好设置按钮,支持在右上角用户的下拉框中展示;偏好设置中支持设置时区(多一个入口); (#7931)
* feat: 偏好设置按钮支持显示在用户下拉窗中的特性

* feat: 时区设置功能支持在偏好设置列表进行配置

* feat: 偏好设置按钮支持显示在用户下拉窗中的特性 - 完善示例代码

* feat: 时区设置功能支持在偏好设置列表进行配置

* feat: 时区设置功能支持在偏好设置列表进行配置-单元测试报错问题

* feat: 时区设置功能支持在偏好设置列表进行配置-修改代码QC问题

---------

Co-authored-by: PanFu <panfu@zhihuaai.com>
2026-05-20 17:24:53 +08:00
PanFu
769c970e08 fix: 修复三个问题:偏好配置项修改后重启不会生效;锁屏弹出框的按钮文字错误;右上角偏好设置按钮排序问题优化; (#7930)
* fix: 修正偏好设置加载缓存的偏好设置并与初始配置合并的顺序

* fix: 修正锁屏弹出框的按钮文字

* fix: 偏好设置按钮排序优化(取第三个占位的数字,若是第三个占位不是数字,则自动分配排序索引)

* fix: 修正偏好设置加载缓存的偏好设置并与初始配置合并的顺序 - 代码QC问题修改

---------

Co-authored-by: PanFu <panfu@zhihuaai.com>
2026-05-20 17:24:18 +08:00
xingyu4j
4d3c481a93 fix: 当lint检查出问题后,不能在终端终止 2026-05-20 16:39:03 +08:00
xingyu4j
fda4b39c01 chore: update deps 2026-05-20 16:38:19 +08:00
xingyu4j
74d8942f39 chore: remove Unused deps 2026-05-20 16:24:59 +08:00
xingyu4j
9ec98f0846 refactor: replace depcheck with knip for dependency checking
Migrate vsh check-dep from depcheck to knip, reducing
315 transitive dependencies. Config is inlined in
DEFAULT_CONFIG, matching the check-circular pattern.
Also re-export CollapsibleParams from @vben/common-ui.
2026-05-20 16:14:21 +08:00
xingyu4j
f71094e878 refactor: migrate json-viewer from vue-json-viewer to vue-json-pretty
- Replace vue-json-viewer with vue-json-pretty (actively maintained, Vue 3 native)
- Map original props to vue-json-pretty API in bindProps for backward compatibility
- Implement copy functionality via renderNodeActions slot with i18n support
- Update style.scss from .jv-* to .vjs-* class names
2026-05-20 14:39:17 +08:00
xingyu4j
9badda3d11 chore: update deps 2026-05-20 11:30:03 +08:00
xingyu4j
03fcecadcc fix: lefthook config 2026-05-19 16:13:00 +08:00
xingyu4j
3a88adb8a6 feat: test 2026-05-19 16:10:18 +08:00
xingyu4j
c759cb2593 fix: lint config 2026-05-19 16:08:10 +08:00
xingyu4j
f77166b3a2 chore: update deps 2026-05-19 15:59:43 +08:00
xingyu4j
f7928b34b2 fix: lint 2026-05-19 15:58:34 +08:00
xingyu4j
454a5b54bd chore: lefthook config 2026-05-19 15:54:03 +08:00
Jin Mao
a4dd9d30ce chore: release 5.7.0 2026-05-19 14:39:46 +08:00
Jin Mao
76cfcda2e9 Merge branch 'fork/xingyu4j/antdv-next' 2026-05-19 14:14:28 +08:00
橙子
7ec4df4995 fix: search-panel when ArrowUp and ArrowDown (#7922) 2026-05-19 13:40:50 +08:00
leo
d71c81e8ff style: apply vsh lint formatting (#7923) 2026-05-19 13:40:13 +08:00
xingyu4j
aac626da32 feat: add system user view 2026-05-18 21:45:17 +08:00
xingyu4j
5db93a345a fix: doc build error 2026-05-18 20:17:35 +08:00
xingyu4j
7c943cc06b chore: disable oxc typeAware and add --threads option for oxlint
Type-aware linting was consuming excessive memory with nearly all
type-aware rules turned off. Add --threads CLI option to control
oxlint parallelism.
2026-05-18 18:46:13 +08:00
xingyu4j
5c8dd1a6da chore: docs config 2026-05-18 18:35:34 +08:00
xingyu4j
6592135cc5 chore: update vitepress v2 2026-05-18 18:23:10 +08:00
xingyu4j
2f16ee7cf5 docs: use antdv-next 2026-05-18 17:30:59 +08:00
xingyu4j
0c1b737325 fix: fix lint && typecheck 2026-05-18 16:50:14 +08:00
xingyu4j
5bbdcffb97 Merge branch 'main' into antdv-next 2026-05-18 16:38:56 +08:00
xingyu4j
1867015e97 Merge branch 'antdv-next' of https://github.com/xingyu4j/vue-vben-admin into antdv-next 2026-05-18 16:35:28 +08:00
xingyu
b6aeadd9a2 Merge branch 'main' into antdv-next 2026-05-18 16:35:18 +08:00
xingyu4j
1e44b87359 refactor: migrate playground deprecated antdv-next APIs
- Modal: destroyOnClose → destroyOnHidden
- Card: body-style → styles.body
- Alert: message prop/slot → title
- Spin: tip → description, wrapper-class-name → classes.root
2026-05-18 16:35:06 +08:00
xingyu4j
a1081bf7a6 fix: 修复 InputNumber 组件宽度在表单中不占满的问题 2026-05-18 16:28:19 +08:00
xingyu4j
9bb2026b4d fix: 修复 InputNumber 组件宽度在表单中不占满的问题 2026-05-18 16:25:49 +08:00
xingyu4j
4554cd016a fix: lint 2026-05-18 16:19:53 +08:00
xingyu4j
350d5ee60e refactor: playground use antdv-next 2026-05-18 16:18:46 +08:00
xingyu4j
1ec058cbe3 chore: update deps 2026-05-18 15:06:59 +08:00
xingyu4j
eb9cdbab9b fix: lint 2026-05-16 12:55:19 +08:00
xingyu4j
4907f281af chore: 更新 pnpm-workspace.yaml 配置,添加 publicHoistPattern 和 allowBuilds 设置 2026-05-16 11:56:39 +08:00
xingyu4j
aadf9b6e39 chore: update deps 2026-05-16 11:52:20 +08:00
Jin Mao
84e77f64ea Merge remote-tracking branch 'origin/main' 2026-05-16 11:18:52 +08:00
guoqiangui
3e89077d46 fix(deploy): use IMAGE_NAME variable in remove_image instead of hardcoded name (#7907)
The `remove_image` function was using a hardcoded image name `vben-admin-pro`
instead of the `$IMAGE_NAME` variable (`vben-admin-local`), so the old local
image was never actually cleaned up before each build.

Co-authored-by: guoqiangui <guoqiangui@zhongshitech.cn>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 11:18:09 +08:00
Jin Mao
c8c425538f Merge branch 'fork/xueyitt/main' 2026-05-16 10:49:33 +08:00
Jin Mao
b04c10aab6 Merge branch 'fork/LayHuts/feature/vxe-table-viewed' 2026-05-16 10:48:00 +08:00
JyQAQ
b5f79db321 fix(@vben/plugins): 修复 tiptap 重复注册扩展警告 (#7917)
StarterKit v3.22.0 已默认包含 Document、Link、Underline 扩展,
与单独导入产生重复注册,导致控制台警告:
[tiptap warn]: Duplicate extension names found: ['link', 'doc', 'underline']

- 移除 Document 单独导入和使用,StarterKit 已内置
- 移除 Underline 单独导入和使用,StarterKit 已内置
- StarterKit 配置中添加 link: false,禁用内置 Link,
  保留自定义配置的 Link.configure({...})
2026-05-16 10:45:30 +08:00
PanFu
42d82875ce feat: 1、完善tree组件的全选状态不正确、全选没有label、item内容超长导致复选框对齐错乱、item内容超长没有tips无法看到完整内容的问题 (#7915)
Co-authored-by: PanFu <panfu@zhihuaai.com>
2026-05-16 10:44:55 +08:00
Akuria
4d8d2de6ad fix: guard svg icon loading during docs SSR (#7912) 2026-05-16 10:43:47 +08:00
Akuria
294700a247 fix: skip fixed footer height in auto-content-height calculation (#7910)
* fix: skip fixed footer height in auto-content-height calculation

When the Page component's footer has position: fixed, it is removed
from the normal document flow and should not be subtracted from the
available content height. Previously, the footer's offsetHeight was
always subtracted, causing incorrect height calculation for fixed
footers.

Also reset shouldAutoHeight before recalculating to prevent stale
state on hot reload.

Fixes #4576

* fix: replace getComputedStyle footer height check with footerFixed prop

Use an explicit `footerFixed` boolean prop instead of runtime
getComputedStyle detection to determine whether the footer height
should be excluded from content height calculation. This avoids
unreliable style queries and makes the behavior deterministic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 10:43:10 +08:00
Akuria
f55b18ffd7 fix: update primary color when toggling dark/light mode with custom theme (#7909)
When a custom theme is selected and the user toggles between dark and
light mode, the primary color was not being recalculated. This was caused
by a guard condition in the builtin theme watcher that skipped updating
themeColorPrimary for custom themes during mode changes.

Remove the guard so that the primary color is always recalculated from
the theme preset when the mode changes, ensuring Element Plus CSS
variables stay in sync.

Fixes #6615
2026-05-16 10:42:35 +08:00
Akuria
ca5931e8c4 fix: preserve tree default value when treeData starts empty (#7908)
When treeData is initially an empty array (e.g. before async data
arrives), updateTreeValue() would clear the modelValue because no
matching items could be found in the empty flattened data. This caused
default values to be lost.

Only call updateTreeValue() when flattenData has items, so that
modelValue is preserved until the actual tree data arrives.

Fixes #6522
2026-05-16 10:41:42 +08:00
JyQAQ
aac4e88353 docs(@vben/docs): 添加 VCropper 图片裁剪组件文档 (#7904)
* docs(@vben/docs): 添加 VCropper 图片裁剪组件文档

- 新增中文文档 docs/src/components/common-ui/vben-cropper.md
- 新增英文文档 docs/src/en/components/common-ui/vben-cropper.md
- 新增基础用法示例 demos/vben-cropper/basic
- 新增固定比例裁剪示例 demos/vben-cropper/aspect-ratio
- 更新侧边栏配置添加 Cropper 入口

* fix: 更正跨域图片描述并修复 demo 内存泄漏

- 文档更正:网络图片导出裁剪结果需服务端 CORS 支持
- 修复 URL.createObjectURL 内存泄漏:添加 revokeObjectURL 释放
2026-05-11 12:50:44 +08:00
雪忆天堂
9b54e50510 fix: 修正vxtable不存在formConfig时,直接变更data失效问题 2026-05-11 10:10:34 +08:00
xueyitt
30356a24e6 fix: 修正vxtable不存在formConfig时,直接变更data失效问题
修正vxtable不存在formConfig时,直接变更data失效问题
2026-05-11 10:09:47 +08:00
MistyMoon
ba60bc3c14 fix(@vben/layouts): correct logo theme in semi-dark sidebar (#7785) (#7902) 2026-05-10 10:41:43 +08:00
JyQAQ
f5feddc6c7 feat(@vben/plugins): tiptap 组件新增 maxHeight 属性支持及文档 (#7897)
* feat(@vben/plugins): tiptap 组件新增 maxHeight 属性支持

- 新增 maxHeight prop 支持限制编辑器最大高度
- 编辑器内容区超出最大高度时自动滚动
- 移除 style.css 中未使用的 CSS 规则

* docs(@vben/plugins): 添加 tiptap 富文本编辑器组件文档

- 新增中文文档 docs/src/components/common-ui/vben-tiptap.md
- 新增英文文档 docs/src/en/components/common-ui/vben-tiptap.md
- 新增基础用法示例 demos/vben-tiptap/basic
- 新增图片上传示例 demos/vben-tiptap/image-upload
- 更新文档侧边栏配置

* fix(@vben/plugins): extract URL from response in image upload callback

The upload callback was incorrectly passing the AxiosResponse object
to resolve() instead of extracting the actual image URL string.
This caused the editor to insert [object Object] as image src,
resulting in broken images.

* chore: add changeset for tiptap maxHeight feature

* Revert "chore: add changeset for tiptap maxHeight feature"

This reverts commit a28fc4441f14641f6af6c1a143aa6959591315b2.
2026-05-10 10:38:21 +08:00
Yanghai.Lin
0868deb82b fix: 修复 Input 组件 focus 边框被裁剪问题 (#7894)
* fix(other): 修复 oxlint ignorePatterns 导致 pre-commit hook 失败

* fix(@vben-core/shadcn-ui): 修复 Input 组件 focus 时边框被父容器 overflow hidden 裁剪

* fix(@vben-core/shadcn-ui): 修复 SelectTrigger 组件 focus 时边框被父容器 overflow hidden 裁剪
2026-05-10 10:37:39 +08:00
layhuts
1299acd8f9 fix: 获取已存在的 key,避免重复写入刷新过期时间 2026-05-09 23:27:25 +08:00
layhuts
43717807a4 style: reformat code 2026-05-09 23:18:44 +08:00
layhuts
1b4e126128 fix: 处理localStorageDriver在某些受限浏览器环境下报错导致无法使用 2026-05-09 23:10:05 +08:00
layhuts
d23b246aee fix: 处理localStorage prefix === ''时提醒用户可能会全部删除数据 2026-05-09 23:04:38 +08:00
layhuts
71f2e5f504 fix: 去除onConfirm中的setTimeout 2026-05-09 22:51:39 +08:00
layhuts
4bbd34fab9 fix: 修复 labelField 和 nameField 2026-05-09 22:28:03 +08:00
layhuts
08e4bb40b4 style: reformat code 2026-05-09 18:28:23 +08:00
layhuts
9c49f4bb1e refactor: 优化viewed实例初始化,rowStyle rowClassName 从最新的配置中读取 2026-05-09 18:27:09 +08:00
layhuts
28905b0bec style: reformat code 2026-05-09 14:08:19 +08:00
layhuts
bbcad709ca style: 修改注释 2026-05-09 14:07:30 +08:00
layhuts
a5f0537cb0 fix: 处理viewedKeys由于 FIFO 顺序,显式提供的值可能会被异步存储恢复静默地移除。 2026-05-09 13:38:50 +08:00
layhuts
c78d89f549 fix: 处理非原子性的先清除后写入操作会setKeys带来永久性数据丢失的风险 2026-05-09 13:16:50 +08:00
layhuts
f2a17cbe78 fix: 处理getViewedKeys()返回对内部可变对象的直接引用问题 2026-05-09 13:10:41 +08:00
layhuts
9667232684 fix: 加上 - 分隔符来避免跨前缀误匹配 2026-05-09 12:56:50 +08:00
layhuts
1f584ff0c9 fix: 处理window不可用时降级使用Memory 2026-05-09 12:49:21 +08:00
layhuts
75ed17a1f9 docs: 更正saveToCache所属组件 2026-05-09 12:41:10 +08:00
layhuts
cc6ccaab38 docs: 为符合 Markdown lint 规范,指定代码块语言。 2026-05-09 12:38:13 +08:00
layhuts
0be4b51eaa fix: 解决window在构造函数中进行访问破坏SSR/Node环境。 2026-05-09 12:33:15 +08:00
layhuts
b40822e3ed fix: 解决indexedDB的getDB在失败重试时永远返回错误Promise问题 2026-05-09 12:02:41 +08:00
layhuts
df5cb426d1 fix: 解决indexedDB的setItem、,removeItem以及clear在请求成功时而不是事务完成时触发resolve问题 2026-05-09 11:54:11 +08:00
layhuts
2d36d6b510 style: reformat code 2026-05-09 11:03:47 +08:00
layhuts
88d5661e0c refactor: 重构ViewedRowPersistOptions处理扁平化问题
改成按 type 字段区分的联合类型
2026-05-09 11:02:16 +08:00
layhuts
8ce773f264 feat: 在storage-manager添加keys方法 2026-05-09 10:58:41 +08:00
layhuts
329fa68207 fix: 手动操作行标记示例时动态获取列表前两行 2026-05-09 09:23:10 +08:00
layhuts
e1f6449073 feat: 表格已读行操作标记 2026-05-08 20:03:05 +08:00
layhuts
51e8b27d4c style(@vben-core/shared): formatting 2026-05-08 17:36:47 +08:00
layhuts
e555ee065e feat(@vben-core/shared): 新增indexedDB
在cache下新增indexedDB缓存
2026-05-08 17:34:54 +08:00
Lin
b5dacd992f feat: add time zone option to preference settings (#7871) 2026-05-03 12:16:58 +08:00
Jin Mao
d55f17670a chore: 更新依赖包版本
- 更新 pkg-types 从 2.3.0 到 2.3.1
- 更新 rolldown 从 1.0.0-rc.15 到 1.0.0-rc.17
- 更新 typescript 从 6.0.2 到 6.0.3
- 更新 vite 从 8.0.8 到 8.0.10
2026-05-01 06:55:42 +08:00
Jin Mao
9f20d52b4e chore: 更新 FTP 部署操作版本
- 将 SamKirkland/FTP-Deploy-Action 从 v4.3.6 升级到 v4.4.0
- 所有部署任务中的 FTP 操作均已更新到新版本
- 保持了所有现有配置和凭据不变
2026-05-01 06:53:06 +08:00
Jin Mao
86445a38e4 Merge branch 'fork/jyqwq/feature/富文本支持图片上传' 2026-05-01 06:49:48 +08:00
leo
9a73e961fc fix(@vben/stores): respect base URL when opening route in new window (#7837)
* fix(@vben/stores): respect base URL when opening route in new window

* fix(@vben/stores): respect base URL when opening route in new window
2026-05-01 06:49:05 +08:00
boisduval
c0b2ef980e fix: 修复 IconPicker 在手动输入时表单值不更新的问题 (#7869)
- 修复 IconPicker.vue 在 updateCurrentSelect 时未同步更新 modelValue 的 Bug
- 优化 IconPicker 适配器配置,使其兼容 Element Plus 的 ElInput 组件
2026-04-30 15:46:07 +08:00
mew
e8dc464e79 fix: issue in tab bar chrome height is not full due to wrapper element div (#7867)
Caused by PR #7858
2026-04-30 15:45:44 +08:00
mew
99c38c93c8 feat: refactor context menu to capture native events (#7858)
* feat: refactor context menu to capture native events

prevent context-menu to show in html input fields

* fix: refactor context-menu.vue for improved structure

* chore: fix format

* chore: remove dead code

* chore: fix lint

* fix: update contenteditable selector in context menu

proposed fixed by coderabitai
2026-04-28 13:36:13 +08:00
yuan.ji
244c0a5884 fix(@vben/plugins): 根据代码审查意见修复 tiptap 图片上传
- 提取 findPlaceholderPos 辅助函数,消除重复的 descendants 查找
- 添加 editor.isDestroyed 守卫,防止操作已销毁编辑器
- renderHTML 不输出上传状态属性,防止 blob URL 泄露到序列化 HTML
- uploadImage 命令返回 boolean,添加 Commands 类型增强,移除 as any
- 拖拽/粘贴多图时仅处理第一张并提示仅支持单图上传
- 自定义 extensions 时不传 imageUpload 给工具栏,toolbar action 加运行时守卫
2026-04-27 14:33:30 +08:00
yuan.ji
c6fd599784 feat(@vben/plugins): 补充 tiptap 图片上传类型、工具栏和示例
- 新增 ImageUploadOptions 类型定义和 imageUpload 属性
- 工具栏图片按钮支持上传/URL 双模式
- playground 添加图片上传 mock 示例
2026-04-27 13:42:56 +08:00
yuan.ji
4ca2f1c6e8 feat(@vben/plugins): tiptap 支持图片上传功能
- 新增 imageUpload 配置项,支持自定义上传接口
- 支持文件选择、拖拽、粘贴三种上传方式
- 上传中显示 blob 预览图 + loading spinner / 进度条
- 支持 accept 和 maxSize 文件校验
- 支持 onUploadError 自定义错误处理
- 未配置 imageUpload 时保持原有 URL 插入行为不变
- 使用 NodeView 实现实时 DOM 控制的进度展示
2026-04-27 13:42:36 +08:00
Jin Mao
36d7dc23fa Merge branch 'fork/zilvya/ta1' 2026-04-26 16:26:30 +08:00
Lgf
42317ddf41 feat(watermark): 添加暗黑模式水印颜色适配
为水印功能添加暗黑模式适配,根据当前主题自动切换水印颜色。在暗黑模式下使用浅色水印,在亮色模式下使用深色水印,提升视觉体验。
2026-04-24 15:42:07 +08:00
boisduval
c7fd6ffb0e feat: 添加日期格式化函数的空值处理
- 当传入的时间参数为 undefined、null 或空字符串时,直接返回空字符串而不是继续执行格式化逻辑,避免无效的时间值导致程序错误。
2026-04-23 17:25:10 +08:00
AxiosLeo
3a83fb0c3e fix: ensure trigger function is awaited in useDependencies (#7830) 2026-04-22 07:09:09 +08:00
AxiosLeo
5907c04e00 fix: update selector for active menu item in useMenuScroll hook (#7829) 2026-04-22 07:08:40 +08:00
Jin Mao
6885927441 chore: fix actions error 2026-04-15 18:49:07 +08:00
2ylllll
c8c589ae7e fix: type check (#7818) 2026-04-15 18:11:06 +08:00
Jin Mao
d0889b5cc4 chore: 更新依赖包版本
- 更新 @tsdown/css 从 0.21.7 到 0.21.8
- 更新 @vitejs/plugin-vue 从 6.0.5 到 6.0.6
- 更新 dotenv 从 17.4.1 到 17.4.2
- 更新 globals 从 17.4.0 到 17.5.0
- 更新 happy-dom 从 20.8.9 到 20.9.0
- 更新 oxlint 从 1.59.0 到 1.60.0
- 更新 tsdown 从 0.21.7 到 0.21.8
2026-04-15 17:28:17 +08:00
Jin Mao
2f1a866cb4 chore: 更新 pnpm action 版本
- 将 .github/actions/setup-node/action.yml 中的 pnpm/action-setup 从 v4 升级到 v6
- 将 .github/workflows/ci.yml 中的 pnpm/action-setup 从 v5 升级到 v6
- 移除重复的 pnpm 安装步骤配置
- 统一使用最新的 pnpm action 版本以获得更好的兼容性
- 保持 run_install 配置为 false 以跳过自动安装依赖
2026-04-15 17:27:57 +08:00
Jin Mao
fee32c1d12 chore: 优化通知组件逻辑
- 移除冗余的空值检查,简化数组长度判断条件
- 统一导入语句的引号格式为双引号
- 优化 VbenScrollbar 组件的显示条件逻辑
- 更新清除按钮的禁用状态判断逻辑
2026-04-15 17:22:39 +08:00
Jin Mao
f2652833a1 Merge branch 'fork/xueyitt/main' 2026-04-15 17:20:10 +08:00
allen
c3aa63982f fix: devtools warning 2026-04-15 16:44:13 +08:00
allen
4a968d9379 fix: devtools warning 2026-04-15 16:39:56 +08:00
allen
ec9b323195 docs: demo注释修改 2026-04-15 14:54:40 +08:00
allen
d69455e8ef fix: dependencies访问extendApi 2026-04-15 14:42:21 +08:00
allen
33e2582f60 fix: fix lint and add new form-ui features
feat(form-ui): 在 dependencies 里提供访问extendApi的能力
2026-04-15 14:21:39 +08:00
allen
991408b451 fix: default precision error 2026-04-13 21:08:03 +08:00
allen
a096073a8e fix: lint 2026-04-13 20:43:29 +08:00
allen
f30157fa59 fix: class 2026-04-13 20:41:43 +08:00
allen
12a81a7a7d fix: demo validator usage & types import 2026-04-13 20:15:47 +08:00
allen
e808fe74c1 Merge branch 'feature-collapsible-component' of https://github.com/2yllll/vue-vben-admin into feature-collapsible-component 2026-04-13 19:44:45 +08:00
allen
4665a787a0 fix: collapsible component css fix 2026-04-13 19:44:21 +08:00
2ylllll
b7774fc9d9 Merge branch 'main' into feature-collapsible-component 2026-04-13 19:26:23 +08:00
allen
6f18718c87 feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启
feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启
- shadcn-ui 增加 collapsible组件,collapsible-params组件
- form新增支持单项折叠
- collapsible-params组件在Form表单应用
2026-04-13 19:20:01 +08:00
Caisin
2a32715c99 feat: enable project-scoped preferences extension tabs (#7803)
* feat: enable project-scoped preferences extension tabs

Add a typed extension schema so subprojects can define extra settings,
render them in the shared preferences drawer only when configured, and
consume them in playground as a real feature demo. Extension labels now
follow locale keys instead of hardcoded app-specific strings.

Constraint: Reuse the shared preferences drawer and field blocks
Rejected: Add app-specific fields to core preferences | too tightly coupled
Rejected: Inline localized label objects | breaks existing locale-key flow
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep extension labels as locale keys rendered via $t in UI
Tested: Vitest preferences tests
Tested: Turbo typecheck for preferences, layouts, web-antd, and playground
Tested: ESLint for touched preferences and playground files
Not-tested: Manual browser interaction in playground preferences drawer

* fix: satisfy lint formatting for preferences extension demo

Adjust the playground preferences extension demo template so formatter and
Vue template lint rules agree on the rendered markup. This keeps CI green
without changing runtime behavior.

Constraint: Must preserve the existing demo behavior while fixing CI only
Rejected: Disable the Vue newline rule | would weaken shared lint guarantees
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Prefer computed/template structures that avoid formatter-vs-lint conflicts
Tested: pnpm run lint
Not-tested: Manual browser interaction in playground preferences extension demo

* fix: harden custom preferences validation and i18n labels

Tighten custom preferences handling so numeric extension fields respect
min, max, and step constraints. Number inputs now ignore NaN values,
and web-antd extension metadata uses locale keys instead of raw strings.
Also align tip-based hover guards in shared preference inputs/selects.

Constraint: Keep fixes scoped to verified findings only
Rejected: Broader refactor of preferences field components | not needed for these fixes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Reuse the same validation path for updates and cache hydration
Tested: Vitest preferences tests
Tested: ESLint for touched preferences and widget files
Tested: Typecheck for web-antd, layouts, and core preferences
Not-tested: Manual browser interaction for all preference field variants

* fix: remove localized default from playground extension config

Drop the hardcoded Chinese default value from the playground extension
report title field and fall back to an empty string instead. This keeps
extension config locale-neutral while preserving localized labels and
placeholders through translation keys.

Constraint: Keep the fix limited to the verified localized default issue
Rejected: Compute the default from runtime locale in config | unnecessary for this finding
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Avoid embedding localized literals in extension default values
Tested: ESLint for playground/src/preferences.ts
Tested: Oxfmt check for playground/src/preferences.ts
Not-tested: Manual playground preferences interaction

* docs: document project-scoped preferences extension workflow

Add Chinese and English guide sections explaining how to define,
initialize, read, and update project-scoped preferences extensions.
Also document numeric field validation and point readers to the
playground demo for a complete example.

Constraint: Keep this docs-only and aligned with the shipped API
Rejected: Update only Chinese docs | would leave English docs inconsistent
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep zh/en examples and playground demo paths synchronized
Tested: git diff --check; pnpm build:docs
Not-tested: Manual browser review of the rendered docs site

* fix: harden custom preferences defaults and baselines

Use a locale-neutral default for the web-antd report title.
Also stop preference getters from exposing mutable baseline
or extension schema objects, and add a regression test for
external mutation attempts.

Constraint: Keep behavior compatible with the shipped preferences API
Rejected: Return raw refs with readonly typing only | callers could still mutate internals
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep defensive copies for baseline and schema getters unless storage semantics change
Tested: eslint, oxlint, targeted vitest, filtered typecheck, git diff --check
Not-tested: Full monorepo typecheck and test suite

* test: relax custom preference cache key matching

Avoid coupling the custom-number cache test to one exact
localStorage key string. Match the intended cache lookup
more loosely so the test still verifies filtering behavior
without depending on the full namespaced cache key.

Constraint: Focus the test on cache filtering behavior
Rejected: Assert one exact key | brittle with namespace changes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Prefer behavior tests over literal storage keys
Tested: targeted vitest, eslint, git diff --check
Not-tested: Full monorepo test suite

---------

Co-authored-by: caisin <caisin@caisins-Mac-mini.local>
2026-04-13 17:52:17 +08:00
Caisin
46f323431c feat(form-ui): support schema valueFormat for getValues payload shaping (#7804)
* feat(@vben-core/form-ui): support schema valueFormat on getValues

Some form fields emit UI-friendly structures such as time-range arrays,
while consumers and backend APIs often need a different payload shape.
This adds schema-level `valueFormat` hooks so `getValues()` can
normalize field output at read time without forcing callers to
post-process every submission path.

Constraint: Must preserve existing range-time mapping and nested field behavior
Constraint: Must not mutate live vee-validate form state while formatting output
Rejected: Global formatter config | too coarse for per-field payload shaping
Rejected: Post-submit-only transform | misses reset/query/change handlers
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep `getValues()` output derivation side-effect free
Directive: Clone raw form values before formatting derived payloads
Tested: vitest form-api test for valueFormat and existing getValues paths
Tested: oxlint on changed form-ui source and test files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors

* fix(@vben-core/form-ui): restore mount compatibility and share field path parsing

Follow-up review found two real regressions and one missing assertion in the
new value formatting flow. `FormApi.mount()` had become breaking by requiring
`componentRefMap`, and delete path resolution duplicated field-name parsing
instead of sharing the reader grammar. This patch restores backward
compatibility, centralizes field-name path parsing, and extends the test to
prove formatting does not mutate live form values.

Constraint: Must preserve current valueFormat behavior and nested field support
Constraint: Must not reintroduce mutation of live vee-validate values
Rejected: Keep duplicated delete parsing | risks grammar drift from read path
Rejected: Only loosen mount tests | would leave consumer-facing API breakage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Reuse shared field-name parsing for read/delete semantics in form-ui
Tested: vitest form-api test suite
Tested: oxlint on changed form-ui files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors
EOF && git push hekx feature-form-value-format

* fix(@vben-core/form-ui): clear stale component refs on unmount

A follow-up review found that `unmount()` left the private component ref map
populated. Because `mount()` now accepts an optional `componentRefMap`, a later
mount without a new map could silently reuse stale refs from a prior form
instance. This change clears the ref map on unmount and adds a regression test
covering remount behavior without a new ref map.

Constraint: Must preserve backward-compatible optional `mount()` ref map behavior
Constraint: Focus and field-ref lookups must not observe stale refs after unmount
Rejected: Clear refs only during next mount | stale state would still leak between lifecycle calls
Rejected: Remove mount fallback entirely | would undo the compatibility fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When mount falls back to internal refs, unmount must always reset that cache
Tested: vitest form-api test suite
Tested: oxlint on changed form-api source and test files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors

* refactor(@vben-core/form-ui): trim redundant valueFormat plumbing

Review feedback identified a few small cleanups in the value formatting path.
This removes an unnecessary shallow clone in `getValues()`, reuses the
already-parsed `rawKey` from `resolveFieldNamePath()` instead of re-resolving
it in multiple helpers, and clarifies the `FormValueFormat` contract for
undefined-as-delete decomposition behavior.

Constraint: Must not change runtime valueFormat behavior or payload shape
Constraint: Documentation and helper cleanup should stay behavior-preserving
Rejected: Leave duplicate raw-key resolution in place | adds needless parsing churn
Rejected: Expand the formatter API further | outside the scope of this cleanup
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep read/format helper plumbing lean and avoid duplicate field-name parsing
Tested: vitest form-api test suite
Tested: oxlint on changed form-ui source and test files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors

* feat(@vben-core/form-ui): document valueFormat with live examples

The new `valueFormat` feature needed a concrete usage path in both the
playground and the docs so users can understand how raw component values differ
from the final payload returned by `getValues()`. This adds a dedicated form
example, wires it into the playground menu, and documents the API with an
interactive docs demo. The preview panels now stay in sync when values are set,
reset, or submitted.

Constraint: Must demonstrate both return-value and setValue decomposition flows
Constraint: Example previews must react to setValues, reset, and manual edits
Rejected: Only document via markdown snippet | insufficient for verifying live payload behavior
Rejected: Reuse an existing basic form page | would bury feature-specific behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep playground and docs demos behaviorally aligned when extending valueFormat examples
Tested: eslint on playground/docs valueFormat demo files and route module
Tested: oxlint on playground route module
Not-tested: Full docs/playground app runtime was not launched in this session

* chore(@vben-core/form-ui): normalize valueFormat demo formatting

The previous feature/docs commit left a few formatter-only adjustments unstaged
after hooks rewrote line wrapping in the new demo and docs pages. This commit
captures those final non-behavioral formatting updates so the branch matches the
current working tree.

Constraint: Must not change runtime behavior or docs meaning
Rejected: Leave post-hook diffs unstaged | branch would not reflect local state
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: After hook-driven rewrites, verify the working tree is clean before final push
Tested: Git diff inspection of remaining changes
Not-tested: No additional runtime verification needed; formatting-only follow-up
EOF && git push hekx feature-form-value-format

* fix(@vben-core/form-ui): remove docs demo dayjs dependency

The docs valueFormat demo imported `dayjs` directly even though the docs
package does not declare it as a dependency. That caused `@vben/docs:build`
to fail in CI during VitePress bundling. This change removes the direct
import, keeps the preview formatter generic for day-like values, and drops
the docs-only preset button that required constructing dayjs instances.

Constraint: Docs build must succeed without adding new package dependencies
Constraint: Playground example should remain unchanged and fully interactive
Rejected: Add dayjs to docs dependencies | unnecessary for a small display demo
Rejected: Externalize dayjs in VitePress build | hides a package boundary issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Docs demos should avoid imports only available through transitive deps
Tested: pnpm exec eslint docs/src/demos/vben-form/value-format/index.vue
Tested: pnpm --dir docs run build
Not-tested: No browser-side manual verification of the docs demo in this session

---------

Co-authored-by: caisin <caisin@caisins-Mac-mini.local>
2026-04-13 17:52:17 +08:00
Caisin
2b1fcb038b feat(common-ui): add labelFn support to ApiComponent (#7801)
* feat: allow api-component labels to be derived from option data

ApiComponent already normalizes option records into the label/value shape used by
consuming controls, but label text could only come from a single field. Add
labelFn so callers can build labels from the full option record while keeping
labelField as the fallback path.

This keeps the change inside the existing component instead of introducing a
wrapper, and it also normalizes direct options through the same transform path
as API-loaded options for consistent behavior.

Constraint: Must extend the existing ApiComponent API instead of adding a second
Constraint: wrapper component
Rejected: Add a separate ApiLabelComponent wrapper |
Rejected: extra surface area for one option-mapping concern
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep labelFn as a presentation transform and preserve labelField
Directive: fallback for existing callers
Tested: pnpm exec eslint api-component.vue index.ts types.ts
Tested: pnpm exec vue-tsc --noEmit -p packages/effects/common-ui/tsconfig.json
Not-tested: runtime integration in consuming select/tree-select components

* Update packages/effects/common-ui/src/components/api-component/api-component.vue

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-13 17:52:17 +08:00
过冬
d779a39862 fix: antdv-next message/notification 跟随暗色主题 (#7799) 2026-04-13 17:52:17 +08:00
Jin Mao
0c8db0f6be fix: 修复VITE_APP_TITLE变量替换语法
- 将index.html中的<%= VITE_APP_TITLE %>替换为%VITE_APP_TITLE%
- 更新web-antd、web-antdv-next、web-ele、web-naive、web-tdesign应用
- 修改文档中loading组件的VITE_APP_TITLE引用方式
- 修复vite-config插件中默认加载模板的变量语法
- 统一所有应用和模板中的环境变量引用格式
2026-04-13 17:52:17 +08:00
Jin Mao
37393e1820 chore: fix actions error 2026-04-13 17:52:16 +08:00
xingyu4j
93e23c6b05 chore: update deps 2026-04-13 17:52:16 +08:00
xingyu4j
e023a0852a fix: tailwindcss lint config 2026-04-13 17:52:16 +08:00
xingyu4j
306739cabb fix: lint 2026-04-13 17:52:16 +08:00
xingyu4j
093479bb33 chore: remove vite-plugin-html 2026-04-13 17:52:16 +08:00
xingyu4j
bd369df3bc fix: check deps 2026-04-13 17:52:16 +08:00
xingyu4j
4f7b396ebb chore: update deps 2026-04-13 17:52:16 +08:00
xingyu4j
d700b04841 fix: tailwindcss lint 2026-04-13 17:52:16 +08:00
xingyu4j
243456d928 fix: ts config 2026-04-13 17:52:16 +08:00
Jin Mao
7e8b9c2368 fix: 配置 TypeScript 构建根目录
- 添加 rootDir 编译选项指向 ./src 目录
- 保持现有编译配置不变
- 排除测试文件和 node_modules 目录
2026-04-13 17:52:15 +08:00
Jin Mao
92298bac2b chore: update deps 2026-04-13 17:52:15 +08:00
dullathanol
436a865bef chore: 修正注释 2026-04-13 17:52:15 +08:00
dullathanol
185011ee21 fix: tailwindcss config 2026-04-13 17:52:15 +08:00
过冬
feca01484b Merge branch 'vbenjs:main' into main 2026-04-13 17:52:15 +08:00
Jin Mao
35e1166b9f chore: fix lint 2026-04-13 17:52:15 +08:00
dullathanol
d0c17090ef fix: 修正 Modal/Drawer 中 loading 属性注释 2026-04-13 17:52:15 +08:00
dullathanol
cc32ebeb12 feat: 支持 overflow 配置以允许拖拽超出可视区 2026-04-13 17:52:15 +08:00
dullathanol
02372f12ba fix: 弹窗组件拖拽后全屏位置异常 2026-04-13 17:52:15 +08:00
Jin Mao
e9bdbb610a chore: fix lint
- 关闭 vitest/require-mock-type-parameters 规则
2026-04-13 17:52:15 +08:00
dullathanol
2013ba4de4 fix: 补全 ComponentPropsMap 与 Vxe 表格表单链路的类型 2026-04-13 17:52:15 +08:00
dullathanol
e417a2c209 fix: 修复 FormField 在 SFC 中的运行时异常 2026-04-13 17:52:14 +08:00
dullathanol
f1273571c7 fix: 修复部分情况 component 类型丢失问题 2026-04-13 17:52:14 +08:00
dullathanol
064a6a20f0 feat: 表单 Schema 支持组件 Props 映射泛型,同步适配VxeGrid 2026-04-13 17:52:14 +08:00
dullathanol
cb5e7e00f1 refactor: 简化 componentProps 回调的类型写法 2026-04-13 17:52:14 +08:00
dullathanol
5ed39c03a4 fix: 函数式 componentProps 按已注册 component 的 Props 校验返回值 2026-04-13 17:52:14 +08:00
dullathanol
794b103aa9 feat: Schema 中 componentProps 随注册组件联动类型提示 2026-04-13 17:52:14 +08:00
墨苒孤
c7b1bb3492 fix(form): 修复表单示例中 switch 组件无法切换的问题 (#7636) (#7763) 2026-04-13 17:52:14 +08:00
橙子
89349f8d91 fix: interface DropdownMenuProps don‘t have key prop (#7757) 2026-04-13 17:52:14 +08:00
HaroldZhangCode91
8e0b65a0e1 feat: fix oxlint error for oxlint upgrade (#7756)
1. remove unknown rule out of oxlint
2. add the corresponding back to eslint-config
3. fixed the eslint error for package.json
2026-04-13 17:52:13 +08:00
墨苒孤
b096e83f93 fix: make search case-insensitive (#7689) (#7755) 2026-04-13 17:52:13 +08:00
橙子
4a69c9e665 perf: replace onUnMounted to tryOnScopeDispose (#7747)
* perf: replace `onUnMounted` to `tryOnScopeDispose`

* perf: replace `onUnMounted` to `tryOnScopeDispose`
2026-04-13 17:52:13 +08:00
Jin Mao
7a60a081a2 chore: update deps 2026-04-13 17:52:13 +08:00
xingyu4j
c693c854a7 fix: extension-document 2026-04-13 17:52:13 +08:00
xingyu4j
5eca357a5c feat(@vben/plugins): add tiptap rich text editor 2026-04-13 17:52:13 +08:00
xingyu4j
2930dcd78e chore: typescript config is expired‌ 2026-04-13 17:52:13 +08:00
xingyu4j
e421d9d99b chore: update deps 2026-04-13 17:52:12 +08:00
xueyitt
6c029bfa32 Merge branch 'main' into main 2026-04-13 16:10:29 +08:00
雪忆天堂
fe77bc8bc9 feat: 通知模块自定义加强 2026-04-13 16:05:42 +08:00
Caisin
ccabbf0e97 feat: enable project-scoped preferences extension tabs (#7803)
* feat: enable project-scoped preferences extension tabs

Add a typed extension schema so subprojects can define extra settings,
render them in the shared preferences drawer only when configured, and
consume them in playground as a real feature demo. Extension labels now
follow locale keys instead of hardcoded app-specific strings.

Constraint: Reuse the shared preferences drawer and field blocks
Rejected: Add app-specific fields to core preferences | too tightly coupled
Rejected: Inline localized label objects | breaks existing locale-key flow
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep extension labels as locale keys rendered via $t in UI
Tested: Vitest preferences tests
Tested: Turbo typecheck for preferences, layouts, web-antd, and playground
Tested: ESLint for touched preferences and playground files
Not-tested: Manual browser interaction in playground preferences drawer

* fix: satisfy lint formatting for preferences extension demo

Adjust the playground preferences extension demo template so formatter and
Vue template lint rules agree on the rendered markup. This keeps CI green
without changing runtime behavior.

Constraint: Must preserve the existing demo behavior while fixing CI only
Rejected: Disable the Vue newline rule | would weaken shared lint guarantees
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Prefer computed/template structures that avoid formatter-vs-lint conflicts
Tested: pnpm run lint
Not-tested: Manual browser interaction in playground preferences extension demo

* fix: harden custom preferences validation and i18n labels

Tighten custom preferences handling so numeric extension fields respect
min, max, and step constraints. Number inputs now ignore NaN values,
and web-antd extension metadata uses locale keys instead of raw strings.
Also align tip-based hover guards in shared preference inputs/selects.

Constraint: Keep fixes scoped to verified findings only
Rejected: Broader refactor of preferences field components | not needed for these fixes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Reuse the same validation path for updates and cache hydration
Tested: Vitest preferences tests
Tested: ESLint for touched preferences and widget files
Tested: Typecheck for web-antd, layouts, and core preferences
Not-tested: Manual browser interaction for all preference field variants

* fix: remove localized default from playground extension config

Drop the hardcoded Chinese default value from the playground extension
report title field and fall back to an empty string instead. This keeps
extension config locale-neutral while preserving localized labels and
placeholders through translation keys.

Constraint: Keep the fix limited to the verified localized default issue
Rejected: Compute the default from runtime locale in config | unnecessary for this finding
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Avoid embedding localized literals in extension default values
Tested: ESLint for playground/src/preferences.ts
Tested: Oxfmt check for playground/src/preferences.ts
Not-tested: Manual playground preferences interaction

* docs: document project-scoped preferences extension workflow

Add Chinese and English guide sections explaining how to define,
initialize, read, and update project-scoped preferences extensions.
Also document numeric field validation and point readers to the
playground demo for a complete example.

Constraint: Keep this docs-only and aligned with the shipped API
Rejected: Update only Chinese docs | would leave English docs inconsistent
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep zh/en examples and playground demo paths synchronized
Tested: git diff --check; pnpm build:docs
Not-tested: Manual browser review of the rendered docs site

* fix: harden custom preferences defaults and baselines

Use a locale-neutral default for the web-antd report title.
Also stop preference getters from exposing mutable baseline
or extension schema objects, and add a regression test for
external mutation attempts.

Constraint: Keep behavior compatible with the shipped preferences API
Rejected: Return raw refs with readonly typing only | callers could still mutate internals
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep defensive copies for baseline and schema getters unless storage semantics change
Tested: eslint, oxlint, targeted vitest, filtered typecheck, git diff --check
Not-tested: Full monorepo typecheck and test suite

* test: relax custom preference cache key matching

Avoid coupling the custom-number cache test to one exact
localStorage key string. Match the intended cache lookup
more loosely so the test still verifies filtering behavior
without depending on the full namespaced cache key.

Constraint: Focus the test on cache filtering behavior
Rejected: Assert one exact key | brittle with namespace changes
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Prefer behavior tests over literal storage keys
Tested: targeted vitest, eslint, git diff --check
Not-tested: Full monorepo test suite

---------

Co-authored-by: caisin <caisin@caisins-Mac-mini.local>
2026-04-13 15:11:57 +08:00
Caisin
5b84ac5b13 feat(form-ui): support schema valueFormat for getValues payload shaping (#7804)
* feat(@vben-core/form-ui): support schema valueFormat on getValues

Some form fields emit UI-friendly structures such as time-range arrays,
while consumers and backend APIs often need a different payload shape.
This adds schema-level `valueFormat` hooks so `getValues()` can
normalize field output at read time without forcing callers to
post-process every submission path.

Constraint: Must preserve existing range-time mapping and nested field behavior
Constraint: Must not mutate live vee-validate form state while formatting output
Rejected: Global formatter config | too coarse for per-field payload shaping
Rejected: Post-submit-only transform | misses reset/query/change handlers
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep `getValues()` output derivation side-effect free
Directive: Clone raw form values before formatting derived payloads
Tested: vitest form-api test for valueFormat and existing getValues paths
Tested: oxlint on changed form-ui source and test files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors

* fix(@vben-core/form-ui): restore mount compatibility and share field path parsing

Follow-up review found two real regressions and one missing assertion in the
new value formatting flow. `FormApi.mount()` had become breaking by requiring
`componentRefMap`, and delete path resolution duplicated field-name parsing
instead of sharing the reader grammar. This patch restores backward
compatibility, centralizes field-name path parsing, and extends the test to
prove formatting does not mutate live form values.

Constraint: Must preserve current valueFormat behavior and nested field support
Constraint: Must not reintroduce mutation of live vee-validate values
Rejected: Keep duplicated delete parsing | risks grammar drift from read path
Rejected: Only loosen mount tests | would leave consumer-facing API breakage
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Reuse shared field-name parsing for read/delete semantics in form-ui
Tested: vitest form-api test suite
Tested: oxlint on changed form-ui files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors
EOF && git push hekx feature-form-value-format

* fix(@vben-core/form-ui): clear stale component refs on unmount

A follow-up review found that `unmount()` left the private component ref map
populated. Because `mount()` now accepts an optional `componentRefMap`, a later
mount without a new map could silently reuse stale refs from a prior form
instance. This change clears the ref map on unmount and adds a regression test
covering remount behavior without a new ref map.

Constraint: Must preserve backward-compatible optional `mount()` ref map behavior
Constraint: Focus and field-ref lookups must not observe stale refs after unmount
Rejected: Clear refs only during next mount | stale state would still leak between lifecycle calls
Rejected: Remove mount fallback entirely | would undo the compatibility fix
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When mount falls back to internal refs, unmount must always reset that cache
Tested: vitest form-api test suite
Tested: oxlint on changed form-api source and test files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors

* refactor(@vben-core/form-ui): trim redundant valueFormat plumbing

Review feedback identified a few small cleanups in the value formatting path.
This removes an unnecessary shallow clone in `getValues()`, reuses the
already-parsed `rawKey` from `resolveFieldNamePath()` instead of re-resolving
it in multiple helpers, and clarifies the `FormValueFormat` contract for
undefined-as-delete decomposition behavior.

Constraint: Must not change runtime valueFormat behavior or payload shape
Constraint: Documentation and helper cleanup should stay behavior-preserving
Rejected: Leave duplicate raw-key resolution in place | adds needless parsing churn
Rejected: Expand the formatter API further | outside the scope of this cleanup
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep read/format helper plumbing lean and avoid duplicate field-name parsing
Tested: vitest form-api test suite
Tested: oxlint on changed form-ui source and test files
Not-tested: Full repo typecheck baseline has unrelated .vue module resolution errors

* feat(@vben-core/form-ui): document valueFormat with live examples

The new `valueFormat` feature needed a concrete usage path in both the
playground and the docs so users can understand how raw component values differ
from the final payload returned by `getValues()`. This adds a dedicated form
example, wires it into the playground menu, and documents the API with an
interactive docs demo. The preview panels now stay in sync when values are set,
reset, or submitted.

Constraint: Must demonstrate both return-value and setValue decomposition flows
Constraint: Example previews must react to setValues, reset, and manual edits
Rejected: Only document via markdown snippet | insufficient for verifying live payload behavior
Rejected: Reuse an existing basic form page | would bury feature-specific behavior
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep playground and docs demos behaviorally aligned when extending valueFormat examples
Tested: eslint on playground/docs valueFormat demo files and route module
Tested: oxlint on playground route module
Not-tested: Full docs/playground app runtime was not launched in this session

* chore(@vben-core/form-ui): normalize valueFormat demo formatting

The previous feature/docs commit left a few formatter-only adjustments unstaged
after hooks rewrote line wrapping in the new demo and docs pages. This commit
captures those final non-behavioral formatting updates so the branch matches the
current working tree.

Constraint: Must not change runtime behavior or docs meaning
Rejected: Leave post-hook diffs unstaged | branch would not reflect local state
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: After hook-driven rewrites, verify the working tree is clean before final push
Tested: Git diff inspection of remaining changes
Not-tested: No additional runtime verification needed; formatting-only follow-up
EOF && git push hekx feature-form-value-format

* fix(@vben-core/form-ui): remove docs demo dayjs dependency

The docs valueFormat demo imported `dayjs` directly even though the docs
package does not declare it as a dependency. That caused `@vben/docs:build`
to fail in CI during VitePress bundling. This change removes the direct
import, keeps the preview formatter generic for day-like values, and drops
the docs-only preset button that required constructing dayjs instances.

Constraint: Docs build must succeed without adding new package dependencies
Constraint: Playground example should remain unchanged and fully interactive
Rejected: Add dayjs to docs dependencies | unnecessary for a small display demo
Rejected: Externalize dayjs in VitePress build | hides a package boundary issue
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Docs demos should avoid imports only available through transitive deps
Tested: pnpm exec eslint docs/src/demos/vben-form/value-format/index.vue
Tested: pnpm --dir docs run build
Not-tested: No browser-side manual verification of the docs demo in this session

---------

Co-authored-by: caisin <caisin@caisins-Mac-mini.local>
2026-04-13 11:22:04 +08:00
Caisin
6be3a0e204 feat(common-ui): add labelFn support to ApiComponent (#7801)
* feat: allow api-component labels to be derived from option data

ApiComponent already normalizes option records into the label/value shape used by
consuming controls, but label text could only come from a single field. Add
labelFn so callers can build labels from the full option record while keeping
labelField as the fallback path.

This keeps the change inside the existing component instead of introducing a
wrapper, and it also normalizes direct options through the same transform path
as API-loaded options for consistent behavior.

Constraint: Must extend the existing ApiComponent API instead of adding a second
Constraint: wrapper component
Rejected: Add a separate ApiLabelComponent wrapper |
Rejected: extra surface area for one option-mapping concern
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep labelFn as a presentation transform and preserve labelField
Directive: fallback for existing callers
Tested: pnpm exec eslint api-component.vue index.ts types.ts
Tested: pnpm exec vue-tsc --noEmit -p packages/effects/common-ui/tsconfig.json
Not-tested: runtime integration in consuming select/tree-select components

* Update packages/effects/common-ui/src/components/api-component/api-component.vue

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-12 14:29:18 +08:00
过冬
a9b76ba2ed fix: antdv-next message/notification 跟随暗色主题 (#7799) 2026-04-12 11:51:23 +08:00
Jin Mao
53ccec1d80 fix: 修复VITE_APP_TITLE变量替换语法
- 将index.html中的<%= VITE_APP_TITLE %>替换为%VITE_APP_TITLE%
- 更新web-antd、web-antdv-next、web-ele、web-naive、web-tdesign应用
- 修改文档中loading组件的VITE_APP_TITLE引用方式
- 修复vite-config插件中默认加载模板的变量语法
- 统一所有应用和模板中的环境变量引用格式
2026-04-11 14:29:18 +08:00
Jin Mao
4af5d6152b chore: fix actions error 2026-04-11 14:13:13 +08:00
xingyu4j
307781f437 chore: update deps 2026-04-10 22:16:33 +08:00
xingyu4j
1326994d8e fix: tailwindcss lint config 2026-04-10 22:14:09 +08:00
xingyu4j
fd70a3f3e0 fix: lint 2026-04-10 22:01:01 +08:00
xingyu4j
298930b0d7 chore: remove vite-plugin-html 2026-04-10 22:00:33 +08:00
xingyu4j
54d95b8761 fix: check deps 2026-04-10 21:23:24 +08:00
xingyu4j
4a16040d3e chore: update deps 2026-04-10 21:18:26 +08:00
xingyu4j
ee95548340 fix: tailwindcss lint 2026-04-10 21:13:04 +08:00
xingyu4j
320e687bad fix: ts config 2026-04-10 21:08:54 +08:00
Jin Mao
ad43c6817e fix: 配置 TypeScript 构建根目录
- 添加 rootDir 编译选项指向 ./src 目录
- 保持现有编译配置不变
- 排除测试文件和 node_modules 目录
2026-04-09 14:48:26 +08:00
Jin Mao
c8747c079d chore: update deps 2026-04-08 18:25:39 +08:00
dullathanol
224bfe7fcb chore: 修正注释 2026-04-08 10:19:53 +08:00
dullathanol
f443bfbc7b fix: tailwindcss config 2026-04-08 10:11:05 +08:00
过冬
195b2ea0d2 Merge branch 'vbenjs:main' into main 2026-04-08 09:34:24 +08:00
Jin Mao
4150479549 chore: fix lint 2026-04-08 07:20:52 +08:00
dullathanol
5ebf513498 fix: 修正 Modal/Drawer 中 loading 属性注释 2026-04-07 12:28:57 +08:00
dullathanol
4e4ffc439c feat: 支持 overflow 配置以允许拖拽超出可视区 2026-04-07 11:41:39 +08:00
dullathanol
ad7ed50b52 fix: 弹窗组件拖拽后全屏位置异常 2026-04-06 22:26:27 +08:00
Jin Mao
92f8916225 chore: fix lint
- 关闭 vitest/require-mock-type-parameters 规则
2026-04-06 21:20:53 +08:00
dullathanol
7e4edd270d fix: 补全 ComponentPropsMap 与 Vxe 表格表单链路的类型 2026-04-05 19:03:03 +08:00
dullathanol
332ff44219 fix: 修复 FormField 在 SFC 中的运行时异常 2026-04-05 03:05:01 +08:00
dullathanol
834ce3efc0 fix: 修复部分情况 component 类型丢失问题 2026-04-05 01:59:17 +08:00
dullathanol
5211f5065d feat: 表单 Schema 支持组件 Props 映射泛型,同步适配VxeGrid 2026-04-04 23:40:27 +08:00
dullathanol
96d6f89732 refactor: 简化 componentProps 回调的类型写法 2026-04-03 15:02:32 +08:00
dullathanol
6ab06584eb fix: 函数式 componentProps 按已注册 component 的 Props 校验返回值 2026-04-03 13:36:03 +08:00
dullathanol
a6433c2b50 feat: Schema 中 componentProps 随注册组件联动类型提示 2026-04-03 01:39:49 +08:00
墨苒孤
128a131797 fix(form): 修复表单示例中 switch 组件无法切换的问题 (#7636) (#7763) 2026-04-02 18:18:56 +08:00
橙子
c775d7ed80 fix: interface DropdownMenuProps don‘t have key prop (#7757) 2026-04-02 08:33:26 +08:00
HaroldZhangCode91
b8b4308e1c feat: fix oxlint error for oxlint upgrade (#7756)
1. remove unknown rule out of oxlint
2. add the corresponding back to eslint-config
3. fixed the eslint error for package.json
2026-04-01 19:28:57 +08:00
墨苒孤
80d6e2255f fix: make search case-insensitive (#7689) (#7755) 2026-04-01 19:17:36 +08:00
橙子
4e0968d4b7 perf: replace onUnMounted to tryOnScopeDispose (#7747)
* perf: replace `onUnMounted` to `tryOnScopeDispose`

* perf: replace `onUnMounted` to `tryOnScopeDispose`
2026-04-01 10:30:54 +08:00
Jin Mao
44a5809a46 chore: update deps 2026-04-01 08:10:49 +08:00
xingyu4j
2428fb1407 fix: extension-document 2026-03-30 19:50:44 +08:00
xingyu4j
bb78882f72 feat(@vben/plugins): add tiptap rich text editor 2026-03-30 19:36:29 +08:00
xingyu4j
df88a23102 chore: typescript config is expired‌ 2026-03-30 18:26:07 +08:00
xingyu4j
ca5f360231 chore: update deps 2026-03-30 18:24:25 +08:00
Anonymouscen
147b50ec45 chore: 修复名称错误问题,帐户改为账户 (#7735) 2026-03-29 14:17:00 +08:00
橙子
34439dce4e fix: history search can not remove (#7732) 2026-03-29 14:16:32 +08:00
Jin Mao
9a22027b35 chore: 更新 GitHub Actions 依赖版本
- 将 pnpm/action-setup 从 v4 升级到 v5
- 将 release-drafter/release-drafter 从 v6 升级到 v7
- 更新所有工作流中的依赖版本以确保兼容性
2026-03-25 17:58:47 +08:00
Jin Mao
282a102826 chore: fix lint 2026-03-25 17:31:33 +08:00
Jin Mao
417e6c2ade chore: fix lint && typecheck 2026-03-25 16:33:41 +08:00
Jin Mao
9d69d7f46c Merge branch 'main' into chore/plugins 2026-03-25 15:19:21 +08:00
Jin Mao
87d1593a1f refactor(effects): 扩展 echarts 类型定义并优化插件配置合并逻辑
- 添加 PieSeriesOption 和 RadarSeriesOption 到 echarts 类型定义
- 添加 LegendComponentOption 和 ToolboxComponentOption 组件选项
- 重构 providePluginsOptions 函数实现深合并逻辑
- 优化 vxe-table 初始化中的表单工厂优先级处理
- 调整 playground 中的 import 语句顺序和格式
2026-03-25 15:16:24 +08:00
过冬
7fbdf3d914 fix(@vben/common-ui): 修复 JsonViewer 在 Vite 下因 CJS 默认导出未解包导致的渲染失败 (#7728)
* fix: lint

* fix(@vben/common-ui): 修复 JsonViewer 在 Vite 下因 CJS 默认导出未解包导致的渲染失败
2026-03-25 14:54:14 +08:00
JyQAQ
65287cf4b7 feat: Dockerfile构建调整 (#7727)
Co-authored-by: 吉远 <jiyuan@txhmo.com>
2026-03-25 14:53:26 +08:00
Jin Mao
6da3017dcf feat: 插件新增依赖注入功能 2026-03-25 14:46:55 +08:00
Jin Mao
5c02057198 refactor(effects): 替换上下文创建逻辑为全局选项管理
- 移除 createContext 依赖并实现全局插件选项存储
- 添加 providePluginsOptions 函数用于提供插件配置
- 添加 injectPluginsOptions 函数用于注入插件配置
- 添加 resetPluginsOptions 函数用于重置插件配置
- 更新 package.json 导出配置添加主入口点定义
2026-03-25 14:42:40 +08:00
Jin Mao
a7ca7cdb9f refactor(vxe-table): 重构 useTableForm 函数实现并优化初始化逻辑
- 将 useTableForm 从箭头函数改为普通函数声明
- 简化表单工厂函数的获取逻辑,支持上下文注入
- 移除初始化时的重复上下文注入代码
- 改进错误提示信息的准确性
- 调整代码结构以提高可读性和维护性
- 将 SetupVxeTable 接口中的 useVbenForm 字段改为可选参数
2026-03-25 14:42:31 +08:00
Jin Mao
79408d406d feat: 添加插件模块导出
- 导出 types 模块
- 导出 plugins-context 模块
2026-03-25 13:33:12 +08:00
Jin Mao
e555f71bf8 feat(vxe-table): 集成表格插件并优化初始化配置
- 添加了完整的 vxe-table 插件功能实现
- 实现了插件上下文选项注入机制
- 重构了 useTableForm 工厂函数的初始化逻辑
- 支持通过参数或上下文注入 useVbenForm 函数
- 优化了组件导入和类型定义
- 添加了插件使用文档说明
- 移除了未使用的组件注释代码
- 统一了字符串引号格式为双引号
2026-03-25 13:32:21 +08:00
Jin Mao
4c320346c3 docs(motion): 添加 motion 插件文档
- 创建了 Motion 插件的 README.md 文件
- 添加了插件导出组件和类型的说明表格
- 提供了插件的基本使用示例代码
- 包含了 MotionOptions 和 MotionVariants 类型导入说明
2026-03-25 13:27:10 +08:00
Jin Mao
e5ec88169a refactor: 重构 ECharts 插件类型定义和导出结构
- 将 ECOption 类型定义移至独立的 types.ts 文件
- 修改 echarts.ts 文件导入 ECOption 类型而不是定义
- 更新 index.ts 添加 types 导出
- 移除 echarts.ts 中冗余的类型导入和定义
- 添加完整的 README.md 文档说明插件使用方法
- 优化代码组织结构提高可维护性
2026-03-25 13:27:02 +08:00
Jin Mao
914711ae04 feat: 添加插件上下文和类型定义
- 创建了插件选项的 createContext 功能
- 定义了 VbenPluginsOptions 接口结构
- 添加了表单、模态框、消息和组件的插件选项接口
- 提供了插件选项的注入和提供功能
2026-03-25 13:25:19 +08:00
Jin Mao
4c1e3b9548 Merge branch 'fork/Voidlurk/fix-default' 2026-03-24 10:36:40 +08:00
Jin Mao
9cd3987475 Merge branch 'main' into fix 2026-03-24 10:24:18 +08:00
xueyitt
47a853330d feat: ApiSelect增加shouldFetch控制,在api请求之前的判断是否允许请求的回调函数 (#7713) 2026-03-24 10:22:02 +08:00
xueyitt
2aced2f659 feat: 增加table 帮助信息help通过表单values动态展示内容 (#7712) 2026-03-24 10:20:43 +08:00
Jin Mao
cd955df02f chore: fix lint 2026-03-24 10:19:24 +08:00
Bk201
0a819df2bf fix bug
[Vue warn]: Invalid prop: custom validator check failed for prop "variant".
2026-03-24 03:01:00 +08:00
xingyu4j
67afcadcf0 fix: rollup -> rolldown 2026-03-23 17:51:46 +08:00
xingyu4j
1128ef5acd chore: update deps 2026-03-23 17:13:39 +08:00
雪忆天堂
6b3506f128 feat: table 类型VxeTableGridColumns替代VxeTableGridOptions['columns']魔法值写法 2026-03-23 10:06:35 +08:00
雪忆天堂
5613dcef99 feat: table允许通过props动态变化data数据 2026-03-23 10:05:32 +08:00
MistyMoon
3528517fe0 fix(project): fix sub sidebar theme in two-column menu (#7708)
- Reset light tokens for nested sub sidebars.
- Align the extra title logo theme with sidebarThemeSub.
2026-03-22 09:15:56 +08:00
Jin Mao
2a5b520ec9 chore: update deps 2026-03-22 09:15:27 +08:00
lmx
e2fb3602f1 fix: 修复菜单项外部链接跳转问题
- 引入 isHttpUrl 工具函数用于判断 URL 类型
- 添加 isHttp 计算属性检测父路径是否为 HTTP 链接
- 修改菜单项渲染逻辑,对外部链接直接使用原地址跳转
- 调整 HTML 结构,将链接属性移到正确位置
- 确保内部路由和外部链接都能正常工作
2026-03-19 23:56:13 +08:00
lmx
da3580cbd7 Merge remote-tracking branch 'origin/main' 2026-03-19 22:07:20 +08:00
xingyu
0c300d040c fix: tailwindcss config (#7693)
* chore: update deps

* fix: tailwindcss config

* fix: lint

* fix: lint

* chore: update deps
2026-03-19 15:51:09 +08:00
橙子
d43a3729c3 fix: playground(drawer) comment (#7695) 2026-03-19 09:03:40 +08:00
Jin Mao
bed97a84d8 revert: "fix: sass-embedded@1.98.0 在 macOS 13 会直接崩 (#7692)" 2026-03-19 09:02:28 +08:00
afe1
b908076846 fix: sass-embedded@1.98.0 在 macOS 13 会直接崩 (#7692)
* fix: catelog

* fix: system
2026-03-18 20:18:25 +08:00
Noah Lan
885a0a9a00 fix: build.mjs commands could not be executed correctly. (#7682)
* fix: Fix issue where commands could not be executed correctly when they contained spaces

* chore: oxfmt
2026-03-17 18:59:35 +08:00
Jin Mao
340baf4f0b chore: 处理合并的一些问题 2026-03-16 20:50:01 +08:00
Jin Mao
82cda0edaa Merge branch 'fork/xingyu4j/tsdown'
# Conflicts:
#	pnpm-lock.yaml
2026-03-16 20:36:27 +08:00
Jin Mao
9fe875355a fix: 修复构建脚本中的进程执行问题
- 移除平台特定的 pnpm 命令路径检测逻辑
- 统一使用 'pnpm' 命令执行
- 启用 shell 模式以正确处理命令执行
- 确保子进程继承正确的标准输入输出流
2026-03-16 20:30:27 +08:00
Jin Mao
5f21bd2036 Merge branch 'fork/jyqwq/feature/antd上传组件支持拖拽排序' 2026-03-16 19:54:08 +08:00
Sun
5b5ea6d2d8 chore: Configure the ESLint auto-repair feature (#7670) 2026-03-16 18:26:42 +08:00
JyQAQ
3dcfd23036 perf: 裁剪组件默认输出格式调整 (#7673)
Co-authored-by: 吉远 <jiyuan@txhmo.com>
2026-03-16 18:25:04 +08:00
xingyu
186914bcac fix: vxe i18n (#7675)
* chore: engines node

* chore: update deps

* fix: oxlint

* chore: fix lint issues

* chore: update deps

* fix: vxe i18n
2026-03-16 18:24:28 +08:00
吉远
4b3205fee8 feat: antd Upload 组件上传文件组支持拖拽排序 2026-03-16 15:01:43 +08:00
lmx
e4453841db fix: 修复在vue-router 的模式hash复制路径不对 2026-03-16 01:32:11 +08:00
xingyu4j
32db4cbd11 fix: build warn 2026-03-15 23:11:59 +08:00
xingyu4j
5558249cd3 chore: unbuild 2026-03-15 23:11:41 +08:00
xingyu4j
86b636ae54 fix(@vben/vite-config): externalize node utils dependency 2026-03-15 22:47:28 +08:00
xingyu4j
c9f7154524 chore(tsdown): remove unbuild leftovers 2026-03-15 22:04:48 +08:00
xingyu4j
d72f872369 refactor(tsdown): migrate styled ui-kit packages 2026-03-15 21:42:44 +08:00
xingyu4j
b300011d07 refactor(tsdown): migrate ui-kit vue packages 2026-03-15 21:30:13 +08:00
xingyu4j
3946253d6e chore(tsdown): align stub scripts and package exports 2026-03-15 21:17:41 +08:00
xingyu4j
11fc367845 chore: migrate vite build to tsdown 2026-03-15 21:04:12 +08:00
xingyu4j
bdc65cc250 chore: update deps 2026-03-15 21:03:41 +08:00
xingyu4j
70dad0f600 fix(node-utils): avoid find-up sync API in monorepo root lookup 2026-03-15 20:50:07 +08:00
xingyu4j
26e9aa244b fix(vite): adapt rolldown workspace resolution 2026-03-15 20:27:01 +08:00
xingyu4j
913f77fd2f chore(pnpm): sync lockfile for tsdown migration 2026-03-15 19:41:17 +08:00
xingyu4j
dba774e1c7 chore(vsh): migrate build to tsdown 2026-03-15 19:41:00 +08:00
xingyu4j
af09d652a3 chore(turbo-run): migrate build to tsdown 2026-03-15 19:40:54 +08:00
xingyu4j
0babdfbc44 chore(core-preferences): migrate build to tsdown 2026-03-15 19:40:48 +08:00
xingyu4j
f154d53be9 chore(core-composables): migrate build to tsdown 2026-03-15 19:40:42 +08:00
xingyu4j
ed3cd2fe3b chore(core-typings): migrate build to tsdown 2026-03-15 19:40:36 +08:00
xingyu4j
59912a00bc chore(core-shared): migrate build to tsdown 2026-03-15 19:40:29 +08:00
xingyu4j
675d8b0179 chore(core-icons): migrate build to tsdown 2026-03-15 19:40:17 +08:00
xingyu4j
a1ca296fc0 chore(node-utils): migrate build to tsdown 2026-03-15 19:40:09 +08:00
xingyu4j
c1b1fe90fd chore(oxlint-config): migrate build to tsdown 2026-03-15 19:39:59 +08:00
xingyu4j
30b5610a73 chore(oxfmt-config): migrate build to tsdown 2026-03-15 19:39:51 +08:00
xingyu4j
db9b9df8f7 chore(eslint-config): migrate build to tsdown 2026-03-15 19:39:43 +08:00
xingyu4j
ae6a75e913 build: migrate core ts packages to tsdown 2026-03-15 18:13:49 +08:00
xingyu
37d72c1628 fix: oxlint config (#7661)
* chore: engines node

* chore: update deps

* fix: oxlint

* chore: fix lint issues
2026-03-15 17:41:47 +08:00
xingyu4j
ab3e6bb37c chore: fix lint issues 2026-03-15 16:35:34 +08:00
xingyu4j
9ddb899a1a fix: oxlint 2026-03-15 16:23:18 +08:00
xingyu4j
1f0cda8aee chore: update deps 2026-03-15 12:35:42 +08:00
xingyu4j
90ae85317c chore: engines node 2026-03-15 12:33:23 +08:00
Jin Mao
a8ae891aff chore: update pnpm-lock 2026-03-15 03:57:55 +08:00
Jin Mao
1f2df3e944 Merge branch 'fork/Lmx1220/main'
# Conflicts:
#	pnpm-lock.yaml
2026-03-15 03:53:43 +08:00
Jin Mao
e39a432210 Merge branch 'fork/caodachen/fix_useEcharts' 2026-03-15 03:50:21 +08:00
xingyu4j
6b3bcee582 docs(@vben/docs): sync component docs with current APIs 2026-03-14 21:34:48 +08:00
xingyu4j
6c274b75b8 docs(@vben/docs): align guide docs with current tooling 2026-03-14 21:33:55 +08:00
xingyu4j
278032c94b docs: remove docs 2026-03-14 20:29:49 +08:00
xingyu4j
23a8982f5c chore: add prepare 2026-03-14 20:19:34 +08:00
xingyu4j
5df6c32d04 fix: lint 2026-03-14 20:14:35 +08:00
xingyu4j
7cae330c3c chore: deps 2026-03-14 19:48:49 +08:00
xingyu4j
100aaa4cee chore: vsh lint 2026-03-14 19:48:40 +08:00
xingyu4j
ead0b73e7b fix: lint 2026-03-14 19:47:02 +08:00
xingyu4j
2ace846e38 chore: recommend vscode yaml extension 2026-03-14 19:41:28 +08:00
xingyu4j
1d98393f0c chore: recommend vscode eslint extension 2026-03-14 19:39:37 +08:00
xingyu4j
c48ee2a364 revert: restore vscode extensions comments 2026-03-14 19:38:09 +08:00
xingyu4j
95d1e8432f fix: surface eslint diagnostics in vscode 2026-03-14 19:36:27 +08:00
xingyu4j
4d59ac78bd fix: lint 2026-03-14 19:34:22 +08:00
xingyu4j
f1143e134e fix: match root json files in oxfmt overrides 2026-03-14 19:33:55 +08:00
xingyu4j
e3e869faee fix: align oxfmt json commas with lint 2026-03-14 19:31:01 +08:00
xingyu4j
8350e72393 fix: align json formatter with lint rules 2026-03-14 19:28:25 +08:00
xingyu4j
15f74b9d97 refactor: drop unused turbo eslint shim 2026-03-14 19:20:39 +08:00
xingyu4j
55b54e24fe refactor: migrate command lint to oxlint 2026-03-14 19:13:50 +08:00
xingyu4j
46b4ce81e4 refactor: shrink eslint fallback layer 2026-03-14 19:10:22 +08:00
xingyu4j
7a723d03d0 fix: tailwindcss 2026-03-14 18:48:30 +08:00
xingyu4j
9d6fbfd0d6 refactor: replace simple px utility styles 2026-03-14 18:43:00 +08:00
xingyu4j
8fd6bf47b1 revert: restore px-based calc utilities 2026-03-14 18:36:31 +08:00
xingyu4j
f25f3a34d0 refactor: replace px calc spacing utilities 2026-03-14 18:31:35 +08:00
xingyu4j
2823848fae refactor: migrate spacing utilities to tailwind v4 syntax 2026-03-14 18:27:16 +08:00
xingyu4j
b9467b2bc3 chore: configure tailwind v4 dynamic spacing 2026-03-14 18:14:44 +08:00
xingyu4j
fa190e0975 chore: checkpoint tailwind spacing updates 2026-03-14 18:11:08 +08:00
xingyu4j
90dc8cf997 chore: update deps 2026-03-14 17:52:40 +08:00
Jin Mao
a6a6efdf59 chore: release 5.7.0
- 更新 backend-mock 包版本
- 更新 web-antd 包版本
- 更新 web-antdv-next 包版本
- 更新 web-ele 包版本
- 更新 web-naive 包版本
- 更新 web-tdesign 包版本
- 更新 docs 包版本
- 更新 commitlint-config 包版本
- 更新 eslint-config 包版本
- 更新 oxfmt-config 包版本
- 更新 oxlint-config 包版本
- 更新 stylelint-config 包版本
- 更新 node-utils 包版本
- 更新 tsconfig 包版本
- 更新 vite-config 包版本
- 更新 @core/base/design 包版本
- 更新 @core/base/icons 包版本
- 更新 @core/base/shared 包版本
- 更新 @core/base/typings 包版本
- 更新 @core/composables 包版本
- 更新 @core/preferences 包版本
- 更新 @core/ui-kit/form-ui 包版本
- 更新 @core/ui-kit/layout-ui 包版本
- 更新 @core/ui-kit/menu-ui 包版本
- 更新 @core/ui-kit/popup-ui 包版本
- 更新 @core/ui-kit/shadcn-ui 包版本
- 更新 @core/ui-kit/tabs-ui 包版本
- 更新 constants 包版本
- 更新 access 包版本
- 更新 common-ui 包版本
- 更新 hooks 包版本
- 更新 layouts 包版本
- 更新 plugins 包版本
- 更新 request 包版本
- 更新 icons 包版本
- 更新 locales 包版本
- 更新 preferences 包版本
- 更新 stores 包版本
- 更新 styles 包版本
- 更新 types 包版本
- 更新 utils 包版本
- 更新 playground 包版本
- 更新 turbo-run 包版本
- 更新 vsh 包版本
- 更新根目录包版本
2026-03-14 09:14:23 +08:00
xingyu4j
8043faf6c7 docs: remove doc 2026-03-14 00:18:54 +08:00
xingyu4j
ebed9e64ed fix: lint 2026-03-14 00:17:06 +08:00
xingyu4j
913636ae44 refactor: simplify oxc eslint compatibility 2026-03-14 00:16:27 +08:00
xingyu4j
7b064e9f33 chore: vsh lint 2026-03-13 23:41:17 +08:00
xingyu4j
16da0eaca3 fix: vsh lint 2026-03-13 23:31:19 +08:00
xingyu4j
6acfee2737 fix: vsh lint 2026-03-13 23:26:29 +08:00
xingyu4j
92abf7edaa chore: oxlint-tsgolint 2026-03-13 23:22:52 +08:00
xingyu4j
395babc1f5 feat: tsgolint 2026-03-13 23:13:10 +08:00
xingyu4j
68cde54bad feat: add tsgolint 2026-03-13 23:13:01 +08:00
xingyu4j
c7d7529c00 chore: ts 2026-03-13 23:07:04 +08:00
xingyu4j
748f60c7bb chore: lint config 2026-03-13 22:12:15 +08:00
xingyu4j
ffee62e940 chore(vscode): update workspace editor settings 2026-03-13 22:02:50 +08:00
xingyu4j
a0ea221131 style(common-ui): normalize generic formatting 2026-03-13 21:57:18 +08:00
xingyu4j
2846bcb84e fix(common-ui): guard resize drag start state 2026-03-13 21:56:53 +08:00
xingyu4j
542ed6c08f refactor: rename formatter utilities 2026-03-13 21:56:23 +08:00
xingyu4j
6dabb848a5 build: migrate formatting pipeline to oxfmt 2026-03-13 21:56:08 +08:00
xingyu4j
de0181e0d9 fix: lint 2026-03-13 20:58:07 +08:00
xingyu4j
a850d426ef chore: fix lint and typecheck issues 2026-03-13 20:57:52 +08:00
xingyu4j
771277d5d9 fix: align oxlint compat config typing 2026-03-13 20:28:56 +08:00
xingyu4j
20b4f5c99f chore: oxlint config 2026-03-13 20:26:10 +08:00
xingyu4j
e7fa87b301 chore: finalize oxlint migration config 2026-03-13 20:25:25 +08:00
xingyu4j
40c66958bc chore: remove un use deps 2026-03-13 15:59:04 +08:00
xingyu4j
600fc71aed fix: eslint 2026-03-13 15:58:53 +08:00
xingyu4j
443e4b04cd chore: update vite 8 2026-03-13 15:51:28 +08:00
Lmx1220
556a3c0fab Update pnpm-lock.yaml 2026-03-13 15:25:14 +08:00
Lmx1220
1eca52f962 Update pnpm-lock.yaml
更新到使用vue3版本
2026-03-13 15:20:58 +08:00
Lmx1220
e21adb395b Merge branch 'main' into main 2026-03-13 15:08:24 +08:00
xingyu4j
0e4bf80bf4 chore: update deps 2026-03-13 10:33:23 +08:00
cdc
107750971b fix: fix useEcharts 2026-03-10 21:48:33 +08:00
橙子
24e1be47ca fix: fix component type (#7601) 2026-03-10 05:11:06 +08:00
Mr. Xie
7e0978c764 fix: 修复验证码登录发送逻辑,未校验手机号或发送失败仍开始倒计时的问题 (#7616) 2026-03-10 05:10:34 +08:00
Leo
83a0c9662d fix: 修复路由重复 (#7590)
* fix: 修复路由重复

优化mixed模式路由合并逻辑。以backend 为基础,并从frontend补充

* fix: 修复名称冲突时子项合并顺序/覆盖(后端子项可能会丢失)。

* fix: 优化可访问性判断逻辑

* fix: error Forbidden non-null assertion

* refactor(access): 调整可访问性判断逻辑
2026-03-10 05:09:48 +08:00
xingyu
a4736a49f8 feat: migrate to Tailwind CSS v4 (#7614)
* chore: update deps

* feat: use jsonc/x language

* chore: update eslint 10.0

* fix: no-useless-assignment

* feat: add CLAUDE.md

* chore: ignore

* feat: claude

* fix: lint

* chore: suppot eslint v10

* fix: lint

* fix: lint

* fix: type check

* fix: unit test

* fix: Suggested fix

* fix: unit test

* chore: update stylelint v17

* chore: update all major deps

* fix:  echarts console warn

* chore: update vitest v4

* feat: add skills ignores

* chore: update deps

* chore: update deps

* fix: cspell

* chore: update deps

* chore: update tailwindcss v4

* chore: remove postcss config

* fix: no use catalog

* chore: tailwind v4 config

* fix: tailwindcss v4 sort

* feat: use eslint-plugin-better-tailwindcss

* fix: Interference between enforce-consistent-line-wrapping, jsx-curly-brace-presence and Prettier

* fix: Interference between enforce-consistent-line-wrapping, jsx-curly-brace-presence and Prettier

* fix(lint): resolve prettier and better-tailwindcss formatting conflicts

* fix(tailwind): update theme references and lint sources

* style(format): normalize apps docs and playground vue files

* style(format): normalize core ui-kit components

* style(format): normalize effects ui and layout components
2026-03-10 05:08:45 +08:00
zouawen
aa7d8630b5 fix: 侧边菜单栏拖拽优化 (#7606)
* fix: 拖拽使用遮罩层实现,使得拖拽到min或max临界值时cursor显示not-allowed,同时拖拽线条颜色变浅,类似于disabled,提升用户体验

* fix: 修复代码审查建议;修复lint和test报错

* fix: 修复遮罩层挡住hover:bg-primary视觉效果问题
2026-03-07 05:32:09 +08:00
lmx
36313f378e chore: update package.json and cspell.json 2026-03-04 19:17:33 +08:00
lmx
45054d3238 chore: update pnpm-lock.yaml 2026-03-04 19:03:41 +08:00
lmx
173e6b08c9 fix: 修复执行: check:cspell 命令路径参数传入没有转义导致检测路径失效。添加菜单右键功能。优化:多余监听 activePath 变化,自动滚动到激活项 watch 2026-03-04 18:25:15 +08:00
zouawen
2a86404ba5 fix: 修复混合双列布局侧边栏拖拽线条位置显示bug,同步修复普通布局和混合双列布局切换时width计算导致侧边栏宽度显示异常问题,新增普通布局和混合双列布局侧边栏菜单折叠状态同步 (#7596) 2026-03-02 15:31:29 +08:00
han
b8a0199cde feat(preferences): add toggle for copy preferences button (#7594)
Co-authored-by: hl <hl@nmcsoft.com>
2026-03-02 15:30:38 +08:00
Bryan Qiu
a46ed55a86 fix: bump version to 5.6.0 (#7592) 2026-03-02 04:22:21 +08:00
Jin Mao
1a9fbddef4 Merge branch 'main' into main 2026-02-28 12:04:48 +08:00
zouawen
1209aaafb4 fix: 修复lint报错 2026-02-28 11:25:08 +08:00
zouawen
8e71261d49 fix: 侧边栏菜单拖拽功能在设置内增加开关 2026-02-28 11:19:24 +08:00
dependabot[bot]
49e45eab54 chore(deps): bump vue-router from 4.6.4 to 5.0.3 (#7583)
Bumps [vue-router](https://github.com/vuejs/router) from 4.6.4 to 5.0.3.
- [Release notes](https://github.com/vuejs/router/releases)
- [Commits](https://github.com/vuejs/router/compare/v4.6.4...v5.0.3)

---
updated-dependencies:
- dependency-name: vue-router
  dependency-version: 5.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 17:06:18 +08:00
Jin Mao
bd22793ceb feat: add the attribute routeCached to route to control cache the DOM corresponding to the route 2026-02-27 16:04:01 +08:00
zouawen
b2013436c5 fix: 优化最大值限制 2026-02-27 11:12:51 +08:00
zouawen
cc808cb8c5 fix: 优化最小值限制 2026-02-27 11:07:28 +08:00
zouawen
afffc4b3f0 fix: 优化侧边栏拖拽逻辑,支持展开和折叠 2026-02-27 10:54:15 +08:00
zouawen
99710ef9dc feat: 优化侧边栏拖拽逻辑 2026-02-26 18:11:53 +08:00
zouawen
3d4ae04d9b Merge branch 'main' into main 2026-02-26 10:20:32 +08:00
zouawen
707b391449 feat: 侧边栏宽度拖拽改为composable实现,同时修复tabbar.ts文件lint报错 2026-02-26 10:09:57 +08:00
ming4762
45b843f344 fix: fix bug where renderEcharts gets stuck in a dead loop (#7561)
* 触发条件:echart所在页面开启keepalive 在其他页面切换颜色模式
2026-02-26 06:21:08 +08:00
Wu Clan
191fd90f06 chore: 更新表单描述显示样式 (#6938) 2026-02-26 06:17:04 +08:00
moil-xm
05920cd66d feat(vite-config): vite export typing (#7569)
* feat(vite-config): vite export typing

* feat(vite-config): add type

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2026-02-26 06:14:12 +08:00
Jin Mao
01508d5e42 fix: fix lint 2026-02-26 05:45:36 +08:00
zouawen
57cf6cbc9e feature: 简易版菜单宽度拖拽功能 2026-02-25 17:50:22 +08:00
AxiosLeo
03ebbea46a fix(menu): update hover color variable to use the correct reference (#7544)
* fix(menu): update hover color variable to use the correct reference

Medium Severity

In the horizontal .is-light menu section, --menu-item-hover-color is set to hsl(var(--menu-item-color)), but --menu-item-color is already defined as hsl(var(--accent-foreground)). This results in hsl(hsl(...)) at computed-value time, which is invalid CSS. The non-horizontal .is-light block correctly uses var(--menu-item-color) without the extra hsl() wrapper.

* fix(menu): simplify hover styles by removing redundant nested hover rules

Low Severity

The SCSS &:not(.is-active):hover { &:hover { ... } } compiles to a :hover:hover pseudo-class chain, which is functionally identical to a single :hover. The inner &:hover nesting is redundant and adds unnecessary complexity compared to placing styles directly inside the &:not(.is-active):hover block.
2026-02-12 22:22:53 +08:00
zouawen
8e7a5d1ec3 fix: Fix layout change, ensure div[ref="asideRef"] is contained within <aside> (#7551) 2026-02-12 22:22:34 +08:00
AxiosLeo
aa74a2535b fix(tabbar): visitHistory field (#7543)
High Severity

The visitHistory field is a Stack<string> class instance persisted to sessionStorage via pinia-plugin-persistedstate. There's no custom serializer or hydration hook. When the page reloads, JSON.parse(JSON.stringify(stack)) produces a plain object {dedup, items, maxSize} that lacks all Stack methods (push, pop, remove, retain, etc.) and the size getter. Pinia's $patch replaces the Stack instance with this plain object, so subsequent calls like this.visitHistory.push(...) will throw a TypeError.
2026-02-11 16:09:37 +08:00
zouawen
32379ba4b7 fix: 双列菜单模式下新增深色侧边栏和深色侧边栏子栏 (#7542)
* fix: 双列菜单模式下新增深色侧边栏和深色侧边栏子栏

* fix: 修复报错 config.test.ts.snap

* fix: 修复lint报错

* fix: 修复侧边栏菜单文本内容溢出问题

* fix: 修复lint报错
2026-02-11 16:08:32 +08:00
moil-xm
7fe8d7b4be fix: ts 错误: 类型实例化过深,且可能无限 2026-02-10 16:13:36 +08:00
Bin
aace726a91 feat(playground): add antdv-next router link (#7532)
Co-authored-by: fuwb <fuwb@sunsharing.com.cn>
2026-02-10 13:09:34 +08:00
Jin Mao
e6f6e5464a Merge branch 'main' into main 2026-02-10 12:08:16 +08:00
Aliner
7d04b600fb fix: correct updateDate to updateData in the echarts hook (#7538)
* fix(@vben/plugins): Fixed the misspelling of the data update method name in the echarts hook

Correct updateDate to updateData, ensuring that the API method name is correct and consistent

* Revert "fix(@vben/plugins): Fixed the misspelling of the data update method name in the echarts hook"

This reverts commit 86d679cf25631bd1abd56d4f971e6db3a9b9d6d5.

* fix(@vben/plugins): fixed the misspelling of the data update method name in the echarts hook

Correct updateDate to updateData, ensuring that the API method name is correct and consistent
2026-02-10 11:19:45 +08:00
zouawen
463bfde2ac fix: config.test.ts.snap新增showRefresh参数 2026-02-10 08:50:06 +08:00
zouawen
893f74dc3e fix: 优化横向布局时菜单激活或聚焦时背景色,标签工具栏新增刷新按钮,其他样式优化 2026-02-09 16:32:02 +08:00
Jin Mao
8a215fbcc7 chore: release 5.6.0 2026-02-09 05:09:57 +08:00
Jin Mao
ac5e4c4722 chore: update deps 2026-02-09 04:52:06 +08:00
Jin Mao
04d01b0bab chore: fix lint 2026-02-09 04:49:06 +08:00
Jin Mao
cb1d7565a3 Merge branch 'fork/ffgenius/antd-vue-next' 2026-02-09 03:09:01 +08:00
Jin Mao
1d9b6407a4 chore: 更新开发环境端口号配置
- 将 VITE_PORT 从 5555 修改为 5999
- 保持其他环境变量配置不变
2026-02-09 03:04:42 +08:00
MistyMoon
22ed522711 feat: support menuVisibleWithForbidden in generate-routes-backend (#7526)
当后端菜单项 `meta.menuVisibleWithForbidden` 为 true 时,将其路由组件替换为 403 页,菜单仍展示该项,访问时展示 403,便于用户知悉功能并申请权限。
2026-02-09 02:44:29 +08:00
Jin Mao
a3598ef859 chore: fix lint 2026-02-09 02:42:50 +08:00
Jin Mao
6fe09ec2dd perf: optimize the closing jump logic of tabs 2026-02-09 02:36:38 +08:00
Jin Mao
57911d9e09 Merge branch 'tab-2026020401' of https://github.com/ming4762/smart-boot-ui-vben into ming4762-tab-2026020401 2026-02-09 02:36:04 +08:00
Bin
3aee283495 Revert "feat(web): cancel pnpm-lock.yaml submission"
This reverts commit 54b24c2677.
2026-02-08 23:14:31 +08:00
Bin
54b24c2677 feat(web): cancel pnpm-lock.yaml submission 2026-02-08 23:12:40 +08:00
Bin
8cadad0a1e feat(web): add antdv-next model 2026-02-08 23:00:19 +08:00
zhongming4762
633c5f3cda perf: optimize the closing jump logic of tabs
* 依据tab访问历史回退上一个tab,原逻辑是返回一下个 或 上一个
 * 支持在配置中开启或关闭
2026-02-08 20:50:54 +08:00
zhongming4762
a8431e2040 perf: optimize the closing jump logic of tabs
* 依据tab访问历史回退上一个tab,原逻辑是返回一下个 或 上一个
 * 支持在配置中开启或关闭
2026-02-08 20:36:32 +08:00
zhongming4762
7a2b916387 perf: optimize the closing jump logic of tabs
* 依据tab访问历史回退上一个tab,原逻辑是返回一下个 或 上一个
 * 支持在配置中开启或关闭
2026-02-08 20:36:16 +08:00
Jin Mao
f4dfb68b7b Merge branch 'MrLeo-main' 2026-02-06 15:41:55 +08:00
Jin Mao
8f4f27d860 Merge branch 'main' of https://github.com/MrLeo/vue-vben-admin into MrLeo-main 2026-02-06 15:39:48 +08:00
tikitoki
e9eab29953 fix:fix password input icon visual bug in certain browser (#7521)
Co-authored-by: nick8799981325 <zc1078134211@163.com>
2026-02-06 15:28:48 +08:00
Leo Caan (陈栋)
4f1eeb7da5 fix: 修复设置default-expanded-level后无法check更低层级节点 logic and tree value updates (#7155)
假设缺省展开2级,当check 3级节点时,会触发effectWatch重新收缩到2级,并丢失check操作check操作andling.
2026-02-06 12:55:14 +08:00
Leo
6fd426d719 fix: 当 showToolbar 为 false 时禁用工具栏(vxe-table),减少无 Toolbar 的 Gird 留白 2026-02-04 22:20:17 +08:00
zhongming4762
331da3c8c7 perf: optimize the closing jump logic of tabs
* 依据tab访问历史回退上一个tab,原逻辑是返回一下个 或 上一个
2026-02-04 19:29:33 +08:00
ming4762
c48943bc67 fix: fix Nested Objects dependencies not effective (#7345) 2026-01-31 16:44:20 +08:00
xingyu
7680b33b99 fix: #7140 (#7153)
* chore: add yaml eslint validate

* chore: update deps

* fix: unused ts lint

* fix: 弹窗只能点击一次 #7140

* chore: update actions/checkout v6

* chore: update node version v22.22.0
2026-01-28 18:05:20 +08:00
Jin Mao
bb5d75bc7e fix: 修复表单展开无效 (#7148)
- 修正模板中 ref 属性的引用名称
2026-01-27 11:35:50 +08:00
ming4762
528395e2c3 perf: optimizing hidden fields cannot trigger dependencies (#7142) 2026-01-26 16:12:26 +08:00
programmer
6a9012e5e4 chore: 给个人中心的2个按钮加上 i18n (#7138)
* fix: 个人中心按钮i18n

* fix: eslint format

* fix: 调整label宽度让英文显示在一行

* chore: 调整label小点

* fix: import

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2026-01-26 16:12:09 +08:00
橙子
6e8315ab40 fix: arguments order update (#7132)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2026-01-26 16:11:37 +08:00
Jin Mao
7cb2699f19 chore: 更新pnpm工作区配置
- 移除 neverBuiltDependencies 配置
- 移除 peerDependencyRules.allowedVersions 配置
- 重新整理 overrides 和 catalog 部分
- 保持 @ast-grep/napi 等依赖的目录引用配置
- 维持 eslint 版本约束在目录顶层配置中
2026-01-25 19:55:53 +08:00
Jin Mao
4b5da81ba6 chore(deps): 更新项目依赖包版本
- 更新 @playwright/test 从 1.57.0 到 1.58.0
- 更新 @tanstack/vue-query 从 5.92.8 到 5.92.9
- 更新 cheerio 从 1.1.2 到 1.2.0
- 更新 eslint-config-turbo 从 2.7.5 到 2.7.6
- 更新 playwright 从 1.57.0 到 1.58.0
- 更新 turbo 从 2.7.5 到 2.7.6
- 更新 vxe-pc-ui 从 4.12.10 到 4.12.12
- 更新 @babel/helper-define-polyfill-provider 从 0.6.5 到 0.6.6
- 更新 @cspell/dict-fullstack 从 3.2.7 到 3.2.8
- 更新 @cspell/dict-git 从 3.0.7 到 3.1.0
- 更新 @cspell/dict-node 从 5.0.8 到 5.0.9
- 更新 @cspell/dict-npm 从 5.2.29 到 5.2.30
- 更新 @parcel/watcher 相关包从 2.5.4 到 2.5.6
- 更新 @tanstack/query-core 从 5.90.19 到 5.90.20
- 更新 babel-plugin-polyfill 相关包到最新版本
- 更新 baseline-browser-mapping 从 2.9.17 到 2.9.18
- 更新 caniuse-lite 从 1.0.30001765 到 1.0.30001766
- 更新 electron-to-chromium 从 1.5.277 到 1.5.278
- 更新 eslint-plugin-turbo 从 2.7.5 到 2.7.6
- 更新 playwright-core 从 1.57.0 到 1.58.0
- 更新 turbo 平台特定版本到 2.7.6
- 更新 undici 从 7.19.0 到 7.19.1
2026-01-25 15:05:14 +08:00
Jin Mao
6aca9a9c99 test(dom): 更新元素可见区域计算的测试用例
- 修正了getElementVisibleRect函数的期望值断言
- 将bottom值从800更正为0以匹配实际计算结果
- 将left值从1100更正为0以匹配实际计算结果
- 将right值从1000更正为0以匹配实际计算结果
- 将top值从900更正为0以匹配实际计算结果
2026-01-25 14:22:22 +08:00
xingyu4j
fa195fde8e fix: lint 2026-01-23 14:48:21 +08:00
xingyu4j
1057f2932b chore: update deps 2026-01-23 14:48:06 +08:00
Jin Mao
b9224fc379 Merge branch 'main' into fix 2026-01-23 13:48:54 +08:00
Jin Mao
57dd818170 Merge branch 'fork/kuchaguangjie/fix' 2026-01-23 13:47:54 +08:00
Jin Mao
49256ec1b7 Merge branch 'main' into fix 2026-01-23 13:46:59 +08:00
Jin Mao
f6f92e5403 Merge branch 'fork/kuchaguangjie/fix' 2026-01-23 13:45:38 +08:00
Jin Mao
613c311076 Merge branch 'fork/abcd0f/dev/sun-local' 2026-01-23 13:25:03 +08:00
Jin Mao
9ee7a7d9ff Merge branch 'fork/Child-qjj/patch-1' 2026-01-23 13:21:49 +08:00
橙子
44f8aed06d fix: timer not need reactivity (#7128) 2026-01-23 13:16:09 +08:00
Sun
d5d4a5c591 feat(effects-plugins): 添加 echarts 图表更新功能
新增 updateDate 方法用于更新 echarts 图表选项,支持合并配置、
完全替换和延迟更新等模式。该方法会在组件未初始化时自动执
行首次渲染,并能够合并全局配置如 backgroundColor 等选项。
2026-01-23 11:06:45 +08:00
yuhengshen
74381aa8c1 fix: 嵌套弹窗,错误 merge options (#7126) 2026-01-22 20:07:13 +08:00
橙子
203ee9b623 fix(@vben-core/shared): element outside viewport, the element visible rect each prop expect 0 (#7120)
* fix(@vben-core/shared): element outside viewport

* fix(@vben-core/shared): element outside viewport
2026-01-22 11:37:01 +08:00
JyQAQ
6c8c49966a Perf: 优化antd upload组件参数获取 (#7114)
* perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用

* perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用

* perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用

* perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用
2026-01-21 17:20:53 +08:00
Qiu
3862942e9f fix: chart instance disposal condition
dom has been disposed in vue3 v-if,but chartInstance exist
2026-01-21 11:47:01 +08:00
xingyu
8571fc43b0 Merge branch 'main' into fix 2026-01-19 15:28:12 +08:00
JyQAQ
59aabd956d Perf: Optimization of cropping component result acquisition & optimization of cropping frame prompts (#7111)
* perf(cropper): enhance image cropping functionality and add output type support

* perf(cropper): enhance image cropping functionality and add output type support
2026-01-19 14:18:36 +08:00
xingyu
9b09ba4483 Merge branch 'main' into fix 2026-01-19 10:54:43 +08:00
橙子
686c3f9208 docs(@vben-core/preferences): update comments (#7107) 2026-01-14 19:36:45 +08:00
MRSWC
8a7e2bd8e4 fix:修复默认默认首页如果携带参数时刷新页面参数丢失问题 (#7102)
Co-authored-by: chenwei <chenw@jiuzhekan.com>
2026-01-14 15:38:55 +08:00
JyQAQ
174c4ae749 fix(antd Upload onChange Event): rewrite onChange event to handle upl… (#7098)
* fix(antd Upload onChange Event): rewrite onChange event to handle upload success or error messages

* fix(antd Upload onChange Event): rewrite onChange event to handle upload success or error messages

* fix(antd Upload onChange Event): rewrite onChange event to handle upload success or error messages
2026-01-14 15:38:21 +08:00
JyQAQ
67da9417a8 feat(upload prop:crop,aspectRatio): from Upload component accept prop… (#7095)
* feat(upload prop:crop,aspectRatio): from Upload component accept prop crop,aspectRatio

* feat(upload prop:crop,aspectRatio): from Upload component accept prop crop,aspectRatio

* feat(upload prop:crop,aspectRatio): from Upload component accept prop crop,aspectRatio

* feat(upload prop:crop,aspectRatio): from Upload component accept prop crop,aspectRatio
2026-01-14 15:38:05 +08:00
ppxb
f4a4ced88d fix: header auto mode issue (#7096) 2026-01-12 21:40:26 +08:00
ppxb
19b2d7af41 fix: tdesign theme toggle and demos (#7087) 2026-01-10 14:11:08 +08:00
yuhengshen
343d8a1c1e fix: 切换时区后,页面不刷新 (#7085)
* fix: 切换时区后,页面不刷新

* fix: keep-alive 的页面,i18n 和 timezone 不更新
2026-01-10 14:10:27 +08:00
JyQAQ
9480f8272a feat(common-ui cropper): Implement the image cropping component VCropper (#7082)
* feat(common-ui cropper): Implement the image cropping component VCropper

* feat(common-ui cropper): Implement the image cropping component VCropper

* feat(common-ui cropper): Implement the image cropping component VCropper

* feat(common-ui cropper): Implement the image cropping component VCropper

* feat(common-ui cropper): Implement the image cropping component VCropper
2026-01-10 14:08:15 +08:00
ppxb
0d9e260a6a fix: vite.config.mts type error (#7081) 2026-01-10 14:07:28 +08:00
ppxb
51bca25345 fix(lint): pnpm format lint warning (#7080) 2026-01-10 14:06:03 +08:00
eric
694396dcfb refactor: move cleanup to finally block 2026-01-09 23:22:49 +08:00
eric
1cb53e943e refactor: 将跳转放到最后 2026-01-09 23:13:06 +08:00
eric
13c8318adc refactor: 1. 用 ref 包装 flag; 2. 在最后 清理 flag; 2026-01-09 23:05:05 +08:00
eric
48ed797055 fix: 防止 /logout 死循环 2026-01-09 22:38:11 +08:00
xingyu4j
3c2285141c chore: update deps 2026-01-06 13:58:42 +08:00
xingyu
49b884c0b1 Merge branch 'main' into fix 2026-01-06 13:50:26 +08:00
ppxb
24d20ca9ee refactor: preference manager and export (#7068)
* fix: preferences drawer outline z-index

* refactor: preferencesManager and exports
2026-01-06 12:42:32 +08:00
wyc001122
6f02181024 fix(layout): 修复双列模式下重复显示logo的问题(#7071) (#7072) 2026-01-05 21:13:06 +08:00
ppxb
ed3353a271 fix: preferences drawer outline z-index (#7067) 2026-01-04 14:44:33 +08:00
xingyu4j
965f5f96b7 chore: update deps 2026-01-04 11:03:19 +08:00
xingyu4j
61d9df7f58 feat: add cascader cspell:words 2026-01-04 11:00:07 +08:00
xingyu4j
231a5169ec fix: lint 2026-01-04 10:56:55 +08:00
xingyu4j
ce7b7b910a Merge branch 'main' of https://github.com/xingyu4j/vue-vben-admin into fix 2026-01-04 10:56:14 +08:00
JyQAQ
81a61558cb feat(upload prop:maxSize): from Upload component accept prop maxSize (AI prompt fixed) (#7059)
* feat(upload prop:maxSize): from component accept prop maxSize

* feat(upload prop:maxSize): from component accept prop maxSize

* feat(upload prop:maxSize): from component accept prop maxSize

* feat(upload prop:maxSize): from component accept prop maxSize
2026-01-03 13:19:40 +08:00
ppxb
7d2bc2e885 fix: dropdown raido menu type error (#7062)
* fix: dropdown raido menu type

* chore: format code
2026-01-02 14:25:19 +08:00
luoqiz
89b237f6b4 feat: 添加上下文菜单演示,添加菜单项隐藏性 (#7057)
Co-authored-by: luoqiz <851092732@qq.com>
2026-01-02 14:22:19 +08:00
JyQAQ
a1bb132233 feat(api-cascader): 添加联级组件ApiCascader (#7031) 2025-12-22 20:00:31 +08:00
zhenghaoyang24
022d538940 Fix formatting in thin.md for clarity (#7008)
修改一些语句错误
2025-12-22 19:58:05 +08:00
xueyitt
ccf70a1b76 feat: 修正菜单排序在二级菜单不生效问题 (#7007)
* treeUtil增加对树形结构数据进行递归排序

* 菜单sort排序各级菜单均生效
2025-12-22 19:57:21 +08:00
xingyu4j
af3fe53ec8 fix: type error 2025-12-22 17:50:06 +08:00
xingyu4j
e981fb159f chore: update deps 2025-12-22 17:49:51 +08:00
xingyu
79b9d55854 Merge branch 'main' into fix 2025-12-22 16:42:15 +08:00
xingyu4j
b2055a4457 chore: update deps 2025-12-17 13:47:37 +08:00
JyQAQ
1479f159aa feat(CellImage): CellImage组件支持图片属性写入 (#6992) 2025-12-06 10:12:58 +08:00
xingyu4j
7bf7e09002 fix: lint 2025-12-05 15:09:43 +08:00
xingyu4j
de8d39ffed chore: workspace file is deprecated 2025-12-05 15:07:39 +08:00
xingyu4j
543a7e3962 chore: update deps 2025-12-05 14:55:44 +08:00
xingyu4j
9dfe3f5af8 fix: type not find 2025-12-04 18:03:55 +08:00
xingyu4j
f11b08d8cb chore: update deps 2025-12-04 17:59:12 +08:00
xingyu4j
45b6f08984 chore: neverBuiltDependencies 2025-12-03 16:34:14 +08:00
xingyu4j
92a4676f8d chore: update version 2025-12-03 16:27:14 +08:00
xingyu4j
7bf7c0bb06 chore: remove unused deps 2025-12-03 16:26:58 +08:00
xingyu4j
8f8cf5b704 chore: update deps 2025-12-03 16:22:14 +08:00
xingyu4j
6be238430d chore: add cspell 2025-12-03 16:12:08 +08:00
xingyu4j
f77216d8f4 feat: lint add pnpm config 2025-12-03 16:11:51 +08:00
xingyu4j
d42a9b2409 feat: lint add yaml config 2025-12-03 16:01:27 +08:00
xingyu4j
6753834054 fix: lint 2025-12-03 15:37:33 +08:00
xingyu4j
77a4a64eb4 fix: stylelint 2025-12-03 15:37:20 +08:00
xingyu4j
49db40d557 feat: cspell sort 2025-12-03 15:37:04 +08:00
xingyu4j
0032c608f1 chore: update deps 2025-12-03 15:36:43 +08:00
xingyu4j
fa603b32b1 fix: locales 2025-12-03 13:51:30 +08:00
JyQAQ
9105d4d14a feat(api-component): api-component组件的options支持指定disabled值 (#6991) 2025-12-03 10:03:23 +08:00
luoqiz
c76db7d8d1 fix: 修复icon丢失根属性导致的样式错误 (#6986) 2025-12-01 09:51:27 +08:00
Jin Mao
6f39e9136e Merge branch 'main' into feature/antd上传组件支持调用Image组件查看图片 2025-11-24 21:59:34 +08:00
milletpeak
1f1ba16ead Merge branch 'main' into milletpeak-fontsize 2025-11-24 08:55:54 +08:00
panda7
b6edc5f574 fix: centered 为 true 时设置 draggable 无法拖拽 (#6948)
Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-11-24 08:38:10 +08:00
Jin Mao
8b0f138100 Merge branch 'main' into deps 2025-11-24 08:23:02 +08:00
ming4762
a810cd0b48 fix: fix IconPicker reporting an error when using search if no icon is found (#6944)
* 修复未搜索图标时分页报错的问题
 * 优化`IconPicker` 的分页逻辑,由total触发跳转到第一页,而不是用户控制
2025-11-24 07:56:55 +08:00
aonoa
b17fec41b0 fix: tree component cannot set a value (#6941)
Signed-off-by: aonoa <1991849113@qq.com>
2025-11-24 07:56:16 +08:00
Rex
5fcfabf1d4 fix: 修复菜单管理中标题栏样式异常问题 (#6934) 2025-11-24 07:00:08 +08:00
yuan.ji
1d77b018bb feat(function): add antd上传组件支持调用Image组件查看图片 2025-11-21 17:33:59 +08:00
米山
f7d9d1b1af chore: update package.json and app.vue imports, and ensure global styles are included 2025-11-19 11:13:06 +08:00
米山
aaf0274fe9 feat: add menu font size variable and update related components
- Introduced a new CSS variable `--menu-font-size` calculated from the base font size.
- Updated `PreferenceManager` to trigger CSS variable updates when `fontSize` is modified.
- Adjusted `updateCSSVariables` to set the new `--menu-font-size` based on the theme's font size.
- Ensured that the menu components utilize the updated font size with `!important` to maintain styling consistency.
2025-11-19 10:51:10 +08:00
米山
c142af482b fix: update snapshot for defaultPreferences immutability test to reflect fontSize change
- Adjusted the snapshot to ensure consistency with the updated defaultPreferences configuration, specifically retaining the fontSize property.
2025-11-19 10:19:16 +08:00
米山
cd7c11c7d0 fix: run 'pnpm format' update various components and improve layout structure
- Updated demo-preview and preview-group components for better error handling and layout.
- Enhanced drawer and modal components for improved auto-height functionality.
- Refactored layout components including header, footer, sidebar, and tabbar for better responsiveness and usability.
- Adjusted tooltip and help tooltip components for better user guidance.
- Fixed issues in various UI components to ensure consistent styling and functionality across the application.
2025-11-19 10:14:04 +08:00
milletpeak
fb8f36eeec Merge branch 'main' into milletpeak-fontsize 2025-11-19 09:41:55 +08:00
xingyu4j
9a4f0f6ab1 feat: Consider multi-line formatting for readability 2025-11-17 14:24:43 +08:00
xingyu4j
bed52983a0 fix: lint 2025-11-17 12:15:14 +08:00
xingyu4j
655ce2c2e8 chore: update node version >=20.12.0 2025-11-17 12:11:43 +08:00
xingyu4j
f841568e5a chore: update vue-tsc 2025-11-17 12:04:30 +08:00
xingyu4j
469697845c fix: sort 2025-11-17 11:21:49 +08:00
xingyu4j
273f3cbaf8 Merge branch 'main' of https://github.com/xingyu4j/vue-vben-admin into deps 2025-11-17 11:21:16 +08:00
xingyu4j
96f671908e chore: update deps 2025-11-17 11:17:47 +08:00
Jin Mao
f09aace765 fix: 更新依赖后的表格显示问题 2025-11-14 21:44:32 +08:00
Copilot
772529e2fb fix: rollback zod-defaults version to 0.1.3 (#6925)
* Initial plan

* fix: downgrade zod-defaults from 0.2.3 to 0.1.3 for Zod v3 compatibility

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
2025-11-14 16:16:45 +08:00
xingyu4j
eeff017b9f chore: add tdesign cspell 2025-11-14 10:22:18 +08:00
xingyu4j
22394ebdeb chore: move tdesign-vue-next to workspace 2025-11-14 10:21:48 +08:00
dependabot[bot]
ffbc9b22a7 chore(deps): bump the non-breaking-changes group across 1 directory with 26 updates (#6914)
Bumps the non-breaking-changes group with 20 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [vue](https://github.com/vuejs/core) | `3.5.23` | `3.5.24` |
| [@clack/prompts](https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts) | `0.10.1` | `0.11.0` |
| [@iconify/json](https://github.com/iconify/icon-sets) | `2.2.404` | `2.2.406` |
| [@pnpm/workspace.read-manifest](https://github.com/pnpm/pnpm) | `1000.2.5` | `1000.2.6` |
| [@tanstack/vue-query](https://github.com/TanStack/query/tree/HEAD/packages/vue-query) | `5.90.7` | `5.91.0` |
| [@tanstack/vue-store](https://github.com/TanStack/store/tree/HEAD/packages/vue-store) | `0.7.7` | `0.8.0` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.46.3` | `8.46.4` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.46.3` | `8.46.4` |
| [@vue/shared](https://github.com/vuejs/core/tree/HEAD/packages/shared) | `3.5.23` | `3.5.24` |
| [commitlint-plugin-function-rules](https://github.com/vidavidorra/commitlint-plugin-function-rules) | `4.1.0` | `4.1.1` |
| [eslint-config-turbo](https://github.com/vercel/turborepo/tree/HEAD/packages/eslint-config-turbo) | `2.6.0` | `2.6.1` |
| [lucide-vue-next](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-vue-next) | `0.507.0` | `0.553.0` |
| [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) | `0.6.14` | `0.7.1` |
| [sass](https://github.com/sass/dart-sass) | `1.93.3` | `1.94.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.8.2` | `5.9.3` |
| [unplugin-element-plus](https://github.com/element-plus/unplugin-element-plus) | `0.10.0` | `0.11.1` |
| [vxe-pc-ui](https://github.com/x-extends/vxe-pc-ui) | `4.10.16` | `4.10.22` |
| [vxe-table](https://github.com/x-extends/vxe-table) | `4.17.10` | `4.17.14` |
| [zod-defaults](https://github.com/Ced-Sharp/zod-defaults) | `0.1.3` | `0.2.3` |
| [@ast-grep/napi](https://github.com/ast-grep/ast-grep) | `0.37.0` | `0.39.9` |



Updates `vue` from 3.5.23 to 3.5.24
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.23...v3.5.24)

Updates `@clack/prompts` from 0.10.1 to 0.11.0
- [Release notes](https://github.com/bombshell-dev/clack/releases)
- [Changelog](https://github.com/bombshell-dev/clack/blob/@clack/prompts@0.11.0/packages/prompts/CHANGELOG.md)
- [Commits](https://github.com/bombshell-dev/clack/commits/@clack/prompts@0.11.0/packages/prompts)

Updates `@iconify/json` from 2.2.404 to 2.2.406
- [Commits](https://github.com/iconify/icon-sets/compare/2.2.404...2.2.406)

Updates `@pnpm/workspace.read-manifest` from 1000.2.5 to 1000.2.6
- [Release notes](https://github.com/pnpm/pnpm/releases)
- [Commits](https://github.com/pnpm/pnpm/commits)

Updates `@tanstack/vue-query` from 5.90.7 to 5.91.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/vue-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/vue-query@5.91.0/packages/vue-query)

Updates `@tanstack/vue-store` from 0.7.7 to 0.8.0
- [Release notes](https://github.com/TanStack/store/releases)
- [Commits](https://github.com/TanStack/store/commits/v0.8.0/packages/vue-store)

Updates `@typescript-eslint/eslint-plugin` from 8.46.3 to 8.46.4
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.4/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.46.3 to 8.46.4
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.4/packages/parser)

Updates `@vue/shared` from 3.5.23 to 3.5.24
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.24/packages/shared)

Updates `autoprefixer` from 10.4.21 to 10.4.22
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.4.21...10.4.22)

Updates `commitlint-plugin-function-rules` from 4.1.0 to 4.1.1
- [Release notes](https://github.com/vidavidorra/commitlint-plugin-function-rules/releases)
- [Changelog](https://github.com/vidavidorra/commitlint-plugin-function-rules/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vidavidorra/commitlint-plugin-function-rules/compare/v4.1.0...v4.1.1)

Updates `eslint-config-turbo` from 2.6.0 to 2.6.1
- [Release notes](https://github.com/vercel/turborepo/releases)
- [Changelog](https://github.com/vercel/turborepo/blob/main/release.md)
- [Commits](https://github.com/vercel/turborepo/commits/v2.6.1/packages/eslint-config-turbo)

Updates `lodash.clonedeep` from 4.5.0 to 
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/commits)

Updates `lodash.get` from 4.4.2 to 
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/commits)

Updates `lodash.isequal` from 4.5.0 to 
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/commits)

Updates `lodash.set` from 4.3.2 to 
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/commits)

Updates `lucide-vue-next` from 0.507.0 to 0.553.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.553.0/packages/lucide-vue-next)

Updates `prettier-plugin-tailwindcss` from 0.6.14 to 0.7.1
- [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.6.14...v0.7.1)

Updates `sass` from 1.93.3 to 1.94.0
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.93.3...1.94.0)

Updates `typescript` from 5.8.2 to 5.9.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.2...v5.9.3)

Updates `unplugin-element-plus` from 0.10.0 to 0.11.1
- [Release notes](https://github.com/element-plus/unplugin-element-plus/releases)
- [Commits](https://github.com/element-plus/unplugin-element-plus/compare/v0.10.0...v0.11.1)

Updates `vxe-pc-ui` from 4.10.16 to 4.10.22
- [Release notes](https://github.com/x-extends/vxe-pc-ui/releases)
- [Commits](https://github.com/x-extends/vxe-pc-ui/commits)

Updates `vxe-table` from 4.17.10 to 4.17.14
- [Release notes](https://github.com/x-extends/vxe-table/releases)
- [Commits](https://github.com/x-extends/vxe-table/compare/4.17.10...4.17.14)

Updates `zod-defaults` from 0.1.3 to 0.2.3
- [Commits](https://github.com/Ced-Sharp/zod-defaults/commits)

Updates `@ast-grep/napi` from 0.37.0 to 0.39.9
- [Release notes](https://github.com/ast-grep/ast-grep/releases)
- [Changelog](https://github.com/ast-grep/ast-grep/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ast-grep/ast-grep/compare/0.37.0...0.39.9)

Updates `@vue/reactivity` from 3.5.23 to 3.5.24
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.24/packages/reactivity)

---
updated-dependencies:
- dependency-name: vue
  dependency-version: 3.5.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@clack/prompts"
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: "@iconify/json"
  dependency-version: 2.2.406
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@pnpm/workspace.read-manifest"
  dependency-version: 1000.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@tanstack/vue-query"
  dependency-version: 5.91.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: "@tanstack/vue-store"
  dependency-version: 0.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.46.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.46.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: "@vue/shared"
  dependency-version: 3.5.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: autoprefixer
  dependency-version: 10.4.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: commitlint-plugin-function-rules
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: eslint-config-turbo
  dependency-version: 2.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: lodash.clonedeep
  dependency-version: 
  dependency-type: direct:production
  dependency-group: non-breaking-changes
- dependency-name: lodash.get
  dependency-version: 
  dependency-type: direct:production
  dependency-group: non-breaking-changes
- dependency-name: lodash.isequal
  dependency-version: 
  dependency-type: direct:production
  dependency-group: non-breaking-changes
- dependency-name: lodash.set
  dependency-version: 
  dependency-type: direct:production
  dependency-group: non-breaking-changes
- dependency-name: lucide-vue-next
  dependency-version: 0.553.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: prettier-plugin-tailwindcss
  dependency-version: 0.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: sass
  dependency-version: 1.94.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: unplugin-element-plus
  dependency-version: 0.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: vxe-pc-ui
  dependency-version: 4.10.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: vxe-table
  dependency-version: 4.17.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
- dependency-name: zod-defaults
  dependency-version: 0.2.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: "@ast-grep/napi"
  dependency-version: 0.39.9
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-breaking-changes
- dependency-name: "@vue/reactivity"
  dependency-version: 3.5.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-14 08:05:04 +08:00
Jin Mao
c3a7562e2c Merge branch 'main' into milletpeak-fontsize 2025-11-13 17:01:42 +08:00
Jin Mao
605e0ea128 chore: 更新依赖 2025-11-13 09:23:12 +08:00
dependabot[bot]
c89a83f980 chore(deps): bump stylelint-config-recommended from 14.0.1 to 17.0.0 (#6901)
Bumps [stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) from 14.0.1 to 17.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-recommended/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-recommended/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-recommended/compare/14.0.1...17.0.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended
  dependency-version: 17.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 09:17:44 +08:00
Jin Mao
cfad88666b chore: 替换 lodash-es为 es-toolkit 并调整样式引入方式 (#6912) 2025-11-13 08:20:30 +08:00
Jin Mao
f918dc24c6 chore(@vben/utils): 移除冗余的 lodash 依赖 2025-11-13 07:47:48 +08:00
Jin Mao
e898993fe8 chore(@vben/utils): 替换 lodash 工具函数为 es-toolkit 实现 2025-11-13 07:45:10 +08:00
Jin Mao
5cafcb4a01 refactor(shared): 替换 lodash 工具函数为 es-toolkit 兼容版本
- 将 get 和 isEqual 方法从 lodash 替换为 es-toolkit/compat 导出
- 更新 package.json 添加 es-toolkit 依赖
- 注释掉原有的 lodash.get 和 lodash.isequal 导出语句- 锁定 es-toolkit 版本至1.41.0 并更新相关依赖配置
2025-11-13 07:28:52 +08:00
Jin Mao
03154cde88 chore: update depends 2025-11-13 07:16:28 +08:00
dependabot[bot]
a74bf7b63f chore(deps): bump echarts from 5.6.0 to 6.0.0 (#6859)
Bumps [echarts](https://github.com/apache/echarts) from 5.6.0 to 6.0.0.
- [Release notes](https://github.com/apache/echarts/releases)
- [Commits](https://github.com/apache/echarts/compare/5.6.0...6.0.0)

---
updated-dependencies:
- dependency-name: echarts
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-11-13 07:10:59 +08:00
米山
0bc7169698 feat: add global font size adjustment 2025-11-12 17:39:07 +08:00
米山
24b6e7a835 feat: add global font size adjustment 2025-11-12 17:38:41 +08:00
Jin Mao
f59e33682c Merge branch 'main' into main 2025-11-12 04:27:57 +08:00
Jin Mao
d811af37dd Merge branch 'main' into profile 2025-11-12 04:03:27 +08:00
Jin Mao
0e1a7d61f3 Merge branch 'main' into fix-downloader 2025-11-12 02:05:03 +08:00
panda7
573637222d feat: add form handleCollapsedChange event (#6893)
* feat: add form handleCollapsedChange event

* fix: ts lint

* fix: ts error

---------

Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-11-12 02:03:12 +08:00
shixi
05e9d65251 fix: for tdesign 2025-11-12 01:14:28 +08:00
shixi
0bbb20fee0 feat: sport notification link 2025-11-12 00:55:24 +08:00
shixi
dbc5ea32ae feat: add read and delete for each notification 2025-11-12 00:02:13 +08:00
xingyu4j
0319604863 feat: playground add profile 2025-11-10 18:00:50 +08:00
xingyu4j
acf99f2441 feat: tdesign add profile 2025-11-10 18:00:25 +08:00
xingyu4j
cbf2a02877 feat: naive add profile 2025-11-10 17:59:58 +08:00
xingyu4j
226d9bd1ad feat: ele add profile 2025-11-10 17:59:33 +08:00
xingyu4j
48b3d30088 fix: types 2025-11-10 17:54:01 +08:00
xingyu4j
e311cfb8da feat: route add profile 2025-11-10 17:51:10 +08:00
xingyu4j
4347fba80a feat: antdv add profile 2025-11-10 17:50:47 +08:00
xingyu4j
7dc68ed368 feat: add profile comps 2025-11-10 17:49:58 +08:00
Jin Mao
a4aa133db5 Merge branch 'main' into tdesign 2025-11-09 12:04:30 +08:00
Utopia
6bbe523f6f chore: 增强 util-formatDate ts 类型提示 (#6886)
* feat: 为 auth layout 添加 slot: logo, 提升组件的灵活性和可复用性

* feat: 增强 util-formatDate ts 类型提示
2025-11-09 12:00:49 +08:00
Copilot
fef1e35c54 fix: prevent JSONBigInt parsing error on non-string data (#6891)
* Initial plan

* Fix json-bigint serialization error when data is not a string

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
2025-11-09 11:49:38 +08:00
xingyu
20410aeb03 fix: updating Tailwind CSS variables from --radix-* to --reka-* (#6890)
* fix: radix => reka

* chore: add reka cspell

* fix: Composition url
2025-11-09 11:49:01 +08:00
Li Kui
83ea27af3e fix: fix lint 2025-11-06 23:16:23 +08:00
Li Kui
b7bfd69788 fix: fix lint 2025-11-06 23:10:03 +08:00
Li Kui
99d663a6f2 fix: fix typecheck 2025-11-06 23:02:24 +08:00
Li Kui
1af11240a6 chore: add tdesign logo 2025-11-06 22:37:55 +08:00
Li Kui
3e8e8690e3 chore: Merge branch 'main' into tdesign 2025-11-06 17:49:51 +08:00
Li Kui
be843300be chore: update demo routes 2025-11-06 17:44:34 +08:00
Li Kui
dc77721c11 chore: demo i18n 2025-11-06 16:42:25 +08:00
Li Kui
a38d081f17 chore: enable VITE_NITRO_MOCK 2025-11-06 16:37:15 +08:00
Copilot
1e09fa4642 feat: migrate from Radix Vue to Reka UI (#6870)
* Initial plan

* Update dependencies and imports from radix-vue to reka-ui

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

* Fix type errors after reka-ui migration

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

* Run formatter to fix code style

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
2025-11-06 16:22:24 +08:00
Li Kui
bd8ff73f9e fix: fix build error 2025-11-06 15:26:24 +08:00
Li Kui
57620dc2ea chore: apply coderabbitai suggestions 2025-11-06 15:11:28 +08:00
Li Kui
b472fbb72f Merge branch 'main' into tdesign 2025-11-06 14:36:27 +08:00
Copilot
04321b16c1 fix: replace ant-design-vue with tdesign components in web-tdesign app (#6880)
* Initial plan

* fix: replace ant-design-vue with tdesign components

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

* fix: remove trailing comma in package.json

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
2025-11-06 14:35:50 +08:00
Olexandr88
7268824612 Update README.md (#6828)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-11-04 04:34:55 +08:00
dependabot[bot]
23c41a8059 chore(deps): bump @types/node from 12.20.55 to 24.9.2 (#6860)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 12.20.55 to 24.9.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.9.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 04:16:59 +08:00
dependabot[bot]
fdc78faa13 chore(deps): bump @vueuse/integrations from 12.8.2 to 14.0.0 (#6861)
Bumps [@vueuse/integrations](https://github.com/vueuse/vueuse/tree/HEAD/packages/integrations) from 12.8.2 to 14.0.0.
- [Release notes](https://github.com/vueuse/vueuse/releases)
- [Commits](https://github.com/vueuse/vueuse/commits/v14.0.0/packages/integrations)

---
updated-dependencies:
- dependency-name: "@vueuse/integrations"
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 04:16:37 +08:00
dependabot[bot]
c8af580866 chore(deps): bump SamKirkland/FTP-Deploy-Action (#6873)
Bumps the non-breaking-changes group with 1 update: [SamKirkland/FTP-Deploy-Action](https://github.com/samkirkland/ftp-deploy-action).


Updates `SamKirkland/FTP-Deploy-Action` from 4.3.5 to 4.3.6
- [Release notes](https://github.com/samkirkland/ftp-deploy-action/releases)
- [Commits](https://github.com/samkirkland/ftp-deploy-action/compare/v4.3.5...v4.3.6)

---
updated-dependencies:
- dependency-name: SamKirkland/FTP-Deploy-Action
  dependency-version: 4.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 04:14:47 +08:00
Jin Mao
348c97710f Revert "refactor(jwt): 更新 JWT 工具函数实现方式 (#6869)"
This reverts commit a854760d26.
2025-11-03 19:34:45 +08:00
zengmingyong
a854760d26 refactor(jwt): 更新 JWT 工具函数实现方式 (#6869)
refactor(jwt): 更新 JWT 工具函数实现方式

- 将默认导入 jsonwebtoken 改为解构导入 sign 和 verify 方法
2025-11-03 13:08:06 +08:00
Copilot
c7c39047de feat: add theme-aware logo support via optional sourceDark configuration (#6866)
* Initial plan

* Initial exploration and setup

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* Add support for separate light and dark theme logos

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* Update documentation with dark theme logo configuration

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* feat: Add theme-aware logo support for authentication/login page

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* revert: .npmrc

Signed-off-by: aonoa <1991849113@qq.com>

---------

Signed-off-by: aonoa <1991849113@qq.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>
Co-authored-by: Li Kui <90845831+likui628@users.noreply.github.com>
Co-authored-by: aonoa <1991849113@qq.com>
2025-11-03 13:07:36 +08:00
Jin Mao
f8a7a0a9a2 chore: lint 2025-10-31 09:11:40 +08:00
Jin Mao
2cb3dcf499 Merge branch 'fork/ming4762/timezone-fix-20251030'
# Conflicts:
#	packages/stores/src/modules/timezone.ts
2025-10-31 09:08:08 +08:00
Jin Mao
b36d32b66d chore(timezone): 添加 $reset 方法并初始化时区存储
- 在时区存储中添加 `$reset` 方法
-修复导入语句引号为双引号
- 优化时区初始化错误处理逻辑- 导出 `$reset` 方法以便外部调用- 确保时区设置与默认选项同步
- 提升代码一致性和可维护性
2025-10-30 23:27:53 +08:00
zhongming4762
639ea96bb9 fix: resolve the issue of logout failure caused by the timezone store 2025-10-30 17:12:45 +08:00
zhongming4762
565be77e96 fix: resolve the issue of logout failure caused by the timezone store 2025-10-30 17:01:49 +08:00
Jin Mao
ac6de0324c Merge branch 'fork/ming4762/timezone-20251020' 2025-10-30 00:42:05 +08:00
zhongming4762
f46ae023ba feat: increase support for multiple time zones
* 优化实现方法
2025-10-29 20:14:48 +08:00
zhongming4762
212144feca feat: increase support for multiple time zones
* 优化实现方法
2025-10-29 20:10:25 +08:00
zhongming4762
3eed51fd3e feat: increase support for multiple time zones
* 优化实现方法
2025-10-29 20:03:21 +08:00
zhongming4762
4d713db546 feat: increase support for multiple time zones
* 优化实现方法
2025-10-29 19:47:10 +08:00
Jin Mao
0cd9f4615c Merge branch 'main' into fix-downloader 2025-10-26 15:28:34 +08:00
Jin Mao
cfbce0d737 chore: fix lint 2025-10-26 15:27:28 +08:00
Jin Mao
1d9cd88dd7 Merge branch 'main' into fix-downloader 2025-10-26 15:16:32 +08:00
Jin Mao
0e62862119 chore: fix lint 2025-10-26 15:01:58 +08:00
Jin Mao
6b6cdef42d chore: 一些调整 2025-10-26 15:00:47 +08:00
Jin Mao
33b7a605c0 chore: 一些调整和兼容性更新 2025-10-26 14:23:37 +08:00
zhongming4762
e01803ce9d feat: increase support for multiple time zones 2025-10-22 20:39:00 +08:00
zhongming4762
e3e5755903 feat: increase support for multiple time zones 2025-10-22 20:29:53 +08:00
zhongming4762
61ce53b686 feat: increase support for multiple time zones 2025-10-22 20:23:55 +08:00
zhongming4762
b029f77b6a feat: increase support for multiple time zones 2025-10-22 20:18:25 +08:00
zhongming4762
0a8339a405 feat: increase support for multiple time zones 2025-10-22 19:52:01 +08:00
Jin Mao
2264eaae18 feat: 新增集成tdesign组件的apps 2025-10-21 10:47:39 +08:00
zoumingjun
38f91da5af fix: 优化双列布局组件点击展开左侧按钮位置不居中问题 2025-10-18 21:22:58 +08:00
zoumingjun
8edea3aee5 fix: 调整zh-CN/system.json中<"title": "系统管理">配置位置,保持与en-US/system.json中位置一致 2025-10-18 21:20:54 +08:00
zoumingjun
08b6e7713e fix: 修复对话框modal组件方法名错误 2025-10-18 21:13:34 +08:00
zoumingjun
6a89814b83 chore: 优化侧边栏展开折叠图标 2025-10-18 20:59:14 +08:00
zoumingjun
52d3aa9315 fix: 优化Checkbox组件hover样式,Input组件placeholder样式,TabsList组件圆角样式 2025-10-18 20:58:48 +08:00
zoumingjun
4e264c503a fix: 锁屏和解锁密码输入框自动聚焦 2025-10-18 20:55:53 +08:00
zoumingjun
32051e9ca0 fix: 优化左侧和右侧认证面板动画效果 2025-10-18 20:52:27 +08:00
zoumingjun
2b0079580b fix: 修复mock-data.ts父id错误 2025-10-18 16:32:23 +08:00
lidongcheng88
03ce030e7c fix: 混合双列 onMounted 关闭二级侧边栏 (#6807)
Co-authored-by: ldc <lidchen@tencent.com>
2025-10-14 06:56:00 +08:00
zouawen
59097e2466 chore: 新增是否开启首选项导航栏(外观、布局、快捷键、通用)吸顶效果,调整部分样式 (#6804) 2025-10-14 06:55:11 +08:00
zouawen
2ce161e585 feat: 1、新增水印文案自定义功能;2、input-item输入框组件新增清除功能(可用于快捷清除水印文案);3、偏好设置、主题切换、语言选择、是否全屏按钮新增动画效果 (#6800)
* feature: 新增水印文案自定义功能;

* chore: 偏好设置、主题切换、语言选择、是否全屏按钮新增动画效果
2025-10-07 06:15:41 +08:00
zouawen
33306a5aff chore: 更换其他登录方式图标为彩色图标,新增其他登录方式tooltip文字提示 (#6799) 2025-10-06 06:56:54 +08:00
lw567
6e03de5011 fix: watermark create error #6788 (#6789)
Co-authored-by: liwei567 <liwei567@example.com>
2025-09-30 19:53:09 +08:00
HaHa
713281a8ba fix(theme-button): fix flicker during theme switch (#6782) 2025-09-30 05:10:51 +08:00
shierd
42e3de9e2c fix: 修复设置表单值时合并子对象的错误 (#6780)
Co-authored-by: shier <shier@shierd.com>
2025-09-30 05:10:39 +08:00
vent
1e029dbc9a fix: 修复部分场景 echarts 在使用 v-show 时获取不到 width 的问题 (#6776) 2025-09-30 05:09:33 +08:00
Cup_Of_Bread
81bb7456f8 fix: 修复注解错误 (#6763) 2025-09-23 23:49:25 +08:00
pangyajun123
26f8d2aa30 perf: 表单需要通过权限控制是否需要展示,dependencies.if 这种方式需要别triggerFields触发字段,不能满足要求 (#6756) 2025-09-23 23:48:27 +08:00
boisduval
a8b848d367 fix: 解决搜索表单中arrayToStringFields属性无法在搜索时生效的问题 (#6591)
- 合并handleArrayToStringFields与handleStringToArrayFields为handleMultiFields
- 在handleMultiFields判断arrayToString或stringToArray
- 删除两个方法在其他地方的引用 统一为在handleRangeTimeValue内调用handleMultiFields实现数据转换

Co-authored-by: 宛晴 <wanqing@mengtaigroup.com>
2025-09-23 23:45:26 +08:00
ming4762
482ce981ce chore: update deps vxe, vxe-table: 4.14.4 -> 4.16.11 vxe-pc-ui: 4.7.… (#6752)
* chore: update deps vxe, vxe-table: 4.14.4 -> 4.16.11  vxe-pc-ui: 4.7.12 -> 4.9.29

* fix: resolve the ESLint warning generated by #6317
2025-09-17 17:42:39 +08:00
Jin Mao
97ad93ab1f Merge branch 'zouawen-main' 2025-09-17 17:41:05 +08:00
Jin Mao
319b7fcdaa Merge branch 'main' of https://github.com/zouawen/vue-vben-admin into zouawen-main 2025-09-17 17:37:43 +08:00
Jin Mao
4de8220ce6 feat(common-ui): 添加 Tree 组件
- 在 common-ui 包中新增 Tree 组件
- 实现了基本的树形结构展示功能
- 集成了 VbenTree 组件和自定义样式- 添加了空数据时的占位展示
2025-09-17 17:28:39 +08:00
Jin Mao
9114692f95 Merge branch 'main' into main 2025-09-15 08:08:31 +08:00
Jin Mao
0f7731b611 Merge branch 'ming4762-stream-2025091101' 2025-09-15 08:03:52 +08:00
Jin Mao
8040bc1a22 Merge branch 'stream-2025091101' of https://github.com/ming4762/smart-boot-ui-vben into ming4762-stream-2025091101 2025-09-15 08:02:30 +08:00
oc
11d273cbb6 feat(authentication): 二维码登录和验证码登录组件增加返回按钮显隐配置 (#6713)
- 在 CodeLogin 和 QrcodeLogin 组件中添加 showBack 属性
- 根据 showBack 属性决定是否显示返回按钮
- 默认值为 true,即默认显示返回按钮
2025-09-15 07:43:18 +08:00
zouawen
a4b8f8f795 fix: 节点选中时过滤父节点禁用状态 2025-09-12 17:31:42 +08:00
zouawen
dfa2ac3435 fix: 全选时过滤disabled的节点 2025-09-11 18:16:10 +08:00
zouawen
39820c783c fix:
1、VbenTree新增是否全选、展开折叠功能;
2、解决当点击子节点label文字区域,而非checkbox时,关联父组件不能选中问题;
3、优化子节点选中时关联父节点选中功能:删除VbenTree中processParentSelection方法,改为在onSelect中实现,原因:processParentSelection在每次模型值更新时都会被调用,且计算复杂度为O(n^2),onSelect只在交互时触发,复杂度为O(n);
4、新增中间层tree组件,处理无数据时显示场景(显示图标Inbox和国际化comom.noData文本);
5、为防止父组件传值子组件boolean类型默认false问题,新增treePropsDefaults方法,为TreeProps赋默认值,Tree组件和VbenTree组件统一使用;
6、优化VbenTree组件整体样式(优化padding、margin、gap值,优化type为button时outline左右空白区域不对称问题),优化内部header、footer插槽样式。
2025-09-11 16:41:42 +08:00
zhongming4762
66822a5f95 feat: add SSE support to request-client 2025-09-11 11:22:47 +08:00
zhongming4762
eb4f1f8164 feat: add SSE support to request-client 2025-09-11 10:50:19 +08:00
谦元吉
6a85b3ab84 fix(@vben/common-ui): resolve #6698 (#6700)
* fix(@vben/common-ui): resolve #6698

* fix(@vben/common-ui): resolve #6698

* fix(@vben/common-ui): resolve conversation

* fix(@vben/common-ui): resolve #6698
2025-09-02 17:02:11 +08:00
谦元吉
c57592f3a6 fix(@vben-core/shadcn-ui): 修复VbenPinInput组件在点击获取验证码和回车同时进行时会重复触发计时器的问题 (#6705) 2025-09-02 16:56:46 +08:00
Ken Hai
b0d7b177be fix: 角色管理,授权的树形组件,取消勾选叶子节点,父级节点状态错误 (#6680)
Co-authored-by: haiyinlong <haiyinlong@uhigame.com>
2025-08-25 21:32:01 +08:00
LinaBell
566d3bdf7c fix: appendToMain warning (#6684) 2025-08-25 21:25:29 +08:00
螃蟹
adbf793e79 fix(@vben/web-ele): the main color tone for switching between dark and light themes has been reset (#6678) 2025-08-25 11:03:54 +08:00
LinaBell
cf6c4c9aae fix: cannot read properties of null (reading 'nextSibling') (#6667) 2025-08-21 22:26:10 +08:00
Ken Hai
ffaf85c8f3 fix: 修复角色修改时VbenTree组件没有回显选中 (#6662)
* fix: 修复角色修改时VbenTree组件没有回显选中

* chore: use nextTick

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: merge

* chore: 更新

---------

Co-authored-by: haiyinlong <haiyinlong@uhigame.com>
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-21 15:30:58 +08:00
panda7
2cc78f925f fix: the bug in the lock method of the vbenModal component (#6648) 2025-08-21 15:17:55 +08:00
ming4762
93f0eea4e7 fix: fix the issue of excessive line spacing in vbenForm (#6653)
* gap-2和 pb-4/2 重叠导致间距过宽,gap-x只保留列间距
2025-08-21 12:41:04 +08:00
lighua
fddfc6d494 fix: 增加对不支持的 HTTP 方法的错误处理 2025-08-21 11:13:02 +08:00
谦元吉
58e3941810 chore(docs): update the component import of the form adapter description in the document (#6656) 2025-08-19 16:48:10 +08:00
Svend
3ad433a50b fix: 修复在 hash 路由模式下无法在新窗口打开路由的问题 (#6652)
此问题是由于 PR #6583 中新增的 `resolveHref` 函数导致的。其在 hash 路由模式下,得到的 URL 会包含 #/ 前缀。在经过 openRouteInNewWindow 的逻辑后就会出现两次 /# 前缀
2025-08-19 16:47:45 +08:00
ming4762
8ac2db5b7c fix: fix the issue of VbenForm compact reactive failure (#6654) 2025-08-19 16:46:14 +08:00
Elm1992
a441dcebae fix: meta.link invalid issue 2025-08-19 16:40:16 +08:00
Vben
ff4704d5ea chore: Upgrade vite to version 7.x (#6645) 2025-08-16 22:50:31 +08:00
菠萝吹雪
6ddfbd84b0 chore: modify the contributor showcase in the README (#6636) 2025-08-16 22:47:08 +08:00
ming4762
1e6417f95b feat: vBenForm add layout: inline (#6644) 2025-08-16 22:41:08 +08:00
vben
e147a9d2fd chore: release 5.5.9 2025-08-16 22:16:02 +08:00
谦元吉
4efebb8c0b fix:update (#6635) 2025-08-14 22:01:12 +08:00
gxc685
9ce0df88ae fix: 修复mock里面eventHandler重复导致无法启动 (#6631) 2025-08-14 22:00:54 +08:00
谦元吉
3cf0c0eb04 fix(@vben/backend-mock): go back to the last modification (#6634)
* fix(@vben/backend-mock): the version went back to the last submission, and the latest submission was completely useless.

* fix: resolve conflicts
2025-08-14 12:06:41 +08:00
谦元吉
ab7e363279 fix(@vben/backend-mock): fix all ts type errors in this module (#6613)
* fix(@vben/backend-mock): 修复所有 ts 类型报错

* fix(@vben/backend-mock): 修复该模块所有 ts 类型报错

* fix(@vben/backend-mock): 解决 coderabbitai

* fix(@vben/backend-mock): 解决 coderabbitai

* fix(@vben/backend-mock): 解决 coderabbitai
2025-08-12 17:23:39 +08:00
lighua
df655015b1 fix: 优化文件下载器方法 2025-08-08 15:31:31 +08:00
xueyang
9fc594434f perf: 优化useVbenForm样式 (#6611)
* perf(style): 优化useVbenForm垂直布局 actions 样式

* perf(style): 优化useVbenForm actions 布局样式

- 操作按钮组显示位置
```
actionPosition?: 'center' | 'left' | 'right';
```
- 操作按钮组的样式
```
actionType?: 'block' | 'inline'
inline: 行类显示,block: 新一行单独显示
```

* perf: 优化useVbenForm actions 布局样式

删除 actionType
增加 actionLayout
- actionLayout?: 'inline' | 'newLine' | 'rowEnd';
- newLine: 在新行显示。rowEnd: 在行内显示,靠右对齐(默认)。inline: 使用grid默认样式
- 删除无用代码 queryFormStyle

* perf: 优化useVbenForm使用案例

* perf: 优化form组件样式

去掉padding,改为gap

* docs: update vben-form.md

* fix: 修复FormMessage位置

* perf: Avoid direct mutation of props object.

-  props.actionLayout = props.actionLayout || 'rowEnd';
-  props.actionPosition = props.actionPosition || 'right';
+  const actionLayout = props.actionLayout || 'rowEnd';
+  const actionPosition = props.actionPosition || 'right';

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: 修复 wrapperClass 权重

* fix: 全局搜索结果不匹配 #6603

* fix: 避免FormMessage溢出

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-07 23:48:34 +08:00
leo
b93e22c45a fix(@vben/layouts): respect base URL when opening route in new window (#6583)
Previously, the generated URL for opening routes in a new window did not include the router base,
which led to incorrect paths when the app was deployed under a subdirectory (e.g., /admin/).
This change ensures that the resolved path includes the configured base by using
router.resolve(path).href.
2025-07-29 13:46:05 +08:00
Jin Mao
193f5b6512 Merge branch 'main' into 2025072604 2025-07-28 15:53:04 +08:00
Jin Mao
cb3f96683f fix: 修复双列布局模式下,路由为hideInMenu时,空白右列 2025-07-28 15:50:21 +08:00
zhongming4762
06ffdf164a feat: add dingding login 2025-07-25 22:02:55 +08:00
ming4762
5b75e5e917 perf: perf the control logic of VbenModal full screen and header (#6566)
* resolve the issue of header=false and full screen button display but not operable
2025-07-25 21:45:45 +08:00
aonoa
fad0b49841 fix: adding roles does not automatically refresh (#6548)
* fix: adding roles does not automatically refresh

* style: fix code style err
2025-07-25 21:35:57 +08:00
Jin Mao
260e45cd7b Merge branch 'main' into feat/add-vben-modal-animation 2025-07-25 21:33:11 +08:00
vben
1575619d53 chore: release v5.5.8 2025-07-19 22:19:50 +08:00
Jin Mao
d5a36a167d fix: fix vxeTable commit proxy (#6536)
* fix: 修正use-vxe-grid中的代理配置提交类型

* chore: change config
2025-07-19 16:07:15 +08:00
panda7
fc9ea347ca Merge branch 'main' into feat/add-vben-modal-animation 2025-07-18 00:38:54 +08:00
 panda7
1a9b0509d5 feat: add animation effects to VbenModal component 2025-07-18 00:15:40 +08:00
panda7
07b64ad384 feat: add function support for formItemClass prop (#6511)
* feat: add function support for formItemClass prop

* feat: add try-catch to formItemClass function

* fix: formItemClass function ts error

---------

Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-07-17 09:37:39 +08:00
Jin Mao
1bc5d2986b chore: update-vxe-table (#6516)
* chore: update vxe-pc-ui,vxe-table

* fix(ui): 修复代理配置初始化方法名错误

* fix(ui): 修改远程表格刷新配置

* chroe: update vxeTable

更新到最新
2025-07-16 19:15:39 +08:00
HamWong
bb36cca315 fix: 锁定屏幕页面样式自适应 (#6480) 2025-07-15 09:08:08 +08:00
Jin Mao
b8bf482c6a Merge branch 'main' into form 2025-07-07 09:16:54 +08:00
Jin Mao
3b673ca915 Merge branch 'main' into feature/scroll_to_the_error_field 2025-07-07 08:21:50 +08:00
Jin Mao
bbf0287511 chore: fix lint warning (#6487) 2025-07-07 08:21:25 +08:00
panda7
d4786f3f75 Merge branch 'main' into feature/scroll_to_the_error_field 2025-07-06 21:19:52 +08:00
xue-jn
b333fd676d docs: update vben-drawer.md (#6478)
* docs: update vben-drawer.md

* docs: update vben-drawer.md

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-07-06 20:26:06 +08:00
sqchen
f1051c8773 feat: add scrollToFirstError to the form component 2025-07-05 00:50:53 +08:00
sqchen
243f3a201d feat: add scrollToFirstError to the form component 2025-07-05 00:19:12 +08:00
陈蔚然
2f7de243f6 Merge branch 'vbenjs:main' into slider-translate-captcha 2025-07-04 10:28:40 +08:00
chenweiran
1aafb43103 feat: 增加基于图片拼图切片平移的验证码 2025-07-04 10:26:02 +08:00
chenweiran
8ccd01ade5 feat: 增加基于图片拼图切片平移的验证码 2025-07-04 10:23:47 +08:00
Netfan
e6bfbce6cb revert: page height fixed,revert #6422 2025-07-03 22:29:48 +08:00
someone-cool
1f63aed64c fix(@vben/common-ui): add page footer's height to calc contentstyle (#6422) 2025-07-03 18:36:42 +08:00
xue-jn
253b0da7d2 fix: 接口返回子节点为空数组时,还会显示折叠箭头 (#6463) 2025-07-03 18:35:03 +08:00
RanMaoting
33a4d524db feat(@vben/plugins): 新增VxeGrid组件插槽类型定义 (#6452)
* feat(@vben/plugins): 新增VxeGrid组件插槽类型定义

Closes: #6451

* fix(@vben/plugins): 优化vxe-table组件的插槽类型定义

修复Omit导致的类型丢失
2025-07-03 18:34:37 +08:00
chenweiran
bbd8a53d9d feat: 增加基于图片拼图切片平移的验证码 2025-07-03 18:20:20 +08:00
chenweiran
8554924cb9 feat: 增加基于图片拼图切片平移的验证码 2025-07-03 17:43:19 +08:00
RanMaoting
fee811d950 fix: 优化组件方法透传并新增表单弹窗示例 (#6443) 2025-07-02 19:58:48 +08:00
Netfan
78076e70b4 chore: update deps 2025-07-02 16:55:55 +08:00
chewenye
b78bc65ce7 feat: 组件json-viewer支持bigint数据显示 cwy (#6377)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-06-29 04:32:30 +08:00
Utopia
b1fb623113 feat: 为 auth layout 添加 slot: logo, 提升组件的灵活性和可复用性 (#6442) 2025-06-27 19:23:24 +08:00
Stephen Chang
de14908fd3 fix(icon-picker): 解决icon-picker组件切换分页后,关键词检索失效问题 (#6437)
当icon-picker组件切换分页后,在输入关键词检索,但是分页没有重置,导致检索结果异常
2025-06-27 19:21:23 +08:00
Li Kui
5c3972196a fix: Add $t import to login expired modal (#6429)
closes #6230
2025-06-27 19:20:25 +08:00
CG.gatspy
3230781538 feat: [vben-tree]增加数据disabled (#6343)
* feat: [vben-tree]增加数据disabled

* Update packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-06-27 19:09:30 +08:00
broBinChen
e7fd0e3b6a feat(hooks): useHoverToggle的入参refElement支持传入响应式数组 (#6333)
* feat(hooks): useHoverToggle的入参refElement支持传入响应式数组

* feat(hooks): 1、增加 useHoverToggle 中 refElement 参数关于传入响应式数组的注释说明。 2、修改 watch 监听深度,仅需浅层监听 refs 变化。 3、使用 effectScope 管理 useElementHover 实例,避免 refs 变化时事件监听器累积导致的内存泄漏问题

* feat(hooks): 在useHoverToggle中增强 updateHovers  的边界处理,优化watch方案,只监听元素数量变化而不是整个数组变化,避免过度依赖收集

---------

Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
2025-06-27 19:08:41 +08:00
yuhengshen
2f7d1f009d fix: 全屏状态下弹窗圆角优化 (#6413) 2025-06-24 23:41:54 +08:00
yuhengshen
946f91f387 feat: optimize modal dragging range(#6414)
* 当弹窗指定了容器时,拖拽将被限制在容器范围内
2025-06-24 17:05:59 +08:00
lghuahua
986eacae9a fix: improve request logic in api-component
* 修复某些情况下updateParam设置的参数可能不会提交给api的问题
* 修复在上一个请求尚未完成前如果params发生了变更,将不会再触发新的api请求
---------

Co-authored-by: Netfan <netfan@foxmail.com>
2025-06-20 08:42:45 +08:00
Netfan
97b8e28a2b docs: fix delete request usage (#6389) 2025-06-17 08:52:59 +08:00
Netfan
c0962fec18 fix: auto close popup on deactivated (#6368)
* 修复挂载到内容区域的弹窗和抽屉被意外关闭的问题
2025-06-11 12:20:52 +08:00
XiaoHetitu
8ba7bdf2bd fix(button): 为按钮添加type属性防止表单提交意外触发表单验证机制 (#6340)
在按钮组件中,按钮元素缺少type="button"属性可能导致在表单中意外提交。添加此属性以确保按钮行为符合预期。

Co-authored-by: yuanwj <ywj6792341@qq.com>
2025-06-08 17:56:24 +08:00
zyy
b015fbc9fc fix: [adapter] 表格配置类型报错 (#6327)
配置toolbarConfig中的search时会有类型报错
2025-06-08 17:53:55 +08:00
broBinChen
b69320c070 feat(hooks): support separate enter/leave delays in useHoverToggle (#6325)
Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
2025-06-08 17:53:29 +08:00
zhang
dcccc213ce fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提… (#6300)
* fix: requestClient.upload会将vbenform中value为undefined的值转为字符串undefined’提交给后台保存

* fix: requestClient.upload会将vbenform中value为undefined的值转为字符串'undefined’提交给后台保存
2025-06-08 17:51:16 +08:00
ali-pay
c0e601c020 fix: menu type is not 'button' (#6277)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-06-08 17:50:44 +08:00
RanMaoting
017ed1a9e1 types: 为useVbenVxeGrid添加泛型声明,使grid实例上能正确获取到行数据的类型 (#5653)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-06-08 17:43:02 +08:00
vben
b9aef618fe chore: release 5.5.7 2025-06-04 05:33:06 +08:00
Netfan
4102cc2211 feat: improve vbenCheckButtonGroup (#6329)
* 按钮组支持单选清除和多选限制最大选项数

* 按钮组支持icon插槽来定制图标
2025-06-03 23:11:56 +08:00
chewenye
ea776aa710 types: 扩展user-dropdown组件的menus类型,支持iconify (#6283)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-06-03 06:07:06 +08:00
huanghezhen
feb96dc8ea fix: resolve onClosed method failure in connectedComponent of useVbenModal (#6309) 2025-06-02 08:16:48 +08:00
wyc001122
470fd43b49 fix: 修复使用useVbenVxeGrid配置hasEmptyText、hasEmptyRender不生效的问题 (#6310) 2025-06-02 08:16:26 +08:00
zhang
76d106e474 fix: When defaultHomePage is inconsistent with user.homePath, the pa… (#6299)
* fix:  When defaultHomePage is inconsistent with user.homePath, the page refresh route jump will be abnormal

* fix:  When defaultHomePage is inconsistent with user.homePath, the page refresh route jump will be abnormal
2025-06-02 08:07:06 +08:00
wyc001122
78c3c9da6f docs(settings): 完善'生产环境动态配置'步骤 (#6297) 2025-06-02 08:05:23 +08:00
Netfan
081d08a7f8 fix: alert width fixed in small screen (#6312) 2025-05-30 19:54:26 +08:00
Netfan
96a10ca83f style: fix lint error (#6298) 2025-05-28 19:23:21 +08:00
wyc001122
f31360ba4e feat: support for hybrid permission access control mode (#6294)
* feat: 添加混合权限访问控制模式

* feat: 文档补充
2025-05-28 17:01:58 +08:00
wyc001122
4eb16d6d3a fix: fix table-title slot not work (#6295) 2025-05-28 17:01:11 +08:00
liqiang0330
53304514b6 fix: Update index.ts (#6268)
* Update index.ts

VxeGridPropTypes.原文件缺少这个,现在补全!

* Update index.ts

增加空格!
2025-05-26 13:29:27 +08:00
Netfan
6fbf1387f5 fix: reset slider-captcha after login failed (#6275) 2025-05-25 16:04:56 +08:00
Netfan
e5c937396d fix: json-bigint parse used in vxeTable (#6271)
* 修复vxeTable不能加载json-bigint解析的数据的问题
2025-05-24 13:01:58 +08:00
littlesparklet
af186f878d fix: repair the unexpected form default value (#5567)
* fix: Fix inconsistent spacing around search form (issue #5429)

* fix: repair the unexpected default value in validated form.(issue #5451)

* Update packages/@core/ui-kit/form-ui/src/use-form-context.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-05-23 16:05:11 +08:00
wyc001122
97894a940e feat: optimize logo display (#6267)
* feat(VbenAvatar): add fit property to VbenAvatar component

* feat(VbenLogo): add fit property to VbenLogo component

* feat(VbenLogo): add logo fit preference configuration

- Add preferences.logo.fit setting for logo display control
- Include corresponding documentation for the new preference

* feat(preferences): add default value for logo.fit preference

- Set default configuration for logo fit behavior
- Ensures consistent logo display across applications

* test(preferences): update configuration snapshots

---------

Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-23 15:24:01 +08:00
yingzi2019
48d70182b4 feat: improve check updates (#6257)
Co-authored-by: monkey <maotao@tutamail.com>
2025-05-23 15:23:06 +08:00
Netfan
a1091bad46 feat: enhances compatibility with APIs returning large numeric values (#6250) 2025-05-23 15:22:18 +08:00
zhang
9f9be21e2a fix: component Input is not registered when initialize page (#6246)
* fix: Component Input is not registered when initialize page

* fix: Component Input is not registered when initialize page
2025-05-23 15:21:09 +08:00
panda7
a2bdcd6e49 feat: ellipsis text automatically displays tooltip based on ellipsis (#6244)
* feat: ellipsis text automatically displays tooltip based on ellipsis

* feat: ellipsis text automatically displays tooltip based on ellipsis

---------

Co-authored-by: sqchen <9110848@qq.com>
Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-05-23 15:20:38 +08:00
ali-pay
11b2b5bcc2 fix: 修复菜单管理中按钮类型值错误的问题 (#6255) 2025-05-22 09:09:31 +08:00
LinaBell
ebef2c91e2 fix: tab cannot be displayed correctly after browser refresh (#6256) 2025-05-22 09:04:40 +08:00
Netfan
0c3edb10b0 fix: getFieldComponentRef will return actual ref within AsyncComponentWrapper (#6252)
修复异步加载组件时,表单的getFieldComponentRef方法没能获取到正确的组件实例
2025-05-21 14:48:51 +08:00
zwtvip
f7bae8ac0f chore: export framework components for use in independent pages 2025-05-21 00:37:26 +08:00
wyc001122
8ac97688da fix(preferences): 更新内容内边距默认值 (#6233)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-20 09:50:23 +08:00
李轻舟
2efacb3e5b docs: Update build.md (#6228) 2025-05-19 16:30:39 +08:00
wyc001122
dae46abb71 feat: additional-settings (#6225)
* feat(preferences): 补充VbenAdminLayout传入属性(来自偏好设置)

* docs(@vben/docs):update settings doc

---------

Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-19 16:29:15 +08:00
wyc001122
5ee2a74e2d fix(use-design-tokens): 完善element-plus暗色主题颜色 (#6224)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-19 16:27:34 +08:00
afe1
d0b8349a2d perf: stub unbuild params (#6210) 2025-05-18 10:35:20 +08:00
wyc001122
34c4ecb047 fix: in mixed layout mode, the sidebar does not display when the first child node is an external link (#6219)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-18 10:34:41 +08:00
ming4762
3d9dba965f perf: perf the control logic of Tab (#6220)
* perf: perf the control logic of Tab

* 每个标签页Tab使用唯一的key来控制关闭打开等逻辑
* 统一函数获取tab的key
* 通过3种方式设置tab key:1、使用router query参数pageKey 2、使用路由meta参数fullPathKey设置使用fullPath或path作为key
* 单个路由可以打开多个标签页
* 如果设置fullPathKey为false,则query变更不会打开新的标签(这很实用)

* perf: perf the control logic of Tab

* perf: perf the control logic of Tab

* 测试用例适配

* perf: perf the control logic of Tab

* 解决AI提示的警告
2025-05-18 10:33:02 +08:00
wyc001122
024c01d350 fix(@vben-core/shadcn-ui): fix disabled functionality not working in VbenTree component (#6205)
* fix(@vben-core/shadcn-ui): fix disabled functionality not working in VbenTree component

* fix(@vben-core/shadcn-ui): add cursor-not-allowed className when disabled and disable onfocus

---------

Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-05-16 14:13:43 +08:00
afe1
2adb8acd80 fix: css style (#6176) 2025-05-16 09:40:40 +08:00
panda7
a23bc4cb5c fix: the mobile terminal can wrap lines and expand slot attributes (#6165)
Co-authored-by: sqchen <chenshiqi@sshlx.com>
2025-05-16 09:40:05 +08:00
XiaoHetitu
cf17a45d8d feat(tabs): 支持计算属性作为标签标题,解决 #6170 的问题 (#6163)
* feat(tabs): 支持动态函数作为标签标题

修改 `setTabTitle` 和 `tabsView` 逻辑,允许传入函数作为标签标题,以便动态生成标题内容

* feat(tabbar): 添加动态设置标签页标题功能

允许设置静态字符串或动态函数作为标签标题,支持根据状态或语言变化动态更新标题

* refactor(tabs): 移除冗余的newTabTitle2变量并优化标题设置逻辑

移除tabs组件中冗余的newTabTitle2变量,直接使用newTabTitle作为标题来源。同时,优化use-tabs和tabbar模块的标题设置逻辑,支持ComputedRef作为动态标题,提升代码简洁性和可维护性。

---------

Co-authored-by: yuanwj <ywj6792341@qq.com>
2025-05-16 09:37:50 +08:00
chewenye
b46ebe756e types: 导出authentication组件的type,自定义toolbarList时类型使用ToolbarType (#6158)
Co-authored-by: 车文烨 <chewy@china-lehua.com>
2025-05-16 09:36:49 +08:00
Netfan
e89cf400c0 fix: refresh command of tabbar issue, fixed: #6162 (#6169) 2025-05-12 23:34:08 +08:00
anyup
9e67929ee7 feat: support to refresh the tab page by route name (#6153)
Co-authored-by: anyup <anyupxing@163.com>
2025-05-10 22:33:31 +08:00
afe1
90625782c0 fix: delete useless code (#6143) 2025-05-08 16:51:12 +08:00
wyc001122
84ef207d9c docs(@vben/docs): update settings doc (#6128)
Co-authored-by: wyc001122 <wangyongchao@testor.com.cn>
2025-05-07 12:04:48 +08:00
zyf0624
e68fff58e8 fix: tsconfig moduleResolution (#6122)
Co-authored-by: pzzyf <2279948211@qq.com>
2025-05-07 12:04:15 +08:00
Netfan
bf70539221 fix: missing argument for getPopupContainer 2025-05-06 22:48:03 +08:00
Leeson
5949c73a30 fix: delete Popconfirm being obscured by fixed columns (#6118)
* fix: delete Popconfirm being obscured by fixed columns

* fix: opened popConfirm will prevent the table from scrolling

---------

Co-authored-by: Netfan <netfan@foxmail.com>
2025-05-06 22:33:17 +08:00
vben
cc6c9bf7a0 chore: release v5.5.6 2025-05-06 22:32:58 +08:00
Jin Mao
6b1aab9c67 fix: handle undefined children in generate-menus (#6117)
When children is undefined, use empty array as fallback to prevent potential runtime errors. This matches the behavior when hideChildrenInMenu is true.
2025-05-06 14:29:50 +08:00
LinaBell
8f4d3d418d fix: when keepAlive is enabled, returning directly through browser buttons/gestures will not close pop ups (#6113) 2025-05-06 14:02:23 +08:00
ming4762
3b3f8e4e44 fix: fix IconPicker props warning (#6108)
Invalid prop: type check failed for prop "onUpdate:value". Expected Function, got Array
2025-05-06 09:30:37 +08:00
vben
f94ca10adf chore: remove prepare script from package.json 2025-05-04 07:33:36 +08:00
vben
4471bc7a5d chore: update prepare script in package.json to remove lefthook installation 2025-05-04 00:05:29 +08:00
Vben
5689ac60ff feat(project): migrate from husky and lint-staged to lefthook (#6104) 2025-05-03 19:43:12 +08:00
Vben
045bc4e5ee feat: support smooth auto-scroll to active menu item (#6102) 2025-05-03 18:05:26 +08:00
Vben
17a18fc9ba chore: close eslint object sorting (#6101) 2025-05-03 16:06:36 +08:00
aonoa
41152d1722 refactor: modify the default homepage path loaded from the preference… (#6099)
* refactor: modify the default homepage path loaded from the preferences.ts

Signed-off-by: aonoa <1991849113@qq.com>

* refactor: modify the default homepage path loaded from the preferences.ts

Signed-off-by: aonoa <1991849113@qq.com>

---------

Signed-off-by: aonoa <1991849113@qq.com>
2025-05-03 16:03:08 +08:00
Netfan
f1af9f8f6e fix: add triggerClass binding to PopoverTrigger and update icon-picker styles (#6095)
* Popover支持设置trigger的样式
* 修正icon-picker的input值更新
2025-05-01 21:40:45 +08:00
Netfan
0517a7014f fix: add missing translation for preferences drawer (#6094) 2025-05-01 20:08:44 +08:00
Netfan
3e6d608a2f fix: destroyOnClose incorrect default value, fixed #6092 (#6093) 2025-05-01 14:09:37 +08:00
ming4762
5de954baa4 fix: fix LoginExpiredModal in some cases, message may be obscured (#6086) 2025-05-01 10:40:42 +08:00
Netfan
add1e61b6f fix: show validation message as tooltip in compact form (#6087)
* 紧凑模式表单的校验消息将显示为一个tooltip
2025-04-30 23:41:44 +08:00
Jin Mao
20c15f352f perf: page componet supports custom height offset for flexible content height … (#6081)
* perf: Page supports custom height offset for flexible content height control.

允许通过 height 属性调整页面内容高度计算。修改了 Page 组件以支持自定义高度偏移量,用于更灵活的内容高度控制。

* chore: typo

* perf(page): replace height with heightOffset for flexible content sizing

The `height` prop was replaced with `heightOffset` to better describe its purpose when used with `autoContentHeight`. The new prop allows custom offset values (in pixels) to adjust content area sizing, with clearer documentation.
2025-04-29 18:15:12 +08:00
Netfan
8aa7dabeff fix: calculation for collapsing search form is incorrect while initially hidden (#6068)
* 修复当默认隐藏搜索表单时,折叠位置的计算不正确的问题
2025-04-28 23:20:33 +08:00
vben
78c7c1589a chore: update readme.md 2025-04-28 23:11:34 +08:00
Vben
dd833ca56b chore: update dependencies and documentation, optimize build toolchain (#6060)
* chore: update packageManager version to pnpm@10.9.0 for compatibility improvements

* chore: Update dependent versions and configurations to improve compatibility and stability

- Update Node version to 22.1.0
- Updated pnpm version to 10.10.0
- Fixed syntax error in prettier command in lintstagedrc
- Update dependent versions in pnpm-lock.yaml to ensure consistency
- Update format and content in README documents to improve readability

* fix: lint error
2025-04-28 23:08:05 +08:00
vem
681c1dc267 fix: Update existing route index to prevent 404 on user switch (#6003)
Co-authored-by: tars-macmini <vem@qq.com>
2025-04-28 18:19:47 +08:00
Netfan
4545422ee0 fix: lock state will not change overflow style in drawer and modal (#6067)
* Modal和Drawer的锁定状态不再修改overflow样式
2025-04-28 17:02:54 +08:00
Gahotx
ca94ca906f fix: add rounded corners to project and quick nav items (#5296) 2025-04-27 22:50:42 +08:00
Vben
76de450c71 chore: update dependency version for improved stability and compatibility (#6023)
* chore: update dependency version for improved stability and compatibility

* fix: optimize clearPoints function in useCaptchaPoints hook to improve performance

* fix: make several props optional in various components for better flexibility
2025-04-27 22:06:49 +08:00
Trivikram Kamat
dd2b1ed580 fix: install corepack from npm (#5905)
* fix: install corepack from npm

* docs: install corepack from npm
2025-04-27 22:03:35 +08:00
ming4762
baec89f896 perf: resolve duplicate component names (#6039) 2025-04-27 22:02:38 +08:00
vben
7c7051a11e chore: release v5.5.5 2025-04-27 21:45:10 +08:00
Netfan
aa27a2f7a1 feat: encrypt the privacy data when it is persisted (#6056)
* 对私密数据持久化时执行加密
* 将锁屏密码合并到accessStore中进行加密
2025-04-27 20:59:10 +08:00
Jin Mao
9ee6d06d50 docs: add deepWiki doc link (#6057) 2025-04-27 20:54:07 +08:00
ming4762
0cc1cb5a7b perf: improve destroyOnClose for VbenDrawer&VbenModal (#6051)
* fix: fix that the default value of modal destroyOnClose does not take effect

* perf: improve destroyOnClose for VbenDrawer
2025-04-27 11:26:50 +08:00
Netfan
0a9fc4e02d fix: title of search button in vxeTable toolbar (#6046)
* 修改vxeTable工具栏里的搜索按钮的提示文案
2025-04-26 01:08:41 +08:00
Netfan
be840460d8 feat: vbenSelect support prop allowClear (#6043) 2025-04-25 23:37:03 +08:00
Netfan
cb45987fe2 docs: update example (#6036)
* 跟进后端菜单逻辑的修改,现已无需传递basicLayout布局
2025-04-25 11:44:47 +08:00
panda7
5ffd7db8e0 fix: the initial value echo for the check-button-group (#6029)
Co-authored-by: sqchen <9110848@qq.com>
2025-04-25 08:35:03 +08:00
Netfan
14377705e7 fix: alert confirm state in beforeClose callback (#6019) 2025-04-23 12:20:52 +08:00
pangyajun123
b985ff0584 fix: vxe-table theme token follow primary color (#6007) 2025-04-21 19:15:05 +08:00
wyc001122
b148b8ec92 fix: fix geader menu activation path (#5997)
Co-authored-by: 王泳超 <wangyongchao@testor.com.cn>
2025-04-19 14:35:33 +08:00
Netfan
79de6bcbf7 fix: alert send wrong confirm state to beforeClose (#5991)
* 修复alert在按下Esc或者点击遮罩关闭时,可能发送错误的isConfirm状态
2025-04-17 22:23:05 +08:00
Netfan
14bd6dd25d fix: destroyOnClose works within connectedComponent (#5989)
* 修复destroyOnClose没能销毁connectedComponent自身的问题
2025-04-17 20:25:49 +08:00
PIPEDREA_WZJ
7f269e0d69 Update tailwindcss.md (#5602)
tailwindcss最新的版本已经是v4.x,vben中使用的是3.x的tailwindcss。在未进行兼容前,会出现运行失败的问题
2025-04-17 14:01:39 +08:00
yuh
4baec83db5 feat: add examples: form-upload (#5955)
* feat: add examples: form-upload

* fix: upload: accept and label

* fix: upload: 设置表单值、图片预览
2025-04-17 14:00:46 +08:00
Netfan
f7a4d13a4c fix: fixed arguments of callbacks in formApi (#5970)
* 修复 `handleValuesChange` 传递的参数不是处理后的表单值的问题

* 修复 `handleReset` 未能传递正确参数的问题
2025-04-16 14:11:04 +08:00
Netfan
0936861da1 feat: pass fieldsChanged into the handleValuesChange callback function (#5968)
* fieldsChanged(已被改变值的字段名)将传入handleValuesChange回调函数
2025-04-16 11:29:01 +08:00
ming4762
3318d76bab perf: improve destroyOnClose for VbenModal (#5964) 2025-04-16 11:28:36 +08:00
LinaBell
8f3881eabf perf: beforeClose of drawer support promise (#5932)
* perf: the beforeClose function of drawer is consistent with that of modal

* refactor: drawer test update
2025-04-16 11:27:13 +08:00
zhouda1fu
5252480b09 fix: missing await in department form(#5967) 2025-04-16 11:22:59 +08:00
Netfan
d18f56177c docs: update alert and apiComponent docs (#5961) 2025-04-15 20:52:23 +08:00
wyc001122
333998b518 fix: determine if scrollbar has been totally scrolled (#5934)
* 修复在系统屏幕缩放比例不为100%的情况下,滚动组件对是否已滚动到边界的判断可能不正确的问题
2025-04-15 20:51:38 +08:00
ming4762
3fb4fba1cb fix: modal closing animation (#5960) 2025-04-15 18:49:57 +08:00
ming4762
c7e6210c8d feat: modal&drawer support center-footer slot (#5956) 2025-04-15 16:04:44 +08:00
lztb
d864085c13 feat: vben-form添加arrayToStringFields属性 (#5957)
* feat: vben-form添加arrayToStringFields属性

* feat: 修改handleArrayToStringFields和handleStringToArrayFields中嵌套数组格式的处理不一致

---------

Co-authored-by: 米山 <17726957223@189.cn>
2025-04-15 16:03:20 +08:00
Netfan
fcdc1a1602 feat: add more expose methods for apiComponent (#5958)
* 为ApiComponent组件添加getOptions和getValue导出方法。
2025-04-15 15:32:30 +08:00
Netfan
bf7496f0d5 feat: add useAlertContext for Alert component (#5947)
* 新增Alert的子组件中获取弹窗上下文的能力
2025-04-15 00:00:05 +08:00
Netfan
9700150653 fix: table actions in fixed column (#5945) 2025-04-14 19:56:52 +08:00
Netfan
f0e9e55af2 feat: alert support customize footer (#5940)
* Alert组件支持自定义footer
2025-04-14 11:48:21 +08:00
Netfan
ff88274554 fix: long navigation menu can be scrolled (#5939)
* 修复超长的导航菜单无法纵向滚动的问题
2025-04-14 11:18:33 +08:00
ming4762
afce9dc5c0 perf: improve destroyOnClose for VbenModal (#5935)
* perf: 优化Vben Modal destroyOnClose,解决destroyOnClose=false,Modal依旧会被销毁的问题

影响范围(重要):destroyOnClose默认为true,这会导致所有的modal都会默认渲染到body
radix-vue Dialog组件默认会销毁挂载的组件,所以即使destroyOnClose=false,Modal依旧会被销毁的问题
对于一些大表单重复渲染导致卡顿,ApiComponent也会频繁的加载数据

* fix: modal closing animation

---------

Co-authored-by: Netfan <netfan@foxmail.com>
2025-04-13 23:02:07 +08:00
ming4762
b5700bd0b1 perf: improve autoSelect of ApiComponent (#5936)
* fix: 修复autoSelect不生效的问题,props.valueField已经被omit了

* feat: ApiComponent autoSelect支持使用函数,可以满足灵活性要求更高的场景
2025-04-13 20:03:18 +08:00
Netfan
a8c4786311 feat: api-component support autoSelect prop (#5931)
* feat: api-component support autoSelect prop

* docs: add version requirement
2025-04-12 14:02:35 +08:00
Netfan
2971ccc0b7 docs: docs modal z-index fixed, update alert docs (#5930) 2025-04-12 13:41:40 +08:00
Netfan
4a2c7b313f fix: alert animation (#5927) 2025-04-12 10:37:47 +08:00
Netfan
36bf6fc149 fix: builtin color change throttled in preference drawer (#5924)
修复偏好设置中的自定义主题色拖动选择颜色时页面会明显卡顿的问题
2025-04-12 01:44:08 +08:00
Netfan
f46ec30995 fix: theme mode follow the system only auto (#5923)
* 修复主题在未设置为auto时,仍然会跟随系统主题变化的问题。
2025-04-12 01:16:57 +08:00
Netfan
9bd5a190c2 fix: alert action button focus, fixed #5921 (#5922)
* 修复Alert组件的按钮焦点切换问题
2025-04-12 00:59:56 +08:00
zhang
86da3cedc2 chore: 导出框架自带的组件,方便独立页面使用 (#5876) 2025-04-09 16:16:56 +08:00
Netfan
329a176a5c perf: optimize bootstrap modules to speed up first-screen loading (#5899)
优化首屏加载速度
2025-04-09 01:05:20 +08:00
Netfan
9379093a4f feat: customizable table separator (#5898)
* 表格的分隔条支持定制背景色或完全移除
2025-04-08 20:28:50 +08:00
ming4762
c9014d0338 perf: 优化关闭页面切换动画的tab切换性能 (#5883) 2025-04-08 20:27:03 +08:00
Netfan
ed26dca64e chore: update pnpm-lock.yaml 2025-04-08 16:31:41 +08:00
Netfan
08c6496e24 chore: update deps 2025-04-08 14:56:40 +08:00
Netfan
a8c5df38e9 fix: possible circular reference issue during build (#5894)
* 修复构建期间出现的循环引用警告
2025-04-08 14:50:05 +08:00
Netfan
71e8d12b70 fix: improve prompt component (#5879)
* fix: prompt component render fixed

* fix: alert buttonAlign default value
2025-04-07 01:21:30 +08:00
Netfan
d216fdca44 feat: support logo text slot (#5872)
* 基础布局中的LOGO的文字区域允许通过插槽logo-text定制
2025-04-05 13:07:52 +08:00
wyc001122
384c5d7dbb fix: 布局为双列菜单或者水平模式下, 一级菜单高亮问题 (#5870)
Co-authored-by: 王泳超 <wangyongchao@testor.com.cn>
2025-04-05 11:04:59 +08:00
Netfan
b0ad08dbbc feat: use the not-found component instead of the invalid route component in the backend mode (#5871)
* 后端菜单模式下,使用not-found组件代替无效的路由组件
2025-04-04 15:21:09 +08:00
Rascal-Coder
3600603016 fix: vxeGrid height fixed #5861 (#5862) 2025-04-04 13:16:31 +08:00
superdl1996
cde1a85394 docs: typo (#5855) 2025-04-03 19:11:40 +08:00
Netfan
c623604ea9 docs: fix alert demo in docs 2025-04-02 20:37:41 +08:00
Netfan
7933da8f66 chore: update deps (#5854) 2025-04-02 18:07:06 +08:00
Netfan
ecf518bb02 fix: alert beforeClose callback arguments fixed (#5845) 2025-04-01 22:55:29 +08:00
ming4762
1d9f1be004 fix: 解决AccessModeType:backend登录过期,重新登录不会重新生成路由的问题,重现步骤分析: (#5830)
1、长时间未登录登录过期,再次打开页面构开始生成动态路由
2、fetchMenuListAsync后台返回401登录过期:doReAuthenticate函数跳转到登录页面
3、异常被拦截,return []
4、gurad.ts accessStore.setIsAccessChecked(true); 被错误的标识为已生成路由
5、重新登录后,accessStore.isAccessChecked=true未能正确的重新生成路由
2025-04-01 15:50:45 +08:00
Netfan
44138f578f feat: add preset alert, confirm, prompt components that can be simple called (#5843)
* feat: add preset alert, confirm, prompt components that can be simple called

* fix: type define
2025-04-01 15:10:18 +08:00
Joeshu
0e3abc2b53 docs: add third-party libraries to check update methods (#5819) 2025-03-31 19:28:28 +08:00
Arthur Darkstone
a96be3db98 docs: add explanation and related script configuration to distinguish build environment (#5826)
* docs: add explanation and related script configuration to distinguish build environment

* docs: fix spell error
2025-03-31 19:28:02 +08:00
Netfan
d6f239c564 docs: fix api-component demo link (#5828) 2025-03-31 12:08:45 +08:00
Netfan
166e9a0e82 chore: add demo for apiComponent with caching and concurrency (#5827)
* chore: add demo for apiComponent with caching and concurrency

* docs: update api component docs
2025-03-31 11:51:57 +08:00
Netfan
06ccad9db0 fix: vbenTree modelValue synchronization (#5825) 2025-03-31 10:18:35 +08:00
Jin Mao
18722ce434 feat: sidebar button config (#5818)
* feat: 新增 PreferenceCheckboxItem 组件

* feat(preferences): 添加侧边栏按钮配置功能

* feat: 新增按钮点击事件触发功能

* feat(SidebarPreferences): 新增侧边栏折叠按钮与固定按钮配置

* feat(ui): 新增侧边栏固定按钮及配置选项

* fix(test): 修正侧边栏配置项缺失问题
2025-03-31 10:17:42 +08:00
Netfan
a0feeb1966 fix: watermark settings in the preferences modified accidentally (#5823) 2025-03-31 09:06:02 +08:00
Jin Mao
df6341f0b8 feat(tabbar): 添加右键菜单过滤功能 (#5820) 2025-03-30 16:23:24 +08:00
anyup
dbc0b7e4a9 fix: route root.children duplicate problem (#5755)
Co-authored-by: anyup <anyupxing@163.com>
2025-03-29 22:38:30 +08:00
zhang
aa2907323f style: 更正引用格式 (#5784) 2025-03-29 22:29:16 +08:00
Netfan
96d2bc52e9 feat: pre-set serialization methods for request parameters (#5814)
添加快捷设置请求参数序列化方法的配置
2025-03-29 19:21:21 +08:00
Netfan
e91e4e0eea docs: fix form compact docs (#5811)
* docs: fix form `compact` docs

* docs: remove `compact` from FormCommonConfig
2025-03-28 16:15:35 +08:00
Netfan
c2b5f6497d fix: vben tree component warning (#5809) 2025-03-28 16:01:30 +08:00
Netfan
3c2d325d8c perf: improve api-component for using in form (#5796) 2025-03-27 15:51:11 +08:00
Netfan
a77bb8e68d perf: improve component packaging to enable instance method retrieval (#5795)
改进组件适配器里的包装函数,使得组件暴露的方法可以透传
2025-03-27 15:13:13 +08:00
Netfan
870cd86393 fix: auto check parent after node selected (#5794) 2025-03-27 14:22:05 +08:00
Netfan
0b650367f3 fix: popover background color in dark mode (#5783)
* 修复dark主题下的弹出层背景色在某些浏览器上表现为完全透明的问题
2025-03-25 21:17:55 +08:00
zhang
1616a06bfd perf: 优化多文件上传入参是数组的情况 (#5757)
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
2025-03-25 10:26:19 +08:00
Netfan
3f0d30897f fix: menu drawer can not be closed after a successful submitting (#5770) 2025-03-23 11:34:21 +08:00
Jin Mao
66c1d390b9 feat(ui): logo icon support click events (#5725)
* feat(ui): 扩展auth页面添加点击 Logo 的事件处理

在 `authentication.vue` 中新增 `clickLogo` 属性,允许在点击 Logo 时执行自定义操作。在 `auth.vue` 中实现了一个示例的点击事件处理函数,用于测试该功能。

* feat(layout): 添加点击 logo 的事件处理函数

在 BasicLayout 组件中添加了 clickLogo 事件处理函数,并通过 emit 方法触发 clickLogo 事件。同时,在 basic.vue 中实现了 handleClickLogo 函数,用于处理 logo 点击事件。

* fix(ui): 移除logo点击事件的控制台日志
2025-03-23 10:02:22 +08:00
Netfan
03ceb2aac5 fix: default value for nested fields (#5763) 2025-03-21 16:06:14 +08:00
Netfan
39888cebaa fix: base component focus color in form (#5760) 2025-03-21 09:41:42 +08:00
Netfan
efb69fc75f feat: add form-is-required class for required items. fixed: #5739 (#5759) 2025-03-21 09:25:38 +08:00
Jin Mao
711a179c69 chore: update codeowners (#5750) 2025-03-21 08:37:21 +08:00
Netfan
3133f8f8b9 fix: table form reset will trigger reload twice while submitOnChange set (#5756)
* 修复表格的搜索表单在重置时,可能会触发2次表格刷新的问题
2025-03-20 19:42:22 +08:00
anyup
f0a43912d1 fix: sort the menu so that it doesn't get replaced with 999 when order=0 (#5753)
Co-authored-by: anyup <anyupxing@163.com>
2025-03-20 19:41:46 +08:00
vben
b92ac5c36d chore: release 5.5.4 2025-03-18 21:43:27 +08:00
chen-d-yu
504070f3eb fix: vsh stylelint script fixed (#5729) 2025-03-17 09:23:27 +08:00
Netfan
feab6b3b30 fix: form item style adjustment (#5694) 2025-03-11 02:47:06 +08:00
Netfan
2d4ac33046 fix: miss default value in vbenLayout 2025-03-10 18:59:56 +08:00
Netfan
17e2a02281 feat: auto set component name for keep-alive (#5690)
* fix: auto set component name for keep-alive

* fix: type define
2025-03-10 16:25:30 +08:00
Netfan
096545c5a1 docs: update table slots docs 2025-03-10 10:53:17 +08:00
Netfan
04dff33ac5 feat: improved formApi for component instance support
* 改进表单API以支持组件实例的获取,以及焦点字段的获取
2025-03-10 02:56:44 +08:00
Netfan
cfa18c2b8e fix: improve component repackaging 2025-03-10 02:56:44 +08:00
Netfan
13354955db chore: update depts 2025-03-10 02:56:44 +08:00
ijackwu
bb683804f4 fix: live-server set port use [--port=PORT] (#5687) 2025-03-09 08:49:06 +08:00
Netfan
e2a577de24 feat: add size prop to avatar component and update logo component for size handling (#5684) 2025-03-08 11:37:02 +08:00
Netfan
89d963c81a fix: vxeTable search button not working with slot (#5678) 2025-03-07 22:35:09 +08:00
Netfan
b37ed48b9d feat: role management page with component tree (#5675)
* feat: add shadcn tree

* fix: update vbenTree component

* feat: role management demo page

* feat: add cellSwitch renderer for vxeTable

* chore: remove tree examples
2025-03-07 16:03:08 +08:00
Netfan
4b9cfcb867 fix: demo nested menu path (#5667)
* 修复演示的嵌套菜单path配置导致的面包屑跳转问题
2025-03-06 22:48:54 +08:00
Netfan
f86c9f90ad fix: keepAlive not working for popup appendToMain (#5666)
* 修复弹窗和抽屉 `appendToMain` 时且启用`keepAlive` 时未能正确缓存的问题
2025-03-06 22:22:45 +08:00
Netfan
31a6ab59fb feat: vben checkbox support indeterminate state and transition animation (#5662) 2025-03-06 16:11:02 +08:00
Netfan
34789645f7 fix: nitro server cookie maxAge fixed 2025-03-04 22:29:27 +08:00
Netfan
f380452ef0 feat: modal and drawer locking improve (#5648)
* feat: add `unlock` for modalApi

* fix: modal's close button style in locking

* fix: fix modal's close button disabled on locking

* feat: add `lock` and `unlock` for drawerApi
2025-03-04 22:00:32 +08:00
jasonz18
decd9c55e5 docs: typo 2025-03-04 21:40:34 +08:00
Netfan
e815f0ff89 docs: vbenVxeTable slots docs update 2025-03-01 22:16:36 +08:00
Netfan
5ea6b4a8d8 fix: logo style in login page is affected by the globally-imported antd styles
* 修复登录页左上角LOGO部分的文字在全局导入antd样式的时候位置不正确的问题
2025-02-28 22:39:42 +08:00
Netfan
a53ca3faf1 chore: update depts 2025-02-28 20:30:59 +08:00
Netfan
86fdd6c93b fix: drawer close icon placement default value 2025-02-28 14:54:11 +08:00
Netfan
0e0661fe02 fix: breadcrumb style is affected by the globally-imported antd styles (#5627)
* 修复全局引入Antd时,面包屑的样式会受到影响的问题
2025-02-27 22:28:59 +08:00
Netfan
86ce65e0ea fix: hideChildrenInMenu demo code (#5626) 2025-02-27 20:21:48 +08:00
Netfan
c3eb4fab13 docs: fix zod rules docs 2025-02-27 17:27:00 +08:00
jinmao88
7a476372e1 fix: useDrawer中closeIconPlacement设置无效 (#5624) 2025-02-27 14:34:42 +08:00
Netfan
5e421ce607 chore: demo page menu management (#5619)
* 添加菜单管理演示页面
2025-02-27 01:22:25 +08:00
Netfan
1d8676f456 chore: remove sleep in department list api 2025-02-25 22:15:27 +08:00
Netfan
0c3dd92592 fix: getPopupContainer will return closet form first (#5612) 2025-02-25 22:07:56 +08:00
Netfan
d33261d0c2 chore: demo page for system/department (#5611)
* feat: department management demo

* perf: department page improve

* feat: demo api middleware

* fix: add losing import
2025-02-25 19:47:45 +08:00
Netfan
7041c6a106 chore: output console error for invalid route component (#5593) 2025-02-24 16:03:52 +08:00
littlesparklet
12ffb310bf fix: Fix inconsistent spacing around search form (issue #5429) (#5495) 2025-02-24 15:57:50 +08:00
Netfan
d9799fec70 fix: search take no effect in icon-picker with antd (#5592) 2025-02-24 14:13:53 +08:00
Netfan
4570d5b54b feat: add VbenButtonGroup and VbenCheckButtonGroup with demo (#5591)
* 添加按钮组、选择按钮组以及相应的Demo
2025-02-24 13:50:50 +08:00
Netfan
d49e3e81a4 fix: loading and spinner style fixed and improved (#5588) 2025-02-23 15:30:17 +08:00
Netfan
579b1b486c feat: loading and spinner component with directive (#5587)
* 添加loading和spinner组件,以及对应的vue指令
2025-02-23 12:41:54 +08:00
Netfan
eba372062e feat: improve form demo (#5582) 2025-02-21 12:07:32 +08:00
Netfan
c9ccd2bbab fix: form label and control style (#5580)
* fix: form label and control style

* fix: empty label mark with required rules
2025-02-21 11:14:59 +08:00
handsomeFu
5aff8bac10 fix: CountTo component resolve separator prop not taking effect (#5578) 2025-02-21 11:06:18 +08:00
Netfan
1a12687027 fix: vben count to animator event name fixed (#5573) 2025-02-20 23:53:47 +08:00
Netfan
a221d2b491 fix: form item overflow fixed and layout improved (#5572)
* fix: form item overflow fixed and layout improved

* fix: basic form demo update

* feat: form label support render

* fix: form docs update
2025-02-20 23:05:08 +08:00
anyup
ccd99eb24d fix: solve the problem of inconsistent returns of formSchema custom field names when code login (#5563) 2025-02-20 09:09:32 +08:00
Netfan
c5c6760b5d chore: eslint rules update 2025-02-18 15:41:16 +08:00
Netfan
c07281bf41 fix: form item slot context fixed (#5552)
* 修复表单插槽
2025-02-17 21:37:05 +08:00
Netfan
24bad09c74 refactor: new CountTo component with demo (#5551) 2025-02-17 21:16:10 +08:00
Netfan
cddf71e600 fix: playground route missing 2025-02-17 17:57:15 +08:00
Netfan
9f82052c71 feat: demo of motion plugin (#5550)
添加Motion的用法例子
2025-02-17 15:25:45 +08:00
Netfan
e0eb57d38d fix: nitro server cors support with cookie (#5549)
* 修复nitro server在使用cookie时的跨域配置
2025-02-17 15:17:31 +08:00
Netfan
b6b97accb1 feat: add more event for jsonViewer (#5546)
* 为JsonViewer添加事件支持
2025-02-17 10:41:09 +08:00
Netfan
799934171a style: code style fixed 2025-02-16 23:32:06 +08:00
Netfan
10ebf03698 fix: auth api definition 2025-02-16 23:06:20 +08:00
Netfan
cd258fbb52 chore: update deps 2025-02-16 23:03:41 +08:00
Netfan
6cba181fad feat: new component jsonViewer (#5544)
* 添加新组件JsonViewer用于展示JSON结构数据
2025-02-16 22:57:00 +08:00
jinmao88
f9504cece3 chore: add qq group 5 (#5530) 2025-02-13 14:45:51 +08:00
Netfan
182f1c9da8 fix: userDropdown triggered unnecessary while overlay shown (#5520)
* 修复顶部的用户资料下拉在弹窗被打开时,仍然可以被触发的问题
2025-02-12 17:59:59 +08:00
Netfan
e7b009786b fix: width for ellipsisText tooltip in popover content (#5517)
* 修复省略文本用在气泡中时,提示弹出层的宽度计算有误的问题
2025-02-12 14:25:12 +08:00
Netfan
5262233312 feat: tabbar support max count limit (#5490)
* 标签栏支持限制打开的最大数量
2025-02-06 19:33:10 +08:00
Netfan
a9f9031f49 docs: update form docs (#5485) 2025-02-06 09:45:28 +08:00
Netfan
061fcf926d chore: update deps 2025-02-04 17:14:14 +08:00
Netfan
7e7a5f3fd4 chore: remove testing code 2025-02-04 15:07:10 +08:00
Netfan
f8bb396dc4 fix: ant tag icon default style (#5473) 2025-02-04 10:19:39 +08:00
jsxz
a832edce0d docs: update request and access docs (#5468)
* fix: Update server.md

* docs: update request and access docs
2025-02-03 16:29:34 +08:00
Netfan
67d1f299b3 fix: renderComponentContent lose slot props data (#5466)
* 修复FormItem传递插槽时丢失插槽props的问题
2025-01-26 22:33:16 +08:00
Netfan
cb7c0ecaa2 fix: menu data for backend mode fixed (#5465) 2025-01-26 20:37:37 +08:00
Netfan
e225159cce fix: request download and upload not support responseReturn (#5456)
* fix: request download and upload not support `responseReturn`

* docs: update

* fix: type of request client upload result
2025-01-22 00:59:10 +08:00
vben
195ceec9b4 chore: release 5.5.3 2025-01-21 22:07:55 +08:00
Netfan
5bd73867b6 feat: auto fetch icon list in iconPicker (#5446)
* feat: auto fetch icon list in iconPicker

* fix: add timeout controller for fetching

* feat: add pending controller

* fix: icon demo prefix
2025-01-21 13:09:42 +08:00
Netfan
22e6f28464 perf: easy to define fieldName of response data (#5442) 2025-01-20 18:38:49 +08:00
Netfan
5611f6c7f5 perf: request support to set how to return response (#5436)
* feat: request support to set how to return response

* docs: typo

* fix: test unit

* test: add request responseReturn test
2025-01-19 17:41:26 +08:00
Netfan
3f0f4d50a1 fix: antd button icon style (#5421) 2025-01-17 14:30:49 +08:00
Netfan
2d0859a727 fix: mouse events ignored on modal loading (#5409) 2025-01-16 12:17:08 +08:00
Netfan
509b268fba docs: update docs (#5408) 2025-01-16 11:30:03 +08:00
Netfan
c3129663eb fix: form update state error before form mounted (#5406) 2025-01-15 20:11:32 +08:00
Netfan
816d1f5a69 fix: demos route fixed (#5405) 2025-01-15 19:24:02 +08:00
Netfan
8cc903c0e1 feat: modal state locked on submitting (#5401)
* feat: modal state locked on submitting

* docs: 更新modal文档
2025-01-15 17:00:46 +08:00
Netfan
13087a10b7 refactor: fix popup component zIndex (#5397) 2025-01-15 12:32:03 +08:00
Netfan
27a3888e35 style: element plus loading style fixed (#5393)
* Element Plus的loading组件默认zIndex太高
2025-01-15 00:49:11 +08:00
Netfan
fb0ec05ff8 perf: improve fieldMappingTime to support format function (#5392) 2025-01-14 18:15:00 +08:00
Netfan
76c4aa2c55 fix: hide root route in breadcrumb 2025-01-14 17:51:39 +08:00
Netfan
e1c503e51e feat: support set default props for drawer and modal (#5390)
* feat: support set default props for drawer and modal

* docs: fix typo
2025-01-14 17:11:18 +08:00
Netfan
5965755caa fix: root router config fixed (#5389) 2025-01-14 15:15:02 +08:00
Netfan
1ad54561b0 feat: add noBasicLayout in route meta (#5386)
所有菜单数据无需配置component为BasicLayout,它们将会默认使用基础布局,也可以通过meta.noBasicLayout来阻止这一行为
2025-01-14 12:12:08 +08:00
Netfan
42e322012c fix: spinner may stop playing animation after dismiss (#5365)
* fix: spinner may stop playing animation after dismiss

* fix: animation paused more safely
2025-01-12 15:43:44 +08:00
Netfan
8cf6e8ec75 style: popover bgColor is too close to common (#5364)
修复Dark主题下,弹出层的背景色与主体背景色太过接近的问题
2025-01-12 14:48:05 +08:00
Netfan
79d4d2fb22 ci: retry deploy while faild 2025-01-12 11:58:07 +08:00
Netfan
b785bc5704 fix: useEcharts return invalid instance (#5360) 2025-01-12 09:54:37 +08:00
Netfan
6719e2679f feat: popup component support overlay blur effect (#5359) 2025-01-11 23:37:17 +08:00
Netfan
cb9c8db5ba feat: improve tippy demo (#5357) 2025-01-11 20:42:38 +08:00
Netfan
a2637313f8 feat: integrate new component Tippy with demo (#5355)
* 添加新的工具提示组件Tippy
2025-01-11 17:35:59 +08:00
Netfan
467689525f perf: add nested modal demo (#5353) 2025-01-11 12:12:50 +08:00
Netfan
1a04a05b79 perf: modal and drawer api support chain calls (#5351)
* perf: modal and drawer api support chain calls

* fix: typo
2025-01-11 10:56:54 +08:00
Netfan
b8bffd884c feat: allow close tab when mouse middle button click (#5347)
* 偏好设置增加鼠标中键关闭标签页的设置
2025-01-10 20:52:31 +08:00
Netfan
624beb6fa0 fix: locale switching logic correction (#5344)
* 修复语言切换后的数据更新逻辑

* 表单默认按钮的content属性可提供computed类型的值
2025-01-10 14:32:21 +08:00
Netfan
7606b86854 fix: vxeGrid init without search form (#5342)
* 修复vxeGrid在未使用表单的情况下,自动加载数据失效的问题。

* 暂时将vxeGrid版本锁定在4.10.0,新版本尺寸计算尚有问题
2025-01-10 11:53:06 +08:00
Netfan
e10cbe23b9 chore: update deps 2025-01-10 09:22:32 +08:00
Netfan
d34838bdd8 fix: primaryColor calculation (#5337) 2025-01-10 01:51:38 +08:00
Netfan
c979c23e6b fix: form valid-error style in naive (#5336) 2025-01-10 01:15:30 +08:00
Netfan
516d0b8dc8 fix: form fieldMappingTime improve and modelPropName support (#5335)
* 表单的fieldMappingTime支持将格式化掩码设为null以便原值映射,这样可以支持非日期时间类型的组件;
* 表单增加modelPropName设置组件的双向绑定属性名,用于支持未提前注册的双向绑定属性为非默认名称的组件。
* 增加一些经常会有人提到的组合字段演示,
2025-01-09 22:49:28 +08:00
Netfan
99c7fd72f8 fix: code lint 2025-01-09 13:04:14 +08:00
Netfan
2828e7a7b6 fix: form fieldMappingTime is not working (#5333)
* fix: form option `fieldMappingTime` is not working

* fix: form merge support `fieldMappingTime`
2025-01-09 12:28:33 +08:00
王文庭
16162c01ed fix: download from url triggered twice sometimes (#5319)
解决Chrome、Safari通过路径一次下载两个文件的BUG
2025-01-08 16:01:23 +08:00
clddup
bbbdbfa912 feat: useEcharts exports echarts instance#5294 (#5299) 2025-01-05 15:54:13 +08:00
John
06cccc53fa chore: update quick-start.md (#5303)
change COREPACK_REGISTRY to COREPACK_NPM_REGISTRY
2025-01-05 15:52:27 +08:00
Netfan
801c640724 fix: vben select placeholder color (#5286) 2025-01-02 10:19:20 +08:00
Vben
081d2aed23 perf: format code with better style (#5283) 2025-01-01 11:39:49 +08:00
Netfan
4d81b9d18d fix: sidebar preferences fixed (#5276) 2024-12-31 12:36:45 +08:00
Netfan
e9dc613548 fix: breadcrumb setting not valid for header-sidebar-nav layout (#5275) 2024-12-31 12:00:50 +08:00
Netfan
3af22f7e91 fix: header logo may not be collapsed in header-sidebar-nav layout (#5274) 2024-12-31 11:35:58 +08:00
Netfan
2135cb8ece feat: new layout sidebar nav with full header (#5270) 2024-12-31 00:30:15 +08:00
Netfan
376aad5d26 feat: drawer close icon placement (#5269) 2024-12-30 23:30:53 +08:00
Netfan
27ba45aa75 docs: update dialog and drawer docs 2024-12-30 22:21:01 +08:00
Netfan
de17007788 feat: drawer support destroy on close 2024-12-30 22:21:01 +08:00
Netfan
e86a7906fe feat: drawer support onOpened & onClosed 2024-12-30 22:21:01 +08:00
Netfan
4a8e6abc06 feat: modal support destroy on close 2024-12-30 22:21:01 +08:00
Netfan
2eb7fed9f4 fix: header-mixed layout side-menu active (#5265)
* fix: header-mixed layout side-menu active

* fix: config test
2024-12-30 15:24:01 +08:00
Netfan
ff8d5ca351 feat: header mixed layout (#5263)
* feat: new layout header-mixed

* fix: header-mixed layout update

* feat: layout preference update

* fix: extra menus follow layout setting
2024-12-30 14:01:17 +08:00
vben
07c4ad05f4 chore: release 5.5.2 2024-12-28 22:15:00 +08:00
Netfan
548c2e5500 chore: downgrade vue-tsc version 2024-12-28 17:53:39 +08:00
Netfan
ec2c6eff6f feat: header menu align support (#5256)
* feat: header menu align support

* fix: typo
2024-12-28 16:16:48 +08:00
Netfan
15fe82c62f chore: update deps 2024-12-28 16:15:39 +08:00
Netfan
cb5ecf4a8a chore: add apiSelect remote search demo (#5246) 2024-12-26 19:23:59 +08:00
1973 changed files with 152972 additions and 25166 deletions

View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@@ -0,0 +1,7 @@
---
'@vben/styles': patch
'@vben-core/form-ui': patch
'@vben/web-naive': patch
---
feat(@core/form-ui): 新增 useVbenForm 数组编辑器 VbenFormFieldArray

View File

@@ -1,10 +0,0 @@
---
description:
alwaysApply: true
---
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
2. 注意不要生成太多的空行上一部分代码和下一部分代码中间的空行不要大于2行
3. 有封装好的方法、组件需要复用,不要重复造轮子
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面记得使用v-if而不是v-show
5. 数据库的created_at、updated_at、deleted_at统一使用时间戳不要使用字符串并且我在查询器中一级格式化成字符串了无需再次格式化

View File

@@ -10,6 +10,7 @@ alwaysApply: true
3. 有封装好的方法、组件需要复用,不要重复造轮子
4. 小程序端的抽屉全部需要用 page-container 来防止用户意外退出页面(记得使用 v-if 而不是 v-show
5. 数据库的created_at、updated_at、deleted_at统一使用时间戳不要使用字符串并且我在查询器中一级格式化成字符串了无需再次格式化
6. 卡片强调样式:**禁止**用左侧竖色条(`border-left: Npx solid 色值` / 左侧色块做卡片强调或分类标识统一用「1px 同色系边框 + 微光」——`border: 1px solid hsl(var(--主题色) / 30%)` 配 `box-shadow: 0 0 6px hsl(var(--主题色) / 18%)`;历史页面暂不强制回改,新增/改动的卡片必须遵守
# 全局架构规范
@@ -107,6 +108,37 @@ alwaysApply: true
- 上一部分代码和下一部分代码中间空行不超过 2 行
## VIP 功能判断PC 管理端强制)
- **判断方法**:统一用 `#/utils/vip` 的 `hasVipPermission` / `useVipPermission` / `VIP_FEATURE` 常量
- **显隐组件**:模板包一层 `<VipGate code="medical_record" :vip="receptionVip">``#/components/vip/VipGate.vue`
- 接诊/复诊/开方:**必须传履约诊所 vip**`get-current-store-type` 的 `vip`),禁止只靠登录态判断门店权益
- 显式传 `null` 表示「尚无履约诊所权益」,**不会**回落登录态;不传第二参才用 `userInfo.vip`
- 禁止在业务页再手写 `permissions.indexOf('medical_record')` 或复制一套 VIP 拉取逻辑
## 暗色模式适配PC 管理端强制)
后台支持亮/暗主题切换,**新增或改样式时必须同时适配暗色**,禁止只按白底写死颜色。
### 优先用法(推荐)
- 颜色、边框、背景一律用主题 CSS 变量,例如:
- 文字:`hsl(var(--foreground))` / `hsl(var(--muted-foreground))`
- 背景:`hsl(var(--background))` / `hsl(var(--card, var(--background)))` / `hsl(var(--muted) / 0.25)`
- 边框:`hsl(var(--border))`
- 强调/选中:`hsl(var(--primary))`、`hsl(var(--primary) / 0.1)`
- 警告:`hsl(var(--warning))`、`hsl(var(--warning) / 0.12)`(参考 `AiDisclaimerBanner.vue`
- **禁止**业务样式写死 `#fff`、`#000`、`#0f172a`、`#f8fafc`、`#64748b` 等仅适合亮色的色值
- **禁止**用 `:global(.dark)` 改全局变量以免污染整站配色
- 优先「一套变量样式自动跟主题」,避免再写一套 `.dark .xxx { ... }`;仅当变量无法表达(如图片反色、特殊阴影)才用局部 `.dark .xxx`(且必须 `scoped`
### 自检清单
- 弹窗 / 抽屉 / 卡片 / 预览对照区:在暗色下背景、文字、边框对比度可读
- 选中态、成功/导入高亮:用 `--primary` 透明度,不要写死浅绿底 `#f0faf8`
- 表格空态、分割线、次要文案:用 `--muted` / `--muted-foreground` / `--border`
- 改完后切换暗色主题肉眼过一遍,不要只测亮色
## Vben 组件使用规范
### VbenModal来自 @vben/common-ui
@@ -145,6 +177,7 @@ alwaysApply: true
- 表单必须用 `useVbenForm`,禁止直接 `<form>` 或 antd `Form`
- 动态修改 schema 必须用 `formApi.updateSchema([...])`**vben form 没有 `setComponentProps` 这个方法**(这是已踩过的坑)
- 重置表单用 `formApi.resetForm()`**严禁 `formApi.resetFields()`**(那是 antd Form 的 APIVben Form 没有,会报 `resetFields is not a function`;已踩过坑)
- 取值/赋值用 `formApi.getValues()` / `formApi.setValues(obj)`,禁止自己 `v-model` 收集
- 校验用 `formApi.validate().then(e => { if (e.valid) {...} })` 或 `formApi.validateAndSubmitForm()`
- 必填规则用字符串 `'required'` / `'selectRequired'`,规则在 `#/adapter/form.ts` 的 `defineRules` 注册
@@ -152,82 +185,3 @@ alwaysApply: true
- 密码/敏感字段用 `VbenInputPassword` 组件,禁止用 `VbenInput` 配 `type="password"`
- 单选/多选选项来自接口时,必须在弹窗/页面 `onMounted` 或 `onOpenChange` 里拉取后通过 `updateSchema` 注入,禁止写死常量
# 专项模块规范
## OA 通知模块前端规则
### 组件与封装复用
- 所有 OA 配置入口(系统配置 tab、CRUD 页面、员工 OA 账号弹窗)必须复用 `Tabs.TabPane`、`useVbenVxeGrid`、`useVbenModal`、`useVbenForm`,禁止用原生 antd 表格/表单
### 平台动态化(禁止写死)
- **平台列表必须从接口 `oa-platform/list` 动态拉取**,禁止在前端写死 `['work_wechat', 'dingtalk', 'feishu']` 常量数组
- 系统配置 tab 中的平台开关行、机器人表单中的平台下拉、场景表单中的机器人分组,都要走接口动态渲染
- 平台编码统一为字符串 snake_case如 `work_wechat`、`dingtalk`、`feishu`),与后端 `xk_oa_platform.platform_code` 字段对齐
### 系统配置 tab 交互
- 系统配置 tab 中的「总开关关闭」必须禁用下方所有平台子开关(视觉上置灰),禁止总开关关闭时仍能切换子开关
- 平台子开关切换必须立即调 `oa-platform/update-enabled` 接口,**禁止依赖保存按钮批量提交**(避免总开关与子开关状态不一致)
- 系统配置页的总开关走现有 `getSystemConfigList` / `saveSystemConfig` 接口config_key = `oa_notify_enabled`**禁止新建专用接口**
### 表单字段约定
- @ 人配置必须按平台分组(用 Tabs 或 Collapse 分组),员工必须从 `xk_admin` 表选择(复用员工 picker 组件)
- OA 机器人表单中的 webhook、secret 字段必须用密码型输入框(`InputPassword`),禁止明文展示
- OA 场景表单中的机器人多选必须按平台分组展示(用 `Tabs` 或 `Collapse` 分组)
### 消息类型全量支持(数据库驱动 schema
- 场景弹窗每个平台 Tab 内的消息类型选项**必须**通过 `GET /oa-scene/message-types` 接口动态拉取,
按 `platform_code` 过滤当前平台支持的所有 `message_type`
- 每个消息类型对应的 payload 表单**必须**按后端返回的 `payload_schema`JSON 字符串)动态渲染,
禁止前端写死 schema数据库可启停类型前端硬编码会不同步
- payload 字段渲染规则(与后端 `payload_schema` 约定):
- `Textarea`:多行文本
- `Input` / `InputNumber`:单行文本/数字
- `RadioGroup` / `Select`:选项组
- `GalleryPickLink`:素材库选择器,透传 `props.acceptTypes: number[]` 过滤文件类型
- `TemplateCardEditor`:企微模板卡片专用编辑器(用 `views/system/oa-scene/components/template-card-form.vue`
- 提交结构:`message_config: { platform_code: { message_type, payload } }`
### image-gallery-picker 文件类型过滤
- `image-gallery-picker.vue` 和 `gallery-pick-link.vue` 都支持 `acceptTypes?: number[]` prop
- 取值对应 `xk_file_type.value`1=图片、2=视频、3=音频、6=文件 等
- 不传 `acceptTypes` 时显示全部类型(默认行为,保持向后兼容)
- 传数组时只展示匹配类型 tab且自动锁定到第一个匹配类型
- OA 场景中图片字段必须传 `[1]`、语音字段传 `[3]`、文件字段传 `[6]`,避免用户选错类型
### 通用拖拽可视化编辑器(`components/oa-template-card-editor/`
- 任何「左侧组件库 → 右侧画布拖拽组装 → 自动生成 JSON」的需求**必须复用此通用组件**,禁止重复造轮子
- schema 驱动:新增块类型只需扩展 `BlockSchema[]` 配置,禁止改组件内部
- SortableJS 用法参照 `views/system/role/components/quick-nav-transfer.vue`(动态 import `sortablejs/modular/sortable.complete.esm.js`
- 双向绑定走 `v-model``modelValue` + `update:modelValue`),禁止内部直接修改 props
- OA 场景中的企微模板卡片必须用 `views/system/oa-scene/components/template-card-form.vue`(在通用编辑器之上做 OA 封装)
### 测试发送(全类型循环)
- OA 机器人 modal 的「测试发送」必须循环跑该平台支持的所有消息类型(单平台最多 8 种)
- 后端返回 `results` 数组,前端必须遍历分类型展示成功/失败(不要只展示第一条)
- 测试按钮 loading 用独立状态(不要复用 modal 的 `confirmLoading`
- 测试发送结果以列表形式展示在表单下方,每种类型显示状态/耗时/错误信息
- 测试发送支持两套入口(共用 `testSendOaRobot` API
- **表单弹窗内测试**modal.vue用于「保存前验证密钥」直接传 webhook_url/secret不带 id
- **行级测试**(独立组件 `test-send-modal.vue`):用于列表中已有机器人,仅传 `id`,后端自动取已加密密钥
- 行级测试弹窗入参:`test_content` + `test_at_all` 开关 + `test_mobiles` 手机号多选
- 飞书 webhook 不支持手机号 @,前端必须在飞书平台下隐藏手机号字段并提示用户(仅 @all 有效)
### 群聊绑定(按平台过滤的 N:N 多选)
- 「OA群聊管理」是独立菜单pid=232 OA 通知父级下,菜单 id=233路由 `/system/oa-chat`
- 群聊管理页结构必须与 `oa-robot` 一致:`index.vue + api/index.ts + config/{table,search,form}.ts + components/modal.vue`
- 机器人表单中的「绑定群聊」字段必须用 `Checkbox.Group`
- 群聊列表通过 `getOaChatListByPlatform()` 一次性拉取全量(按平台分组的对象)
- 前端按当前 `platform_code` 过滤展示(平台切换时自动过滤掉不属于当前平台的勾选)
- 平台切换通过 `componentProps.onChange` 监听vben form 字段值变化无现成 watch必须通过 schema 注入 onChange 回调)
- 编辑场景下,机器人详情接口返回 `chat_ids: number[]`,前端 `selectedChatIds` 直接回显
- 提交时把 `chat_ids` 数组合并到 payload后端 `OaRobotController::notRequest` 已包含该字段)
- 列表展示用后端拼接好的 `chat_names` 字符串(按「、」拼接),详情接口才返回 `chat_ids` 数组

16
.gitattributes vendored
View File

@@ -4,8 +4,18 @@
* text=auto eol=lf
# Declare files that will always have CRLF line endings on checkout.
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.cmd text eol=crlf
*.[cC][mM][dD] text eol=crlf
*.bat text eol=crlf
*.[bB][aA][tT] text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
*.ico binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.svg binary
*.woff binary
*.woff2 binary

18
.github/CODEOWNERS vendored
View File

@@ -1,14 +1,14 @@
# default onwer
* anncwb@126.com vince292007@gmail.com netfan@foxmail.com
* anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
# vben core onwer
/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
# vben team onwer
apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com
docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com

View File

@@ -6,10 +6,10 @@ runs:
using: 'composite'
steps:
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .node-version
cache: 'pnpm'

3
.github/config.yml vendored
View File

@@ -21,12 +21,10 @@ newPRWelcomeComment: |
firstPRMergeComment: >
Thanks for your contribution! 🎉🎉🎉
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Thanks for opening your first issue! Be sure to follow the issue template and provide every bit of information to help the developers!
# *OPTIONAL* default titles to check against for lack of descriptiveness
# MUST BE ALL LOWERCASE
requestInfoDefaultTitles:
@@ -36,4 +34,3 @@ requestInfoDefaultTitles:
# *Required* Comment to reply with
requestInfoReplyComment: >
Thanks for filing this issue/PR! It would be much appreciated if you could provide us with more information so we can effectively analyze the situation in context.

View File

@@ -19,11 +19,9 @@ Project maintainers have the right and responsibility to remove, edit, or reject
- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
- If adding a new feature:
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing bug:
- Provide a detailed description of the bug in the PR. Live demo preferred.
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.

View File

@@ -30,7 +30,7 @@ jobs:
- windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0

View File

@@ -28,12 +28,12 @@ jobs:
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
run_install: false
@@ -67,7 +67,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -90,7 +90,7 @@ jobs:
- windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0

View File

@@ -57,11 +57,11 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -89,6 +89,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: '/language:${{matrix.language}}'

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -30,7 +30,7 @@ jobs:
run: pnpm build:play
- name: Sync Playground files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
uses: SamKirkland/FTP-Deploy-Action@v4.4.0
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }}
@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -54,7 +54,7 @@ jobs:
run: pnpm build:docs
- name: Sync Docs files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
uses: SamKirkland/FTP-Deploy-Action@v4.4.0
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
@@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -85,7 +85,7 @@ jobs:
run: pnpm run build:antd
- name: Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
uses: SamKirkland/FTP-Deploy-Action@v4.4.0
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}
@@ -98,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -116,7 +116,7 @@ jobs:
run: pnpm run build:ele
- name: Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
uses: SamKirkland/FTP-Deploy-Action@v4.4.0
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}
@@ -129,7 +129,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -147,9 +147,26 @@ jobs:
run: pnpm run build:naive
- name: Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
uses: SamKirkland/FTP-Deploy-Action@v4.4.0
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
local-dir: ./apps/web-naive/dist/
rerun-on-failure:
name: Rerun on failure
needs:
- deploy-playground-ftp
- deploy-docs-ftp
- deploy-antd-ftp
- deploy-ele-ftp
- deploy-naive-ftp
if: failure() && fromJSON(github.run_attempt) < 10
runs-on: ubuntu-latest
steps:
- name: Retry ${{ fromJSON(github.run_attempt) }} of 10
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: gh workflow run rerun.yml -F run_id=${{ github.run_id }}

View File

@@ -20,6 +20,6 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
- uses: release-drafter/release-drafter@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -19,7 +19,7 @@ jobs:
steps:
# 关闭未活动的 Issues
- name: Close Inactive Issues
uses: actions/stale@v9
uses: actions/stale@v11
with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: needs-reproduction # Label that flags an issue as stale.

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- name: remove enhancement pending
if: github.event.label.name == 'enhancement'
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper-backup@d65454423c6fbbd20026b9b499d403f79422ac69
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
@@ -27,7 +27,7 @@ jobs:
- name: remove bug pending
if: github.event.label.name == 'bug'
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper-backup@d65454423c6fbbd20026b9b499d403f79422ac69
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
@@ -36,7 +36,7 @@ jobs:
- name: needs reproduction
if: github.event.label.name == 'needs reproduction'
uses: actions-cool/issues-helper@v3
uses: actions-cool/issues-helper-backup@d65454423c6fbbd20026b9b499d403f79422ac69
with:
actions: 'create-comment, remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -14,7 +14,7 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
- uses: dessant/lock-threads@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-inactive-days: '14'

View File

@@ -3,78 +3,48 @@ name: Create Release Tag
on:
push:
tags:
- 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10
env:
HUSKY: '0'
- 'v*.*.*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to create (e.g. v1.2.3)'
required: true
type: string
permissions:
pull-requests: write
contents: write
jobs:
build:
release:
name: Create Release
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
# - name: Checkout code
# uses: actions/checkout@v4
# with:
# fetch-depth: 0
# - name: Install pnpm
# uses: pnpm/action-setup@v4
# - name: Use Node.js ${{ matrix.node-version }}
# uses: actions/setup-node@v4
# with:
# node-version: ${{ matrix.node-version }}
# cache: "pnpm"
# - name: Install dependencies
# run: pnpm install --frozen-lockfile
# - name: Test and Build
# run: |
# pnpm run test
# pnpm run build
- name: version
- name: Extract version
id: version
run: |
tag=${GITHUB_REF/refs\/tags\//}
version=${tag#v}
major=${version%%.*}
echo "tag=${tag}" >> $GITHUB_OUTPUT
echo "version=${version}" >> $GITHUB_OUTPUT
echo "major=${major}" >> $GITHUB_OUTPUT
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
raw_tag="${{ inputs.tag }}"
else
raw_tag="${GITHUB_REF_NAME}"
fi
# Normalize: ensure v prefix
tag="${raw_tag}"
[[ "${tag:0:1}" != "v" ]] && tag="v${tag}"
version="${tag#v}"
major="${version%%.*}"
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
echo "version=${version}" >> "${GITHUB_OUTPUT}"
echo "major=${major}" >> "${GITHUB_OUTPUT}"
- uses: release-drafter/release-drafter@v6
- uses: release-drafter/release-drafter@v7
with:
version: ${{ steps.version.outputs.version }}
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: force update major tag
# run: |
# git tag v${{ steps.version.outputs.major }} ${{ steps.version.outputs.tag }} -f
# git push origin refs/tags/v${{ steps.version.outputs.major }} -f
# - name: Create Release for Tag
# id: release_tag
# uses: ncipollo/release-action@v1
# with:
# token: ${{ secrets.GITHUB_TOKEN }}
# generateReleaseNotes: "true"
# body: |
# > Please refer to [CHANGELOG.md](https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md) for details.

19
.github/workflows/rerun.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Rerun workflow
on:
workflow_dispatch:
inputs:
run_id:
description: The workflow id to relanch
required: true
jobs:
rerun:
runs-on: ubuntu-latest
steps:
- name: rerun ${{ inputs.run_id }}
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
gh run watch ${{ inputs.run_id }} > /dev/null 2>&1
gh run rerun ${{ inputs.run_id }} --failed

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v5
uses: amannn/action-semantic-pull-request@v6
with:
wip: true
subjectPattern: ^(?![A-Z]).+$

View File

@@ -9,7 +9,7 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v11
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'

14
.gitignore vendored
View File

@@ -22,7 +22,8 @@ yarn.lock
package-lock.json
.VSCodeCounter
**/backend-mock/data
.omx
.pnpm-store
# local env files
.env.local
.env.*.local
@@ -51,3 +52,14 @@ vite.config.ts.*
.history
/.mimocode/
/.cursor/skills/
.cursor
# AI
.agent
.agents
.claude
.codex
skills-lock.json
.atomcode
datalog
.playwright-mcp

View File

@@ -2,5 +2,5 @@ ports:
- port: 5555
onOpen: open-preview
tasks:
- init: corepack enable && pnpm install
- init: npm i -g corepack && pnpm install
command: pnpm run dev:play

View File

@@ -1,6 +0,0 @@
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.

View File

@@ -1,3 +0,0 @@
# 每次 git pull 之后, 安装依赖
pnpm install

View File

@@ -1,20 +0,0 @@
export default {
'*.{js,jsx,ts,tsx}': [
'prettier --cache --ignore-unknown --write',
'eslint --cache --fix',
],
'*.{scss,less,styl,html,vue,css}': [
'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,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write--parser json',
],
'package.json': ['prettier --cache --write'],
};

View File

@@ -1 +1 @@
20.14.0
24.16.0

17
.npmrc
View File

@@ -1,13 +1,4 @@
registry = "https://registry.npmmirror.com"
public-hoist-pattern[]=husky
public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss
public-hoist-pattern[]=stylelint
public-hoist-pattern[]=*postcss*
public-hoist-pattern[]=@commitlint/*
public-hoist-pattern[]=czg
strict-peer-dependencies=false
auto-install-peers=true
dedupe-peer-dependents=true
registry=https://registry.npmmirror.com
# Electron 二进制与 electron-builder 打包工具链的国内镜像(不配置在国内网络下大概率下载超时失败)
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

View File

@@ -1,18 +0,0 @@
dist
dev-dist
.local
.output.js
node_modules
.nvmrc
coverage
CODEOWNERS
.nitro
.output
**/*.svg
**/*.sh
public
.npmrc
*-lock.yaml

View File

@@ -1 +0,0 @@
export { default } from '@vben/prettier-config';

View File

@@ -2,3 +2,7 @@ dist
public
__tests__
coverage
.codex
.claude
.agent
.agents

View File

@@ -0,0 +1,32 @@
import { eventHandler, setHeader } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const data = `
{
"code": 0,
"message": "success",
"data": [
{
"id": 123456789012345678901234567890123456789012345678901234567890,
"name": "John Doe",
"age": 30,
"email": "john-doe@demo.com"
},
{
"id": 987654321098765432109876543210987654321098765432109876543210,
"name": "Jane Smith",
"age": 25,
"email": "jane@demo.com"
}
]
}
`;
setHeader(event, 'Content-Type', 'application/json');
return data;
});

View File

@@ -0,0 +1,16 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(600);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,16 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(1000);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,16 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(2000);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,62 @@
import { faker } from '@faker-js/faker';
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
pid: 0,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
),
remark: faker.lorem.sentence(),
};
if (faker.datatype.boolean()) {
dataItem.children = Array.from(
{ length: faker.number.int({ min: 1, max: 5 }) },
() => ({
id: faker.string.uuid(),
pid: dataItem.id,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
),
remark: faker.lorem.sentence(),
}),
);
}
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(10);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const listData = structuredClone(mockData);
return useResponseSuccess(listData);
});

View File

@@ -0,0 +1,13 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(MOCK_MENU_LIST);
});

View File

@@ -0,0 +1,29 @@
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const namesMap: Record<string, any> = {};
function getNames(menus: any[]) {
menus.forEach((menu) => {
namesMap[menu.name] = String(menu.id);
if (menu.children) {
getNames(menu.children);
}
});
}
getNames(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, name } = getQuery(event);
return (name as string) in namesMap &&
(!id || namesMap[name as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});

View File

@@ -0,0 +1,29 @@
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const pathMap: Record<string, any> = { '/': 0 };
function getPaths(menus: any[]) {
menus.forEach((menu) => {
pathMap[menu.path] = String(menu.id);
if (menu.children) {
getPaths(menu.children);
}
});
}
getPaths(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, path } = getQuery(event);
return (path as string) in pathMap &&
(!id || pathMap[path as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});

View File

@@ -0,0 +1,84 @@
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
const menuIds = getMenuIds(MOCK_MENU_LIST);
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
name: faker.commerce.product(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
),
permissions: faker.helpers.arrayElements(menuIds),
remark: faker.lorem.sentence(),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const {
page = 1,
pageSize = 20,
name,
id,
remark,
startTime,
endTime,
status,
} = getQuery(event);
let listData = structuredClone(mockData);
if (name) {
listData = listData.filter((item) =>
item.name.toLowerCase().includes(String(name).toLowerCase()),
);
}
if (id) {
listData = listData.filter((item) =>
item.id.toLowerCase().includes(String(id).toLowerCase()),
);
}
if (remark) {
listData = listData.filter((item) =>
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
);
}
if (startTime) {
listData = listData.filter((item) => item.createTime >= startTime);
}
if (endTime) {
listData = listData.filter((item) => item.createTime <= endTime);
}
if (['0', '1'].includes(status as string)) {
listData = listData.filter((item) => item.status === Number(status));
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});

View File

@@ -0,0 +1,85 @@
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
name: faker.commerce.product(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
),
deptId: faker.string.uuid(),
remark: faker.lorem.sentence(),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const {
page = 1,
pageSize = 20,
name,
id,
remark,
startTime,
endTime,
deptId,
status,
} = getQuery(event);
let listData = structuredClone(mockData);
if (name) {
listData = listData.filter((item) =>
item.name.toLowerCase().includes(String(name).toLowerCase()),
);
}
if (id) {
listData = listData.filter((item) =>
item.id.toLowerCase().includes(String(id).toLowerCase()),
);
}
if (remark) {
listData = listData.filter((item) =>
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
);
}
if (startTime) {
listData = listData.filter((item) => item.createTime >= startTime);
}
if (endTime) {
listData = listData.filter((item) => item.createTime <= endTime);
}
if (['0', '1'].includes(status as string)) {
listData = listData.filter((item) => item.status === Number(status));
}
if (deptId) {
listData = listData.filter((item) => item.deptId === deptId);
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});

View File

@@ -0,0 +1,12 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
import { getTimezone } from '~/utils/timezone-utils';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(getTimezone());
});

View File

@@ -0,0 +1,11 @@
import { eventHandler } from 'h3';
import { TIME_ZONE_OPTIONS } from '~/utils/mock-data';
import { useResponseSuccess } from '~/utils/response';
export default eventHandler(() => {
const data = TIME_ZONE_OPTIONS.map((o) => ({
label: `${o.timezone} (GMT${o.offset >= 0 ? `+${o.offset}` : o.offset})`,
value: o.timezone,
}));
return useResponseSuccess(data);
});

View File

@@ -0,0 +1,26 @@
import { eventHandler, readBody, setResponseStatus } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { TIME_ZONE_OPTIONS } from '~/utils/mock-data';
import {
unAuthorizedResponse,
useResponseError,
useResponseSuccess,
} from '~/utils/response';
import { setTimezone } from '~/utils/timezone-utils';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const body = await readBody<{ timezone?: unknown }>(event);
const timezone =
typeof body?.timezone === 'string' ? body.timezone : undefined;
const allowed = TIME_ZONE_OPTIONS.some((o) => o.timezone === timezone);
if (!timezone || !allowed) {
setResponseStatus(event, 400);
return useResponseError('Bad Request', 'Invalid timezone');
}
setTimezone(timezone);
return useResponseSuccess({});
});

View File

@@ -0,0 +1,14 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess({
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
});
// return useResponseError("test")
});

View File

@@ -0,0 +1,9 @@
let mockTimeZone: null | string = null;
export const setTimezone = (timeZone: string) => {
mockTimeZone = timeZone;
};
export const getTimezone = () => {
return mockTimeZone;
};

2
apps/desktop/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
out/
release/

46
apps/desktop/README.md Normal file
View File

@@ -0,0 +1,46 @@
# @vben/desktop — 萧康云医 PC 桌面端
Electron 壳工程:打包加载 `apps/web-antd` 的生产构建产物,提供桌面通知、开机自启、系统托盘、未读角标、静默打印、全局快捷键与自动更新能力。
## 常用命令(在仓库根目录执行)
```bash
# 开发:并行启动 web-antd dev server:5666与 Electron 壳(壳会自动等待/重试连接 dev server
pnpm dev:desktop
# 打包turbo 先构建 web-antd dist再构建主进程并出安装包产物在 apps/desktop/release/
pnpm build:desktop
```
- Windows 机器出 NSIS 安装包mac 包必须在 macOS 机器上执行同一命令electron-builder 不支持交叉编译 mac 包)。
- 不需要打桌面包的同事 / CI可设置环境变量 `ELECTRON_SKIP_BINARY_DOWNLOAD=1` 跳过 Electron 二进制下载,不影响 web 开发。
## 环境切换dev 走本地,打包走生产)
| 模式 | 页面来源 | API 走向 |
|---|---|---|
| `pnpm dev:desktop` | `http://localhost:5666`web-antd vite dev server | vite 代理转发到**本地后端**`/api/*``localhost:18001``/im-api/*``localhost:12080`(见 `apps/web-antd/vite.config.ts` |
| 打包后的安装包 | `app://local/`(内置 web-antd 生产 dist | 主进程协议处理器转发到**生产上游**`/api/admin/*``api.xiaokang88.com``/im-api/*``api.ws.g.xiaokang88.com/api`(见 `src/main/config.ts`,可用环境变量覆盖) |
即:开发调试自动对接本地后端(与网页开发完全一致),只有 `pnpm build:desktop` 出的安装包才请求生产环境,无需手动切换。
## 环境变量
| 变量 | 用途 |
|---|---|
| `XK_DESKTOP_API_UPSTREAM` | 覆盖 `/api/admin` 转发上游(联调测试环境用) |
| `XK_DESKTOP_IM_UPSTREAM` | 覆盖 `/im-api` 转发上游 |
| `XK_DESKTOP_DEV_URL` | 覆盖开发模式加载的 dev server 地址(默认 `http://localhost:5666` |
| `CSC_LINK` / `CSC_KEY_PASSWORD` | 代码签名证书;**未配置时自动跳过签名,出未签名包,构建不报错** |
| `APPLE_ID` / `APPLE_APP_SPECIFIC_PASSWORD` / `APPLE_TEAM_ID` | mac 公证三件套;三者齐全才启用公证 |
未签名 mac 包可内部分发:首次打开需右键「打开」,且 mac 自动更新不可用(应用内会降级为提示前往下载页手动安装)。
## 发布自动更新
`release/` 里的以下产物上传到静态托管地址(`electron-builder.config.cjs``publish.url`
- Windows`latest.yml` + `xk-desktop-*.exe` + `*.blockmap`
- mac`latest-mac.yml` + `*.dmg` + `*.zip` + `*.blockmap`zip 是 mac 自动更新必需格式)
前端每次发版后跟着跑一次 `pnpm build:desktop` 并上传产物,桌面端即可自动更新。

View File

@@ -0,0 +1,78 @@
/**
* electron-builder 打包配置
* 用 JS 配置而不是 ymlmac 公证需要按「环境变量是否配齐」动态开关。
*
* 签名策略(按需求做成条件化,未配置证书时出未签名包且构建不报错):
* - Windows / mac 代码签名:配置了 CSC_LINK + CSC_KEY_PASSWORD或 mac 钥匙串里有
* Developer ID 证书)时 electron-builder 自动签名;否则自动跳过,只输出告警
* - mac 公证APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD + APPLE_TEAM_ID 三者齐全才启用
* - 未签名 mac 包仍可内部分发:首次打开需右键「打开」,且自动更新会降级为提示手动下载
*/
const canNotarize = Boolean(
process.env.APPLE_ID &&
process.env.APPLE_APP_SPECIFIC_PASSWORD &&
process.env.APPLE_TEAM_ID,
);
/** @type {import('electron-builder').Configuration} */
const config = {
// appId 必须与主进程 setAppUserModelId 一致,否则 Windows 系统通知不显示
appId: 'com.xiaokang.xkadmin',
productName: '萧康云医',
// 安装包文件名用 ASCII避免中文名在静态托管/下载链接上出编码问题
artifactName: 'xk-desktop-${version}-${os}-${arch}.${ext}',
directories: {
output: 'release',
buildResources: 'resources',
},
files: ['out/**'],
extraResources: [
// web-antd 生产构建产物:放 resources/web不进 asar为后续渲染层热更新留路
{ from: '../web-antd/dist', to: 'web' },
// 应用图标供运行时使用托盘、Windows 窗口图标)
{ from: 'resources/icon.png', to: 'icon.png' },
],
asar: true,
compression: 'normal',
win: {
// TODO(待确认):正式图标待提供,当前用 web-antd logo 预生成的 ico 占位
// 直接给 .ico 让 builder 跳过 png 转换(本机 builder 的 wasm 图标工具会报内存错误)
icon: 'resources/icon.ico',
target: [{ target: 'nsis', arch: ['x64'] }],
},
nsis: {
// 非一键安装:允许用户选择安装目录,符合企业内部软件分发习惯
oneClick: false,
perMachine: false,
allowToChangeInstallationDirectory: true,
createDesktopShortcut: true,
createStartMenuShortcut: true,
shortcutName: '萧康云医',
},
mac: {
icon: 'resources/icon.png',
category: 'public.app-category.medical',
// dmg 给用户安装zip 是 electron-updater 在 mac 自动更新的必需格式,缺一不可
target: [
{ target: 'dmg', arch: ['x64', 'arm64'] },
{ target: 'zip', arch: ['x64', 'arm64'] },
],
hardenedRuntime: true,
gatekeeperAssess: false,
entitlements: 'resources/entitlements.mac.plist',
entitlementsInherit: 'resources/entitlements.mac.plist',
// IM 音视频通话必需的隐私用途描述,缺失时 mac 上调用摄像头/麦克风直接崩溃
extendInfo: {
NSCameraUsageDescription: '视频问诊需要使用摄像头',
NSMicrophoneUsageDescription: '语音/视频通话需要使用麦克风',
},
notarize: canNotarize,
},
publish: {
provider: 'generic',
// TODO(待确认):更新包静态托管地址,需与主进程 config.ts 的 DOWNLOAD_PAGE_URL 保持一致
url: 'https://static.xiaokang88.com/xk-desktop/',
},
};
module.exports = config;

View File

@@ -0,0 +1,26 @@
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
/**
* electron-vite 构建配置
* 只构建主进程与预加载脚本,不配置 renderer
* - 开发模式渲染层直接加载 web-antd 的 vite dev serverhttp://localhost:5666
* - 打包模式渲染层加载 app://local/web-antd 的生产 dist由主进程自定义协议提供服务
* externalizeDepsPlugindependencieselectron-updater / electron-log保持外部化
* 由 electron-builder 按生产依赖收集进安装包,避免打包器转译这类含运行时文件读取的库出问题
*/
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
build: {
outDir: 'out/main',
lib: { entry: 'src/main/index.ts' },
},
},
preload: {
plugins: [externalizeDepsPlugin()],
build: {
outDir: 'out/preload',
lib: { entry: 'src/preload/index.ts' },
},
},
});

27
apps/desktop/package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "@vben/desktop",
"version": "1.0.0",
"private": true,
"description": "萧康云医 PC 桌面端Electron 壳,打包加载 web-antd 生产构建产物)",
"author": "萧康云医",
"main": "out/main/index.js",
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"pack": "electron-builder --config electron-builder.config.cjs",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"electron-log": "catalog:",
"electron-updater": "^6.8.9"
},
"devDependencies": {
"@types/node": "catalog:",
"@vben/web-antd": "workspace:*",
"electron": "^43.4.0",
"electron-builder": "catalog:",
"electron-vite": "catalog:",
"typescript": "catalog:",
"vite": "^7.3.6"
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- hardenedRuntime 下 ElectronV8 JIT运行必需的内存权限 -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<!-- IM 音视频通话:摄像头 / 麦克风访问权限 -->
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -0,0 +1,30 @@
import { app } from 'electron';
import log from 'electron-log/main';
/**
* 开机自启基于系统登录项实现Windows 写 HKCU\...\Run 注册表、mac 写「系统设置 → 登录项」),
* 无需管理员权限;系统登录项本身就是持久化存储,无需额外落盘记录状态。
*/
/** 读取当前开机自启状态(直接以系统登录项为准,避免自维护状态与系统不同步) */
export function getAutoLaunchEnabled(): boolean {
// 开发模式下登录项指向 electron 开发二进制,无意义,统一视为关闭
if (!app.isPackaged) return false;
return app.getLoginItemSettings().openAtLogin;
}
/**
* 设置开机自启
* --hidden 参数让开机自启时静默进托盘(主进程入口据此不弹主窗口),避免开机打扰
*/
export function setAutoLaunchEnabled(enable: boolean): boolean {
if (!app.isPackaged) {
log.warn('开发模式不支持设置开机自启(登录项会错误指向开发二进制)');
return false;
}
app.setLoginItemSettings({
openAtLogin: enable,
args: enable ? ['--hidden'] : [],
});
return getAutoLaunchEnabled();
}

View File

@@ -0,0 +1,24 @@
import { app, nativeImage } from 'electron';
import { getMainWindow } from './window';
/**
* 未读角标:按平台分支实现
* - macDock 图标原生支持文字角标,直接 setBadge
* - Windows任务栏无原生数字角标用「覆盖图标」实现图由 preload 侧 canvas 绘制成 dataURL 传来,
* 因为主进程没有 DOM/canvas在渲染侧画图是社区标准做法
*/
export function applyBadge(count: number, image?: string): void {
if (process.platform === 'darwin') {
app.dock?.setBadge(count > 0 ? String(Math.min(count, 99)) : '');
return;
}
const win = getMainWindow();
if (!win) return;
if (count > 0 && image) {
win.setOverlayIcon(nativeImage.createFromDataURL(image), `${count} 条未读消息`);
} else {
// 数量归零或没有角标图时清除覆盖图标
win.setOverlayIcon(null, '');
}
}

View File

@@ -0,0 +1,54 @@
import { app } from 'electron';
import { join } from 'node:path';
/**
* 桌面端全局配置
* 打包模式下渲染层加载的是本地 dist所有「按环境调整」的地址都收敛在这里
* 并支持环境变量覆盖,便于联调测试环境时不改代码切换上游。
*/
/** 应用标识:必须与 electron-builder 的 appId 一致,否则 Windows 系统通知不显示 */
export const APP_ID = 'com.xiaokang.xkadmin';
/** 自定义协议,注册为特权协议后 app://local/ 等价一个「本地 https 站点」(安全上下文) */
export const APP_SCHEME = 'app';
export const APP_HOST = 'local';
export const APP_ORIGIN = `${APP_SCHEME}://${APP_HOST}`;
export const APP_INDEX_URL = `${APP_ORIGIN}/`;
/** 开发模式加载 web-antd 的 vite dev server端口对齐 apps/web-antd/.env.development 的 VITE_PORT=5666 */
export const DEV_SERVER_URL = process.env.XK_DESKTOP_DEV_URL ?? 'http://localhost:5666';
/**
* /api/admin/* 的转发上游(仅打包后的 app:// 协议使用dev 模式加载 vite dev server
* 由 vite 代理转发到本地后端 localhost:18001不经过这里
* 地址来源apps/web-antd/vite.config.ts 代理注释中的生产目标 https://api.xiaokang88.com/api/admin/
*/
export const API_UPSTREAM = process.env.XK_DESKTOP_API_UPSTREAM ?? 'https://api.xiaokang88.com';
/**
* /im-api/* 的转发上游(转发规则对齐开发代理:/im-api/* → 上游 /api/*;同样仅打包后生效,
* dev 模式由 vite 代理转发到本地 IM localhost:12080
* 地址来源vite.config.ts 代理注释的生产目标 https://api.ws.g.xiaokang88.com/api/,与生产 WS 同主机
*/
export const IM_UPSTREAM = process.env.XK_DESKTOP_IM_UPSTREAM ?? 'https://api.ws.g.xiaokang88.com';
/**
* 更新包下载页地址(与 electron-builder.config.cjs 的 publish.url 保持一致)
* 兼作 mac 未签名时「自动更新不可用 → 手动下载」的跳转页
* TODO(待确认):更新包静态托管地址
*/
export const DOWNLOAD_PAGE_URL = 'https://static.xiaokang88.com/xk-desktop/';
/** 全局快捷键:唤起/隐藏主窗口CommandOrControl 在 Windows 映射 Ctrl、mac 映射 Cmd */
export const MAIN_WINDOW_SHORTCUT = 'CommandOrControl+Alt+X';
/**
* 解析运行时资源路径(图标等)
* 打包后 extraResources 位于 process.resourcesPath开发时直接读工程 resources 目录
*/
export function getResourcePath(name: string): string {
return app.isPackaged
? join(process.resourcesPath, name)
: join(app.getAppPath(), 'resources', name);
}

View File

@@ -0,0 +1,81 @@
import { app, Menu } from 'electron';
import log from 'electron-log/main';
import { APP_ID } from './config';
import { registerIpcHandlers } from './ipc';
import { registerAppProtocol, registerSchemePrivileges } from './protocol';
import { registerGlobalShortcut, unregisterGlobalShortcut } from './shortcut';
import { createTray } from './tray';
import { initUpdater } from './updater';
import { createMainWindow, getMainWindow, markQuitting, showMainWindow } from './window';
/**
* 主进程入口:负责应用生命周期编排
* 启动顺序:单实例锁 → 特权协议注册(必须在 ready 前)→ ready 后依次初始化
* 协议服务 / IPC / 主窗口 / 托盘 / 全局快捷键 / 自动更新
*/
// 显式设置应用名userData / 日志目录会随之落到 %APPDATA%\萧康云医,
// 不设置的话 Electron 会取 package.json 的 name@vben/desktop生成嵌套目录不利于运维排查
app.setName('萧康云医');
// Windows 通知归属与任务栏分组标识,必须与 electron-builder 的 appId 一致,
// 否则系统通知不显示或显示为「electron.app.Electron」
app.setAppUserModelId(APP_ID);
// 单实例锁:避免多开导致托盘、自动更新、本地设置互相干扰
if (app.requestSingleInstanceLock()) {
// 特权协议必须在 app ready 之前注册,否则 app:// 无法成为安全上下文
registerSchemePrivileges();
// 重复启动(用户再次双击图标)时唤起已有窗口
app.on('second-instance', () => showMainWindow());
app.whenReady().then(() => {
// electron-log 初始化:主进程日志落 userData/logs便于排查线上问题
log.initialize();
// mac 必须保留应用菜单,否则 Cmd+C/V/Q 等系统快捷键失效Windows 隐藏菜单栏
if (process.platform === 'darwin') {
Menu.setApplicationMenu(
Menu.buildFromTemplate([
{ role: 'appMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
]),
);
} else {
Menu.setApplicationMenu(null);
}
registerAppProtocol();
registerIpcHandlers();
// 开机自启带 --hidden 参数:静默启动进托盘,不弹主窗口打扰用户
const startHidden = process.argv.includes('--hidden');
createMainWindow({ startHidden });
createTray();
registerGlobalShortcut();
initUpdater();
});
// mac 点击 Dock 图标恢复窗口(关窗只是隐藏,符合平台惯例)
app.on('activate', () => {
if (getMainWindow()) showMainWindow();
});
// 常驻托盘模式窗口全部关闭也不退出close 已被拦截为隐藏,这里是兜底),退出走托盘菜单/Cmd+Q
app.on('window-all-closed', () => {
// 故意留空:不调用 app.quit()
});
// 任何退出路径(托盘退出 / Cmd+Q / 自动更新安装)都先放行窗口 close 拦截
app.on('before-quit', () => markQuitting());
// 退出前注销全局快捷键,避免残留占用
app.on('will-quit', () => unregisterGlobalShortcut());
} else {
app.quit();
}

View File

@@ -0,0 +1,117 @@
import { app, ipcMain, Notification } from 'electron';
import { getAutoLaunchEnabled, setAutoLaunchEnabled } from './auto-launch';
import { applyBadge } from './badge';
import { printHtml, type PrintHtmlOptions } from './printer';
import { getSettings, updateSettings, type DesktopSettings } from './settings';
import { refreshTrayMenu } from './tray';
import { checkForUpdates } from './updater';
import { getMainWindow, isTrustedPageUrl, showMainWindow } from './window';
/**
* IPC 汇总preload 暴露的 desktopAPI 全部经由这里进入主进程
* 所有 handler 先校验调用方来源app:// 或开发服务器),
* 防止页面内嵌第三方 iframe 等场景越权调用桌面能力
*/
/** 校验 IPC 调用来源,不可信来源直接抛错拒绝 */
function assertTrustedSender(event: Electron.IpcMainInvokeEvent): void {
const url = event.senderFrame?.url ?? '';
if (!isTrustedPageUrl(url)) {
throw new Error(`拒绝来自不可信来源的 IPC 调用: ${url}`);
}
}
/** 系统通知参数route 用于点击通知后让渲染端跳转对应页面) */
interface NotifyPayload {
title: string;
body: string;
route?: string;
}
export function registerIpcHandlers(): void {
// 应用版本号(渲染端「关于/设置」展示用)
ipcMain.handle('desktop:get-version', (event) => {
assertTrustedSender(event);
return app.getVersion();
});
// 系统通知:点击后唤起主窗口,并把目标路由回传渲染端执行 router.push
ipcMain.handle('desktop:notify', (event, payload: NotifyPayload) => {
assertTrustedSender(event);
if (!Notification.isSupported()) return false;
const notification = new Notification({
title: payload.title,
body: payload.body,
});
notification.on('click', () => {
showMainWindow();
if (payload.route) {
getMainWindow()?.webContents.send('desktop:navigate', payload.route);
}
});
notification.show();
return true;
});
// 未读角标mac Dock 数字 / Windows 任务栏覆盖图标,图由 preload canvas 绘制)
ipcMain.handle('desktop:set-badge', (event, payload: { count: number; image?: string }) => {
assertTrustedSender(event);
applyBadge(payload.count, payload.image);
});
// 任务栏闪烁提醒:仅窗口未聚焦时触发(聚焦状态下闪烁是干扰),聚焦后由窗口 focus 事件自动清除
ipcMain.handle('desktop:flash-frame', (event) => {
assertTrustedSender(event);
const win = getMainWindow();
if (win && !win.isFocused()) win.flashFrame(true);
});
// 开机自启读取/设置(设置后同步刷新托盘菜单勾选态)
ipcMain.handle('desktop:get-auto-launch', (event) => {
assertTrustedSender(event);
return getAutoLaunchEnabled();
});
ipcMain.handle('desktop:set-auto-launch', (event, enable: boolean) => {
assertTrustedSender(event);
const result = setAutoLaunchEnabled(enable);
refreshTrayMenu();
return result;
});
// 打印机列表(渲染端打印设置下拉用)
ipcMain.handle('desktop:get-printers', async (event) => {
assertTrustedSender(event);
const win = getMainWindow();
if (!win) return [];
return await win.webContents.getPrintersAsync();
});
// 静默打印:渲染端拼好完整 HTML 传入,主进程隐藏窗口出纸
ipcMain.handle('desktop:print-html', (event, options: PrintHtmlOptions) => {
assertTrustedSender(event);
return printHtml(options);
});
// 桌面端设置(打印机选择、静默开关等)读取/增量更新
ipcMain.handle('desktop:get-settings', (event) => {
assertTrustedSender(event);
return getSettings();
});
ipcMain.handle('desktop:set-settings', (event, patch: Partial<DesktopSettings>) => {
assertTrustedSender(event);
return updateSettings(patch);
});
// 手动检查更新(渲染端设置页/关于入口)
ipcMain.handle('desktop:check-update', (event) => {
assertTrustedSender(event);
checkForUpdates(true);
});
// 渲染端主动唤起主窗口(如页内提醒的「查看」动作)
ipcMain.handle('desktop:focus-window', (event) => {
assertTrustedSender(event);
showMainWindow();
});
}

View File

@@ -0,0 +1,75 @@
import { unlink, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { app, BrowserWindow } from 'electron';
import log from 'electron-log/main';
/**
* 静默打印:隐藏窗口加载 HTML → webContents.print 直接出纸
* 为什么用隐藏窗口:渲染层的处方打印本来就是拼好的完整 HTML含内联样式
* 加载到独立隐藏窗口打印可以完全复用现有模板,且不阻塞/不干扰主窗口。
* 为什么落临时文件而不是 data: URL处方 HTML 可能较大data URL 有长度上限风险,
* 临时文件 loadFile 对任意大小都稳定,打完即删。
*/
export interface PrintHtmlOptions {
/** 完整的打印 HTML含样式由渲染层拼好传入 */
html: string;
/** 目标打印机名称;不传时使用系统默认打印机 */
deviceName?: string;
/** 是否静默打印true 不弹系统打印对话框) */
silent?: boolean;
}
export interface PrintResult {
ok: boolean;
message?: string;
}
/**
* 执行打印,返回是否成功与失败原因(渲染层据此提示用户)
*/
export async function printHtml(options: PrintHtmlOptions): Promise<PrintResult> {
const { html, deviceName, silent = true } = options;
// 临时文件带时间戳防并发打印互相覆盖
const tempFile = join(app.getPath('temp'), `xk-print-${Date.now()}-${Math.random().toString(36).slice(2)}.html`);
await writeFile(tempFile, html, 'utf8');
return new Promise<PrintResult>((resolve) => {
let settled = false;
const win = new BrowserWindow({
show: false,
webPreferences: { sandbox: true },
});
/** 统一收尾:销毁隐藏窗口、清理临时文件、只 resolve 一次 */
const finish = (ok: boolean, message?: string) => {
if (settled) return;
settled = true;
if (!win.isDestroyed()) win.destroy();
unlink(tempFile).catch(() => {});
if (!ok) log.warn(`静默打印失败: ${message}`);
resolve({ ok, message });
};
win.webContents.once('did-finish-load', () => {
win.webContents.print(
{
silent,
deviceName: deviceName || undefined,
printBackground: true,
},
(success, failureReason) => finish(success, success ? undefined : failureReason),
);
});
win.webContents.once('did-fail-load', (_event, _code, desc) => finish(false, desc));
// 兜底超时:防止打印机脱机等场景下隐藏窗口泄漏
setTimeout(() => finish(false, '打印超时(请检查打印机状态)'), 60_000);
win.loadFile(tempFile).catch((error: unknown) => {
finish(false, error instanceof Error ? error.message : String(error));
});
});
}

View File

@@ -0,0 +1,108 @@
import { existsSync, statSync } from 'node:fs';
import { join, normalize } from 'node:path';
import { pathToFileURL } from 'node:url';
import { app, net, protocol } from 'electron';
import { API_UPSTREAM, APP_SCHEME, IM_UPSTREAM } from './config';
/**
* app:// 自定义协议:桌面端的「本地 nginx」
* 1. 静态文件服务:把 web-antd 生产 dist 按 URL 路径映射到本地文件,带 SPA fallback
* (等价 nginx 的 try_files $uri /index.html因此无需把前端路由改成 hash 模式
* 2. 反向代理:/api/admin/* 与 /im-api/* 用 net.fetch 转发到线上上游 ——
* 渲染层看到的是同源请求,天然规避 CORS后端与 nginx 都不需要任何改动
*/
/** Electron net.fetch 支持但标准 RequestInit 类型缺失的扩展字段 */
type ElectronFetchInit = RequestInit & {
/** 转发出去的请求不再进入自定义协议处理器,防止死循环 */
bypassCustomProtocolHandlers?: boolean;
/** 携带流式 body 时 fetch 规范要求显式声明半双工 */
duplex?: 'half';
};
/**
* 注册特权协议,必须在 app ready 之前调用
* standard/secure 让 app:// 成为「安全上下文」navigator.clipboard、getUserMedia、
* IndexedDB、crypto.subtle 等浏览器能力才可用stream 支持流式响应(音频等大文件)
*/
export function registerSchemePrivileges(): void {
protocol.registerSchemesAsPrivileged([
{
scheme: APP_SCHEME,
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
stream: true,
corsEnabled: true,
},
},
]);
}
/**
* 解析 web dist 根目录
* 打包后位于 resources/webelectron-builder extraResources 拷入,不进 asar
* 未打包(本地验证生产包行为)时兜底读仓库内 web-antd/dist
*/
export function getWebRoot(): string {
return app.isPackaged
? join(process.resourcesPath, 'web')
: join(__dirname, '../../../web-antd/dist');
}
/**
* 把渲染层的同源请求转发到远端上游(等价 nginx proxy_pass
* 保留原请求的方法 / 头 / 流式请求体Bearer token 请求头原样透传
*/
function proxyRequest(target: string, request: Request): Promise<Response> {
const init: ElectronFetchInit = {
method: request.method,
headers: request.headers,
body: request.body,
bypassCustomProtocolHandlers: true,
};
if (request.body) init.duplex = 'half';
return net.fetch(target, init);
}
/**
* 静态文件解析:带路径穿越防护 + SPA fallback
* 命中不了真实文件的路径(前端路由地址、不存在的资源)统一回 index.html 交给前端路由
*/
function resolveStaticFile(webRoot: string, pathname: string): string {
const indexHtml = join(webRoot, 'index.html');
try {
const filePath = normalize(join(webRoot, decodeURIComponent(pathname)));
// 防路径穿越:解析结果必须仍在 webRoot 目录内
if (!filePath.startsWith(normalize(webRoot))) return indexHtml;
if (existsSync(filePath) && statSync(filePath).isFile()) return filePath;
} catch {
// decode 失败等异常场景直接回 index.html不让协议处理器抛错
}
return indexHtml;
}
/**
* 注册 app:// 协议处理器,在 app ready 之后调用
*/
export function registerAppProtocol(): void {
const webRoot = getWebRoot();
protocol.handle(APP_SCHEME, (request) => {
const url = new URL(request.url);
// 反代 /api/admin/*:生产构建的 VITE_GLOB_API_URL=/api/admin路径直接透传
if (url.pathname.startsWith('/api/')) {
return proxyRequest(`${API_UPSTREAM}${url.pathname}${url.search}`, request);
}
// 反代 /im-api/*:对齐开发代理规则,剥掉 /im-api 前缀后拼到上游 /api/*
if (url.pathname.startsWith('/im-api/')) {
const stripped = url.pathname.slice('/im-api'.length);
return proxyRequest(`${IM_UPSTREAM}/api${stripped}${url.search}`, request);
}
// 其余全部按静态资源处理net.fetch file:// 会按扩展名自动带上正确的 Content-Type
const fileUrl = pathToFileURL(resolveStaticFile(webRoot, url.pathname)).toString();
return net.fetch(fileUrl, { bypassCustomProtocolHandlers: true } as ElectronFetchInit);
});
}

View File

@@ -0,0 +1,67 @@
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { app } from 'electron';
import log from 'electron-log/main';
/**
* 桌面端本地设置持久化userData/desktop-settings.json
* 为什么不用 electron-store目前只需存打印机等少量键值直接读写 JSON 文件即可,
* 避免引入额外依赖electron-store 新版为纯 ESM与 CJS 主进程产物不兼容,还会拖入 ajv 等重依赖)。
*/
/** 桌面端设置项定义 */
export interface DesktopSettings {
/** 静默打印使用的打印机名称,空串表示未选择 */
printerName: string;
/** 是否启用静默打印true 时打印处方不弹系统打印对话框) */
silentPrint: boolean;
}
const DEFAULT_SETTINGS: DesktopSettings = {
printerName: '',
silentPrint: false,
};
/** 内存缓存,避免每次 IPC 调用都读磁盘 */
let cache: DesktopSettings | null = null;
/** 设置文件落在 userData 目录,随应用卸载数据目录一起清理 */
function getSettingsFilePath(): string {
return join(app.getPath('userData'), 'desktop-settings.json');
}
/**
* 读取设置:文件不存在或解析失败时回退默认值,保证任何情况下不影响应用启动
*/
export function getSettings(): DesktopSettings {
if (cache) return cache;
try {
const filePath = getSettingsFilePath();
if (existsSync(filePath)) {
// JSON.parse 返回 any显式收窄成 Partial 再与默认值合并,保证字段完整
const parsed = JSON.parse(readFileSync(filePath, 'utf8')) as Partial<DesktopSettings>;
const merged: DesktopSettings = { ...DEFAULT_SETTINGS, ...parsed };
cache = merged;
return merged;
}
} catch (error) {
log.warn('读取桌面端设置失败,回退默认值', error);
}
cache = { ...DEFAULT_SETTINGS };
return cache;
}
/**
* 增量更新设置并写盘:写盘失败只告警(内存态仍生效),下次成功写入时自动恢复
*/
export function updateSettings(patch: Partial<DesktopSettings>): DesktopSettings {
const next = { ...getSettings(), ...patch };
cache = next;
try {
writeFileSync(getSettingsFilePath(), JSON.stringify(next, null, 2), 'utf8');
} catch (error) {
log.warn('写入桌面端设置失败', error);
}
return next;
}

View File

@@ -0,0 +1,21 @@
import { globalShortcut } from 'electron';
import log from 'electron-log/main';
import { MAIN_WINDOW_SHORTCUT } from './config';
import { toggleMainWindow } from './window';
/**
* 全局快捷键:任何界面下按 CommandOrControl+Alt+X 快速唤起/隐藏主窗口
* 注册失败(快捷键被其他应用占用)只记录警告,不影响应用启动
*/
export function registerGlobalShortcut(): void {
const ok = globalShortcut.register(MAIN_WINDOW_SHORTCUT, () => toggleMainWindow());
if (!ok) {
log.warn(`全局快捷键 ${MAIN_WINDOW_SHORTCUT} 注册失败(可能被其他应用占用)`);
}
}
/** 应用退出前注销全部快捷键,避免残留占用 */
export function unregisterGlobalShortcut(): void {
globalShortcut.unregisterAll();
}

View File

@@ -0,0 +1,78 @@
import { app, Menu, nativeImage, Tray } from 'electron';
import log from 'electron-log/main';
import { getAutoLaunchEnabled, setAutoLaunchEnabled } from './auto-launch';
import { getResourcePath } from './config';
import { checkForUpdates } from './updater';
import { markQuitting, showMainWindow } from './window';
/**
* 系统托盘:应用常驻入口
* - Windows 单击托盘图标恢复主窗口mac 单击弹出菜单(跟随平台惯例)
* - 菜单:显示主窗口 / 开机自启开关 / 检查更新 / 退出
* 注意:托盘实例必须保持模块级引用,否则会被 GC 导致图标消失Electron 经典坑)
*/
let tray: Tray | null = null;
/** 构建托盘菜单:开机自启是动态勾选项,每次状态变化后需重建菜单 */
function buildTrayMenu(): Menu {
return Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => showMainWindow(),
},
{ type: 'separator' },
{
label: '开机自启',
type: 'checkbox',
checked: getAutoLaunchEnabled(),
// 开发模式登录项无意义,置灰避免误操作
enabled: app.isPackaged,
click: (menuItem) => {
setAutoLaunchEnabled(menuItem.checked);
refreshTrayMenu();
},
},
{
label: '检查更新',
click: () => checkForUpdates(true),
},
{ type: 'separator' },
{
label: '退出',
click: () => {
// 先标记退出,放行主窗口 close 拦截,再走正常退出流程
markQuitting();
app.quit();
},
},
]);
}
/** 刷新托盘菜单(开机自启状态从渲染端 IPC 修改后也要同步勾选态) */
export function refreshTrayMenu(): void {
tray?.setContextMenu(buildTrayMenu());
}
/**
* 创建系统托盘
* 图标用打包的应用图标缩放mac 菜单栏建议 18px、Windows 托盘 16px 的倍数由系统处理)
* TODO(待确认)正式图标到位后mac 建议换单色 Template 图以适配深浅菜单栏
*/
export function createTray(): void {
try {
const icon = nativeImage.createFromPath(getResourcePath('icon.png'));
const trayIcon = process.platform === 'darwin' ? icon.resize({ width: 18, height: 18 }) : icon.resize({ width: 16, height: 16 });
tray = new Tray(trayIcon);
tray.setToolTip('萧康云医');
tray.setContextMenu(buildTrayMenu());
// Windows 单击直接唤起主窗口mac 上设置了 contextMenu 后单击默认弹菜单,不重复绑定
if (process.platform === 'win32') {
tray.on('click', () => showMainWindow());
}
} catch (error) {
// 托盘创建失败(图标缺失等)不阻塞应用启动,仅损失常驻入口
log.error('创建系统托盘失败', error);
}
}

View File

@@ -0,0 +1,139 @@
import { app, dialog, shell } from 'electron';
import log from 'electron-log/main';
import { autoUpdater } from 'electron-updater';
import { DOWNLOAD_PAGE_URL } from './config';
import { getMainWindow, markQuitting, showMainWindow } from './window';
/**
* 自动更新electron-updater + generic 静态托管)
* - 启动 10 秒后自动检查一次 + 每 4 小时轮询 + 托盘/渲染端手动触发
* - autoDownload发现新版本自动后台下载进度事件转发渲染端展示
* - 下载完成弹窗询问「立即重启安装 / 退出时自动安装」
* - mac 未签名包 electron-updater 签名校验会报错:捕获后降级为「打开下载页手动安装」
*/
/** 手动检查标记:手动触发时才提示「已是最新版本」,自动轮询保持安静 */
let isManualCheck = false;
/** 检查中防重入 */
let isChecking = false;
/** 把更新事件转发给渲染端web 侧在 basic.vue 里做轻量提示) */
function sendToRenderer(type: string, data?: unknown): void {
getMainWindow()?.webContents.send('desktop:update-event', { type, data });
}
/** 判断是否 mac 未签名导致的更新失败(这类错误重试无意义,直接引导手动下载) */
function isMacSignatureError(message: string): boolean {
return (
process.platform === 'darwin' &&
/code signature|not signed|codesign|could not get code signature/i.test(message)
);
}
/**
* 初始化自动更新:仅打包后生效(开发模式没有 app-update.yml检查必然失败
*/
export function initUpdater(): void {
if (!app.isPackaged) return;
autoUpdater.logger = log;
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('update-available', (info) => {
log.info(`发现新版本: ${info.version}`);
sendToRenderer('available', { version: info.version });
});
autoUpdater.on('update-not-available', () => {
sendToRenderer('not-available');
if (isManualCheck) {
dialog.showMessageBox({
type: 'info',
title: '检查更新',
message: `当前已是最新版本v${app.getVersion()}`,
});
}
isManualCheck = false;
});
autoUpdater.on('download-progress', (progress) => {
sendToRenderer('progress', { percent: Math.round(progress.percent) });
});
autoUpdater.on('update-downloaded', async (info) => {
sendToRenderer('downloaded', { version: info.version });
// 唤起窗口再弹对话框,避免用户看不到安装确认
showMainWindow();
const { response } = await dialog.showMessageBox({
type: 'question',
title: '发现新版本',
message: `新版本 v${info.version} 已下载完成`,
detail: '立即重启安装,或在退出应用时自动安装。',
buttons: ['立即重启安装', '退出时自动安装'],
defaultId: 0,
cancelId: 1,
});
if (response === 0) {
// 放行窗口 close 拦截,否则 quitAndInstall 会被「关闭进托盘」逻辑挡住
markQuitting();
autoUpdater.quitAndInstall();
}
});
autoUpdater.on('error', async (error) => {
const message = error?.message ?? String(error);
log.error('自动更新失败', message);
sendToRenderer('error', { message });
// mac 未签名:自动更新走不通,引导用户手动下载安装
if (isMacSignatureError(message)) {
const { response } = await dialog.showMessageBox({
type: 'info',
title: '检查更新',
message: '当前安装包不支持自动更新',
detail: '请前往下载页获取最新版本手动安装。',
buttons: ['打开下载页', '取消'],
defaultId: 0,
cancelId: 1,
});
if (response === 0) shell.openExternal(DOWNLOAD_PAGE_URL);
} else if (isManualCheck) {
dialog.showMessageBox({
type: 'warning',
title: '检查更新',
message: '检查更新失败,请稍后重试',
detail: message,
});
}
isManualCheck = false;
isChecking = false;
});
// 启动延时自动检查一次 + 每 4 小时轮询(内部工具常开着,轮询保证及时收到发版)
setTimeout(() => checkForUpdates(false), 10_000);
setInterval(() => checkForUpdates(false), 4 * 60 * 60 * 1000);
}
/**
* 触发一次更新检查
* @param manual 是否手动触发(手动时「已是最新/失败」都会弹窗反馈,自动轮询保持安静)
*/
export async function checkForUpdates(manual: boolean): Promise<void> {
if (!app.isPackaged) {
if (manual) {
dialog.showMessageBox({ type: 'info', title: '检查更新', message: '开发模式不支持检查更新' });
}
return;
}
if (isChecking) return;
isChecking = true;
isManualCheck = manual;
try {
await autoUpdater.checkForUpdates();
} catch {
// 错误已在 error 事件里统一处理
} finally {
isChecking = false;
}
}

View File

@@ -0,0 +1,212 @@
import { join } from 'node:path';
import { app, BrowserWindow, Notification, session, shell, systemPreferences } from 'electron';
import log from 'electron-log/main';
import { APP_INDEX_URL, APP_ORIGIN, DEV_SERVER_URL, getResourcePath } from './config';
/**
* 主窗口管理
* - 加载目标:开发模式加载 web-antd 的 vite dev server未起服务时自动重试打包模式加载 app://local/
* - 安全基线contextIsolation + sandbox 开启webSecurity 不关闭window.open 一律交给系统浏览器
* - 关闭行为双平台都是「关窗隐藏、应用常驻」Windows 走托盘、mac 走 Dock真正退出走托盘菜单/Cmd+Q
* - 权限控制只放行业务需要的媒体IM 音视频)/ 通知 / 剪贴板 / 全屏,其余默认拒绝
*/
let mainWindow: BrowserWindow | null = null;
/** 标记「真正退出」:常驻模式下 close 只是隐藏,只有 quit 流程才放行关闭 */
let isQuitting = false;
/** Windows 首次关窗提示「已最小化到托盘」,只提示一次避免打扰 */
let hasShownHideTip = false;
/** 加载失败重试定时器,避免叠加多个重试 */
let retryTimer: NodeJS.Timeout | null = null;
/** 主窗口要加载的页面地址:打包后走自定义协议,开发走 dev server */
const PAGE_URL = app.isPackaged ? APP_INDEX_URL : DEV_SERVER_URL;
export function markQuitting(): void {
isQuitting = true;
}
export function getMainWindow(): BrowserWindow | null {
return mainWindow;
}
/** 判断 URL 是否属于信任的页面来源本地协议或开发服务器IPC 与权限校验共用 */
export function isTrustedPageUrl(url: string): boolean {
return url.startsWith(APP_ORIGIN) || url.startsWith(DEV_SERVER_URL);
}
/**
* 会话级权限控制:白名单内且来源可信才放行
* media 供 IM 音视频通话notifications 供 HTML5 Notification业务主要走主进程通知这里兜底
*/
function setupPermissionHandlers(): void {
const allowedPermissions = new Set([
'media',
'notifications',
'clipboard-read',
'clipboard-sanitized-write',
'fullscreen',
]);
session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
const trusted = isTrustedPageUrl(webContents.getURL());
if (!trusted || !allowedPermissions.has(permission)) {
callback(false);
return;
}
// mac 首次使用摄像头/麦克风必须先过系统 TCC 授权弹窗,否则页面侧拿不到设备
if (permission === 'media' && process.platform === 'darwin') {
Promise.all([
systemPreferences.askForMediaAccess('microphone'),
systemPreferences.askForMediaAccess('camera'),
])
.then(() => callback(true))
// TCC 弹窗异常时仍放行,让页面侧走自己的错误提示
.catch(() => callback(true));
return;
}
callback(true);
});
session.defaultSession.setPermissionCheckHandler((_webContents, permission, requestingOrigin) => {
return allowedPermissions.has(permission) && isTrustedPageUrl(requestingOrigin);
});
}
/** 加载失败时的内置错误页(数据 URL 形式,不依赖任何本地文件) */
function buildErrorPageUrl(reason: string): string {
const html = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>加载失败</title>
<style>
body { margin: 0; display: flex; align-items: center; justify-content: center; height: 100vh;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif; background: #f5f6f8; color: #333; }
.box { text-align: center; }
.title { font-size: 18px; font-weight: 600; margin-bottom: 8px; }
.desc { font-size: 13px; color: #999; }
</style>
</head>
<body>
<div class="box">
<div class="title">页面加载失败,正在自动重试…</div>
<div class="desc">请检查网络连接(${reason}</div>
</div>
</body>
</html>`;
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
}
/**
* 创建主窗口
* @param options.startHidden 开机自启(--hidden场景下静默启动不弹窗打扰用户
*/
export function createMainWindow(options: { startHidden: boolean }): BrowserWindow {
setupPermissionHandlers();
mainWindow = new BrowserWindow({
width: 1440,
height: 900,
minWidth: 1100,
minHeight: 700,
show: false,
autoHideMenuBar: true,
title: '萧康云医',
// Windows/Linux 窗口与任务栏图标mac 使用打包进 app 的 icns无需在此指定
icon: process.platform === 'darwin' ? undefined : getResourcePath('icon.png'),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
spellcheck: false,
},
});
// 首次渲染完成再显示,避免白屏闪烁;静默启动场景保持隐藏(用户从托盘/快捷键唤起)
mainWindow.once('ready-to-show', () => {
if (!options.startHidden) mainWindow?.show();
});
// window.open / target=_blank外链一律交给系统默认浏览器不在应用内开新窗安全 + 体验)
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('http://') || url.startsWith('https://')) {
shell.openExternal(url);
}
return { action: 'deny' };
});
// 页面内导航限制在信任来源内,跳外部地址时转交系统浏览器
mainWindow.webContents.on('will-navigate', (event, url) => {
if (isTrustedPageUrl(url)) return;
event.preventDefault();
if (url.startsWith('http://') || url.startsWith('https://')) {
shell.openExternal(url);
}
});
// 加载失败dev server 未启动 / 断网等)时展示内置错误页并定时重试
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDesc, _url, isMainFrame) => {
if (!isMainFrame) return;
// ERR_ABORTED-3是导航被主动取消如重复 loadURL不算失败
if (errorCode === -3) return;
log.warn(`主窗口加载失败: ${errorCode} ${errorDesc}`);
mainWindow?.loadURL(buildErrorPageUrl(errorDesc || String(errorCode)));
if (retryTimer) clearTimeout(retryTimer);
// 开发模式快速重试等 vite 起服务;打包模式放慢节奏等网络恢复
retryTimer = setTimeout(() => mainWindow?.loadURL(PAGE_URL), app.isPackaged ? 5000 : 1500);
});
// 窗口聚焦即清除任务栏闪烁提醒(配合新消息 flashFrame
mainWindow.on('focus', () => mainWindow?.flashFrame(false));
// F12 切换 DevTools内部管理系统保留排障入口
mainWindow.webContents.on('before-input-event', (_event, input) => {
if (input.type === 'keyDown' && input.key === 'F12') {
mainWindow?.webContents.toggleDevTools();
}
});
// 关闭行为双平台都拦截为隐藏Windows 常驻托盘 / mac 常驻 Dock退出流程放行
mainWindow.on('close', (event) => {
if (isQuitting) return;
event.preventDefault();
mainWindow?.hide();
// Windows 首次隐藏时提示托盘位置,避免用户以为应用被关闭
if (process.platform === 'win32' && !hasShownHideTip && Notification.isSupported()) {
hasShownHideTip = true;
new Notification({
title: '萧康云医仍在运行',
body: '应用已最小化到系统托盘,点击托盘图标可重新打开窗口',
}).show();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.loadURL(PAGE_URL);
return mainWindow;
}
/** 唤起主窗口:还原最小化、显示并聚焦(通知点击 / 托盘 / 第二实例 / 快捷键共用) */
export function showMainWindow(): void {
if (!mainWindow) return;
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
mainWindow.focus();
}
/** 切换主窗口显隐(全局快捷键使用) */
export function toggleMainWindow(): void {
if (!mainWindow) return;
if (mainWindow.isVisible() && mainWindow.isFocused()) {
mainWindow.hide();
} else {
showMainWindow();
}
}

View File

@@ -0,0 +1,102 @@
import { contextBridge, ipcRenderer } from 'electron';
/**
* 预加载脚本:通过 contextBridge 向渲染层暴露受控的 desktopAPI
* - 渲染层web-antd通过 window.desktopAPI 使用桌面能力;浏览器环境没有该对象,
* 前端封装层apps/web-antd/src/util/desktop.ts会自动 no-op 降级
* - sandbox 模式下本文件必须是 CJS 产物electron-vite 已按包配置输出 CJS
* 且只能使用 Electron 提供的受限 APIcontextBridge / ipcRenderer 等)
*/
/**
* Windows 任务栏角标图:用 canvas 画「红底白字」数字返回 dataURL
* 为什么在 preload 画:主进程没有 DOM/canvaspreload 运行在渲染进程内有 DOM
* 由主进程 setOverlayIcon 消费,这是 Windows 数字角标的社区标准做法
*/
function drawBadgeImage(count: number): string | undefined {
try {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (!ctx) return undefined;
// 红色圆底
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fillStyle = '#f5222d';
ctx.fill();
// 白色数字,超过 99 显示 99+
const text = count > 99 ? '99+' : String(count);
ctx.fillStyle = '#ffffff';
ctx.font = `bold ${text.length > 2 ? 13 : 18}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, size / 2, size / 2 + 1);
return canvas.toDataURL('image/png');
} catch {
// canvas 不可用时退化为无图(主进程会清除角标),不影响业务
return undefined;
}
}
/** 暴露给渲染层的桌面能力集合,字段语义与 web 侧 desktop.ts 封装一一对应 */
const desktopAPI = {
/** 渲染层判断是否运行在桌面端的唯一标记 */
isElectron: true as const,
/** 当前平台win32 / darwin / linux */
platform: process.platform,
/** 获取桌面端版本号(关于/设置展示用) */
getVersion: (): Promise<string> => ipcRenderer.invoke('desktop:get-version'),
/** 发送系统通知route 为点击通知后要跳转的前端路由 */
notify: (options: { title: string; body: string; route?: string }): Promise<boolean> =>
ipcRenderer.invoke('desktop:notify', options),
/** 设置未读角标数量0 表示清除Windows 角标图在此侧 canvas 生成 */
setBadge: (count: number): Promise<void> =>
ipcRenderer.invoke('desktop:set-badge', {
count,
image: count > 0 && process.platform === 'win32' ? drawBadgeImage(count) : undefined,
}),
/** 任务栏闪烁提醒(窗口未聚焦时生效,聚焦后自动清除) */
flashFrame: (): Promise<void> => ipcRenderer.invoke('desktop:flash-frame'),
/** 读取开机自启状态 */
getAutoLaunch: (): Promise<boolean> => ipcRenderer.invoke('desktop:get-auto-launch'),
/** 设置开机自启,返回设置后的实际状态 */
setAutoLaunch: (enable: boolean): Promise<boolean> =>
ipcRenderer.invoke('desktop:set-auto-launch', enable),
/** 获取系统打印机列表(打印设置下拉用) */
getPrinters: (): Promise<Array<{ name: string; displayName: string; isDefault: boolean }>> =>
ipcRenderer.invoke('desktop:get-printers'),
/** 静默打印:传入拼好的完整 HTML主进程隐藏窗口出纸 */
printHTML: (options: {
html: string;
deviceName?: string;
silent?: boolean;
}): Promise<{ ok: boolean; message?: string }> =>
ipcRenderer.invoke('desktop:print-html', options),
/** 读取桌面端设置(打印机名称、静默打印开关等) */
getSettings: (): Promise<{ printerName: string; silentPrint: boolean }> =>
ipcRenderer.invoke('desktop:get-settings'),
/** 增量更新桌面端设置,返回更新后的完整设置 */
setSettings: (
patch: Partial<{ printerName: string; silentPrint: boolean }>,
): Promise<{ printerName: string; silentPrint: boolean }> =>
ipcRenderer.invoke('desktop:set-settings', patch),
/** 手动检查更新(结果由主进程弹窗反馈) */
checkUpdate: (): Promise<void> => ipcRenderer.invoke('desktop:check-update'),
/** 唤起并聚焦主窗口 */
focusWindow: (): Promise<void> => ipcRenderer.invoke('desktop:focus-window'),
/** 监听「通知点击跳转」事件:主进程通知被点击后回传目标路由,渲染层执行 router.push */
onNavigate: (callback: (route: string) => void): void => {
ipcRenderer.on('desktop:navigate', (_event, route: string) => callback(route));
},
/** 监听更新事件available / progress / downloaded / not-available / error渲染层做轻量提示 */
onUpdateEvent: (callback: (payload: { type: string; data?: unknown }) => void): void => {
ipcRenderer.on('desktop:update-event', (_event, payload) => callback(payload));
},
};
contextBridge.exposeInMainWorld('desktopAPI', desktopAPI);
/** 供 web 侧类型声明引用apps/web-antd/src/types/desktop.d.ts 与此保持同步) */
export type DesktopAPI = typeof desktopAPI;

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2023",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"types": ["node"],
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true
},
"include": ["src", "electron.vite.config.ts"]
}

View File

@@ -21,3 +21,4 @@ VITE_ARCHIVER=true
# WebSocket 连接地址
VITE_WS_URL=wss://api.ws.g.xiaokang88.com/ws
# VITE_WS_URL=wss://xk.ws.nailaoyun.cn/ws

View File

@@ -12,7 +12,7 @@
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
<title><%= VITE_APP_TITLE %></title>
<title>%VITE_APP_TITLE%</title>
<link rel="icon" href="/favicon.ico" />
<script>
// 生产环境下注入百度统计

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "5.5.1",
"version": "5.7.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -27,6 +27,7 @@
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@fortawesome/fontawesome-free": "^6.7.2",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/constants": "workspace:*",
@@ -41,15 +42,24 @@
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"axios": "^1.10.0",
"dayjs": "catalog:",
"exceljs": "^4.4.0",
"html2canvas": "^1.4.1",
"js-base64": "^3.7.7",
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.487.0",
"lunar-typescript": "^1.8.6",
"markdown-it": "^14.1.0",
"md-editor-v3": "^5.4.5",
"pinia": "catalog:",
"sortablejs": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:",
"xgplayer": "^3.0.24",
"xlsx": "^0.18.5"
},
"devDependencies": {

View File

@@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

Binary file not shown.

View File

@@ -3,55 +3,616 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { BaseFormComponentType } from '@vben/common-ui';
/* eslint-disable vue/one-component-per-file */
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import type {
AutoCompleteProps,
ButtonProps,
CascaderProps,
CheckboxGroupProps,
CheckboxProps,
DatePickerProps,
DividerProps,
InputNumberProps,
InputProps,
MentionsProps,
RadioGroupProps,
RadioProps,
RateProps,
SelectProps,
SpaceProps,
SwitchProps,
TextAreaProps,
TimePickerProps,
TreeSelectProps,
UploadChangeParam,
UploadFile,
UploadProps,
} from 'ant-design-vue';
import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import type { Component, Ref } from 'vue';
import type {
ApiComponentSharedProps,
BaseFormComponentType,
IconPickerProps,
} from '@vben/common-ui';
import type { Sortable } from '@vben/hooks';
import type { Recordable } from '@vben/types';
import {
AutoComplete,
Button,
Cascader,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
computed,
defineAsyncComponent,
defineComponent,
h,
nextTick,
onMounted,
onUnmounted,
ref,
render,
unref,
watch,
} from 'vue';
import {
ApiComponent,
globalShareState,
IconPicker,
VCropper,
} from '@vben/common-ui';
import { useSortable } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isEmpty } from '@vben/utils';
import { message, Modal, notification } from 'ant-design-vue';
import { registerComponent } from '#/components/form/component-map';
type AdapterUploadProps = UploadProps & {
aspectRatio?: string;
crop?: boolean;
draggable?: boolean;
handleChange?: (event: UploadChangeParam) => void;
maxSize?: number;
onDragSort?: (oldIndex: number, newIndex: number) => void;
onHandleChange?: (event: UploadChangeParam) => void;
};
// ============================================================
// 所有 antd 组件统一用 defineAsyncComponent 异步加载
// ⚠️ 不要再从 'ant-design-vue' 同步 import 这些组件!
// 否则会和下面的 const 声明冲突,导致
// "Identifier 'Xxx' has already been declared"
// ============================================================
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Cascader = defineAsyncComponent(
() => import('ant-design-vue/es/cascader'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const Image = defineAsyncComponent(() => import('ant-design-vue/es/image'));
const PreviewGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),
);
// ============================================================
// withDefaultPlaceholder给组件注入 i18n placeholder + ref 透传
// ============================================================
const withDefaultPlaceholder = <T extends Component>(
component: T,
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
return defineComponent({
name: (component as any).name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () =>
h(
component,
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
slots,
);
},
});
};
const IMAGE_EXTENSIONS = new Set([
'bmp',
'gif',
'jpeg',
'jpg',
'png',
'svg',
'webp',
]);
/**
* 检查是否为图片文件
*/
function isImageFile(file: UploadFile): boolean {
if (file.url) {
try {
const pathname = new URL(file.url, 'http://localhost').pathname;
const ext = pathname.split('.').pop()?.toLowerCase();
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
} catch {
const ext = file.url?.split('.').pop()?.toLowerCase();
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
}
}
if (!file.type) {
const ext = file.name?.split('.').pop()?.toLowerCase();
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
}
return file.type.startsWith('image/');
}
/**
* 创建默认的上传按钮插槽
*/
function createDefaultUploadSlots(listType: string, placeholder: string) {
if (listType === 'picture-card') {
return { default: () => placeholder };
}
return {
default: () =>
h(
Button,
{
icon: h(IconifyIcon, {
icon: 'ant-design:upload-outlined',
class: 'mb-1 size-4',
}),
},
() => placeholder,
),
};
}
/**
* 获取文件的 Base64
*/
function getBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => resolve(reader.result as string));
reader.addEventListener('error', reject);
});
}
/**
* 预览图片
*/
async function previewImage(
file: UploadFile,
visible: Ref<boolean>,
fileList: Ref<UploadProps['fileList']>,
) {
// 非图片文件直接打开链接
if (!isImageFile(file)) {
const url = file.url || file.preview;
if (url) {
window.open(url, '_blank');
} else {
message.error($t('ui.formRules.previewWarning'));
}
return;
}
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
Image,
PreviewGroup,
]);
// 过滤图片文件并生成预览
const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));
for (const imgFile of imageFiles) {
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
imgFile.preview = await getBase64(imgFile.originFileObj);
}
}
const container = document.createElement('div');
document.body.append(container);
let isUnmounted = false;
const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);
const PreviewWrapper = {
setup() {
return () => {
if (isUnmounted) return null;
return h(
PreviewGroupComponent,
{
class: 'hidden',
preview: {
visible: visible.value,
current: currentIndex,
onVisibleChange: (value: boolean) => {
visible.value = value;
if (!value) {
setTimeout(() => {
if (!isUnmounted && container) {
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
}
},
},
},
() =>
imageFiles.map((imgFile) =>
h(ImageComponent, {
key: imgFile.uid,
src: imgFile.url || imgFile.preview,
}),
),
);
};
},
};
render(h(PreviewWrapper), container);
}
/**
* 图片裁剪操作
*/
function cropImage(file: File, aspectRatio: string | undefined) {
return new Promise<Blob | string | undefined>((resolve, reject) => {
const container = document.createElement('div');
document.body.append(container);
let isUnmounted = false;
let objectUrl: null | string = null;
const open = ref(true);
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
const closeModal = () => {
open.value = false;
setTimeout(() => {
if (!isUnmounted && container) {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
};
const CropperWrapper = {
setup() {
return () => {
if (isUnmounted) return null;
if (!objectUrl) {
objectUrl = URL.createObjectURL(file);
}
return h(
Modal,
{
open: open.value,
title: h('div', {}, [
$t('ui.crop.title'),
h(
'span',
{
class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
},
$t('ui.crop.titleTip', [aspectRatio]),
),
]),
centered: true,
width: 548,
zIndex: 9999,
keyboard: false,
maskClosable: false,
closable: false,
cancelText: $t('common.cancel'),
okText: $t('ui.crop.confirm'),
destroyOnClose: true,
onOk: async () => {
const cropper = cropperRef.value;
if (!cropper) {
reject(new Error('Cropper not found'));
closeModal();
return;
}
try {
const dataUrl = await cropper.getCropImage();
if (dataUrl) {
resolve(dataUrl);
} else {
reject(new Error($t('ui.crop.errorTip')));
}
} catch {
reject(new Error($t('ui.crop.errorTip')));
} finally {
closeModal();
}
},
onCancel() {
resolve('');
closeModal();
},
},
() =>
h(VCropper, {
ref: (refValue: any) => (cropperRef.value = refValue),
img: objectUrl as string,
aspectRatio,
}),
);
};
},
};
render(h(CropperWrapper), container);
});
}
/**
* 带预览功能的上传组件
*/
const withPreviewUpload = () => {
return defineComponent({
name: 'Upload',
emits: ['update:modelValue'],
setup(
props: any,
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
) {
const previewVisible = ref<boolean>(false);
const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');
const listType = attrs?.listType || attrs?.['list-type'] || 'text';
const fileList = ref<UploadProps['fileList']>(
attrs?.fileList || attrs?.['file-list'] || [],
);
const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
const aspectRatio = computed(
() => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
);
const handleBeforeUpload = async (
file: UploadFile,
originFileList: Array<File>,
) => {
// 文件大小限制
if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
file.status = 'removed';
return false;
}
// 图片裁剪处理
if (
attrs.crop &&
!attrs.multiple &&
originFileList[0] &&
isImageFile(file)
) {
file.status = 'removed';
const blob = await cropImage(originFileList[0], aspectRatio.value);
if (!blob) {
throw new Error($t('ui.crop.errorTip'));
}
return blob;
}
return attrs.beforeUpload?.(file) ?? true;
};
const handleChange = (event: UploadChangeParam) => {
try {
attrs.handleChange?.(event);
attrs.onHandleChange?.(event);
} catch (error) {
console.error(error);
}
fileList.value = event.fileList.filter(
(file) => file.status !== 'removed',
);
emit(
'update:modelValue',
event.fileList?.length ? fileList.value : undefined,
);
};
const handlePreview = async (file: UploadFile) => {
previewVisible.value = true;
await previewImage(file, previewVisible, fileList);
};
const renderUploadButton = () => {
if (attrs.disabled) return null;
return isEmpty(slots)
? createDefaultUploadSlots(listType, placeholder)
: slots;
};
// 拖拽排序
const draggable = computed(
() => (attrs.draggable ?? false) && !attrs.disabled,
);
const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
const sortableInstance = ref<null | Sortable>(null);
const styleId = `upload-drag-style-${uploadId}`;
function injectDragStyle() {
if (!document.querySelector(`[id="${styleId}"]`)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
[data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; }
[data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
`;
document.head?.append(style);
}
}
function removeDragStyle() {
document.querySelector(`[id="${styleId}"]`)?.remove();
}
async function initSortable(retryCount = 0) {
if (!draggable.value) return;
injectDragStyle();
await nextTick();
await new Promise((resolve) => setTimeout(resolve, 100));
const container = document.querySelector(
`[data-upload-id="${uploadId}"] .ant-upload-list`,
) as HTMLElement | null;
if (!container) {
if (retryCount < 5) {
setTimeout(() => initSortable(retryCount + 1), 200);
}
return;
}
const { initializeSortable } = useSortable(container, {
animation: 300,
delay: 400,
delayOnTouchOnly: true,
filter:
'.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',
onEnd: (evt: { oldIndex?: number; newIndex?: number }) => {
const { oldIndex, newIndex } = evt;
if (
oldIndex === undefined ||
newIndex === undefined ||
oldIndex === newIndex
) {
return;
}
const list = [...(fileList.value || [])];
const [movedItem] = list.splice(oldIndex, 1);
if (movedItem) {
list.splice(newIndex, 0, movedItem);
fileList.value = list;
}
attrs.onDragSort?.(oldIndex, newIndex);
emit('update:modelValue', fileList.value);
},
});
sortableInstance.value = await initializeSortable();
}
// 监听表单值变化
watch(
() => attrs.modelValue,
(res) => {
fileList.value = res;
},
);
onMounted(initSortable);
onUnmounted(() => {
sortableInstance.value?.destroy();
removeDragStyle();
});
return () =>
h(
'div',
{ 'data-upload-id': uploadId, class: 'w-full' },
h(
Upload,
{
...props,
...attrs,
fileList: fileList.value,
beforeUpload: handleBeforeUpload,
onChange: handleChange,
onPreview: handlePreview,
},
renderUploadButton() as any,
),
);
},
});
};
// 表单组件在这里加
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiCascader'
| 'ApiSelect'
| 'ApiTreeSelect'
| 'AutoComplete'
@@ -61,7 +622,8 @@ export type ComponentType =
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'GalleryPickLink' // OA 场景表单的图片/文件素材选择(透传 acceptTypes 过滤类型)
/** OA 场景表单的图片/文件素材选择(透传 acceptTypes 过滤类型) */
| 'GalleryPickLink'
| 'IconPicker'
| 'Input'
| 'InputNumber'
@@ -82,68 +644,92 @@ export type ComponentType =
| 'UploadImage'
| 'UploadImageSortable'
| 'UploadOssFile'
| 'UploadDraggerPaste'
| BaseFormComponentType;
/**
* 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
*/
export interface ComponentPropsMap {
ApiCascader: ApiComponentSharedProps & CascaderProps;
ApiSelect: ApiComponentSharedProps & SelectProps;
ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
AutoComplete: AutoCompleteProps;
Cascader: CascaderProps;
Checkbox: CheckboxProps;
CheckboxGroup: CheckboxGroupProps;
DatePicker: DatePickerProps;
DefaultButton: ButtonProps;
Divider: DividerProps;
IconPicker: IconPickerProps;
Input: InputProps;
InputNumber: InputNumberProps;
InputPassword: InputProps;
Mentions: MentionsProps;
PrimaryButton: ButtonProps;
Radio: RadioProps;
RadioGroup: RadioGroupProps;
RangePicker: RangePickerProps;
Rate: RateProps;
Select: SelectProps;
Space: SpaceProps;
Switch: SwitchProps;
Textarea: TextAreaProps;
TimePicker: TimePickerProps;
TreeSelect: TreeSelectProps;
Upload: AdapterUploadProps;
}
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
component: Cascader,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: Select,
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
}),
AutoComplete,
Cascader,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
DefaultButton: (props: any, { attrs, slots }: any) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs },
slots,
);
},
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'addonAfter',
inputComponent: Input,
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input', {
style: { width: '100%' },
}),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
PrimaryButton: (props: any, { attrs, slots }: any) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
@@ -156,10 +742,10 @@ async function initComponentAdapter() {
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
Upload: withPreviewUpload(),
};
// 自动注册自定义组件
// 自动注册 components/form 与 views/**/components/form 下的业务表单组件(如 Editor、Avatar
registerComponent(components);
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);

View File

@@ -1,49 +1,84 @@
import type {
VbenFormProps as FormProps,
VbenFormSchema as FormSchema,
VbenFormProps,
FormValues,
} from '@vben/common-ui';
import type { ComponentType } from './component';
import type { ComponentPropsMap, ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
UploadImage: 'modelValue',
UploadOssFile: 'modelValue',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
UploadImage: 'modelValue',
UploadOssFile: 'modelValue',
},
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
rules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
/**
* 大陆手机号:必填 + 11 位且号段 13-19与后端 UtilsService::checkPhone 一致)
* 表单字段 rules: 'mobile' 即可,无需再叠 required
*/
mobile: (value, _params, ctx) => {
const phone = typeof value === 'string' ? value.trim() : value;
if (phone === undefined || phone === null || phone === '') {
return $t('ui.formRules.required', [ctx.label]);
}
if (!/^1[3456789]\d{9}$/.test(String(phone))) {
return $t('ui.formRules.mobile', [ctx.label]);
}
return true;
},
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
});
}
const useVbenForm = useForm<ComponentType>;
function useVbenForm<
TFormValues extends FormValues = FormValues,
TSubmitValues extends FormValues = TFormValues,
>(
options: FormProps<
ComponentType,
ComponentPropsMap,
TFormValues,
TSubmitValues
>,
) {
return useForm<TFormValues, ComponentType, ComponentPropsMap, TSubmitValues>(
options,
);
}
export { useVbenForm, z };
export { initSetupVbenForm, useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };
export type VbenFormSchema<TValues extends FormValues = FormValues> =
FormSchema<ComponentType, ComponentPropsMap, TValues>;
export type VbenFormProps<
TFormValues extends FormValues = FormValues,
TSubmitValues extends FormValues = TFormValues,
> = FormProps<ComponentType, ComponentPropsMap, TFormValues, TSubmitValues>;

View File

@@ -1,6 +1,14 @@
import type { FormValues } from '@vben/common-ui';
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { ComponentPropsMap, ComponentType } from './component';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import {
setupVbenVxeTable,
useVbenVxeGrid as useGrid,
} from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
@@ -33,14 +41,15 @@ setupVbenVxeTable({
round: true,
showOverflow: true,
size: 'small',
},
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
renderTableDefault(renderOpts, params) {
const { props } = renderOpts;
const { column, row } = params;
return h(Image, { src: row[column.field] });
return h(Image, { src: row[column.field], ...props });
},
});
@@ -62,6 +71,23 @@ setupVbenVxeTable({
useVbenForm,
});
export { useVbenVxeGrid };
export const useVbenVxeGrid = <
T extends Record<string, any>,
TFormValues extends FormValues = FormValues,
TSubmitValues extends FormValues = TFormValues,
>(
...rest: Parameters<
typeof useGrid<
T,
ComponentType,
ComponentPropsMap,
TFormValues,
TSubmitValues
>
>
) =>
useGrid<T, ComponentType, ComponentPropsMap, TFormValues, TSubmitValues>(
...rest,
);
export type * from '@vben/plugins/vxe-table';

View File

@@ -54,12 +54,15 @@ export namespace AuthApi {
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
// PC 管理端固定 web供单机登录按端区分会话
const clientType = 'web';
if (data.account) {
return requestClient.post<AuthApi.LoginResult>('login', {
account: data.account,
password: data.password,
code: data.code,
admin_id: data.admin_id,
client_type: clientType,
});
}
@@ -67,6 +70,7 @@ export async function loginApi(data: AuthApi.LoginParams) {
login_method: data.login_method || 'phone',
password: data.password,
code: data.code,
client_type: clientType,
};
if (data.login_method === 'phone') {

View File

@@ -1,18 +1,19 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
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';
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { message, Modal } from 'ant-design-vue';
// eslint-disable-next-line n/no-extraneous-import
import { Base64 } from 'js-base64';
@@ -29,14 +30,17 @@ import {downloadByData} from "#/util/tool";
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,
});
/**
* 重新认证逻辑
*/
/** 防重复:并发 401 只弹一次重新登录提示 */
let authModalShowing = false;
async function doReAuthenticate() {
const accessStore = useAccessStore();
const authStore = useAuthStore();
@@ -47,6 +51,21 @@ function createRequestClient(baseURL: string) {
) {
accessStore.setLoginExpired(true);
} else {
// 被踢/登录失效时用模态框提示,确认后再退出
if (!authModalShowing) {
authModalShowing = true;
await new Promise<void>((resolve) => {
Modal.warning({
title: '登录失效',
content: '账号需重新登录',
okText: '重新登录',
centered: true,
onOk: () => resolve(),
onCancel: () => resolve(),
});
});
authModalShowing = false;
}
await authStore.logout(true);
}
}
@@ -157,7 +176,9 @@ function createRequestClient(baseURL: string) {
return client;
}
export const requestClient = createRequestClient(apiURL);
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'result',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@@ -9,7 +9,6 @@ import { App, ConfigProvider, theme } from 'ant-design-vue';
import { antdLocale } from '#/locales';
import { GlobalContextMenu } from '#/components/context-menu';
import DoctorTransferFloat from '#/components/doctor-transfer-float/DoctorTransferFloat.vue';
import { useWebSocket } from '#/views/business/chat/composables/useWebSocket';
import { useChatStore } from '#/views/business/chat/stores/chat';
import { useUserStore } from '#/views/business/chat/stores/user';
@@ -76,13 +75,6 @@ watch(userInfoLoaded, (loaded) => {
initWebsocket();
}
});
/** 诊所医生账号显示传方悬浮窗(有 doctor_id 且绑定门店) */
const showDoctorTransferFloat = computed(() => {
const info = vbenUserStore.userInfo as Record<string, any> | null;
if (!info) return false;
return !!(info.doctor_id && info.store_id);
});
</script>
<template>
@@ -90,7 +82,6 @@ const showDoctorTransferFloat = computed(() => {
<App>
<RouterView />
<GlobalContextMenu />
<DoctorTransferFloat v-if="showDoctorTransferFloat" />
</App>
</ConfigProvider>
</template>

View File

@@ -1,6 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -11,6 +12,7 @@ import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
@@ -18,8 +20,26 @@ async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// 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 +49,17 @@ async function bootstrap(namespace: string) {
// 安装权限指令
registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫
app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {

View File

@@ -1,12 +1,12 @@
import type { SpreadsheetCommand } from '../types';
export function createSetCellValueCommand(
undo: () => void | Promise<void>,
redo: () => void | Promise<void>,
): SpreadsheetCommand {
import type { SpreadsheetCommand } from '../types';
export function createSetCellValueCommand(
undo: () => void | Promise<void>,
redo: () => void | Promise<void>,
): SpreadsheetCommand {
return {
label: 'setCellValue',
undo,
redo,
};
}

View File

@@ -0,0 +1,752 @@
<script lang="ts" setup>
/**
* 中药药名气泡选择器
* - 对齐诊断/医嘱 EntryKeywordBubbleInput 聚焦弹出 Teleport 浮层
* - 远程搜索药品getProductListDoctorReception支持药名/别名/首拼/全拼/数字
* - 列表展示拼音列;命中别名时展示匹配别名及其拼音
* - 选中后回传完整商品行,供接诊/复诊/特色方/常用方复用现有落库逻辑
*/
import {
computed,
nextTick,
onMounted,
onUnmounted,
ref,
watch,
} from 'vue';
import { LoadingOutlined, SearchOutlined } from '@ant-design/icons-vue';
import { useDebounceFn } from '@vueuse/core';
import { Empty, Input, Spin } from 'ant-design-vue';
import {
rankByMatchScore,
splitHighlight,
type HighlightPart,
} from '#/utils/dictSearchRank';
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
export type ChineseDrugBubbleItem = {
id: number;
drug_id: number;
price: number;
buy_price?: number;
drug: Record<string, any>;
[key: string]: any;
};
const props = withDefaults(
defineProps<{
/** 已选药名(展示回填;打开搜索时仍可编辑关键词) */
displayName?: string;
/** 药品类型1 中药,颗粒等由调用方传入 */
type?: number;
storeId?: number;
registerId?: number;
placeholder?: string;
disabled?: boolean;
/** 卡片内紧凑宽度 */
compact?: boolean;
/** 额外 class便于焦点跳转 querySelector */
inputClass?: string;
}>(),
{
displayName: '',
type: 1,
storeId: 0,
registerId: 0,
placeholder: '药名',
disabled: false,
compact: false,
inputClass: '',
},
);
const emit = defineEmits<{
select: [item: ChineseDrugBubbleItem];
}>();
const ROW_HEIGHT = 48;
const HINT_HEIGHT = 34;
const HEAD_HEIGHT = 32;
const PANEL_PAD = 8;
const PREFERRED_PANEL = 360;
const containerRef = ref<HTMLElement | null>(null);
const panelRef = ref<HTMLElement | null>(null);
const listRef = ref<HTMLElement | null>(null);
const inputRef = ref<{ focus?: () => void } | null>(null);
const keyword = ref('');
const loading = ref(false);
const open = ref(false);
/** 是否处于「正在搜」true 时不把 displayName 强写回 keyword */
const searching = ref(false);
const highlightIndex = ref(0);
const options = ref<ChineseDrugBubbleItem[]>([]);
const scrollTop = ref(0);
const listViewportH = ref(200);
const panelStyle = ref<Record<string, string>>({});
const totalHeight = computed(() => options.value.length * ROW_HEIGHT);
const startIndex = computed(() =>
Math.max(0, Math.floor(scrollTop.value / ROW_HEIGHT) - 2),
);
const endIndex = computed(() => {
const visible = Math.ceil(listViewportH.value / ROW_HEIGHT) + 4;
return Math.min(options.value.length - 1, startIndex.value + visible);
});
const visibleRows = computed(() => {
const list = options.value;
const start = startIndex.value;
const end = endIndex.value;
if (list.length === 0 || end < start) return [];
return list.slice(start, end + 1).map((item, i) => ({
item,
index: start + i,
}));
});
const offsetY = computed(() => startIndex.value * ROW_HEIGHT);
/**
* 同步展示名:未打开搜索时用 displayName 回填输入框
*/
watch(
() => props.displayName,
(name) => {
if (!searching.value && !open.value) {
keyword.value = String(name || '');
}
},
{ immediate: true },
);
function partsOf(text: string): HighlightPart[] {
return splitHighlight(text, keyword.value.trim());
}
function drugNameOf(item: ChineseDrugBubbleItem): string {
return String(item?.drug?.drug_name || item?.drug_name || '').trim();
}
/** 关键词命中的别名(后端 matched_alias */
function matchedAliasOf(item: ChineseDrugBubbleItem): string {
return String(
item?.matched_alias || item?.drug?.matched_alias || '',
).trim();
}
/**
* 拼音展示:命中别名时用别名拼音,否则用药名首拼(全拼)
*/
function pinyinLabelOf(item: ChineseDrugBubbleItem): string {
const aliasPy = String(
item?.matched_alias_pinyin || item?.drug?.matched_alias_pinyin || '',
).trim();
if (matchedAliasOf(item) && aliasPy) return aliasPy;
const display = String(item?.drug?.pinyin_display || '').trim();
if (display) return display;
const simple = String(item?.drug?.pinyin_simple || '').trim();
const full = String(item?.drug?.pinyin_full || '').trim();
if (simple && full && simple.toLowerCase() !== full.toLowerCase()) {
return `${simple}(${full})`;
}
return simple || full || '-';
}
function unitLabelOf(item: ChineseDrugBubbleItem): string {
const unit = item?.drug?.unit || item?.unit;
if (typeof unit === 'object' && unit?.name) return String(unit.name);
return String(item?.drug?.specification || item?.specification || '').trim() || '-';
}
function priceLabelOf(item: ChineseDrugBubbleItem): string {
const p = Number(item?.price ?? 0);
return Number.isFinite(p) ? `¥${p.toFixed(2)}` : '-';
}
/**
* 面板 Teleport + fixed避免接诊/Modal overflow 裁切
*/
function updatePanelPlacement() {
const el = containerRef.value;
if (!el) return;
const rect = el.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
const gap = 4;
const spaceBelow = vh - rect.bottom - PANEL_PAD;
const spaceAbove = rect.top - PANEL_PAD;
const placeTop = spaceBelow < 180 && spaceAbove > spaceBelow;
const avail = Math.max(140, placeTop ? spaceAbove : spaceBelow);
const panelMax = Math.min(PREFERRED_PANEL, avail);
listViewportH.value = Math.max(80, panelMax - HINT_HEIGHT - HEAD_HEIGHT);
// 含拼音列后加宽,避免别名/全拼被裁切
const PANEL_MIN_WIDTH = 560;
const width = Math.min(
Math.max(rect.width, PANEL_MIN_WIDTH),
vw - PANEL_PAD * 2,
);
let left = rect.left;
if (left + width > vw - PANEL_PAD) {
left = vw - PANEL_PAD - width;
}
if (left < PANEL_PAD) left = PANEL_PAD;
const base: Record<string, string> = {
position: 'fixed',
zIndex: '4000',
width: `${width}px`,
maxHeight: `${panelMax}px`,
left: `${left}px`,
right: 'auto',
};
if (placeTop) {
panelStyle.value = {
...base,
top: 'auto',
bottom: `${Math.max(PANEL_PAD, vh - rect.top + gap)}px`,
};
} else {
panelStyle.value = {
...base,
top: `${rect.bottom + gap}px`,
bottom: 'auto',
};
}
}
/**
* 远程搜药;空关键词不请求(与原中药 Select 一致)
*/
async function fetchDrugs(kw: string) {
const name = String(kw || '').trim();
if (!name) {
options.value = [];
loading.value = false;
return;
}
loading.value = true;
try {
const res = await getProductListDoctorReception({
name,
type: props.type,
store_id: props.storeId || 0,
...(props.registerId > 0 ? { register_id: props.registerId } : {}),
});
const list = Array.isArray(res) ? res : [];
const mapped: ChineseDrugBubbleItem[] = list.map((item: any) => {
const drug = item.drug || {};
return {
...item,
id: Number(item.id || 0),
drug_id: Number(item.drug_id || drug.id || 0),
price: Number(item.price ?? 0),
buy_price: item.buy_price,
drug: {
...drug,
drug_name: drug.drug_name || item.drug_name || '',
},
};
});
options.value = rankByMatchScore(
mapped,
name,
(it) => drugNameOf(it),
(it) =>
[
it.drug?.pinyin_simple,
it.drug?.pinyin_full,
it.drug?.pinyin_display,
it.drug?.drug_alias,
it.matched_alias || it.drug?.matched_alias,
it.matched_alias_pinyin || it.drug?.matched_alias_pinyin,
it.drug?.search_digits,
it.drug?.specification,
]
.map((v) => String(v || ''))
.join(' '),
);
highlightIndex.value = options.value.length > 0 ? 0 : -1;
scrollTop.value = 0;
if (listRef.value) listRef.value.scrollTop = 0;
} catch (e) {
console.error('中药药名搜索失败:', e);
options.value = [];
} finally {
loading.value = false;
nextTick(() => updatePanelPlacement());
}
}
const debouncedFetch = useDebounceFn((kw: string) => {
void fetchDrugs(kw);
}, 280);
function openPanel() {
if (props.disabled) return;
open.value = true;
searching.value = true;
nextTick(() => {
updatePanelPlacement();
if (keyword.value.trim()) {
debouncedFetch(keyword.value);
} else {
options.value = [];
}
});
}
function closePanel(restoreDisplay = true) {
open.value = false;
searching.value = false;
options.value = [];
highlightIndex.value = 0;
if (restoreDisplay) {
keyword.value = String(props.displayName || '');
}
}
function handleFocus() {
if (props.disabled) return;
// 聚焦时清空为搜索态,便于直接输入新药名
if (props.displayName && keyword.value === props.displayName) {
keyword.value = '';
}
openPanel();
}
function handleInput() {
searching.value = true;
if (!open.value) openPanel();
else updatePanelPlacement();
debouncedFetch(keyword.value);
}
function selectItem(item: ChineseDrugBubbleItem) {
const name = drugNameOf(item);
keyword.value = name;
searching.value = false;
open.value = false;
options.value = [];
emit('select', item);
}
/**
* 键盘高亮循环移动(与诊断 EntryKeywordBubble 一致:第一项再上键 → 最后一项)
*/
function moveHighlight(delta: number) {
if (options.value.length === 0) return;
const len = options.value.length;
let next = highlightIndex.value;
if (next < 0) next = 0;
else next = (next + delta + len) % len;
highlightIndex.value = next;
ensureRowVisible(highlightIndex.value);
}
function handleKeydown(e: KeyboardEvent) {
if (!open.value) {
if (e.key === 'ArrowDown' || e.key === 'Enter') {
e.preventDefault();
openPanel();
}
return;
}
if (e.key === 'Escape') {
e.preventDefault();
closePanel(true);
return;
}
if (options.value.length === 0) return;
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
e.preventDefault();
moveHighlight(1);
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
e.preventDefault();
moveHighlight(-1);
} else if (e.key === 'Enter') {
e.preventDefault();
const hit = options.value[highlightIndex.value];
if (hit) selectItem(hit);
}
}
function ensureRowVisible(index: number) {
const el = listRef.value;
if (!el || index < 0) return;
const top = index * ROW_HEIGHT;
const bottom = top + ROW_HEIGHT;
if (top < el.scrollTop) el.scrollTop = top;
else if (bottom > el.scrollTop + el.clientHeight) {
el.scrollTop = bottom - el.clientHeight;
}
// 同步虚拟列表切片(循环跳到首/尾时必须刷新可见区)
scrollTop.value = el.scrollTop;
}
function onListScroll() {
scrollTop.value = listRef.value?.scrollTop || 0;
}
function onDocPointerDown(e: MouseEvent) {
if (!open.value) return;
const t = e.target as Node;
if (containerRef.value?.contains(t)) return;
if (panelRef.value?.contains(t)) return;
closePanel(true);
}
function onWinChange() {
if (open.value) updatePanelPlacement();
}
onMounted(() => {
document.addEventListener('mousedown', onDocPointerDown, true);
window.addEventListener('resize', onWinChange);
window.addEventListener('scroll', onWinChange, true);
});
onUnmounted(() => {
document.removeEventListener('mousedown', onDocPointerDown, true);
window.removeEventListener('resize', onWinChange);
window.removeEventListener('scroll', onWinChange, true);
});
defineExpose({
focus() {
nextTick(() => {
const el = inputRef.value as any;
el?.focus?.();
const native = containerRef.value?.querySelector(
'input',
) as HTMLInputElement | null;
native?.focus();
});
},
close() {
closePanel(true);
},
});
</script>
<template>
<div
ref="containerRef"
class="chinese-drug-name-bubble"
:class="[{ 'chinese-drug-name-bubble--compact': compact }, inputClass]"
>
<Input
ref="inputRef"
v-model:value="keyword"
:placeholder="placeholder"
:disabled="disabled"
allow-clear
size="small"
@input="handleInput"
@focus="handleFocus"
@keydown="handleKeydown"
>
<template #prefix>
<LoadingOutlined v-if="loading" class="text-muted-foreground" />
<SearchOutlined v-else class="text-muted-foreground" />
</template>
</Input>
<Teleport to="body">
<div
v-if="open"
ref="panelRef"
class="chinese-drug-name-bubble__panel"
:style="panelStyle"
>
<div class="chinese-drug-name-bubble__hint">
<span> 循环切换 · Enter 选用 · Esc 关闭</span>
<span class="chinese-drug-name-bubble__count">
<template v-if="keyword.trim()">搜到 {{ options.length }} </template>
<template v-else>输入药名搜索</template>
</span>
</div>
<Spin :spinning="loading">
<Empty
v-if="!loading && options.length === 0"
:image="Empty.PRESENTED_IMAGE_SIMPLE"
:description="
keyword.trim()
? '未找到匹配药材'
: '请输入药名/别名/拼音/数字搜索'
"
class="py-4"
/>
<template v-else>
<div
class="chinese-drug-name-bubble__head"
:class="{ 'has-score': !!keyword.trim() }"
>
<span class="col-name">药名</span>
<span class="col-py">拼音</span>
<span class="col-spec">规格/单位</span>
<span class="col-price">价格</span>
<span v-if="keyword.trim()" class="col-score">匹配度</span>
</div>
<div
ref="listRef"
class="chinese-drug-name-bubble__list"
:style="{ height: `${listViewportH}px` }"
@scroll="onListScroll"
>
<div
class="chinese-drug-name-bubble__phantom"
:style="{ height: `${totalHeight}px` }"
/>
<div
class="chinese-drug-name-bubble__virtual"
:style="{ transform: `translateY(${offsetY}px)` }"
>
<div
v-for="{ item, index } in visibleRows"
:key="`${item.drug_id}_${index}`"
class="chinese-drug-name-bubble__row"
:class="{
'is-active': index === highlightIndex,
'has-score': !!keyword.trim(),
}"
:style="{ height: `${ROW_HEIGHT}px` }"
@mousedown.prevent="selectItem(item)"
@mouseenter="highlightIndex = index"
>
<span
class="col-name"
:title="
matchedAliasOf(item)
? `${drugNameOf(item)}(别名:${matchedAliasOf(item)}`
: drugNameOf(item)
"
>
<span class="col-name__main">
<template
v-for="(p, pi) in partsOf(drugNameOf(item))"
:key="`n_${pi}`"
>
<em v-if="p.hit" class="hit">{{ p.text }}</em>
<template v-else>{{ p.text }}</template>
</template>
</span>
<span
v-if="matchedAliasOf(item)"
class="col-name__alias"
>
别名
<template
v-for="(p, pi) in partsOf(matchedAliasOf(item))"
:key="`a_${pi}`"
>
<em v-if="p.hit" class="hit">{{ p.text }}</em>
<template v-else>{{ p.text }}</template>
</template>
</span>
</span>
<span class="col-py" :title="pinyinLabelOf(item)">
<template v-if="pinyinLabelOf(item) === '-'">-</template>
<template v-else>
<template
v-for="(p, pi) in partsOf(pinyinLabelOf(item))"
:key="`py_${pi}`"
>
<em v-if="p.hit" class="hit">{{ p.text }}</em>
<template v-else>{{ p.text }}</template>
</template>
</template>
</span>
<span class="col-spec" :title="unitLabelOf(item)">
{{ unitLabelOf(item) }}
</span>
<span class="col-price">{{ priceLabelOf(item) }}</span>
<span v-if="keyword.trim()" class="col-score">
{{ Number(item.match_score || 0) }}%
</span>
</div>
</div>
</div>
</template>
</Spin>
</div>
</Teleport>
</div>
</template>
<style scoped>
.chinese-drug-name-bubble {
position: relative;
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
/* 锁死宽度:避免接诊/复诊/特色方/常用方 flex 布局把药名框撑满整行 */
width: 100px !important;
min-width: 88px !important;
max-width: 110px !important;
flex: 0 0 100px !important;
overflow: hidden;
}
.chinese-drug-name-bubble--compact {
width: 100px !important;
min-width: 88px !important;
max-width: 110px !important;
flex: 0 0 100px !important;
}
.chinese-drug-name-bubble :deep(.ant-input-affix-wrapper) {
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
padding-inline: 6px;
box-sizing: border-box;
}
.chinese-drug-name-bubble :deep(.ant-input-prefix) {
margin-inline-end: 2px;
}
.chinese-drug-name-bubble :deep(.ant-input) {
width: 100% !important;
min-width: 0 !important;
overflow: hidden;
text-overflow: ellipsis;
}
.chinese-drug-name-bubble__panel {
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: 8px;
border: 1px solid hsl(var(--border));
background: hsl(var(--popover, var(--background)));
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.chinese-drug-name-bubble__hint {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 6px 12px;
font-size: 12px;
color: hsl(var(--muted-foreground));
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.35);
}
.chinese-drug-name-bubble__count {
flex-shrink: 0;
font-weight: 600;
color: hsl(var(--foreground));
}
.chinese-drug-name-bubble__list {
position: relative;
overflow-y: auto;
overflow-x: hidden;
}
.chinese-drug-name-bubble__phantom {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: -1;
pointer-events: none;
}
.chinese-drug-name-bubble__virtual {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.chinese-drug-name-bubble__head,
.chinese-drug-name-bubble__row {
display: grid;
grid-template-columns: minmax(90px, 1.1fr) minmax(110px, 1.3fr) 90px 64px;
gap: 8px;
padding: 6px 12px;
font-size: 13px;
align-items: center;
box-sizing: border-box;
}
.chinese-drug-name-bubble__head.has-score,
.chinese-drug-name-bubble__row.has-score {
grid-template-columns: minmax(90px, 1.1fr) minmax(100px, 1.2fr) 80px 56px 48px;
}
.chinese-drug-name-bubble__head {
flex-shrink: 0;
font-weight: 600;
color: hsl(var(--muted-foreground));
background: hsl(var(--popover, var(--background)));
border-bottom: 1px solid hsl(var(--border));
}
.chinese-drug-name-bubble__row {
cursor: pointer;
border-bottom: 1px solid hsl(var(--border) / 0.6);
}
.chinese-drug-name-bubble__row:hover,
.chinese-drug-name-bubble__row.is-active {
background: hsl(var(--accent));
color: hsl(var(--accent-foreground));
}
.col-name,
.col-py,
.col-spec {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.col-name {
display: flex;
flex-direction: column;
gap: 1px;
min-width: 0;
font-weight: 500;
white-space: normal;
line-height: 1.25;
}
.col-name__main {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.col-name__alias {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 11px;
font-weight: 400;
color: hsl(var(--muted-foreground));
}
.col-py {
font-size: 12px;
color: hsl(var(--muted-foreground));
}
.col-spec {
font-size: 12px;
color: hsl(var(--muted-foreground));
}
.col-price {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.col-score {
text-align: right;
font-variant-numeric: tabular-nums;
font-weight: 600;
color: #d4380d;
white-space: nowrap;
}
.chinese-drug-name-bubble__row.is-active .col-spec,
.chinese-drug-name-bubble__row.is-active .col-py,
.chinese-drug-name-bubble__row.is-active .col-name__alias {
color: inherit;
opacity: 0.85;
}
.chinese-drug-name-bubble__row.is-active .col-score {
color: inherit;
}
.hit {
font-style: normal;
color: #cf1322;
background: rgba(255, 214, 102, 0.55);
border-radius: 2px;
padding: 0 1px;
}
</style>

View File

@@ -0,0 +1,5 @@
/**
* 中药药名气泡选择器
*/
export { default as ChineseDrugNameBubble } from './chinese-drug-name-bubble.vue';
export type { ChineseDrugBubbleItem } from './chinese-drug-name-bubble.vue';

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
/**
* 医生端 PC 传方悬浮窗:悬浮按钮 + 子窗口列表 + 状态操作
* 医生端 PC 传方入口:顶栏按钮 + 子窗口列表 + 状态操作
* 权限:有 doctor_id 且绑定 store_id 时由外层 v-if 控制,本组件只负责展示与操作
*/
import {
computed,
@@ -11,6 +12,7 @@ import {
} from 'vue';
import {
FileTextOutlined,
ReloadOutlined,
ZoomInOutlined,
ZoomOutOutlined,
@@ -19,6 +21,7 @@ import { usePreferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import {
Badge,
Button,
Dropdown,
Image,
@@ -29,10 +32,10 @@ import {
Spin,
Tag,
Textarea,
Tooltip,
message,
} from 'ant-design-vue';
import FloatFab from '#/components/float-fab/FloatFab.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import {
DEFAULT_SUB_WINDOW_Z_INDEX,
@@ -355,12 +358,20 @@ defineExpose({ loadList });
</script>
<template>
<FloatFab
storage-key="pc_doctor_transfer_fab"
label="传方"
:badge="pendingCount"
@click="openWindow"
/>
<div class="doctor-transfer-header-entry">
<Tooltip title="门店传方">
<Badge :count="pendingCount" :overflow-count="99" size="small">
<Button
type="text"
class="transfer-header-btn"
@click="openWindow"
>
<FileTextOutlined />
<span class="transfer-header-btn__text">传方</span>
</Button>
</Badge>
</Tooltip>
</div>
<SubWindow
v-model:open="windowOpen"
title="门店传方"
@@ -848,4 +859,25 @@ defineExpose({ loadList });
margin: 0 auto;
user-select: none;
}
.doctor-transfer-header-entry {
display: flex;
align-items: center;
margin-right: 4px;
}
.transfer-header-btn {
display: inline-flex;
align-items: center;
gap: 4px;
height: 32px;
padding: 0 8px;
color: hsl(var(--foreground));
}
.transfer-header-btn__text {
font-size: 13px;
}
@media (max-width: 640px) {
.transfer-header-btn__text {
display: none;
}
}
</style>

View File

@@ -6,19 +6,20 @@
* - 支持中药/西药类型切换
* - 下拉选项展示:药品图片、名称、供应商、规格、价格
* - 选择时返回完整药品数据(包含默认用法字段)
* - isCard单选确认场景如仓绑药选中后以内嵌卡片展示已选药品
* @author 系统
* @date 2024
*/
import { ref, watch, onMounted, onUnmounted } from 'vue';
import { SearchOutlined, LoadingOutlined } from '@ant-design/icons-vue';
import { Input, Spin } from 'ant-design-vue';
import { Button, Input, Spin } from 'ant-design-vue';
import { debounce } from 'lodash-es';
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
/** 中药无图时与仓库列表一致的占位图 */
const TCM_PLACEHOLDER_IMAGE =
/** 无图时占位(中药/西药等统一兜底,避免仓绑药下拉只剩文字) */
const DRUG_PLACEHOLDER_IMAGE =
'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250510/cd470ebf97e31c4048ba502ba71b66a3.jpg';
// ==================== Props 定义 ====================
@@ -29,12 +30,17 @@ interface Props {
* 1-中药2-西药
*/
type?: number;
/**
* 多药品类型(优先于 type有值时支持空关键词拉全量
* 例如 [2,4] 表示西药+中成药
*/
types?: number[];
/**
* 占位提示文本
*/
placeholder?: string;
/**
* 诊所ID
* 诊所ID(接诊/开方必传;仓药绑定等平台场景可省略,默认 0 走主库 types 检索)
*/
storeId?: number;
/**
@@ -45,14 +51,21 @@ interface Props {
* 是否禁用
*/
disabled?: boolean;
/**
* 是否以卡片展示已选药品(仓绑药等单选确认场景;开方默认 false
*/
isCard?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
type: 1,
types: () => [],
placeholder: '输入药品名称搜索',
storeId: 2,
// 默认 0平台无门店诊所场景由调用方传 :store-id
storeId: 0,
registerId: undefined,
disabled: false,
isCard: false,
});
// ==================== Emits 定义 ====================
@@ -97,10 +110,21 @@ const containerRef = ref<HTMLElement | null>(null);
*/
const highlightIndex = ref(-1);
/**
* isCard 模式下已选药品(用于输入框下方卡片)
*/
const selectedDrug = ref<any | null>(null);
/**
* 搜索框引用,便于「重新选择」时聚焦
*/
const inputRef = ref<{ focus?: () => void } | null>(null);
/**
* 解析下拉展示图:优先真图,无图用统一占位
*/
function resolveDropdownImage(item: any): string {
if (item._image) return item._image;
if (props.type === 1) return TCM_PLACEHOLDER_IMAGE;
return '';
return item._image || DRUG_PLACEHOLDER_IMAGE;
}
// ==================== 方法定义 ====================
@@ -108,10 +132,12 @@ function resolveDropdownImage(item: any): string {
/**
* 搜索药品
* @param keyword 搜索关键词
* @description 调用后端API搜索药品支持按名称和拼音搜索
* @description 调用后端API搜索药品支持按名称和拼音搜索
* 传入 types 时允许空关键词拉取该类型全量列表(用于下拉展开)
*/
const searchDrugs = debounce(async (keyword: string) => {
if (!keyword || keyword.length < 1) {
// 单类型模式:空关键词清空;多类型模式:空关键词仍请求后端拉全量
if ((!keyword || keyword.length < 1) && props.types.length === 0) {
searchResults.value = [];
showDropdown.value = false;
return;
@@ -121,37 +147,43 @@ const searchDrugs = debounce(async (keyword: string) => {
showDropdown.value = true;
try {
// types 优先:有多类型时只传 types不传 type避免后端被单 type 覆盖
const typeParams =
props.types.length > 0
? { types: props.types }
: { type: props.type };
const res = await getProductListDoctorReception({
name: keyword,
type: props.type,
name: keyword || '',
...typeParams,
store_id: props.storeId,
...(props.registerId ? { register_id: props.registerId } : {}),
});
// 处理返回数据
// 处理返回数据(兼容门店 relation 与平台主库打平结构)
if (Array.isArray(res)) {
searchResults.value = res.map((item: any) => ({
// 保留原始数据
...item,
// 提取常用字段便于访问
_id: item.id,
_drugId: item.drug_id || item.drug?.id,
_drugName: item.drug?.drug_name || item.drug_name || '',
_image: item.drug?.image || '',
_specification: item.drug?.specification || '',
_supplier: item.drug?.supplier?.name || '',
_price: item.price || 0,
// 默认用法字段
_timeId: item.drug?.time_id || 0,
_typeId: item.drug?.type_id || 0,
_frequencyId: item.drug?.frequency_id || 0,
_unitId: item.drug?.unit_id || 0,
_number: item.drug?.number || 1,
// 用法名称
_useNum: item.drug?.useNum,
_useType: item.drug?.useType,
_useFrequency: item.drug?.useFrequency,
}));
searchResults.value = res.map((item: any) => {
const drug = item.drug || {};
return {
...item,
_id: item.id,
_drugId: item.drug_id || drug.id || item.id,
_drugName: drug.drug_name || item.drug_name || '',
_image: drug.image || item.image || '',
_specification: drug.specification || item.specification || '',
_supplier: drug.supplier?.name || item.supplier?.name || '',
_price: Number(item.price ?? 0),
// 已绑定配送仓:列表展示「多仓」标识
_hasDeliveryWarehouse: Number(item.has_delivery_warehouse ?? 0) === 1,
_timeId: drug.time_id || 0,
_typeId: drug.type_id || 0,
_frequencyId: drug.frequency_id || 0,
_unitId: drug.unit_id || 0,
_number: drug.number || 1,
_useNum: drug.useNum,
_useType: drug.useType,
_useFrequency: drug.useFrequency,
};
});
} else {
searchResults.value = [];
}
@@ -176,8 +208,14 @@ function handleInput(e: Event) {
/**
* 处理输入框获得焦点
* @description 多类型模式聚焦即拉取列表并展开;单类型模式仅在已有结果时展示下拉
*/
function handleFocus() {
if (props.types.length > 0) {
searchDrugs('');
showDropdown.value = true;
return;
}
if (searchResults.value.length > 0) {
showDropdown.value = true;
}
@@ -186,15 +224,36 @@ function handleFocus() {
/**
* 处理选中药品
* @param drug 选中的药品数据
* @description isCard 时保留 selectedDrug 用于卡片展示;否则清空选中态
*/
function handleSelectDrug(drug: any) {
emit('select', drug);
// 清空搜索状态
searchKeyword.value = '';
searchResults.value = [];
showDropdown.value = false;
highlightIndex.value = -1;
if (props.isCard) {
selectedDrug.value = drug;
} else {
selectedDrug.value = null;
}
}
/**
* 清空已选卡片并重新搜索(仅 isCard
* 同步 emit null便于父级清空 drug_id
*/
function handleReselect() {
selectedDrug.value = null;
searchKeyword.value = '';
searchResults.value = [];
showDropdown.value = false;
highlightIndex.value = -1;
emit('select', null);
// 下一帧聚焦搜索框,方便重新选药
setTimeout(() => {
inputRef.value?.focus?.();
}, 0);
}
/**
@@ -261,6 +320,7 @@ watch(
searchResults.value = [];
showDropdown.value = false;
searchKeyword.value = '';
selectedDrug.value = null;
},
);
</script>
@@ -269,6 +329,7 @@ watch(
<div ref="containerRef" class="drug-search-select">
<!-- 搜索输入框 -->
<Input
ref="inputRef"
v-model:value="searchKeyword"
:placeholder="placeholder"
:disabled="disabled"
@@ -283,6 +344,49 @@ watch(
</template>
</Input>
<!-- isCard已选药品卡片与下拉项同结构带边框区分 -->
<div
v-if="isCard && selectedDrug"
class="drug-item drug-item--selected-card"
>
<div class="drug-item__image">
<img
:src="resolveDropdownImage(selectedDrug)"
alt=""
class="drug-item__img"
@error="
(e) => ((e.target as HTMLImageElement).src = DRUG_PLACEHOLDER_IMAGE)
"
/>
</div>
<div class="drug-item__info">
<div class="drug-item__name">{{ selectedDrug._drugName }}</div>
<div class="drug-item__meta">
<span class="drug-item__id">ID{{ selectedDrug._drugId }}</span>
<span v-if="selectedDrug._specification" class="drug-item__spec">
规格{{ selectedDrug._specification }}
</span>
<span v-if="selectedDrug._supplier" class="drug-item__supplier">
供应商{{ selectedDrug._supplier }}
</span>
</div>
</div>
<div class="drug-item__price">
<template v-if="Number(selectedDrug._price) > 0">
¥{{ Number(selectedDrug._price).toFixed(2) }}
</template>
<span v-else class="drug-item__price--empty">暂无</span>
</div>
<Button
type="link"
size="small"
class="drug-item__reselect"
@click="handleReselect"
>
重新选择
</Button>
</div>
<!-- 下拉列表 -->
<div v-if="showDropdown" class="drug-dropdown">
<!-- 加载中 -->
@@ -321,12 +425,14 @@ watch(
<div v-else class="drug-item__img-placeholder">无图</div>
</div>
<!-- 药品信息 -->
<!-- 药品信息药名 / 多仓 / ID / 规格 / 供应商 -->
<div class="drug-item__info">
<!-- 药品名称 -->
<div class="drug-item__name">{{ item._drugName }}</div>
<!-- 规格和供应商 -->
<div class="drug-item__name-row">
<div class="drug-item__name">{{ item._drugName }}</div>
<span v-if="item._hasDeliveryWarehouse" class="drug-item__multi-wh">多仓</span>
</div>
<div class="drug-item__meta">
<span class="drug-item__id">ID{{ item._drugId }}</span>
<span v-if="item._specification" class="drug-item__spec">
规格{{ item._specification }}
</span>
@@ -336,9 +442,12 @@ watch(
</div>
</div>
<!-- 价格 -->
<!-- 价格无价平台主库常见展示暂无 -->
<div class="drug-item__price">
¥{{ Number(item._price).toFixed(2) }}
<template v-if="Number(item._price) > 0">
¥{{ Number(item._price).toFixed(2) }}
</template>
<span v-else class="drug-item__price--empty">暂无</span>
</div>
</div>
</div>
@@ -347,6 +456,7 @@ watch(
</template>
<style lang="scss" scoped>
/* 只用框架主题变量,随亮暗/主题色自动适配,不写 .dark 硬编码覆盖 */
.drug-search-select {
position: relative;
width: 100%;
@@ -359,8 +469,8 @@ watch(
right: 0;
z-index: 1050;
margin-top: 4px;
background: #fff;
border: 1px solid #e5e7eb;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 8px;
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
max-height: 400px;
@@ -373,7 +483,7 @@ watch(
justify-content: center;
gap: 8px;
padding: 24px;
color: #999;
color: hsl(var(--muted-foreground));
font-size: 14px;
}
@@ -387,7 +497,7 @@ watch(
}
&::-webkit-scrollbar-thumb {
background: #d9d9d9;
background: hsl(var(--border));
border-radius: 3px;
}
@@ -407,7 +517,25 @@ watch(
&:hover,
&--active {
background-color: #f5f7fa;
background-color: hsl(var(--accent-hover));
}
/* 已选确认卡片:固定展示,非下拉浮层 */
&--selected-card {
margin-top: 8px;
cursor: default;
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 8px;
&:hover {
background: hsl(var(--muted) / 0.3);
}
}
&__reselect {
flex-shrink: 0;
padding: 0 4px;
}
&__image {
@@ -421,7 +549,7 @@ watch(
height: 48px;
object-fit: cover;
border-radius: 6px;
border: 1px solid #f0f0f0;
border: 1px solid hsl(var(--border));
}
&__img-placeholder {
@@ -430,10 +558,10 @@ watch(
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
background: hsl(var(--accent));
border-radius: 6px;
font-size: 12px;
color: #bbb;
color: hsl(var(--muted-foreground));
}
&__info {
@@ -442,14 +570,35 @@ watch(
overflow: hidden;
}
&__name-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
min-width: 0;
}
&__name {
font-size: 14px;
font-weight: 500;
color: #333;
color: hsl(var(--foreground));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 4px;
min-width: 0;
}
/* 配送仓绑定药品标识:小号胶囊,不抢主信息 */
&__multi-wh {
flex-shrink: 0;
padding: 0 6px;
height: 18px;
line-height: 16px;
font-size: 11px;
color: #2b6de5;
background: rgba(43, 109, 229, 0.08);
border: 1px solid rgba(43, 109, 229, 0.45);
border-radius: 999px;
}
&__meta {
@@ -457,57 +606,31 @@ watch(
flex-wrap: wrap;
gap: 12px;
font-size: 12px;
color: #999;
color: hsl(var(--muted-foreground));
}
&__id {
color: hsl(var(--muted-foreground));
}
&__spec {
color: #666;
color: hsl(var(--muted-foreground));
}
&__supplier {
color: #1890ff;
color: hsl(var(--primary));
}
&__price {
flex-shrink: 0;
font-size: 16px;
font-weight: 600;
color: #ff4d4f;
}
}
color: hsl(var(--destructive));
/* 暗色模式适配 */
.dark {
.drug-dropdown {
background: #1f2937;
border-color: #374151;
}
.drug-item {
&:hover,
&--active {
background-color: #374151;
}
&__img-placeholder {
background: #374151;
color: #6b7280;
}
&__name {
color: #f3f4f6;
}
&__meta {
color: #9ca3af;
}
&__spec {
color: #d1d5db;
}
&__supplier {
color: #60a5fa;
&--empty {
font-size: 13px;
font-weight: 400;
color: hsl(var(--muted-foreground));
}
}
}

View File

@@ -0,0 +1,196 @@
<script lang="ts" setup>
/**
* 通用文件预览:图片用 Ant ImagePDF fetch→blob→iframe支持多文件切换
* 开票中心与素材库复用,避免两套预览逻辑
*/
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { Button, Image, Modal, Spin, message } from 'ant-design-vue';
const props = withDefaults(
defineProps<{
/** 是否显示 */
open: boolean;
/** 文件 URL 列表 */
files?: string[];
/** 弹窗标题前缀 */
title?: string;
}>(),
{
files: () => [],
title: '文件预览',
},
);
const emit = defineEmits<{
(e: 'update:open', v: boolean): void;
}>();
const currentIndex = ref(0);
const previewIsImage = ref(false);
const previewImageUrl = ref('');
const previewPdfUrl = ref('');
const previewLoading = ref(false);
const visible = computed({
get: () => props.open,
set: (v: boolean) => emit('update:open', v),
});
const currentUrl = computed(() => props.files?.[currentIndex.value] || '');
const displayTitle = computed(() => {
const name = (currentUrl.value || '').split('/').pop() || '';
const total = props.files?.length || 0;
if (total > 1) {
return `${props.title}${currentIndex.value + 1}/${total}${name ? ' - ' + name : ''}`;
}
return name ? `${props.title} - ${name}` : props.title;
});
function isImageUrl(url: string) {
return /\.(jpg|jpeg|png|gif|webp|bmp)(\?|$)/i.test(url || '');
}
function revokePdfBlob() {
if (previewPdfUrl.value && previewPdfUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(previewPdfUrl.value);
}
previewPdfUrl.value = '';
}
/**
* 加载当前文件到预览区
*/
async function loadCurrent() {
const url = currentUrl.value;
revokePdfBlob();
if (!url) {
message.info('暂无文件');
visible.value = false;
return;
}
if (isImageUrl(url)) {
previewIsImage.value = true;
previewImageUrl.value = url;
previewLoading.value = false;
return;
}
previewIsImage.value = false;
previewLoading.value = true;
try {
const resp = await fetch(url);
if (!resp.ok) throw new Error('文件加载失败');
const blob = await resp.blob();
const pdfBlob =
blob.type && blob.type !== 'application/octet-stream'
? blob
: new Blob([blob], { type: 'application/pdf' });
previewPdfUrl.value = URL.createObjectURL(pdfBlob);
} catch {
message.error('预览失败,请尝试下载后查看');
visible.value = false;
} finally {
previewLoading.value = false;
}
}
function prevFile() {
if (currentIndex.value > 0) {
currentIndex.value -= 1;
}
}
function nextFile() {
if (props.files && currentIndex.value < props.files.length - 1) {
currentIndex.value += 1;
}
}
function handleClose() {
visible.value = false;
}
watch(
() => [props.open, props.files] as const,
([open]) => {
if (open) {
currentIndex.value = 0;
loadCurrent();
} else {
revokePdfBlob();
}
},
);
watch(currentIndex, () => {
if (props.open) {
loadCurrent();
}
});
onBeforeUnmount(() => {
revokePdfBlob();
});
</script>
<template>
<Modal
v-model:open="visible"
:title="displayTitle"
:footer="null"
width="860px"
destroy-on-close
@cancel="handleClose"
>
<div class="preview-wrap">
<div v-if="(files?.length || 0) > 1" class="nav">
<Button size="small" :disabled="currentIndex <= 0" @click="prevFile">上一份</Button>
<Button
size="small"
:disabled="currentIndex >= (files?.length || 0) - 1"
@click="nextFile"
>
下一份
</Button>
</div>
<Spin :spinning="previewLoading">
<div v-if="previewIsImage" class="img-box">
<Image :src="previewImageUrl" :preview="true" style="max-width: 100%" />
</div>
<iframe
v-else-if="previewPdfUrl"
:src="previewPdfUrl"
class="pdf-frame"
title="pdf-preview"
/>
</Spin>
</div>
</Modal>
</template>
<style scoped>
.preview-wrap {
min-height: 240px;
color: hsl(var(--foreground));
}
.nav {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.img-box {
text-align: center;
padding: 8px;
border-radius: 8px;
background: hsl(var(--muted) / 0.25);
border: 1px solid hsl(var(--border));
}
.pdf-frame {
width: 100%;
height: 70vh;
border: 1px solid hsl(var(--border));
border-radius: 8px;
background: hsl(var(--background));
}
</style>

View File

@@ -1,7 +1,7 @@
import type { CustomComponentType } from './types';
import type { Component } from 'vue';
import type { CustomComponentType } from './types';
import { getFileNameWithoutExtension, toPascalCase } from '#/util/tool';
const componentMap = new Map<CustomComponentType | string, Component>();

View File

@@ -1,346 +1,346 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import { useDebounceFn, useVModel } from '@vueuse/core';
import { Avatar, Empty, Input, Popover, Spin, Tabs, Tag } from 'ant-design-vue';
import { searchApiLogUsers } from '#/views/log/api-log/api';
defineOptions({
name: 'ApiLogUserPicker',
inheritAttrs: false,
});
export type ApiLogUserFilter = {
id: number;
user_type: 'admin' | 'patient' | 'doctor';
platform_type: 0 | 1 | 2;
name: string;
role_label: string;
avatar?: string;
};
type ApiLogUserOption = ApiLogUserFilter;
type UserTab = 'admin' | 'patient' | 'doctor';
const props = defineProps<{
value?: ApiLogUserFilter;
disabled?: boolean;
onPlatformTypeChange?: (platformType: 0 | 1 | 2) => void;
}>();
const emits = defineEmits<{
'update:value': [value: ApiLogUserFilter | undefined];
}>();
const mValue = useVModel(props, 'value', emits, { passive: true });
const open = ref(false);
const loading = ref(false);
const searchKeyword = ref('');
const activeTab = ref<UserTab>('admin');
const options = ref<ApiLogUserOption[]>([]);
const searchInputRef = ref<InstanceType<typeof Input> | null>(null);
const TAB_ITEMS = [
{ key: 'admin', label: '后台用户' },
{ key: 'patient', label: '患者端用户' },
{ key: 'doctor', label: '医生端用户' },
] as const;
const searchPlaceholder = computed(() => {
if (activeTab.value === 'admin') return '搜索手机号或昵称';
if (activeTab.value === 'patient') return '搜索用户名字';
return '搜索名字或手机号';
});
async function fetchOptions() {
const keyword = searchKeyword.value.trim();
if (!keyword) {
options.value = [];
return;
}
loading.value = true;
try {
const result = await searchApiLogUsers({
user_type: activeTab.value,
keyword,
pageSize: 24,
});
options.value = result?.items ?? [];
} finally {
loading.value = false;
}
}
const debouncedFetch = useDebounceFn(fetchOptions, 300);
watch([searchKeyword, activeTab], () => {
debouncedFetch();
});
function selectOption(item: ApiLogUserOption) {
mValue.value = {
id: item.id,
user_type: item.user_type,
platform_type: item.platform_type,
name: item.name,
role_label: item.role_label,
avatar: item.avatar,
};
props.onPlatformTypeChange?.(item.platform_type);
open.value = false;
searchKeyword.value = '';
options.value = [];
}
function clearSelection(e: Event) {
e.stopPropagation();
mValue.value = undefined;
}
function onOpenChange(next: boolean) {
if (props.disabled) return;
open.value = next;
if (next) {
searchKeyword.value = '';
options.value = [];
nextTick(() => searchInputRef.value?.focus?.());
}
}
watch(
() => props.disabled,
(disabled) => {
if (disabled) open.value = false;
},
);
</script>
<template>
<Popover
:open="open"
trigger="click"
placement="bottomLeft"
overlay-class-name="api-log-user-picker-popover"
@open-change="onOpenChange"
>
<template #content>
<div class="api-log-user-panel">
<Tabs v-model:active-key="activeTab" size="small" class="api-log-user-tabs">
<Tabs.TabPane
v-for="tab in TAB_ITEMS"
:key="tab.key"
:tab="tab.label"
/>
</Tabs>
<Input
ref="searchInputRef"
v-model:value="searchKeyword"
allow-clear
:placeholder="searchPlaceholder"
class="api-log-user-search"
/>
<Spin :spinning="loading">
<div v-if="options.length" class="api-log-user-grid">
<button
v-for="item in options"
:key="`${item.user_type}-${item.id}`"
type="button"
class="api-log-user-card"
:class="{
active:
mValue?.id === item.id && mValue?.user_type === item.user_type,
}"
@click="selectOption(item)"
>
<Avatar :src="item.avatar" :size="30">
{{ (item.name || '?').charAt(0) }}
</Avatar>
<div class="api-log-user-card-id">#{{ item.id }}</div>
<div class="api-log-user-card-name" :title="item.name">
{{ item.name }}
</div>
<Tag class="api-log-user-card-role" :bordered="false">
{{ item.role_label }}
</Tag>
</button>
</div>
<Empty
v-else
:description="searchKeyword.trim() ? '无匹配用户' : '请输入关键词搜索'"
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import { useDebounceFn, useVModel } from '@vueuse/core';
import { Avatar, Empty, Input, Popover, Spin, Tabs, Tag } from 'ant-design-vue';
import { searchApiLogUsers } from '#/views/log/api-log/api';
defineOptions({
name: 'ApiLogUserPicker',
inheritAttrs: false,
});
export type ApiLogUserFilter = {
id: number;
user_type: 'admin' | 'patient' | 'doctor';
platform_type: 0 | 1 | 2;
name: string;
role_label: string;
avatar?: string;
};
type ApiLogUserOption = ApiLogUserFilter;
type UserTab = 'admin' | 'patient' | 'doctor';
const props = defineProps<{
value?: ApiLogUserFilter;
disabled?: boolean;
onPlatformTypeChange?: (platformType: 0 | 1 | 2) => void;
}>();
const emits = defineEmits<{
'update:value': [value: ApiLogUserFilter | undefined];
}>();
const mValue = useVModel(props, 'value', emits, { passive: true });
const open = ref(false);
const loading = ref(false);
const searchKeyword = ref('');
const activeTab = ref<UserTab>('admin');
const options = ref<ApiLogUserOption[]>([]);
const searchInputRef = ref<InstanceType<typeof Input> | null>(null);
const TAB_ITEMS = [
{ key: 'admin', label: '后台用户' },
{ key: 'patient', label: '患者端用户' },
{ key: 'doctor', label: '医生端用户' },
] as const;
const searchPlaceholder = computed(() => {
if (activeTab.value === 'admin') return '搜索手机号或昵称';
if (activeTab.value === 'patient') return '搜索用户名字';
return '搜索名字或手机号';
});
async function fetchOptions() {
const keyword = searchKeyword.value.trim();
if (!keyword) {
options.value = [];
return;
}
loading.value = true;
try {
const result = await searchApiLogUsers({
user_type: activeTab.value,
keyword,
pageSize: 24,
});
options.value = result?.items ?? [];
} finally {
loading.value = false;
}
}
const debouncedFetch = useDebounceFn(fetchOptions, 300);
watch([searchKeyword, activeTab], () => {
debouncedFetch();
});
function selectOption(item: ApiLogUserOption) {
mValue.value = {
id: item.id,
user_type: item.user_type,
platform_type: item.platform_type,
name: item.name,
role_label: item.role_label,
avatar: item.avatar,
};
props.onPlatformTypeChange?.(item.platform_type);
open.value = false;
searchKeyword.value = '';
options.value = [];
}
function clearSelection(e: Event) {
e.stopPropagation();
mValue.value = undefined;
}
function onOpenChange(next: boolean) {
if (props.disabled) return;
open.value = next;
if (next) {
searchKeyword.value = '';
options.value = [];
nextTick(() => searchInputRef.value?.focus?.());
}
}
watch(
() => props.disabled,
(disabled) => {
if (disabled) open.value = false;
},
);
</script>
<template>
<Popover
:open="open"
trigger="click"
placement="bottomLeft"
overlay-class-name="api-log-user-picker-popover"
@open-change="onOpenChange"
>
<template #content>
<div class="api-log-user-panel">
<Tabs v-model:active-key="activeTab" size="small" class="api-log-user-tabs">
<Tabs.TabPane
v-for="tab in TAB_ITEMS"
:key="tab.key"
:tab="tab.label"
/>
</Tabs>
<Input
ref="searchInputRef"
v-model:value="searchKeyword"
allow-clear
:placeholder="searchPlaceholder"
class="api-log-user-search"
/>
<Spin :spinning="loading">
<div v-if="options.length" class="api-log-user-grid">
<button
v-for="item in options"
:key="`${item.user_type}-${item.id}`"
type="button"
class="api-log-user-card"
:class="{
active:
mValue?.id === item.id && mValue?.user_type === item.user_type,
}"
@click="selectOption(item)"
>
<Avatar :src="item.avatar" :size="30">
{{ (item.name || '?').charAt(0) }}
</Avatar>
<div class="api-log-user-card-id">#{{ item.id }}</div>
<div class="api-log-user-card-name" :title="item.name">
{{ item.name }}
</div>
<Tag class="api-log-user-card-role" :bordered="false">
{{ item.role_label }}
</Tag>
</button>
</div>
<Empty
v-else
:description="searchKeyword.trim() ? '无匹配用户' : '请输入关键词搜索'"
class="api-log-user-empty"
/>
</Spin>
</div>
</template>
<div class="api-log-user-trigger" :class="{ disabled: disabled }">
<div v-if="mValue" class="api-log-selected-card">
<Avatar :src="mValue.avatar" :size="36">
{{ (mValue.name || '?').charAt(0) }}
</Avatar>
<div class="api-log-selected-meta">
<div class="api-log-selected-name">{{ mValue.name }}</div>
<div class="api-log-selected-sub">
#{{ mValue.id }} · {{ mValue.role_label }}
</div>
</div>
<span
v-if="!disabled"
class="api-log-clear-btn"
@click="clearSelection"
>
×
</span>
</div>
<div v-else class="api-log-trigger-placeholder">请选择操作用户</div>
</div>
</Popover>
</template>
<style scoped lang="scss">
@use './picker-card-theme.scss' as theme;
.api-log-user-trigger {
width: 100%;
}
.api-log-user-trigger.disabled {
cursor: not-allowed;
opacity: 0.65;
}
.api-log-user-trigger:not(.disabled) {
cursor: pointer;
}
.api-log-selected-card {
@include theme.picker-selected-card;
}
.api-log-selected-meta {
min-width: 0;
flex: 1;
}
.api-log-selected-name {
font-size: 14px;
font-weight: 500;
color: #1d2129;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.api-log-selected-sub {
font-size: 12px;
color: #86909c;
line-height: 1.4;
}
.api-log-trigger-placeholder {
@include theme.picker-trigger-placeholder;
}
.api-log-clear-btn {
@include theme.picker-clear-btn;
}
.dark {
.api-log-selected-card {
@include theme.picker-selected-card-dark-props;
}
.api-log-selected-name {
@include theme.picker-text-primary-dark;
}
.api-log-selected-sub {
@include theme.picker-text-secondary-dark;
}
.api-log-trigger-placeholder {
@include theme.picker-placeholder-dark-props;
}
.api-log-clear-btn {
@include theme.picker-clear-dark-props;
}
}
</style>
<style lang="scss">
@use './picker-card-theme.scss' as theme;
.api-log-user-picker-popover {
.ant-popover-inner {
padding: 12px;
}
.api-log-user-panel {
width: 540px;
max-width: 86vw;
}
.api-log-user-tabs {
margin-bottom: 8px;
}
.api-log-user-search {
margin-bottom: 10px;
}
.api-log-user-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
gap: 8px;
max-height: 300px;
overflow-y: auto;
padding: 2px;
}
.api-log-user-card {
@include theme.picker-card-base;
}
.api-log-user-card-id {
@include theme.picker-card-sub;
}
.api-log-user-card-name {
@include theme.picker-card-name;
}
.api-log-user-card-role {
margin: 0;
font-size: 11px;
line-height: 18px;
padding: 0 6px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.api-log-user-empty {
margin: 16px 0;
}
}
.dark .api-log-user-picker-popover {
.api-log-user-card {
@include theme.picker-card-dark-props;
}
.api-log-user-card-name {
@include theme.picker-text-primary-dark;
}
.api-log-user-card-id {
@include theme.picker-text-secondary-dark;
}
}
</style>

View File

@@ -79,7 +79,7 @@ const handleRemove = (e: Event) => {
</div>
<Icon v-else icon="ant-design:plus-outlined" />
</Upload>
<GalleryPickLink @select="onGallerySelect" />
<GalleryPickLink :accept-types="[0]" @select="onGallerySelect" />
</div>
</template>
<style lang="less" scoped>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { ref } from 'vue';
import { preferences } from '@vben/preferences';
import { QuillEditor } from '@vueup/vue-quill';
// eslint-disable-next-line n/no-extraneous-import
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { useVModel } from '@vueuse/core';
import { message } from 'ant-design-vue';
@@ -11,7 +11,9 @@ import { message } from 'ant-design-vue';
import { uploadFile } from '#/api/core/upload';
import { uploadToOss } from '#/utils/oss-upload';
import { prepareImageForUpload } from '#/utils/prepare-image-upload';
import { preferences } from '@vben/preferences';
// eslint-disable-next-line n/no-extraneous-import
import '@vueup/vue-quill/dist/vue-quill.snow.css';
const props = defineProps({
value: {
@@ -140,26 +142,26 @@ const insertImageToEditor = (quill: any, imageUrl: string): void => {
};
/**
* 图片上传函数
*
*
* 该函数根据preferences配置的上传方式选择使用OSS直传或后端上传
* - 'direct': OSS直传模式文件直接从浏览器上传到阿里云OSS
* - 'backend': 后端上传模式文件先上传到后端服务器再由后端上传到OSS
*
*
* 上传方式说明:
* 1. OSS直传direct
* - 调用uploadToOss函数直接上传到OSS
* - 减少服务器负载,上传速度更快
* - 支持上传进度回调(如果需要可以添加)
*
*
* 2. 后端上传backend
* - 调用uploadFile API通过后端服务器上传
* - 兼容现有功能,所有上传逻辑由后端统一处理
*
*
* 兼容性处理:
* - 两种上传方式返回的数据结构保持一致:{ url: string }
* - 上传成功或失败都会显示相应的提示信息
* - 上传完成后将isUpload标记设置为false允许下次上传
*
*
* @param {File} file - 要上传的图片文件
* @returns {Promise<string>} - 返回上传后的图片URL如果上传失败返回空字符串
*/
@@ -205,22 +207,22 @@ const uploadImage = async (file: File): Promise<string> => {
key: 'imageUpload',
duration: 2,
});
// 重置上传标记,允许下次上传
isUpload.value = false;
// 返回上传后的图片URL
return data.url;
} catch (error) {
// 处理上传错误
console.error('图片上传错误:', error);
// 显示错误提示
message.error({ content: '图片上传失败', key: 'imageUpload', duration: 2 });
// 重置上传标记,允许重试
isUpload.value = false;
// 返回空字符串,表示上传失败
return '';
}

View File

@@ -4,6 +4,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { useVModel } from '@vueuse/core';
import { Empty, Input, Popover, Spin } from 'ant-design-vue';
import { matchExpressByTrackingNo } from '#/utils/matchExpressByTrackingNo';
import { getExpressCompaniesOption } from '#/views/business/express/express-company/api';
defineOptions({
@@ -12,9 +13,11 @@ defineOptions({
});
const props = defineProps<{
value?: string;
disabled?: boolean;
placeholder?: string;
/** 运单号:变化时按前缀自动匹配快递公司(用户手动改选后不再覆盖) */
trackingNo?: string;
value?: string;
}>();
const emits = defineEmits<{
@@ -24,9 +27,9 @@ const emits = defineEmits<{
const mValue = useVModel(props, 'value', emits, { passive: true });
type ExpressCompanyOption = {
code: string;
id: number;
name: string;
code: string;
};
const open = ref(false);
@@ -34,8 +37,13 @@ const loading = ref(false);
const searchKeyword = ref('');
const options = ref<ExpressCompanyOption[]>([]);
const searchInputRef = ref<InstanceType<typeof Input> | null>(null);
/** 用户是否已手动选择/清空;为 true 时不再按单号自动覆盖 */
const userPicked = ref(false);
function filterExpressCompanyOptions(keyword: string, list: ExpressCompanyOption[]) {
function filterExpressCompanyOptions(
keyword: string,
list: ExpressCompanyOption[],
) {
const q = keyword.trim().toLowerCase();
if (!q) return list;
return list.filter((item) => {
@@ -56,20 +64,39 @@ const selectedOption = computed(() =>
const displayText = computed(() => {
const item = selectedOption.value;
if (!item) return '';
return item.code ? `${item.name || '-'}${item.code}` : (item.name || '-');
return item.code ? `${item.name || '-'}${item.code}` : item.name || '-';
});
/**
* 根据 trackingNo 自动选中快递公司
* 规则:尚未手动改选,或当前选中仍与规则结果一致时才覆盖
*/
function tryAutoMatchByTrackingNo() {
if (options.value.length === 0) return;
const matched = matchExpressByTrackingNo(props.trackingNo, options.value);
if (!matched?.code) return;
const matchedCode = matched.code;
if (userPicked.value && mValue.value && mValue.value !== matchedCode) {
// 用户已选其他公司,不覆盖
return;
}
if (mValue.value === matchedCode) return;
mValue.value = matchedCode;
}
async function loadOptions() {
loading.value = true;
try {
const res = await getExpressCompaniesOption({});
options.value = Array.isArray(res) ? res : [];
tryAutoMatchByTrackingNo();
} finally {
loading.value = false;
}
}
function selectOption(item: ExpressCompanyOption) {
userPicked.value = true;
mValue.value = item.code;
open.value = false;
searchKeyword.value = '';
@@ -77,6 +104,7 @@ function selectOption(item: ExpressCompanyOption) {
function clearSelection(e: Event) {
e.stopPropagation();
userPicked.value = true;
mValue.value = undefined;
}
@@ -85,7 +113,7 @@ function onOpenChange(next: boolean) {
open.value = next;
if (next) {
searchKeyword.value = '';
if (!options.value.length) {
if (options.value.length === 0) {
loadOptions();
}
nextTick(() => searchInputRef.value?.focus?.());
@@ -99,6 +127,14 @@ watch(
},
);
watch(
() => props.trackingNo,
() => {
// 单号变化时允许再自动匹配(除非用户已改选为不一致公司)
tryAutoMatchByTrackingNo();
},
);
onMounted(() => {
loadOptions();
});
@@ -107,9 +143,9 @@ onMounted(() => {
<template>
<Popover
:open="open"
trigger="click"
placement="bottomLeft"
overlay-class-name="express-company-select-popover"
placement="bottomLeft"
trigger="click"
@open-change="onOpenChange"
>
<template #content>
@@ -118,17 +154,17 @@ onMounted(() => {
ref="searchInputRef"
v-model:value="searchKeyword"
allow-clear
placeholder="搜索公司名称或编码"
class="express-search"
placeholder="搜索公司名称或编码"
/>
<Spin :spinning="loading">
<div v-if="filteredOptions.length" class="express-list">
<div v-if="filteredOptions.length > 0" class="express-list">
<button
v-for="item in filteredOptions"
:key="item.id"
type="button"
class="express-item"
:class="{ active: item.code === mValue }"
class="express-item"
type="button"
@click="selectOption(item)"
>
<div class="express-meta">
@@ -137,20 +173,20 @@ onMounted(() => {
</div>
</button>
</div>
<Empty v-else description="无匹配快递公司" class="express-empty" />
<Empty v-else class="express-empty" description="无匹配快递公司" />
</Spin>
</div>
</template>
<div
:class="{ disabled, placeholder: !displayText }"
class="express-trigger"
:class="{ disabled: disabled, placeholder: !displayText }"
>
<Input
:value="displayText"
readonly
:disabled="disabled"
:placeholder="placeholder || '请选择快递公司'"
:value="displayText"
class="express-trigger-input"
readonly
>
<template v-if="displayText && !disabled" #suffix>
<span class="clear-btn" @click="clearSelection">×</span>
@@ -163,6 +199,8 @@ onMounted(() => {
<style scoped>
.express-trigger {
width: 100%;
/* min-width: fit-content; */
min-width: 250px;
cursor: pointer;
}
.express-trigger.disabled {
@@ -177,7 +215,7 @@ onMounted(() => {
.clear-btn {
pointer-events: auto;
cursor: pointer;
color: #86909c;
color: hsl(var(--muted-foreground));
font-size: 16px;
line-height: 1;
padding: 0 4px;
@@ -203,10 +241,11 @@ onMounted(() => {
text-align: left;
cursor: pointer;
border-radius: 6px;
color: hsl(var(--foreground));
}
.express-item:hover,
.express-item.active {
background: #f2f3f5;
background: hsl(var(--accent));
}
.express-meta {
min-width: 0;
@@ -214,11 +253,11 @@ onMounted(() => {
}
.express-name {
font-size: 14px;
color: #1d2129;
color: hsl(var(--foreground));
}
.express-sub {
font-size: 12px;
color: #86909c;
color: hsl(var(--muted-foreground));
}
.express-empty {
margin: 12px 0;
@@ -228,5 +267,8 @@ onMounted(() => {
<style>
.express-company-select-popover .ant-popover-inner {
padding: 12px;
background: hsl(var(--card));
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
}
</style>

View File

@@ -9,8 +9,8 @@ const props = withDefaults(
maxCount?: number;
disabled?: boolean;
/**
* 限制可选择的文件类型(透传给 image-gallery-picker按 xk_file_type.value 过滤)
* 例如图片:[1];文件:[6];语音:[3]
* 限制可选择的文件类型(透传给 image-gallery-picker xk_file.type / xk_file_type.value 过滤)
* 例如图片:[0];视频:[1];文件:[6];语音:[3]
*/
acceptTypes?: number[];
}>(),

View File

@@ -18,7 +18,7 @@ const props = withDefaults(
* 限制可选择的文件类型(按 xk_file_type.value 过滤)
* - 不传:显示全部类型(默认行为,保持向后兼容)
* - 传数组:只展示指定类型 tab且自动锁定到第一个匹配的类型
* 例如图片:[1];图片+视频:[1, 2];文件:[6]
* 例如图片:[0];图片+视频:[0, 1];文件:[6]
*/
acceptTypes?: number[];
}>(),
@@ -217,9 +217,9 @@ function itemIcon(item: FileGalleryItem) {
return item.type_icon || resolveTypeIcon(item.type, fileTypes.value);
}
/** 图片类型展示缩略图xk_file_type1=图片) */
/** 图片类型展示缩略图xk_file.type0=图片;兼容历史错误写入的 1 */
function isImageItem(item: FileGalleryItem) {
return Number(item.type) === 1 || Number(item.type) === 0;
return Number(item.type) === 0 || Number(item.type) === 1;
}
</script>

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
/**
* Markdown 编辑器表单组件(封装 md-editor-v3
* 经 component-map 自动注册schema 中以 component: 'MarkdownEditor' 使用vben form 默认 v-model:value 绑定)
* 为什么封装:统一精简工具栏(轻量备注场景不需要图片/公式等重型功能)+ 主题跟随系统亮暗切换,避免各表单重复配置
*/
import type { ToolbarNames } from 'md-editor-v3';
import { computed, defineAsyncComponent } from 'vue';
import { usePreferences } from '@vben/preferences';
import { useVModel } from '@vueuse/core';
import 'md-editor-v3/lib/style.css';
// 本文件经 component-map 的 eager glob 全量打进主包md-editor-v3含 CodeMirror体积大
// 必须异步加载:外壳进主包、库本体在编辑器真正渲染时才拉取,避免拖累全站首屏
const MdEditor = defineAsyncComponent(() =>
import('md-editor-v3').then((m) => m.MdEditor),
);
const props = defineProps({
value: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '请输入内容(支持 Markdown 格式)',
},
/** 编辑器整体高度px弹窗场景建议 200-300 */
height: {
type: Number,
default: 240,
},
});
const emits = defineEmits(['update:value']);
const mValue = useVModel(props, 'value', emits, {
defaultValue: props.value,
passive: true,
});
/** 主题必须跟随系统亮/暗切换,不能写死(与消息详情 MdPreview 的处理一致) */
const { isDark } = usePreferences();
const theme = computed(() => (isDark.value ? 'dark' : 'light'));
/** 同页面可能挂载多个编辑器实例(如弹窗嵌套),随机 id 避免内部 DOM id 冲突 */
const editorId = `md-form-${Math.random().toString(36).slice(2, 9)}`;
/** 精简工具栏:加粗/斜体/删除线、标题/引用/列表/任务、代码/链接/表格,右侧仅保留预览切换 */
const toolbars: ToolbarNames[] = [
'bold',
'italic',
'strikeThrough',
'-',
'title',
'quote',
'unorderedList',
'orderedList',
'task',
'-',
'code',
'link',
'table',
'=',
'preview',
];
</script>
<template>
<MdEditor
:id="editorId"
v-model="mValue"
:theme="theme"
:placeholder="placeholder"
:toolbars="toolbars"
:footers="[]"
:preview="false"
:style="{ height: `${height}px` }"
class="md-form-editor"
/>
</template>
<style scoped>
/* 圆角与边框贴近 antd 输入组件,融入表单视觉 */
.md-form-editor {
width: 100%;
border: 1px solid hsl(var(--border));
border-radius: 6px;
}
</style>

View File

@@ -0,0 +1,605 @@
<script setup lang="ts">
/**
* 门店多选搜索(对齐开方关键字气泡交互)
* - Teleport + fixed 锚定,避免搜索栏 overflow 裁切导致「气泡不见」
* - 多选:点选后不关闭面板,已选项从列表剔除并以 Tag 展示
* - Enter / ↑↓ 键盘选用Esc 关闭
*/
import {
computed,
nextTick,
onMounted,
onUnmounted,
ref,
watch,
} from 'vue';
import { useUserStore } from '@vben/stores';
import { LoadingOutlined, SearchOutlined } from '@ant-design/icons-vue';
import { useDebounceFn, useVModel } from '@vueuse/core';
import { Empty, Input, Spin, Tag } from 'ant-design-vue';
import { searchStoreOption } from '#/views/system/store/api';
defineOptions({
name: 'StoreMultiSearch',
inheritAttrs: false,
});
export type StoreSearchItem = {
id: number;
name: string;
type?: number;
shouzimu?: string;
};
const props = withDefaults(
defineProps<{
value?: number[];
/** 0诊所 1药店不传则诊所+药店 */
storeType?: 0 | 1;
placeholder?: string;
disabled?: boolean;
initialItems?: StoreSearchItem[];
}>(),
{
value: () => [],
storeType: undefined,
placeholder: '输入名称或拼音首拼搜索',
disabled: false,
initialItems: () => [],
},
);
/**
* 模块级搜索结果缓存所有实例共享key = `${userId}|${storeType}|${keyword}`
* 门店数据变更低频TTL 内相同关键词直接命中缓存零等待,避免每敲一个字都等接口转圈。
* key 必须拼登录用户 id不同账号数据权限不同店管只能搜到本店可见门店
* 切换账号后不允许在 TTL 内命中上一账号的结果,拼 id 后 key 天然隔离
*/
const searchResultCache = new Map<
string,
{ at: number; items: StoreSearchItem[] }
>();
const SEARCH_CACHE_TTL = 5 * 60 * 1000;
const SEARCH_CACHE_MAX = 200;
function readSearchCache(key: string): null | StoreSearchItem[] {
const hit = searchResultCache.get(key);
if (!hit) return null;
if (Date.now() - hit.at > SEARCH_CACHE_TTL) {
searchResultCache.delete(key);
return null;
}
return hit.items;
}
function writeSearchCache(key: string, items: StoreSearchItem[]) {
// 简单容量控制:超限整体清空(低频触发,无需 LRU
if (searchResultCache.size >= SEARCH_CACHE_MAX) {
searchResultCache.clear();
}
searchResultCache.set(key, { at: Date.now(), items });
}
const emits = defineEmits<{
'update:value': [value: number[]];
}>();
const userStore = useUserStore();
/** 当前登录用户 id缓存隔离维度后端返回的 admin 行主键是 id兜底 vben 的 userId */
function currentUserId(): number | string {
const info = userStore.userInfo as null | Record<string, any>;
return info?.id ?? info?.userId ?? 0;
}
const mValue = useVModel(props, 'value', emits, {
passive: true,
defaultValue: [],
});
const containerRef = ref<HTMLElement | null>(null);
const panelRef = ref<HTMLElement | null>(null);
const listRef = ref<HTMLElement | null>(null);
const searchKeyword = ref('');
const loading = ref(false);
const showDropdown = ref(false);
const highlightIndex = ref(-1);
const options = ref<StoreSearchItem[]>([]);
const selectedMap = ref<Record<number, StoreSearchItem>>({});
const panelStyle = ref<Record<string, string>>({});
const selectedIds = computed(() =>
Array.isArray(mValue.value) ? mValue.value.map(Number) : [],
);
const selectedItems = computed(() =>
selectedIds.value.map((id) => {
const cached = selectedMap.value[id];
return cached || { id, name: `门店${id}` };
}),
);
/** 下拉中过滤掉已选,方便连续多选 */
const visibleOptions = computed(() =>
options.value.filter((item) => !selectedIds.value.includes(item.id)),
);
function mergeInitialItems(items: StoreSearchItem[]) {
if (!items?.length) return;
const next = { ...selectedMap.value };
for (const item of items) {
if (item?.id) next[item.id] = item;
}
selectedMap.value = next;
}
/**
* 相对输入框视口坐标放置面板(与 EntryKeywordBubble 同思路)
*/
function updatePanelPlacement() {
const el = containerRef.value;
if (!el) return;
const rect = el.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
const gap = 4;
const pad = 8;
const spaceBelow = vh - rect.bottom - pad;
const spaceAbove = rect.top - pad;
const placeTop = spaceBelow < 200 && spaceAbove > spaceBelow;
const avail = Math.max(160, placeTop ? spaceAbove : spaceBelow);
const maxH = Math.min(320, avail);
const width = Math.min(Math.max(rect.width, 360), vw - pad * 2);
let left = rect.left;
if (left + width > vw - pad) left = vw - pad - width;
if (left < pad) left = pad;
const base: Record<string, string> = {
position: 'fixed',
zIndex: '4000',
width: `${width}px`,
maxHeight: `${maxH}px`,
left: `${left}px`,
right: 'auto',
};
panelStyle.value = placeTop
? {
...base,
top: 'auto',
bottom: `${Math.max(pad, vh - rect.top + gap)}px`,
}
: {
...base,
top: `${rect.bottom + gap}px`,
bottom: 'auto',
};
}
async function openPanel() {
showDropdown.value = true;
await nextTick();
updatePanelPlacement();
}
/** 请求序号:只采纳最后一次请求的响应,避免快速输入时慢响应覆盖新结果 */
let fetchSeq = 0;
async function fetchOptions() {
const keyword = searchKeyword.value.trim();
if (!keyword) {
// 递增序号让在途请求作废,避免清空后慢响应又把旧结果写回来
fetchSeq += 1;
loading.value = false;
options.value = [];
showDropdown.value = false;
highlightIndex.value = -1;
return;
}
const cacheKey = `${currentUserId()}|${props.storeType ?? 'all'}|${keyword}`;
const cachedItems = readSearchCache(cacheKey);
if (cachedItems) {
// 命中缓存:直接渲染,不发请求不转 loading体感「秒出」
fetchSeq += 1;
loading.value = false;
options.value = cachedItems;
await openPanel();
highlightIndex.value = visibleOptions.value.length > 0 ? 0 : -1;
await nextTick();
updatePanelPlacement();
return;
}
const seq = (fetchSeq += 1);
loading.value = true;
await openPanel();
try {
const res = await searchStoreOption({
keyword,
...(props.storeType === 0 || props.storeType === 1
? { type: props.storeType }
: {}),
limit: 20,
});
if (seq !== fetchSeq) return;
const items = res?.items ?? [];
writeSearchCache(cacheKey, items);
options.value = items;
highlightIndex.value = visibleOptions.value.length > 0 ? 0 : -1;
await nextTick();
updatePanelPlacement();
} catch {
if (seq !== fetchSeq) return;
options.value = [];
highlightIndex.value = -1;
} finally {
if (seq === fetchSeq) {
loading.value = false;
}
}
}
const debouncedFetch = useDebounceFn(fetchOptions, 300);
function handleInput() {
debouncedFetch();
}
function handleFocus() {
if (searchKeyword.value.trim() && options.value.length > 0) {
openPanel();
if (highlightIndex.value < 0 && visibleOptions.value.length > 0) {
highlightIndex.value = 0;
}
}
}
/**
* 多选:追加 ID保留关键词与面板不关闭气泡
*/
function selectOption(item: StoreSearchItem) {
if (selectedIds.value.includes(item.id)) return;
selectedMap.value = { ...selectedMap.value, [item.id]: item };
mValue.value = [...selectedIds.value, item.id];
// 高亮下一项(列表已剔除刚选的)
nextTick(() => {
if (visibleOptions.value.length === 0) {
highlightIndex.value = -1;
return;
}
highlightIndex.value = Math.min(
Math.max(highlightIndex.value, 0),
visibleOptions.value.length - 1,
);
updatePanelPlacement();
scrollHighlightIntoView();
});
}
function removeSelected(id: number) {
mValue.value = selectedIds.value.filter((x) => x !== id);
const next = { ...selectedMap.value };
delete next[id];
selectedMap.value = next;
}
function closePanel() {
showDropdown.value = false;
highlightIndex.value = -1;
}
/** 仅改 list 的 scrollTop表头固定不动 */
function scrollHighlightIntoView() {
nextTick(() => {
const listEl = listRef.value;
if (!listEl || highlightIndex.value < 0) return;
const el = listEl.querySelector(
`[data-store-opt-index="${highlightIndex.value}"]`,
) as HTMLElement | null;
if (!el) return;
const listRect = listEl.getBoundingClientRect();
const elRect = el.getBoundingClientRect();
const deltaTop = elRect.top - listRect.top;
const deltaBottom = elRect.bottom - listRect.bottom;
if (deltaTop < 0) {
listEl.scrollTop += deltaTop;
} else if (deltaBottom > 0) {
listEl.scrollTop += deltaBottom;
}
});
}
function handleKeydown(e: KeyboardEvent) {
if (!showDropdown.value || visibleOptions.value.length === 0) {
if (e.key === 'Escape') closePanel();
return;
}
const len = visibleOptions.value.length;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
// 头尾循环:最后一项再按下 → 回到第一项
highlightIndex.value =
highlightIndex.value < 0 ? 0 : (highlightIndex.value + 1) % len;
scrollHighlightIntoView();
break;
case 'ArrowUp':
e.preventDefault();
// 头尾循环:第一项再按上 → 跳到最后一项
highlightIndex.value =
highlightIndex.value <= 0 ? len - 1 : highlightIndex.value - 1;
scrollHighlightIntoView();
break;
case 'Enter':
e.preventDefault();
{
const idx = highlightIndex.value >= 0 ? highlightIndex.value : 0;
const item = visibleOptions.value[idx];
if (item) selectOption(item);
}
break;
case 'Escape':
e.preventDefault();
closePanel();
break;
}
}
function handleClickOutside(e: MouseEvent) {
if (!showDropdown.value) return;
const t = e.target as Node;
if (containerRef.value?.contains(t) || panelRef.value?.contains(t)) return;
closePanel();
}
function onWinChange() {
if (showDropdown.value) updatePanelPlacement();
}
onMounted(() => {
document.addEventListener('mousedown', handleClickOutside);
window.addEventListener('resize', onWinChange);
window.addEventListener('scroll', onWinChange, true);
mergeInitialItems(props.initialItems || []);
});
onUnmounted(() => {
document.removeEventListener('mousedown', handleClickOutside);
window.removeEventListener('resize', onWinChange);
window.removeEventListener('scroll', onWinChange, true);
});
watch(
() => props.storeType,
() => {
options.value = [];
closePanel();
searchKeyword.value = '';
},
);
watch(
() => props.initialItems,
(items) => mergeInitialItems(items || []),
{ deep: true },
);
watch(visibleOptions, (list) => {
if (!showDropdown.value) return;
if (list.length === 0) {
highlightIndex.value = -1;
return;
}
if (highlightIndex.value >= list.length) {
highlightIndex.value = list.length - 1;
}
});
</script>
<template>
<div ref="containerRef" class="store-multi-search">
<Input
v-model:value="searchKeyword"
:placeholder="placeholder"
:disabled="disabled"
allow-clear
@input="handleInput"
@focus="handleFocus"
@keydown="handleKeydown"
>
<template #prefix>
<LoadingOutlined v-if="loading" class="text-muted-foreground" />
<SearchOutlined v-else class="text-muted-foreground" />
</template>
</Input>
<Teleport to="body">
<div
v-if="showDropdown"
ref="panelRef"
class="store-multi-search__panel"
:style="panelStyle"
>
<div class="store-multi-search__hint">
<span> 循环切换 · Enter 选用 · Esc 关闭 · 可连续多选</span>
<span class="store-multi-search__count">
{{ visibleOptions.length }} 条可选
</span>
</div>
<div class="store-multi-search__body">
<Spin :spinning="loading">
<Empty
v-if="!loading && visibleOptions.length === 0"
:image="Empty.PRESENTED_IMAGE_SIMPLE"
description="暂无匹配门店(或已全部选中)"
class="py-3"
/>
<div ref="listRef" class="store-multi-search__list">
<button
v-for="(item, index) in visibleOptions"
:key="item.id"
type="button"
class="store-multi-search__option"
:class="{ 'is-active': index === highlightIndex }"
:data-store-opt-index="index"
@mousedown.prevent="selectOption(item)"
>
<div class="store-multi-search__option-name">
{{ item.name }}{{ item.id }}
</div>
<div v-if="item.shouzimu" class="store-multi-search__option-sub">
首拼{{ item.shouzimu }}
</div>
</button>
</div>
</Spin>
</div>
</div>
</Teleport>
<div v-if="selectedItems.length > 0" class="store-multi-search__tags">
<Tag
v-for="item in selectedItems"
:key="item.id"
closable
color="blue"
@close="() => removeSelected(item.id)"
>
<!-- 文本单独包一层才能做省略antd Tag 的内容是裸文本节点无法直接限宽 -->
<span
class="store-multi-search__tag-text"
:title="`${item.name}【${item.id}】`"
>
{{ item.name }}{{ item.id }}
</span>
</Tag>
</div>
</div>
</template>
<style scoped lang="scss">
.store-multi-search {
position: relative;
width: 100%;
}
.store-multi-search__tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
/* 单个超长门店名不能撑破容器:限宽 + 省略号,完整名走 title 悬停提示 */
:deep(.ant-tag) {
display: inline-flex;
align-items: center;
max-width: 100%;
min-width: 0;
/* 间距交给 gap 控制,去掉 antd 自带右外边距避免换行时右侧不齐 */
margin-inline-end: 0;
}
:deep(.ant-tag-close-icon) {
flex-shrink: 0;
}
}
.store-multi-search__tag-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
<style lang="scss">
/* Teleport 到 body非 scoped避免气泡被裁切/丢样式 */
.store-multi-search__panel {
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: 10px;
background: hsl(var(--card, var(--background)));
border: 1px solid hsl(var(--border));
box-shadow:
0 8px 24px hsl(0 0% 0% / 0.12),
0 2px 8px hsl(0 0% 0% / 0.06);
}
.store-multi-search__hint {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-shrink: 0;
padding: 8px 12px;
font-size: 12px;
color: hsl(var(--muted-foreground));
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--muted) / 0.35);
}
.store-multi-search__count {
flex-shrink: 0;
font-variant-numeric: tabular-nums;
}
/* 表头固定,仅 body/list 滚动 */
.store-multi-search__body {
flex: 1;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
.store-multi-search__body .ant-spin-nested-loading,
.store-multi-search__body .ant-spin-container {
flex: 1;
min-height: 0;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.store-multi-search__list {
flex: 1;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 6px;
}
.store-multi-search__option {
display: block;
width: 100%;
text-align: left;
padding: 8px 10px;
border: 0;
border-radius: 6px;
cursor: pointer;
background: transparent;
transition: background 0.15s;
}
.store-multi-search__option:hover,
.store-multi-search__option.is-active {
background: hsl(var(--accent) / 0.85);
}
.store-multi-search__option-name {
font-size: 13px;
font-weight: 500;
color: hsl(var(--foreground));
line-height: 1.4;
}
.store-multi-search__option-sub {
margin-top: 2px;
font-size: 12px;
color: hsl(var(--muted-foreground));
}
</style>

View File

@@ -0,0 +1,371 @@
<script setup lang="ts">
/**
* 通用拖拽 + 粘贴上传Dragger 点击/拖拽,下方外侧「开始监听粘贴」
* v-model 为已上传 URL 字符串数组;列表支持图片/PDF 预览blob避免直接下载
*/
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { preferences } from '@vben/preferences';
import { Button, Image, Modal, Upload, message } from 'ant-design-vue';
import type { UploadFile } from 'ant-design-vue';
import { Icon } from '#/components/icon';
import { uploadFile } from '#/api/core/upload';
import { uploadToOss } from '#/utils/oss-upload';
import {
matchAccept,
usePasteUploadListen,
} from '#/utils/use-paste-upload-listen';
defineOptions({ name: 'UploadDraggerPaste' });
interface FileItem {
uid: string;
name: string;
status: 'uploading' | 'done' | 'error';
url: string;
}
interface Props {
modelValue?: string[];
accept?: string;
multiple?: boolean;
maxCount?: number;
/** 单文件最大 MB */
maxSizeMb?: number;
tip?: string;
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => [],
accept: '.jpg,.jpeg,.png,.gif,.webp,.pdf,image/*,application/pdf',
multiple: true,
maxCount: 9,
maxSizeMb: 20,
tip: '点击或拖拽文件到此处上传',
disabled: false,
});
const emit = defineEmits<{
'update:modelValue': [value: string[]];
}>();
const UploadDragger = Upload.Dragger;
const fileList = ref<FileItem[]>([]);
const uploading = ref(false);
/** 文件预览弹窗状态 */
const previewVisible = ref(false);
const previewTitle = ref('');
const previewIsImage = ref(false);
const previewImageUrl = ref('');
const previewPdfUrl = ref('');
const previewLoading = ref(false);
const maxBytes = computed(() => props.maxSizeMb * 1024 * 1024);
function syncFromValue(urls: string[]) {
fileList.value = (urls || []).map((url) => ({
uid: url,
name: url.split('/').pop() || 'file',
status: 'done' as const,
url,
}));
}
syncFromValue(props.modelValue);
watch(
() => props.modelValue,
(val) => {
const current = fileList.value
.filter((f) => f.status === 'done')
.map((f) => f.url);
if (JSON.stringify(val || []) !== JSON.stringify(current)) {
syncFromValue(val || []);
}
},
{ deep: true },
);
function emitUrls() {
const urls = fileList.value
.filter((f) => f.status === 'done' && f.url)
.map((f) => f.url);
emit('update:modelValue', urls);
}
function isImageUrl(url: string) {
return /\.(jpg|jpeg|png|gif|webp|bmp)(\?|$)/i.test(url || '');
}
function revokePdfBlob() {
if (previewPdfUrl.value && previewPdfUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(previewPdfUrl.value);
}
previewPdfUrl.value = '';
}
/**
* 预览已上传文件:图片用 ImagePDF fetch 成 blob 后 iframe避免 OSS 直接下载
*/
async function previewItem(item: FileItem) {
if (!item.url) return;
revokePdfBlob();
previewTitle.value = item.name || '预览';
if (isImageUrl(item.url)) {
previewIsImage.value = true;
previewImageUrl.value = item.url;
previewVisible.value = true;
return;
}
previewIsImage.value = false;
previewLoading.value = true;
previewVisible.value = true;
try {
const resp = await fetch(item.url);
if (!resp.ok) throw new Error('加载失败');
const blob = await resp.blob();
const pdfBlob =
blob.type && blob.type !== 'application/octet-stream'
? blob
: new Blob([blob], { type: 'application/pdf' });
previewPdfUrl.value = URL.createObjectURL(pdfBlob);
} catch {
message.error('预览失败');
previewVisible.value = false;
} finally {
previewLoading.value = false;
}
}
function onPreviewClose() {
previewVisible.value = false;
revokePdfBlob();
previewImageUrl.value = '';
}
/**
* 上传单个文件并写入列表
*/
async function uploadOneFile(file: File) {
if (props.disabled) return;
const doneCount = fileList.value.filter((f) => f.status === 'done').length;
if (doneCount >= props.maxCount) {
message.warning(`最多上传 ${props.maxCount} 个文件`);
return;
}
if (!matchAccept(file, props.accept)) {
message.warning('文件类型不符合要求');
return;
}
if (file.size > maxBytes.value) {
message.warning(`文件不能超过 ${props.maxSizeMb}MB`);
return;
}
const uid = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
fileList.value = [
...fileList.value,
{ uid, name: file.name, status: 'uploading', url: '' },
];
uploading.value = true;
try {
const uploadMethod = preferences.app.uploadMethod || 'direct';
const res =
uploadMethod === 'direct'
? await uploadToOss({ file })
: await uploadFile({ file });
const url = res?.url || '';
if (!url) throw new Error('上传失败');
fileList.value = fileList.value.map((item) =>
item.uid === uid ? { ...item, status: 'done' as const, url } : item,
);
emitUrls();
} catch (e: any) {
fileList.value = fileList.value.filter((item) => item.uid !== uid);
message.error(e?.message || '上传失败');
} finally {
uploading.value = false;
}
}
/** Ant Design Upload customRequest */
const customRequest = async (options: any) => {
const { file, onSuccess, onError } = options;
try {
await uploadOneFile(file as File);
onSuccess?.({});
} catch (e) {
onError?.(e);
}
};
function beforeUpload(file: File) {
if (props.disabled) return Upload.LIST_IGNORE;
const doneCount = fileList.value.filter((f) => f.status === 'done').length;
if (doneCount >= props.maxCount) {
message.warning(`最多上传 ${props.maxCount} 个文件`);
return Upload.LIST_IGNORE;
}
if (!matchAccept(file, props.accept)) {
message.warning('文件类型不符合要求');
return Upload.LIST_IGNORE;
}
if (file.size > maxBytes.value) {
message.warning(`文件不能超过 ${props.maxSizeMb}MB`);
return Upload.LIST_IGNORE;
}
return true;
}
function handleRemove(file: UploadFile) {
fileList.value = fileList.value.filter((item) => item.uid !== file.uid);
emitUrls();
return true;
}
const {
listening: pasteListening,
toggle: togglePaste,
} = usePasteUploadListen({
get accept() {
return props.accept;
},
get maxSize() {
return maxBytes.value;
},
onFiles: async (files) => {
for (const file of files) {
const doneCount = fileList.value.filter((f) => f.status === 'done').length;
if (doneCount >= props.maxCount) {
message.warning(`最多上传 ${props.maxCount} 个文件`);
break;
}
await uploadOneFile(file);
}
},
});
function onTogglePaste() {
if (props.disabled) return;
togglePaste();
if (pasteListening.value) {
message.success('已开启粘贴监听,可 Ctrl+V 粘贴文件/截图');
} else {
message.info('已停止粘贴监听');
}
}
const showDragger = computed(() => {
if (!props.multiple) {
return fileList.value.filter((f) => f.status === 'done').length === 0;
}
return fileList.value.filter((f) => f.status === 'done').length < props.maxCount;
});
onBeforeUnmount(() => {
revokePdfBlob();
});
</script>
<template>
<div class="upload-dragger-paste">
<UploadDragger
v-if="showDragger"
:file-list="[]"
:accept="accept"
:multiple="multiple"
:disabled="disabled || uploading"
:before-upload="beforeUpload"
:custom-request="customRequest"
:show-upload-list="false"
:max-count="maxCount"
>
<p class="ant-upload-drag-icon">
<Icon icon="ant-design:inbox-outlined" class="text-4xl text-blue-500" />
</p>
<p class="ant-upload-text">{{ tip }}</p>
<p class="ant-upload-hint text-muted-foreground">
支持拖拽或点击选择单文件不超过 {{ maxSizeMb }}MB
</p>
</UploadDragger>
<div v-if="fileList.length" class="mt-3 space-y-2">
<div
v-for="item in fileList"
:key="item.uid"
class="flex items-center gap-2 rounded border border-border bg-muted/20 px-3 py-2 text-sm text-foreground"
>
<Icon
:icon="
item.status === 'uploading'
? 'ant-design:loading-outlined'
: item.status === 'error'
? 'ant-design:close-circle-outlined'
: 'ant-design:paper-clip-outlined'
"
class="shrink-0"
/>
<button
v-if="item.url"
type="button"
class="min-w-0 flex-1 truncate text-left text-primary"
@click="previewItem(item)"
>
{{ item.name }}
</button>
<span v-else class="min-w-0 flex-1 truncate text-muted-foreground">{{ item.name }}</span>
<Button
type="link"
size="small"
danger
:disabled="disabled"
@click="handleRemove(item as any)"
>
删除
</Button>
</div>
</div>
<!-- 粘贴监听按钮放在拖拽区域下方外侧 -->
<div class="mt-3">
<Button
:type="pasteListening ? 'primary' : 'default'"
:danger="pasteListening"
:disabled="disabled"
@click="onTogglePaste"
>
<Icon
:icon="pasteListening ? 'ant-design:stop-outlined' : 'ant-design:snippets-outlined'"
class="mr-1"
/>
{{ pasteListening ? '停止监听粘贴' : '开始监听粘贴' }}
</Button>
<span v-if="pasteListening" class="ml-2 text-xs text-orange-500 dark:text-orange-400">
监听中请将文件或截图 Ctrl+V 粘贴
</span>
</div>
<Modal
:open="previewVisible"
:title="previewTitle"
:footer="null"
width="80%"
destroy-on-close
@cancel="onPreviewClose"
>
<div v-if="previewLoading" class="py-16 text-center text-muted-foreground">加载中</div>
<div v-else-if="previewIsImage" class="flex justify-center rounded border border-border bg-muted/25 p-2">
<Image :src="previewImageUrl" :preview="true" style="max-height: 70vh" />
</div>
<iframe
v-else-if="previewPdfUrl"
:src="previewPdfUrl"
class="h-[70vh] w-full rounded border border-border bg-background"
title="文件预览"
/>
</Modal>
</div>
</template>

View File

@@ -24,12 +24,18 @@ interface Props {
modelValue: string[];
multiple?: boolean;
maxCount?: number;
/**
* 素材库可选类型(透传 GalleryPickLink
* xk_file.type0=图片 1=视频;默认仅图片。不传会展示全部类型
*/
acceptTypes?: number[];
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => [],
multiple: true,
maxCount: 9,
acceptTypes: () => [0],
});
const emit = defineEmits<{
@@ -345,6 +351,7 @@ onMounted(() => {
v-if="showUploadButton"
:multiple="multiple"
:max-count="galleryRemainCount"
:accept-types="acceptTypes"
@select="onGallerySelect"
/>

View File

@@ -1,6 +1,9 @@
<script setup lang="ts">
/**
* 多图上传picture-card+ 下方「开始监听粘贴」支持截图/图片粘贴
*/
import { computed, ref, watch } from 'vue';
import { Modal, Upload } from 'ant-design-vue';
import { Button, Modal, Upload, message } from 'ant-design-vue';
import type { UploadFile } from 'ant-design-vue';
import GalleryPickLink from '#/components/form/components/gallery-pick-link.vue';
@@ -10,9 +13,12 @@ import {
beforeImageUpload,
resolveUploadFile,
} from '#/utils/use-image-upload-pending';
import { usePasteUploadListen } from '#/utils/use-paste-upload-listen';
import { preferences } from '@vben/preferences';
import { Icon } from '#/components/icon';
defineOptions({ name: 'UploadImage' });
interface FileItem {
uid: string;
name: string;
@@ -25,7 +31,8 @@ interface Props {
multiple?: boolean;
maxCount?: number;
/**
* 素材库可选类型(透传 GalleryPickLink,默认仅图片
* 素材库可选类型(透传 GalleryPickLink
* xk_file.type0=图片 1=视频,默认仅图片
*/
acceptTypes?: number[];
}
@@ -34,13 +41,15 @@ const props = withDefaults(defineProps<Props>(), {
modelValue: () => [],
multiple: true,
maxCount: 9,
acceptTypes: () => [1],
acceptTypes: () => [0],
});
const emit = defineEmits<{
'update:modelValue': [value: string[]];
}>();
const IMAGE_ACCEPT = 'image/*,.jpg,.jpeg,.png,.gif,.webp,.bmp';
const fileList = ref<FileItem[]>([]);
const initFileList = () => {
@@ -79,6 +88,41 @@ const updateModelValue = () => {
emit('update:modelValue', urls);
};
/**
* 上传单个图片文件(点击上传与粘贴共用)
*/
async function uploadOneImage(file: File) {
const remain = props.maxCount - fileList.value.filter((f) => f.status === 'done').length;
if (remain <= 0) {
message.warning(`最多上传 ${props.maxCount} 张图片`);
return;
}
const actualFile = resolveUploadFile(file);
const uid = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const uploadingFile: FileItem = {
uid,
name: actualFile.name || file.name || 'paste.png',
status: 'uploading',
url: '',
};
fileList.value = [...fileList.value, uploadingFile];
try {
const uploadMethod = preferences.app.uploadMethod || 'direct';
const res =
uploadMethod === 'direct'
? await uploadToOss({ file: actualFile })
: await uploadFile({ file: actualFile });
fileList.value = fileList.value.map((item) =>
item.uid === uid ? { ...item, status: 'done' as const, url: res.url } : item,
);
updateModelValue();
} catch (error) {
console.error('上传失败', error);
fileList.value = fileList.value.filter((item) => item.uid !== uid);
message.error('上传失败');
}
}
const customRequest = async (options: any) => {
const { file, onProgress, onSuccess, onError } = options;
const actualFile = resolveUploadFile(file as File);
@@ -160,6 +204,30 @@ const showUploadButton = computed(() =>
);
const galleryRemainCount = computed(() => Math.max(props.maxCount - fileList.value.length, 0));
const { listening: pasteListening, toggle: togglePaste } = usePasteUploadListen({
accept: IMAGE_ACCEPT,
maxSize: 20 * 1024 * 1024,
onFiles: async (files) => {
for (const file of files) {
const doneCount = fileList.value.filter((f) => f.status === 'done').length;
if (doneCount >= props.maxCount) {
message.warning(`最多上传 ${props.maxCount} 张图片`);
break;
}
await uploadOneImage(file);
}
},
});
function onTogglePaste() {
togglePaste();
if (pasteListening.value) {
message.success('已开启粘贴监听,可 Ctrl+V 粘贴图片/截图');
} else {
message.info('已停止粘贴监听');
}
}
</script>
<template>
@@ -193,6 +261,25 @@ const galleryRemainCount = computed(() => Math.max(props.maxCount - fileList.val
@select="onGallerySelect"
/>
<!-- 粘贴监听放在上传区下方外侧 -->
<div class="mt-2">
<Button
size="small"
:type="pasteListening ? 'primary' : 'default'"
:danger="pasteListening"
@click="onTogglePaste"
>
<Icon
:icon="pasteListening ? 'ant-design:stop-outlined' : 'ant-design:snippets-outlined'"
class="mr-1"
/>
{{ pasteListening ? '停止监听粘贴' : '开始监听粘贴' }}
</Button>
<span v-if="pasteListening" class="ml-2 text-xs text-orange-500">
监听中Ctrl+V 粘贴图片
</span>
</div>
<Modal v-model:visible="previewVisible" :title="previewTitle" footer="" width="60%">
<img alt="预览图片" style="width: 100%" :src="previewImage" />
</Modal>

View File

@@ -5,4 +5,5 @@ export type CustomComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'IconPicker'
| 'StoreMultiSearch'
| 'WarehouseAdminDrugSearch';

Some files were not shown because too many files have changed in this diff Show More