Compare commits

...

436 Commits

Author SHA1 Message Date
xingyu f542db27f9
!340 feat: 商城订单发货后可再修改发货信息
Merge pull request !340 from hice/master
2026-04-13 08:47:29 +00:00
xingyu4j b2cf1646a4 fix: lint 2026-04-13 16:46:44 +08:00
xingyu4j adecddae67 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2026-04-13 16:46:22 +08:00
xingyu4j a653e428f3 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2026-04-13 16:45:32 +08:00
hice eb62e63a04 feat: 商城订单发货后可再修改发货信息 2026-04-13 16:31:48 +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
芋道源码 f610bd690b
!339 增加 iot、mes 的说明
Merge pull request !339 from 芋道源码/dev
2026-04-12 13:29:10 +00:00
YunaiV 76f9d3d9fc merge: 合并 master 分支,解决 isUrl 冲突(保留从 @vben/utils 导出的方式)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 21:27:05 +08:00
YunaiV 1e6c39a4c6 feat:增加 iot 模块 2026-04-12 21:24:37 +08:00
YunaiV 7a1f8da68f feat:增加 iot 模块 2026-04-12 21:24:19 +08:00
YunaiV 51cae9b00c feat(mes):增加 mes 模块 2026-04-12 16:45:38 +08:00
YunaiV 7cbeaa8390 feat(mes):增加 mes 模块 2026-04-12 16:44:53 +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
xingyu ca39b8d0c9
!337 Merge branch 'main' of <a href="https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fvbenjs%2Fvue-vben-admin">https://github.com/vbenjs/vue-vben-admin</a> into vite8
Merge pull request !337 from xingyu/vite8
2026-03-23 08:57:12 +00:00
xingyu4j fece74f744 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into vite8 2026-03-23 16:55:27 +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
xingyu 53c5ccc00a
!336 chore: vite 8.0
Merge pull request !336 from xingyu/vite8
2026-03-14 05:40:10 +00:00
xingyu4j 06c9e8d7c1 fix: type check 2026-03-14 13:39:51 +08:00
xingyu4j f32818c6aa fix(lint): resolve shared form and utility warnings 2026-03-14 13:28:45 +08:00
xingyu4j fb03afb6b7 fix(lint): clean up ai rich text views 2026-03-14 13:28:13 +08:00
xingyu4j 577efa56a9 fix(lint): update bpmn designer compatibility code 2026-03-14 13:27:38 +08:00
xingyu4j cb98b3a47e fix(lint): add ts-expect-error descriptions 2026-03-14 13:27:00 +08:00
xingyu4j 8daf9a3ce5 docs: update version 2026-03-14 12:32:50 +08:00
xingyu4j a83d8248d7 fix: lint 2026-03-14 12:27:31 +08:00
xingyu4j 4cdc92f759 fix: lint 2026-03-14 12:16:31 +08:00
xingyu4j 54c668c3f0 chore: update deps 2026-03-14 11:37:28 +08:00
xingyu4j ac3fc6b7d3 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into vite8 2026-03-14 11:34:06 +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
YunaiV 1cbdf442ee feat: 添加 URL 验证工具函数并优化 area-select 组件的类型定义 2026-03-07 17:33:02 +08:00
YunaiV f91a2702c9 merge: 合并 master 分支的 form-create 修复
合并 master 分支中关于 form-create 组件的修复,包括 area-select 和 iframe 组件的改进。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:32:54 +08:00
芋道源码 c885c0c71a
!333 fix(form-create): 【ele/antd】完成 vue3 review c153ff93 的所有 TODO 修复
Merge pull request !333 from puhui999/master-fix
2026-03-07 03:23:42 +00:00
YunaiV a8f67ab717 !335 fix: 修复上传头像时,如果图片加载失败,弹框一直loading的问题,针对 ele 版本 2026-03-07 11:21:03 +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
xingyu 75e4d07395
!335 fix: 修复上传头像时,如果图片加载失败,弹框一直loading的问题
Merge pull request !335 from li_shifeng/fix-upload-avatar
2026-03-04 02:21:33 +00: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
li_shifeng 586978f1b0 fix: 修复上传头像时,如果图片加载失败,弹框一直loading的问题 2026-02-28 10:00:39 +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
芋道源码 dd69d7c1a5
!334 feat(iot):增加 modbus 配置 100%
Merge pull request !334 from 芋道源码/dev
2026-02-14 03:04:43 +00:00
YunaiV 63743b6929 feat(iot):增加 modbus 配置 100% 2026-02-14 11:02:56 +08:00
YunaiV 38597dd19d feat(iot):增加 modbus 配置 50% 2026-02-14 09:19:43 +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
puhui999 e7365a4a00 fix(form-create): 【ele/antd】完成 vue3 review c153ff93 的所有 TODO 修复 2026-02-11 17:58:13 +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
xingyu bf4fed78f2
!332 Merge branch 'main' of <a href="https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fvbenjs%2Fvue-vben-admin">https://github.com/vbenjs/vue-vben-admin</a> into dev
Merge pull request !332 from xingyu/dev
2026-02-11 03:12:23 +00:00
xingyu4j 722afc85df Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2026-02-11 11:01:44 +08:00
xingyu 3036596d16
!331 Merge remote-tracking branch 'yudao/dev' into dev
Merge pull request !331 from Jason/dev
2026-02-10 14:34:20 +00:00
jason aee539f37e Merge remote-tracking branch 'yudao/dev' into dev 2026-02-10 22:08:57 +08:00
jason 05b41692ba feat: 移动端 uniapp 流程表单嵌入页面 2026-02-10 22:07:19 +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
xingyu 8a622889ff
!330 feat(form-create): 【ele/antd】新增 iframe 和省市区选择器组件
Merge pull request !330 from puhui999/master-fix
2026-02-10 01:32:01 +00: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
puhui999 f3deefae56 Merge remote-tracking branch 'yudao/master' into master-fix 2026-02-08 11:57:23 +08:00
puhui999 d0a7065991 feat(form-create): 【ele/antd】新增 iframe 和省市区选择器组件
- 新增 iframe 网页嵌入组件,支持 URL 配置和实时预览
- 新增省市区三级联动选择器组件
- 支持 web-ele 和 web-antd 双版本
2026-02-08 11:56:02 +08:00
YunaiV 5b7e7c4d56 fix: 修复新增空目录菜单时 component 为 null 导致路由生成报错 2026-02-07 18:30:03 +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
芋道源码 f6c5bc4b7c
!329 同步最新代码(2026.01 发版前)
Merge pull request !329 from 芋道源码/dev
2026-01-29 15:54:36 +00:00
YunaiV 448f073143 review:【antd/ele】【bpm】工作流相关的代码 2026-01-29 21:20:24 +08:00
xingyu fc31aa3c8f
!328 表单设计器 UserSelect/DeptSelect 支持默认选中当前用户/部门、修复商品 SKU 名称校验失败的问题
Merge pull request !328 from puhui999/master-fix
2026-01-29 03:28:24 +00: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
puhui999 548da70f9f fix(mall): 【antd/ele】修复商品 SKU 名称校验失败的问题
前端创建/编辑商品时,SKU 对象缺少 name 字段初始化,导致提交时后端校验 "商品 SKU 名字不能为空" 失败。

修改内容:
- form/index.vue: 初始化 SKU 添加 name 字段,提交前校验商品名称并赋值给 SKU
- sku-list.vue: createEmptySku 函数添加 name 字段

影响范围:web-antd、web-ele 两个版本
2026-01-28 17:45:20 +08:00
puhui999 0441afc24f Merge remote-tracking branch 'yudao/master' 2026-01-28 17:37:04 +08:00
puhui999 1196dab9e4 feat(form-create): 【antd/ele】表单选择器支持默认选中当前用户/部门
- UserSelect 组件新增 defaultCurrentUser 配置,支持默认选中当前登录用户
- DeptSelect 组件重构为独立的树形选择器,支持 defaultCurrentDept 配置
- DeptSelect 支持 returnType 配置,可返回部门 ID 或部门名称
- 修复 useSelectRule 未将自定义 props 默认值传递给组件的问题
- 修复 DeptSelect 数据加载完成前回显失败的问题
- 同时支持 web-antd 和 web-ele 两个应用
2026-01-28 17:23:39 +08:00
xingyu 00bf48704d
!327 docs: README
Merge pull request !327 from xingyu/dev
2026-01-28 07:25:26 +00:00
xingyu4j 7a1218e6f0 fix: lint 2026-01-28 15:24:55 +08:00
xingyu4j dbb1a19c5d fix: lint 2026-01-28 15:24:49 +08:00
xingyu4j f433b207c6 docs: README lint 2026-01-28 15:24:44 +08:00
xingyu4j 82b91dfed3 fix: lint 2026-01-28 15:22:35 +08:00
xingyu4j 1295aee180 feta: add moddle cspell 2026-01-28 15:22:28 +08:00
xingyu4j 00105e1302 docs: README 2026-01-28 15:12:23 +08:00
xingyu e82433e3e7
!326 fix: 弹窗只能点击一次
Merge pull request !326 from xingyu/dev
2026-01-28 06:33:37 +00:00
xingyu4j ccaef2b591 fix: 弹窗只能点击一次 2026-01-28 14:32:06 +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
xingyu 36aa195378
!325 remove playground
Merge pull request !325 from xingyu/dev
2026-01-26 07:08:43 +00:00
xingyu4j de2662ca8f fix: remove playground 2026-01-26 15:08:14 +08:00
xingyu 35acb9558b
!324 merge dev
Merge pull request !324 from xingyu/dev
2026-01-26 07:06:52 +00:00
xingyu4j c4a262d1e2 docs: README 2026-01-26 15:06:10 +08:00
xingyu4j a1e021074e docs: README 2026-01-26 15:03:32 +08:00
xingyu4j 0a7ead980a feat: naive add AutoComplete 2026-01-26 14:59:34 +08:00
xingyu4j cdcbd58f0e fix: ele auto complete 2026-01-26 14:35:53 +08:00
xingyu4j c57f3d8820 fix: ele auto complete 2026-01-26 14:34:45 +08:00
xingyu4j 02c977f969 fix: IDLPAD 2026-01-26 11:06:38 +08:00
xingyu4j a0019cef04 chore: update deps 2026-01-26 10:37:44 +08:00
xingyu4j e447a8a569 chore: remove unused deps 2026-01-26 10:34:58 +08:00
xingyu4j 071468b716 chore: yaml validate 2026-01-26 10:34:33 +08:00
xingyu4j 24b8bba754 fix: lint 2026-01-26 10:34:14 +08:00
xingyu4j 6d60071515 fix: lint sort 2026-01-26 10:33:23 +08:00
xingyu4j baed599fcc Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev 2026-01-26 10:13:23 +08:00
Jin Mao 7cb2699f19 chore: 更新pnpm工作区配置
- 移除 neverBuiltDependencies 配置
- 移除 peerDependencyRules.allowedVersions 配置
- 重新整理 overrides 和 catalog 部分
- 保持 @ast-grep/napi 等依赖的目录引用配置
- 维持 eslint 版本约束在目录顶层配置中
2026-01-25 19:55:53 +08:00
YunaiV 1ce562601f feat(iot):【网关设备:80%】动态注册的初步实现(已测试) 2026-01-25 18:50:26 +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
YunaiV c55465a6c0 chore: merge origin/dev branch 2026-01-24 09:31:43 +08:00
YunaiV 2ae684bdad feat(iot):【设备定位】添加设备位置功能,支持地图展示和坐标选择 2026-01-23 07:06:28 +00:00
haohao ce2bfa5cd2 refactor:【antd】【iot】将 DeviceSaveReqVO 和 DeviceRespVO 合并到 Device,简化设备 API 接口 2026-01-23 07:06:28 +00:00
jason 63265c1a6b feat: [bpm][antd] 审批签名大小控制 2026-01-23 07:06:28 +00: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
xingyu 13b1ef71a9
!323 Merge remote-tracking branch 'yudao/dev' into dev
Merge pull request !323 from Jason/dev
2026-01-22 03:10:39 +00:00
jason ba08820be8 Merge remote-tracking branch 'yudao/dev' into dev 2026-01-21 23:28:00 +08:00
jason d9e933e3a6 feat: [bpm][antd] 审批签名大小控制 2026-01-21 23:25:48 +08:00
YunaiV 50216e5047 feat(iot):【设备定位】添加设备位置功能,支持地图展示和坐标选择 2026-01-21 21:10:09 +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
芋道源码 4aeb7a489a
!322 refactor:【antd】【iot】将 DeviceSaveReqVO 和 DeviceRespVO 合并到 Device,简化设备 API 接口
Merge pull request !322 from haohaoMT/dev
2026-01-21 05:43:57 +00: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
芋道源码 42f2fecb1e
!320 完成 mall、bpm 的全部迁移
Merge pull request !320 from 芋道源码/dev
2026-01-18 10:12:49 +00:00
YunaiV af0057940a feat:增加 allowedHosts 变量,允许 natapp 转发,对应 https://t.zsxq.com/kSg2A 2026-01-18 17:04:03 +08:00
YunaiV 9e3d75ae65 fix:【infra】代码生成:全选不生效的问题 2026-01-18 16:20:43 +08:00
YunaiV e5f5523bc6 fix:外链是子菜单时,路径不正确的问题,对应 https://t.zsxq.com/aE8AX 问题 2026-01-18 14:46:56 +08:00
YunaiV 4cc900f542 review:【antd/ele】【mall】商城相关的代码 2026-01-18 14:43:49 +08:00
芋道源码 ce34e6e1a0
!319 feat: [bpm][antd] todo 修改
Merge pull request !319 from Jason/dev
2026-01-17 09:09:11 +00:00
jason db1dfae481 feat: [bpm][antd] todo 修改 2026-01-17 15:47:31 +08:00
jason 012412ec22 feat: [bpm][antd] todo 修改 2026-01-17 15:31:54 +08:00
jason db97d414ec feat: [bpm][antd] todo 流程监听器、流程表达式修改 2026-01-17 12:09:46 +08:00
haohao 8bf286fda0 refactor:【antd】【iot】将 DeviceSaveReqVO 和 DeviceRespVO 合并到 Device,简化设备 API 接口 2026-01-16 17:38:02 +08:00
xingyu 91119eac8e
!318 fix:【ele/antd】修复更新个人信息后菜单丢失问题
Merge pull request !318 from zlflying/dev
2026-01-15 08:11:15 +00: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
zlflying d5b49e6a3b fix:【ele/antd】修复更新个人信息后菜单丢失问题
Signed-off-by: zlflying <zlflying@qq.com>
2026-01-14 14:44:17 +08:00
芋道源码 c894617e10
!317 修复一些 review TODO 提到的问题
Merge pull request !317 from puhui999/dev-mall
2026-01-13 12:27:48 +00:00
puhui999 10f4641fee fix:【ele/antd】修复 setup() 函数没有接收 props 参数,导致渲染函数中的 props 无法从 formCreate 传递 2026-01-13 12:37:13 +08:00
puhui999 c478bef269 feat:【ele】cropper todo 优化,对齐 antd 2026-01-13 12:32:00 +08:00
puhui999 0302b70c48 feat:【antd/ele】Element Plus 的 value-format="x" 返回的值可以直接赋值,不需要 Number() 转换,与 antd 版本保持一致 2026-01-13 12:25:27 +08:00
puhui999 2426f891e7 feat:【antd/ele】将文章的商品关联字段从手动输入 SPU 编号改为使用 SpuShowcase 组件选择商品 2026-01-13 12:13:22 +08:00
puhui999 e2433fc531 feat:【antd/ele】使用 productSpuIds 和 productCategoryIds 自定义插槽的表单在验证前同步 formData 中的值到表单中 2026-01-13 12:06:10 +08:00
puhui999 bbc74ae663 feat:【antd/ele】discountActivity 移除 structuredClone 使用 cloneDeep 2026-01-13 11:55:13 +08:00
puhui999 ea79b7d6a1 feat:【antd】cropper-modal 删除多余的 Image 预加载逻辑 2026-01-13 11:33:11 +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
YunaiV 17d5d1b889 review:【antd】【iot】设备管理相关 2026-01-05 20:45:39 +08:00
芋道源码 a5c76ef89d
!315 refactor:【antd】【iot】设备管理跟后端对齐,必要的 ReqVO、RespVO,子设备管理实现
Merge pull request !315 from haohaoMT/dev
2026-01-05 12:10:39 +00:00
YunaiV e622c052a9 feat:【antd/ele】菜单支持查询参数、iframe 内嵌功能 2026-01-05 19:35:01 +08:00
ppxb ed3353a271
fix: preferences drawer outline z-index (#7067) 2026-01-04 14:44:33 +08:00
haohao de28c5c4c2 refactor:【antd】【iot】设备管理跟后端对齐,必要的 ReqVO、RespVO,子设备管理实现 2026-01-04 12:25:25 +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
YunaiV f7f01c9280 feat:【antd/ele】生产环境下,默认开启 CAPTCHA 验证码,保证安全性 2026-01-03 19:20:25 +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
YunaiV a9f21c1acb feat:【system】菜单管理:增加 visible 管理字段 2026-01-02 19:50:38 +08:00
YunaiV 19c7f0d5dd feat: 【框架】更新 operate-log 的实现 2026-01-02 19:44:09 +08:00
YunaiV cd43149429 review:【antd/ele】【bpm】流程模型的迁移 2026-01-02 18:49:47 +08:00
YunaiV 19919f6685 Merge remote-tracking branch 'origin/dev' into dev
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-01-02 18:29:54 +08:00
YunaiV 036ef294db feat:【infra】代码生成:字典筛选时,支持 key、name 两种类型 2026-01-02 18:12:23 +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
芋道源码 d8f685708d
!314 Merge remote-tracking branch 'yudao/dev' into dev
Merge pull request !314 from Jason/dev
2026-01-01 13:27:55 +00:00
jason f8ce09a203 Merge remote-tracking branch 'yudao/dev' into dev 2025-12-31 11:02:11 +08:00
jason 59d83d29cb feat: [bpm][ele] bpm oa 请假迁移 2025-12-31 11:00:13 +08:00
jason 02193755be feat: [bpm][antd] oa 请假优化 2025-12-31 00:04:40 +08:00
芋道源码 6d524906a3
!313 Merge remote-tracking branch 'yudao/dev' into dev
Merge pull request !313 from Jason/dev
2025-12-29 14:43:47 +00:00
jason cbd1f0bcbb Merge remote-tracking branch 'yudao/dev' into dev 2025-12-29 11:53:00 +08:00
jason 2ba2c8e986 feat: [bpm] [ele,antd] todo 优化, 更多设置问题修复 2025-12-29 11:51:48 +08:00
YunaiV 06f1ae1a66 review:【mall】营销相关 2025-12-29 08:17:35 +08:00
YunaiV 029b2ffaab review:【antd/ele】【mall】营销活动的实现 2025-12-29 07:03:00 +08:00
jason 64ac25de00 Merge remote-tracking branch 'yudao/dev' into dev 2025-12-29 00:12:30 +08:00
jason 6606dfd40a feat: [bpm][ele] todo 优化 2025-12-29 00:09:22 +08:00
芋道源码 6da4a39ff9
!312 feat:【ele/antd】mall todo 优化
Merge pull request !312 from puhui999/dev-mall
2025-12-28 13:23:18 +00:00
puhui999 aa95d0e87c feat:【ele/antd】profile todo 优化 2025-12-28 18:55:07 +08:00
puhui999 6353f0a8e9 feat:【ele/antd】discountActivity todo 优化 2025-12-28 18:34:28 +08:00
puhui999 e6327ae9da feat:【ele】spu todo 优化 2025-12-28 17:57:10 +08:00
puhui999 4395353c22 feat:【ele/antd】rewardActivity todo 优化 2025-12-28 17:35:43 +08:00
puhui999 c023ebbdb9 feat:【ele】cropper、form-create\rules todo 优化 2025-12-28 15:44: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
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
950 changed files with 59333 additions and 14380 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -9,7 +9,7 @@ runs:
uses: pnpm/action-setup@v4
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .node-version
cache: 'pnpm'

View File

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

View File

@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
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@v6
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v5
with:
run_install: false
@ -67,7 +67,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@ -90,7 +90,7 @@ jobs:
- windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0

View File

@ -57,7 +57,7 @@ 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@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@ -98,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@ -129,7 +129,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0

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,15 +19,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
node-version: [22]
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
# - name: Checkout code
# uses: actions/checkout@v4
# uses: actions/checkout@v6
# with:
# fetch-depth: 0
@ -58,7 +58,7 @@ jobs:
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

9
.gitignore vendored
View File

@ -22,7 +22,7 @@ yarn.lock
package-lock.json
.VSCodeCounter
**/backend-mock/data
.omx
# local env files
.env.local
.env.*.local
@ -50,3 +50,10 @@ vite.config.ts.*
*.sw?
.history
.cursor
# AI
.agent
.agents
.claude
.codex
skills-lock.json

View File

@ -1 +1 @@
22.1.0
22.22.0

4
.npmrc
View File

@ -1,8 +1,8 @@
registry=https://registry.npmmirror.com
public-hoist-pattern[]=lefthook
public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss
public-hoist-pattern[]=oxfmt
public-hoist-pattern[]=oxlint
public-hoist-pattern[]=stylelint
public-hoist-pattern[]=*postcss*
public-hoist-pattern[]=@commitlint/*

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

@ -2,14 +2,18 @@
"recommendations": [
// Vue 3
"Vue.volar",
// ESLint JavaScript VS Code
// eslint VS Code
"dbaeumer.vscode-eslint",
// oxlint VS Code
"oxc.oxc-vscode",
// Visual Studio Code Stylelint
"stylelint.vscode-stylelint",
// 使 Prettier
"esbenp.prettier-vscode",
// 使 oxfmt
"oxc.oxc-vscode",
// dotenv
"mikestead.dotenv",
// YAML ESLint pnpm-workspace.yaml
"redhat.vscode-yaml",
//
"streetsidesoftware.code-spell-checker",
// Tailwind CSS VS Code

59
.vscode/settings.json vendored
View File

@ -1,5 +1,6 @@
{
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts",
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/theme.css",
"tailwindCSS.lint.suggestCanonicalClasses": "ignore",
// workbench
"workbench.list.smoothScrolling": true,
"workbench.startupEditor": "newUntitledFile",
@ -31,39 +32,51 @@
"editor.autoClosingOvertype": "always",
"editor.autoClosingQuotes": "beforeWhitespace",
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
"editor.quickSuggestions": {
"strings": "on"
},
// lint && format
"oxc.enable": true,
"oxc.typeAware": true,
"oxc.configPath": "oxlint.config.ts",
"oxc.fmt.configPath": "oxfmt.config.ts",
"eslint.useFlatConfig": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.fixAll.stylelint": "explicit",
"source.organizeImports": "never"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "oxc.oxc-vscode",
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "oxc.oxc-vscode"
},
// extensions
"extensions.ignoreRecommendations": true,
@ -79,6 +92,7 @@
"files.insertFinalNewline": true,
"files.simpleDialog.enable": true,
"files.associations": {
"*.css": "tailwindcss",
"*.ejs": "html",
"*.art": "html",
"**/tsconfig.json": "jsonc",
@ -118,7 +132,7 @@
// search
"search.searchEditor.singleClickBehaviour": "peekDefinition",
"search.followSymlinks": false,
// 使/
// 使
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
@ -159,7 +173,7 @@
"emmet.triggerExpansionOnTab": false,
"errorLens.enabledDiagnosticLevels": ["warning", "error"],
"errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"],
"errorLens.excludeBySource": ["cSpell", "Grammarly"],
"stylelint.enable": true,
"stylelint.packageManager": "pnpm",
@ -167,9 +181,10 @@
"stylelint.customSyntax": "postcss-html",
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
"typescript.inlayHints.enumMemberValues.enabled": true,
"typescript.preferences.preferTypeOnlyAutoImports": true,
"typescript.preferences.includePackageJsonAutoImports": "on",
"js/ts.tsdk.path": "node_modules/typescript/lib",
"js/ts.inlayHints.enumMemberValues.enabled": true,
"js/ts.preferences.preferTypeOnlyAutoImports": true,
"js/ts.preferences.includePackageJsonAutoImports": "on",
"eslint.validate": [
"javascript",
@ -181,7 +196,8 @@
"markdown",
"json",
"jsonc",
"json5"
"json5",
"yaml"
],
"tailwindCSS.experimental.classRegex": [
@ -192,7 +208,7 @@
"*": false
},
"cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"],
"cssVariables.lookupFiles": ["packages/@core/base/design/src/**/*.css"],
"i18n-ally.localesPaths": [
"packages/locales/src/langs",
@ -217,12 +233,9 @@
"*.env": "$(capture).env.*",
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
"tailwind.config.mjs": "postcss.*"
"oxlint.config.ts": ".eslintignore,.stylelintignore,.commitlintrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml,oxfmt.config.*,eslint.config.*"
},
"commentTranslate.hover.enabled": false,
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib",
"oxc.enable": false
"vue.server.hybridMode": true
}

230
README.md
View File

@ -9,7 +9,7 @@
## 🐶 新手必读
- nodejs > 20.12.0 && pnpm > 10.22.0 (强制使用pnpm)
- nodejs >= v20.19.0(推荐 v22 / v24 && pnpm >= 10.32.1(强制使用 pnpm
- 演示地址【Vue3 + element-plus】<http://dashboard-vue3.yudao.iocoder.cn>
- 演示地址【Vue3 + vben5(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
- 演示地址【Vue2 + element-ui】<http://dashboard.yudao.iocoder.cn>
@ -20,12 +20,12 @@
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
- 采用最新 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) v5 实现
- 采用最新 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) v5.7.0 实现
- 支持 [Ant Design Vue](https://www.antdv.com/) | [Element Plus](https://element-plus.org/zh-CN/) | [Naive UI](https://www.naiveui.com/) | [TDesign](https://tdesign.tencent.com/) 多种免费开源的中后台模版,具备如下特性:
![首页](.gitee/image/demo/vben.png)
- **最新技术栈**:使用 Vue3、Vite6 等前端前沿技术开发
- **最新技术栈**:使用 Vue3、Vite8 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**: 提供多套主题色彩,可配置自定义主题
- **国际化**:内置完善的国际化方案
@ -33,7 +33,7 @@
- **组件**:二次封装了多个常用的组件
- **示例**:内置丰富的示例
## [外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】](https://www.shuduokeji.com)
## [外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】](https://www.shuduokeji.com?yudao)
![alt 软件定制开发 数舵科技](.gitee/image/wx-xingyu.png)
@ -41,26 +41,26 @@
| 框架 | 说明 | 版本 |
| --- | --- | --- |
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.24 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 7.2.2 |
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.30 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 8.0.0 |
| [Ant Design Vue](https://www.antdv.com/) | Ant Design Vue | 4.2.6 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.10.2 |
| [Naive UI](https://www.naiveui.com/) | Naive UI | 2.42.0 |
| [TDesign](https://tdesign.tencent.com/) | TDesign | 1.17.1 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.13.5 |
| [Naive UI](https://www.naiveui.com/) | Naive UI | 2.44.1 |
| [TDesign](https://tdesign.tencent.com/) | TDesign | 1.18.5 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 超集 | 5.9.3 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库替代 vuex5 | 3.0.3 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 13.4.0 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.1.7 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.5.1 |
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 3.4.18 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库替代 vuex5 | 3.0.4 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 14.2.1 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.3.0 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 5.0.3 |
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 4.2.1 |
| [Iconify](https://iconify.design/) | 图标组件 | 5.0.0 |
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.406 |
| [TinyMCE](https://www.tiny.cloud/) | 富文本编辑器 | 6.1.0 |
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.449 |
| [TinyMCE](https://www.tiny.cloud/) | 富文本编辑器 | 7.3.0 |
| [Echarts](https://echarts.apache.org/) | 图表库 | 6.0.0 |
| [axios](https://axios-http.com/) | http客户端 | 1.10.0 |
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.13 |
| [axios](https://axios-http.com/) | http客户端 | 1.13.6 |
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.20 |
| [vee-validate](https://vee-validate.logaretm.com/) | 表单验证 | 4.15.1 |
| [zod](https://zod.dev/) | 数据验证 | 3.25.67 |
| [zod](https://zod.dev/) | 数据验证 | 3.25.76 |
## 🔥 后端架构
@ -82,33 +82,33 @@
![功能分层](/.gitee/image/common/ruoyi-vue-pro-biz.png)
- 通用模块(必选):系统功能、基础设施
- 通用模块(可选):工作流程、支付系统、数据报表、会员中心
- 业务系统按需ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
* 通用模块(必选):系统功能、基础设施
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
* 业务系统按需ERP 系统、CRM 系统、MES 系统、商城系统、微信公众号、AI 大模型、IoT 物联网
### 系统功能
| | 功能 | 描述 |
|----|-------|---------------------------------|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
| | 功能 | 描述 |
| --- | --- | --- |
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
![功能图](/.gitee/image/common/system-feature.png)
@ -126,32 +126,32 @@
>
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
| 功能列表 | 功能描述 | 是否完成 |
|------------|-------------------------------------------------------------------------------------|------|
| SIMPLE 设计器 | 仿钉钉/飞书设计器支持拖拽搭建表单流程10 分钟快速完成审批流程配置 | ✅ |
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
| 转办 | A 转给其 B 审批B 审批后,进入下一节点 | ✅ |
| 委派 | A 转给其 B 审批B 审批后,转给 AA 继续审批后进入下一节点 | ✅ |
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
| 功能列表 | 功能描述 | 是否完成 |
| --- | --- | --- |
| SIMPLE 设计器 | 仿钉钉/飞书设计器支持拖拽搭建表单流程10 分钟快速完成审批流程配置 | ✅ |
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
| 转办 | A 转给其 B 审批B 审批后,进入下一节点 | ✅ |
| 委派 | A 转给其 B 审批B 审批后,转给 AA 继续审批后进入下一节点 | ✅ |
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
### 支付系统
@ -165,26 +165,26 @@
### 基础设施
| | 功能 | 描述 |
|----|-----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成Java、Vue、SQL、单元测试支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3MinIO、阿里云、腾讯云、七牛云、本地、FTP、数据库等 |
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列Stream 提供集群消费Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 代码生成 | 前后端代码的生成Java、Vue、SQL、单元测试支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3MinIO、阿里云、腾讯云、七牛云、本地、FTP、数据库等 |
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列Stream 提供集群消费Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
![功能图](/.gitee/image/common/infra-feature.png)
@ -197,19 +197,19 @@
### 微信公众号
| | 功能 | 描述 |
|----|--------|-------------------------------|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 |
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 |
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
### 商城系统
@ -219,6 +219,16 @@
![功能图](/.gitee/image/common/mall-preview.png)
### 会员中心
| | 功能 | 描述 |
|-----|------|----------------------------------|
| 🚀 | 会员管理 | 会员是 C 端的消费者,该功能用于会员的搜索与管理 |
| 🚀 | 会员标签 | 对会员的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 会员等级 | 对会员的等级、成长值进行管理,可用于订单折扣等会员权益 |
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
### ERP 系统
演示地址:<https://doc.iocoder.cn/erp-preview/>
@ -231,6 +241,14 @@
![功能图](/.gitee/image/common/crm-feature.png)
### MES 系统
演示地址:<https://doc.iocoder.cn/mes-preview/>
![功能图](/.gitee/image/common/mes-feature.png)
![功能图](/.gitee/image/common/mes-preview.png)
### AI 大模型
演示地址:<https://doc.iocoder.cn/ai-preview/>
@ -238,3 +256,11 @@
![功能图](/.gitee/image/common/ai-feature.png)
![功能图](/.gitee/image/common/ai-preview.gif)
### IoT 物联网
演示地址:<https://doc.iocoder.cn/iot/build>
![功能图](/.gitee/image/common/iot-feature.png)
![预览图](/.gitee/image/common/iot-preview.png)

View File

@ -33,3 +33,6 @@ VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395
VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB
# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==
# 百度地图
VITE_BAIDU_MAP_KEY=Y2aJXiswwPxy6mwFs1z9c7U5gwX9WfUN

View File

@ -21,3 +21,6 @@ VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true

View File

@ -12,16 +12,15 @@
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>
var HM_ID = '<%= VITE_APP_BAIDU_CODE %>'
var HM_ID = '<%= VITE_APP_BAIDU_CODE %>';
if (HM_ID) {
var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src =
'https://hm.baidu.com/hm.js?' + HM_ID;
hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();

View File

@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "5.5.9",
"version": "5.7.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

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

View File

@ -2537,12 +2537,12 @@ interface EditorSelection {
normalize: () => Range;
selectorChanged: (selector: string, callback: (active: boolean, args: {
node: Node;
selector: String;
selector: string;
parents: Node[];
}) => void) => EditorSelection;
selectorChangedWithUnbind: (selector: string, callback: (active: boolean, args: {
node: Node;
selector: String;
selector: string;
parents: Node[];
}) => void) => {
unbind: () => void;
@ -3217,9 +3217,9 @@ interface Tools {
<T, R>(arr: ArrayLike<T> | null | undefined, cb: ArrayCallback<T, R>): R[];
<T, R>(obj: Record<string, T> | null | undefined, cb: ObjCallback<T, R>): R[];
};
extend: (obj: Object, ext: Object, ...objs: Object[]) => any;
extend: (obj: object, ext: object, ...objs: object[]) => any;
walk: <T extends Record<string, any>>(obj: T, f: WalkCallback<T>, n?: keyof T, scope?: any) => void;
resolve: (path: string, o?: Object) => any;
resolve: (path: string, o?: object) => any;
explode: (s: string | string[], d?: string | RegExp) => string[];
_addCacheSuffix: (url: string) => string;
}

View File

@ -3,36 +3,82 @@
* vben-formvben-modalvben-drawer 使,
*/
/* eslint-disable vue/one-component-per-file */
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 type { Component, Ref } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type {
ApiComponentSharedProps,
BaseFormComponentType,
IconPickerProps,
} from '@vben/common-ui';
import type { Sortable } from '@vben/hooks';
import type { Recordable } from '@vben/types';
import {
computed,
defineAsyncComponent,
defineComponent,
h,
nextTick,
onMounted,
onUnmounted,
ref,
render,
unref,
watch,
} from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
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 { notification } from 'ant-design-vue';
import { message, Modal, notification } from 'ant-design-vue';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload';
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;
};
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
@ -124,173 +170,110 @@ const withDefaultPlaceholder = <T extends Component>(
});
};
const withPreviewUpload = () => {
return defineComponent({
name: Upload.name,
emits: ['change', 'update:modelValue'],
setup: (
props: any,
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
) => {
const previewVisible = ref<boolean>(false);
const IMAGE_EXTENSIONS = new Set([
'bmp',
'gif',
'jpeg',
'jpg',
'png',
'svg',
'webp',
]);
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 handleChange = async (event: UploadChangeParam) => {
fileList.value = event.fileList;
emit('change', event);
emit(
'update:modelValue',
event.fileList?.length ? fileList.value : undefined,
);
};
const handlePreview = async (file: UploadFile) => {
previewVisible.value = true;
await previewImage(file, previewVisible, fileList);
};
const renderUploadButton = (): any => {
const isDisabled = attrs.disabled;
// 如果禁用,不渲染上传按钮
if (isDisabled) {
return null;
}
// 否则渲染默认上传按钮
return isEmpty(slots)
? createDefaultSlotsWithUpload(listType, placeholder)
: slots;
};
// 可以监听到表单API设置的值
watch(
() => attrs.modelValue,
(res) => {
fileList.value = res;
},
);
return () =>
h(
Upload,
{
...props,
...attrs,
fileList: fileList.value,
onChange: handleChange,
onPreview: handlePreview,
},
renderUploadButton(),
);
},
});
};
const createDefaultSlotsWithUpload = (
listType: string,
placeholder: string,
) => {
switch (listType) {
case 'picture-card': {
return {
default: () => placeholder,
};
}
default: {
return {
default: () =>
h(
Button,
{
icon: h(IconifyIcon, {
icon: 'ant-design:upload-outlined',
class: 'mb-1 size-4',
}),
},
() => placeholder,
),
};
/**
*
*/
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/');
}
const previewImage = async (
/**
*
*/
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']>,
) => {
// 检查是否为图片文件的辅助函数
const isImageFile = (file: UploadFile): boolean => {
const imageExtensions = new Set([
'bmp',
'gif',
'jpeg',
'jpg',
'png',
'webp',
]);
if (file.url) {
const ext = file.url?.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
}
if (!file.type) {
const ext = file.name?.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
}
return file.type.startsWith('image/');
};
// 如果当前文件不是图片,直接打开
) {
// 非图片文件直接打开链接
if (!isImageFile(file)) {
if (file.url) {
window.open(file.url, '_blank');
} else if (file.preview) {
window.open(file.preview, '_blank');
const url = file.url || file.preview;
if (url) {
window.open(url, '_blank');
} else {
console.warn('无法打开文件没有可用的URL或预览地址');
message.error($t('ui.formRules.previewWarning'));
}
return;
}
// 对于图片文件,继续使用预览组
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
Image,
PreviewGroup,
]);
const getBase64 = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => resolve(reader.result));
reader.addEventListener('error', (error) => reject(error));
});
};
// 从fileList中过滤出所有图片文件
const imageFiles = (unref(fileList) || []).filter((element) =>
isImageFile(element),
);
// 过滤图片文件并生成预览
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)) as string;
imgFile.preview = await getBase64(imgFile.originFileObj);
}
}
const container: HTMLElement | null = document.createElement('div');
document.body.append(container);
// 用于追踪组件是否已卸载
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 () => {
@ -301,12 +284,10 @@ const previewImage = async (
class: 'hidden',
preview: {
visible: visible.value,
// 设置初始显示的图片索引
current: imageFiles.findIndex((f) => f.uid === file.uid),
current: currentIndex,
onVisibleChange: (value: boolean) => {
visible.value = value;
if (!value) {
// 延迟清理,确保动画完成
setTimeout(() => {
if (!isUnmounted && container) {
isUnmounted = true;
@ -319,7 +300,6 @@ const previewImage = async (
},
},
() =>
// 渲染所有图片文件
imageFiles.map((imgFile) =>
h(ImageComponent, {
key: imgFile.uid,
@ -332,6 +312,292 @@ const previewImage = async (
};
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<boolean>(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,
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: (ref: any) => (cropperRef.value = ref),
img: objectUrl as string,
aspectRatio,
}),
);
};
},
};
render(h(CropperWrapper), container);
});
}
/**
*
*/
const withPreviewUpload = () => {
return defineComponent({
name: Upload.name,
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;
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) => {
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,
),
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
@ -369,11 +635,45 @@ export type ComponentType =
| 'Upload'
| 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),
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
component: Cascader,
fieldNames: { label: 'label', value: 'value', children: 'children' },
@ -381,34 +681,20 @@ async function initComponentAdapter() {
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
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,

View File

@ -1,9 +1,9 @@
import type {
VbenFormProps as FormProps,
VbenFormSchema as FormSchema,
VbenFormProps,
} 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';
@ -61,9 +61,9 @@ async function initSetupVbenForm() {
});
}
const useVbenForm = useForm<ComponentType>;
const useVbenForm = useForm<ComponentType, ComponentPropsMap>;
export { initSetupVbenForm, useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };
export type VbenFormSchema = FormSchema<ComponentType, ComponentPropsMap>;
export type VbenFormProps = FormProps<ComponentType, ComponentPropsMap>;

View File

@ -1,6 +1,8 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { Recordable } from '@vben/types';
import type { ComponentPropsMap, ComponentType } from './component';
import { h } from 'vue';
import { IconifyIcon } from '@vben/icons';
@ -10,7 +12,7 @@ import {
AsyncVxeTable,
createRequiredValidation,
setupVbenVxeTable,
useVbenVxeGrid,
useVbenVxeGrid as useGrid,
} from '@vben/plugins/vxe-table';
import {
erpCountInputFormatter,
@ -199,7 +201,7 @@ setupVbenVxeTable({
vxeUI.renderer.add('CellOperation', {
renderTableDefault({ attrs, options, props }, { column, row }) {
const defaultProps = { size: 'small', type: 'link', ...props };
let align = 'end';
let align: string;
switch (column.align) {
case 'center': {
align = 'center';
@ -363,10 +365,13 @@ setupVbenVxeTable({
useVbenForm,
});
export { createRequiredValidation, useVbenVxeGrid };
export { createRequiredValidation };
export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn];
export * from '#/components/table-action';
export const useVbenVxeGrid = <T extends Record<string, any>>(
...rest: Parameters<typeof useGrid<T, ComponentType, ComponentPropsMap>>
) => useGrid<T, ComponentType, ComponentPropsMap>(...rest);
export type * from '@vben/plugins/vxe-table';

View File

@ -16,10 +16,10 @@ export namespace CrmCustomerLimitConfigApi {
/** 客户限制配置类型 */
export enum LimitConfType {
/** 锁定客户数限制 */
CUSTOMER_LOCK_LIMIT = 2,
/** 拥有客户数限制 */
CUSTOMER_QUANTITY_LIMIT = 1,
/** 锁定客户数限制 */
CUSTOMER_LOCK_LIMIT = 2,
}
/** 查询客户限制配置列表 */

View File

@ -35,11 +35,11 @@ export namespace CrmPermissionApi {
* CRM
*/
export enum BizTypeEnum {
CRM_BUSINESS = 4, // 商机
CRM_CLUE = 1, // 线索
CRM_CONTACT = 3, // 联系人
CRM_CONTRACT = 5, // 合同
CRM_CUSTOMER = 2, // 客户
CRM_CONTACT = 3, // 联系人
CRM_BUSINESS = 4, // 商机
CRM_CONTRACT = 5, // 合同
CRM_PRODUCT = 6, // 产品
CRM_RECEIVABLE = 7, // 回款
CRM_RECEIVABLE_PLAN = 8, // 回款计划

View File

@ -3,39 +3,48 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace IotDeviceApi {
// TODO @haohao需要跟后端对齐必要的 ReqVO、RespVO
/** 设备 */
export interface Device {
id?: number; // 设备 ID主键自增
id?: number; // 设备编号
deviceName: string; // 设备名称
nickname?: string; // 备注名称
serialNumber?: string; // 设备序列号
picUrl?: string; // 设备图片
groupIds?: number[]; // 设备分组编号数组
productId: number; // 产品编号
productKey?: string; // 产品标识
productName?: string; // 产品名称(只有部分接口返回,例如 getDeviceLocationList
deviceType?: number; // 设备类型
nickname?: string; // 设备备注名称
gatewayId?: number; // 网关设备 ID
state?: number; // 设备状态
status?: number; // 设备状态(兼容字段)
onlineTime?: Date; // 最后上线时间
offlineTime?: Date; // 最后离线时间
activeTime?: Date; // 设备激活时间
createTime?: Date; // 创建时间
ip?: string; // 设备的 IP 地址
firmwareVersion?: string; // 设备的固件版本
deviceSecret?: string; // 设备密钥,用于设备认证,需安全存储
mqttClientId?: string; // MQTT 客户端 ID
mqttUsername?: string; // MQTT 用户名
mqttPassword?: string; // MQTT 密码
authType?: string; // 认证类型
locationType?: number; // 定位类型
deviceSecret?: string; // 设备密钥,用于设备认证
config?: string; // 设备配置
latitude?: number; // 设备位置的纬度
longitude?: number; // 设备位置的经度
areaId?: number; // 地区编码
address?: string; // 设备详细地址
serialNumber?: string; // 设备序列号
config?: string; // 设备配置
groupIds?: number[]; // 添加分组 ID
picUrl?: string; // 设备图片
location?: string; // 位置信息(格式:经度,纬度)
createTime?: Date; // 创建时间
}
/** 设备更新分组 Request VO */
export interface DeviceUpdateGroupReqVO {
ids: number[]; // 设备编号列表(必填)
groupIds: number[]; // 分组编号列表(必填)
}
/** 设备认证信息 Response VO */
export interface DeviceAuthInfoRespVO {
clientId: string; // 客户端 ID
username: string; // 用户名
password: string; // 密码
}
/** 设备导入 Response VO */
export interface DeviceImportRespVO {
createDeviceNames?: string[]; // 创建成功的设备名称列表
updateDeviceNames?: string[]; // 更新成功的设备名称列表
failureDeviceNames?: Record<string, string>; // 失败的设备名称及原因
}
/** IoT 设备属性详细 VO */
@ -56,25 +65,12 @@ export namespace IotDeviceApi {
updateTime: Date; // 更新时间
}
/** 设备认证参数 VO */
export interface DeviceAuthInfo {
clientId: string; // 客户端 ID
username: string; // 用户名
password: string; // 密码
}
/** 设备发送消息 Request VO */
export interface DeviceMessageSendReq {
deviceId: number; // 设备编号
method: string; // 请求方法
params?: any; // 请求参数
}
/** 设备分组更新请求 */
export interface DeviceGroupUpdateReq {
ids: number[]; // 设备 ID 列表
groupIds: number[]; // 分组 ID 列表
}
}
/** 查询设备分页 */
@ -92,33 +88,33 @@ export function getDevice(id: number) {
/** 新增设备 */
export function createDevice(data: IotDeviceApi.Device) {
return requestClient.post('/iot/device/create', data);
return requestClient.post<number>('/iot/device/create', data);
}
/** 修改设备 */
export function updateDevice(data: IotDeviceApi.Device) {
return requestClient.put('/iot/device/update', data);
return requestClient.put<boolean>('/iot/device/update', data);
}
/** 修改设备分组 */
export function updateDeviceGroup(data: IotDeviceApi.DeviceGroupUpdateReq) {
return requestClient.put('/iot/device/update-group', data);
export function updateDeviceGroup(data: IotDeviceApi.DeviceUpdateGroupReqVO) {
return requestClient.put<boolean>('/iot/device/update-group', data);
}
/** 删除单个设备 */
export function deleteDevice(id: number) {
return requestClient.delete(`/iot/device/delete?id=${id}`);
return requestClient.delete<boolean>(`/iot/device/delete?id=${id}`);
}
/** 删除多个设备 */
export function deleteDeviceList(ids: number[]) {
return requestClient.delete('/iot/device/delete-list', {
return requestClient.delete<boolean>('/iot/device/delete-list', {
params: { ids: ids.join(',') },
});
}
/** 导出设备 */
export function exportDeviceExcel(params: any) {
export function exportDeviceExcel(params: PageParam) {
return requestClient.download('/iot/device/export-excel', { params });
}
@ -141,6 +137,11 @@ export function getDeviceListByProductId(productId: number) {
});
}
/** 获取设备位置列表(用于地图展示) */
export function getDeviceLocationList() {
return requestClient.get<IotDeviceApi.Device[]>('/iot/device/location-list');
}
/** 获取导入模板 */
export function importDeviceTemplate() {
return requestClient.download('/iot/device/get-import-template');
@ -148,10 +149,13 @@ export function importDeviceTemplate() {
/** 导入设备 */
export function importDevice(file: File, updateSupport: boolean) {
return requestClient.upload('/iot/device/import', {
file,
updateSupport,
});
return requestClient.upload<IotDeviceApi.DeviceImportRespVO>(
'/iot/device/import',
{
file,
updateSupport,
},
);
}
/** 获取设备属性最新数据 */
@ -172,7 +176,7 @@ export function getHistoryDevicePropertyList(params: any) {
/** 获取设备认证信息 */
export function getDeviceAuthInfo(id: number) {
return requestClient.get<IotDeviceApi.DeviceAuthInfo>(
return requestClient.get<IotDeviceApi.DeviceAuthInfoRespVO>(
'/iot/device/get-auth-info',
{ params: { id } },
);
@ -196,3 +200,35 @@ export function getDeviceMessagePairPage(params: PageParam) {
export function sendDeviceMessage(params: IotDeviceApi.DeviceMessageSendReq) {
return requestClient.post('/iot/device/message/send', params);
}
/** 绑定子设备到网关设备 */
export function bindDeviceGateway(gatewayId: number, subIds: number[]) {
return requestClient.put<boolean>('/iot/device/bind-gateway', {
gatewayId,
subIds,
});
}
/** 解绑子设备与网关设备 */
export function unbindDeviceGateway(gatewayId: number, subIds: number[]) {
return requestClient.put<boolean>('/iot/device/unbind-gateway', {
gatewayId,
subIds,
});
}
/** 获取网关设备的子设备列表 */
export function getSubDeviceList(gatewayId: number) {
return requestClient.get<IotDeviceApi.Device[]>(
'/iot/device/sub-device-list',
{ params: { gatewayId } },
);
}
/** 获取未绑定的子设备分页 */
export function getUnboundSubDevicePage(params: PageParam) {
return requestClient.get<PageResult<IotDeviceApi.Device>>(
'/iot/device/unbound-sub-device-page',
{ params },
);
}

View File

@ -0,0 +1,30 @@
import { requestClient } from '#/api/request';
export namespace IotDeviceModbusConfigApi {
/** Modbus 连接配置 VO */
export interface ModbusConfig {
id?: number; // 主键
deviceId: number; // 设备编号
ip: string; // Modbus 服务器 IP 地址
port: number; // Modbus 服务器端口
slaveId: number; // 从站地址
timeout: number; // 连接超时时间,单位:毫秒
retryInterval: number; // 重试间隔,单位:毫秒
mode: number; // 模式
frameFormat: number; // 帧格式
status: number; // 状态
}
}
/** 获取设备的 Modbus 连接配置 */
export function getModbusConfig(deviceId: number) {
return requestClient.get<IotDeviceModbusConfigApi.ModbusConfig>(
'/iot/device-modbus-config/get',
{ params: { deviceId } },
);
}
/** 保存 Modbus 连接配置 */
export function saveModbusConfig(data: IotDeviceModbusConfigApi.ModbusConfig) {
return requestClient.post('/iot/device-modbus-config/save', data);
}

View File

@ -0,0 +1,52 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace IotDeviceModbusPointApi {
/** Modbus 点位配置 VO */
export interface ModbusPoint {
id?: number; // 主键
deviceId: number; // 设备编号
thingModelId?: number; // 物模型属性编号
identifier: string; // 属性标识符
name: string; // 属性名称
functionCode?: number; // Modbus 功能码
registerAddress?: number; // 寄存器起始地址
registerCount?: number; // 寄存器数量
byteOrder?: string; // 字节序
rawDataType?: string; // 原始数据类型
scale: number; // 缩放因子
pollInterval: number; // 轮询间隔,单位:毫秒
status: number; // 状态
}
}
/** 获取设备的 Modbus 点位分页 */
export function getModbusPointPage(params: PageParam) {
return requestClient.get<PageResult<IotDeviceModbusPointApi.ModbusPoint>>(
'/iot/device-modbus-point/page',
{ params },
);
}
/** 获取 Modbus 点位详情 */
export function getModbusPoint(id: number) {
return requestClient.get<IotDeviceModbusPointApi.ModbusPoint>(
`/iot/device-modbus-point/get?id=${id}`,
);
}
/** 创建 Modbus 点位配置 */
export function createModbusPoint(data: IotDeviceModbusPointApi.ModbusPoint) {
return requestClient.post('/iot/device-modbus-point/create', data);
}
/** 更新 Modbus 点位配置 */
export function updateModbusPoint(data: IotDeviceModbusPointApi.ModbusPoint) {
return requestClient.put('/iot/device-modbus-point/update', data);
}
/** 删除 Modbus 点位配置 */
export function deleteModbusPoint(id: number) {
return requestClient.delete(`/iot/device-modbus-point/delete?id=${id}`);
}

View File

@ -8,8 +8,9 @@ export namespace IotProductApi {
id?: number; // 产品编号
name: string; // 产品名称
productKey?: string; // 产品标识
productSecret?: string; // 产品密钥
protocolId?: number; // 协议编号
protocolType?: number; // 接入协议类型
protocolType?: string; // 协议类型
categoryId?: number; // 产品所属品类标识符
categoryName?: string; // 产品所属品类名称
icon?: string; // 产品图标
@ -17,16 +18,35 @@ export namespace IotProductApi {
description?: string; // 产品描述
status?: number; // 产品状态
deviceType?: number; // 设备类型
locationType?: number; // 定位类型
netType?: number; // 联网方式
codecType?: string; // 数据格式(编解码器类型)
serializeType?: string; // 序列化类型
dataFormat?: number; // 数据格式
validateType?: number; // 认证方式
registerEnabled?: boolean; // 是否开启动态注册
deviceCount?: number; // 设备数量
createTime?: Date; // 创建时间
}
}
// IoT 协议类型枚举
export enum ProtocolTypeEnum {
COAP = 'coap',
EMQX = 'emqx',
HTTP = 'http',
MODBUS_TCP_CLIENT = 'modbus_tcp_client',
MODBUS_TCP_SERVER = 'modbus_tcp_server',
MQTT = 'mqtt',
TCP = 'tcp',
UDP = 'udp',
WEBSOCKET = 'websocket',
}
// IoT 序列化类型枚举
export enum SerializeTypeEnum {
BINARY = 'binary',
JSON = 'json',
}
/** 查询产品分页 */
export function getProductPage(params: PageParam) {
return requestClient.get<PageResult<IotProductApi.Product>>(
@ -68,8 +88,13 @@ export function updateProductStatus(id: number, status: number) {
}
/** 查询产品(精简)列表 */
export function getSimpleProductList() {
return requestClient.get<IotProductApi.Product[]>('/iot/product/simple-list');
export function getSimpleProductList(deviceType?: number) {
return requestClient.get<IotProductApi.Product[]>(
'/iot/product/simple-list',
{
params: { deviceType },
},
);
}
/** 根据 ProductKey 获取产品信息 */

View File

@ -119,7 +119,9 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
response.data = apiEncrypt.decryptResponse(response.data);
} catch (error) {
console.error('响应数据解密失败:', error);
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
throw new Error(`响应数据解密失败: ${(error as Error).message}`, {
cause: error,
});
}
}
return response;

View File

@ -41,16 +41,8 @@ const [Modal, modalApi] = useVbenModal({
onConfirm: handleOk,
onOpenChange(isOpen) {
if (isOpen) {
// loading CropperImage loading handleReady
modalLoading(true);
const img = new Image();
img.src = src.value;
img.addEventListener('load', () => {
modalLoading(false);
});
img.addEventListener('error', () => {
modalLoading(false);
});
// loading loading
modalLoading(!!src.value);
} else {
//
previewSource.value = '';
@ -73,10 +65,14 @@ function handleBeforeUpload(file: File) {
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
modalLoading(true);
reader.addEventListener('load', (e) => {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
});
reader.addEventListener('error', () => {
modalLoading(false);
});
return false;
}
@ -90,6 +86,10 @@ function handleReady(cropperInstance: CropperType) {
modalLoading(false);
}
function handleCropperError() {
modalLoading(false);
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
@ -141,6 +141,7 @@ async function handleOk() {
:src="src"
height="300px"
@cropend="handleCropend"
@cropend-error="handleCropperError"
@ready="handleReady"
/>
</div>

View File

@ -143,6 +143,10 @@ function getRoundedCanvas() {
context.fill();
return canvas;
}
function handleImageError() {
emit('cropendError');
}
</script>
<template>
@ -154,6 +158,7 @@ function getRoundedCanvas() {
:crossorigin="crossorigin"
:src="src"
:style="getImageStyle"
@error="handleImageError"
class="h-auto max-w-full"
/>
</div>

View File

@ -21,7 +21,7 @@ export function useDescription(options?: Partial<DescriptionProps>) {
inheritAttrs: false,
setup(_props, { attrs, slots }) {
return () => {
// @ts-ignore - 避免类型实例化过深
// @ts-expect-error - 避免类型实例化过深
return h(Description, { ...propsState, ...attrs }, slots);
};
},

View File

@ -0,0 +1,155 @@
<!-- 省市区选择器 (Ant Design Vue 版本) -->
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { AreaLevelEnum } from '@vben/constants';
import { Cascader } from 'ant-design-vue';
import { getAreaTree } from '#/api/system/area';
defineOptions({ name: 'AreaSelect' });
const props = withDefaults(defineProps<Props>(), {
modelValue: undefined,
value: undefined,
level: AreaLevelEnum.DISTRICT,
disabled: false,
placeholder: '请选择省市区',
clearable: true,
showAllLevels: true,
separator: '/',
});
const emit = defineEmits<{
(e: 'update:modelValue', value: number[] | string[] | undefined): void;
(e: 'update:value', value: number[] | string[] | undefined): void;
}>();
//
interface AreaVO {
id: number;
name: string;
code: string;
parentId?: number;
sort?: number;
status?: number;
children?: AreaVO[];
}
//
interface Props {
modelValue?: number[] | string[];
value?: number[] | string[];
level?: (typeof AreaLevelEnum)[keyof typeof AreaLevelEnum];
disabled?: boolean;
placeholder?: string;
clearable?: boolean;
showAllLevels?: boolean;
separator?: string;
// eslint-disable-next-line vue/require-default-prop
formCreateInject?: any;
}
// Ant Design Vue Cascader fieldNames
const fieldNames = {
label: 'name',
value: 'id',
children: 'children',
};
//
const areaTree = ref<AreaVO[]>([]);
//
const selectedValue = ref<number[] | undefined>();
//
const loading = ref(false);
//
async function loadAreaTree(): Promise<void> {
try {
loading.value = true;
const data = await getAreaTree();
// level
areaTree.value = filterTreeByLevel((data || []) as AreaVO[], props.level);
} catch (error) {
console.warn('[AreaSelect] 加载地区数据失败:', error);
areaTree.value = [];
} finally {
loading.value = false;
}
}
//
function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
if (maxLevel <= 0) return [];
return tree.map((node) => {
const newNode = { ...node };
// , children
if (maxLevel === 1) {
delete newNode.children;
} else if (node.children && node.children.length > 0) {
//
newNode.children = filterTreeByLevel(node.children, maxLevel - 1);
}
return newNode;
});
}
//
function handleChange(value: any): void {
if (value === undefined || value === null) {
emit('update:modelValue', undefined);
emit('update:value', undefined);
return;
}
emit('update:modelValue', value);
emit('update:value', value);
}
// modelValue value
function syncSelectedValue(): void {
const newValue = props.modelValue || props.value;
if (newValue === undefined || newValue === null) {
selectedValue.value = undefined;
return;
}
//
selectedValue.value = Array.isArray(newValue)
? (newValue as number[])
: [newValue as number];
}
// modelValue value
watch(() => props.modelValue || props.value, syncSelectedValue, {
immediate: true,
});
//
onMounted(async () => {
await loadAreaTree();
});
</script>
<template>
<Cascader
v-model:value="selectedValue"
class="w-full"
:options="areaTree"
:field-names="fieldNames"
:disabled="disabled"
:placeholder="placeholder"
:allow-clear="clearable"
:show-search="true"
:change-on-select="true"
:loading="loading"
@change="handleChange"
/>
</template>

View File

@ -0,0 +1,206 @@
<!-- 部门选择器 - 树形结构显示 -->
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { useUserStore } from '@vben/stores';
import { handleTree } from '@vben/utils';
import { TreeSelect } from 'ant-design-vue';
import { requestClient } from '#/api/request';
defineOptions({ name: 'DeptSelect' });
const props = withDefaults(defineProps<Props>(), {
multiple: false,
returnType: 'id',
defaultCurrentDept: false,
disabled: false,
placeholder: '',
});
const emit = defineEmits<{
(
e: 'update:modelValue',
value: number | number[] | string | string[] | undefined,
): void;
}>();
// todo @puhui999使 api
/** 部门数据接口 */
interface DeptVO {
id: number;
name: string;
parentId: number;
sort?: number;
leaderUserId?: number;
phone?: string;
email?: string;
status?: number;
}
/** 接受父组件参数 */
interface Props {
// eslint-disable-next-line vue/require-default-prop
modelValue?: number | number[] | string | string[];
multiple?: boolean;
returnType?: 'id' | 'name';
defaultCurrentDept?: boolean;
disabled?: boolean;
placeholder?: string;
// eslint-disable-next-line vue/require-default-prop
formCreateInject?: any;
}
const deptTree = ref<any[]>([]); //
const deptList = ref<DeptVO[]>([]); // returnType='name'
const selectedValue = ref<number | number[] | undefined>(); //
/** 加载部门树形数据 */
async function loadDeptTree(): Promise<void> {
try {
const data = await requestClient.get<DeptVO[]>('/system/dept/simple-list');
deptList.value = data;
deptTree.value = handleTree(data);
} catch (error) {
console.warn('[DeptSelect] 加载部门数据失败:', error);
deptTree.value = [];
}
}
/** 根据 ID 获取部门名称 */
function getDeptNameById(id: number): string | undefined {
const dept = deptList.value.find((item: DeptVO) => item.id === id);
if (!dept) {
console.warn(`[DeptSelect] 未找到 ID 为 ${id} 的部门`);
}
return dept?.name;
}
/** 根据名称获取部门 ID */
function getDeptIdByName(name: string): number | undefined {
const dept = deptList.value.find((item: DeptVO) => item.name === name);
return dept?.id;
}
/** 处理选中值变化 */
function handleChange(value: number | number[] | undefined): void {
if (value === undefined || value === null) {
emit('update:modelValue', props.multiple ? [] : undefined);
return;
}
// returnType
if (props.returnType === 'name') {
if (props.multiple && Array.isArray(value)) {
const names = value
.map((id) => getDeptNameById(id))
.filter(Boolean) as string[];
emit('update:modelValue', names);
} else if (!props.multiple && typeof value === 'number') {
const name = getDeptNameById(value);
emit('update:modelValue', name);
}
} else {
emit('update:modelValue', value);
}
}
/** 树节点过滤方法(支持搜索过滤) */
function filterTreeNode(inputValue: string, treeNode: any): boolean {
if (!inputValue) return true;
return treeNode.name?.toLowerCase().includes(inputValue.toLowerCase());
}
/** 同步 modelValue 到内部选中值 */
function syncSelectedValue(): void {
const newValue = props.modelValue;
if (newValue === undefined || newValue === null) {
selectedValue.value = props.multiple ? [] : undefined;
return;
}
// returnType 'name' ID
if (props.returnType === 'name') {
// deptList
if (deptList.value.length === 0) {
return;
}
if (props.multiple && Array.isArray(newValue)) {
selectedValue.value = (newValue as string[])
.map((name) => getDeptIdByName(name))
.filter(Boolean) as number[];
} else if (!props.multiple && typeof newValue === 'string') {
selectedValue.value = getDeptIdByName(newValue);
}
} else {
selectedValue.value = newValue as number | number[];
}
}
/** 监听 modelValue 变化,同步到内部选中值 */
watch(() => props.modelValue, syncSelectedValue, { immediate: true });
/** 监听 deptList 变化,重新同步选中值(解决数据加载完成后的回显问题) */
watch(() => deptList.value, syncSelectedValue);
/** 检查是否有有效的预设值 */
function hasValidPresetValue(): boolean {
const value = props.modelValue;
if (value === undefined || value === null || value === '') {
return false;
}
if (Array.isArray(value)) {
return value.length > 0;
}
return true;
}
/** 设置默认值(当前用户部门) */
function setDefaultValue(): void {
// defaultCurrentDept true
if (!props.defaultCurrentDept) {
return;
}
//
if (hasValidPresetValue()) {
return;
}
// ID
const userStore = useUserStore();
const deptId = userStore.userInfo?.deptId as number | undefined;
// deptId 0
if (!deptId || deptId === 0) {
return;
}
//
const defaultValue = props.multiple ? [deptId] : deptId;
emit('update:modelValue', defaultValue);
}
/** 组件挂载时加载数据并设置默认值 */
onMounted(async () => {
await loadDeptTree();
//
setDefaultValue();
});
</script>
<template>
<TreeSelect
v-model:value="selectedValue"
class="w-full"
:tree-data="deptTree"
:field-names="{ label: 'name', value: 'id', children: 'children' }"
:multiple="multiple"
:disabled="disabled"
:placeholder="placeholder || '请选择部门'"
:tree-checkable="multiple"
:show-search="true"
:filter-tree-node="filterTreeNode"
:allow-clear="true"
@change="handleChange"
/>
</template>

View File

@ -0,0 +1,95 @@
<!-- 网页 iframe 组件 (Ant Design Vue 版本) -->
<script lang="ts" setup>
import { computed } from 'vue';
import { isUrl } from '#/utils';
defineOptions({ name: 'IframeComponent' });
const props = withDefaults(defineProps<Props>(), {
modelValue: '',
value: '',
url: '',
height: '500px',
width: '100%',
frameborder: '0',
allowfullscreen: true,
loading: 'lazy',
sandbox: '',
});
//
interface Props {
modelValue?: string;
value?: string;
url?: string;
height?: string;
width?: string;
frameborder?: string;
allowfullscreen?: boolean;
loading?: 'eager' | 'lazy';
sandbox?: string;
// eslint-disable-next-line vue/require-default-prop
formCreateInject?: any;
}
// URL使 url prop使 value modelValue
const displayUrl = computed(
() => props.url || props.value || props.modelValue || '',
);
//
const showPreview = computed(() => {
return displayUrl.value && isUrl(displayUrl.value);
});
</script>
<template>
<div class="iframe-component">
<!-- iframe 预览 -->
<div v-if="showPreview" class="iframe-preview">
<iframe
:src="displayUrl"
:width="width"
:height="height"
:frameborder="frameborder"
:allowfullscreen="allowfullscreen"
:loading="loading"
:sandbox="sandbox || undefined"
class="iframe-content"
></iframe>
</div>
<!-- URL 或无效 URL 提示 -->
<div v-else class="iframe-placeholder">
<a-empty description="请在右侧属性面板配置 URL 地址" />
</div>
</div>
</template>
<style scoped>
.iframe-component {
width: 100%;
}
.iframe-preview {
overflow: hidden;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
.iframe-content {
display: block;
border: none;
}
.iframe-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
background-color: #fafafa;
border: 1px dashed #d9d9d9;
border-radius: 4px;
}
</style>

View File

@ -2,6 +2,7 @@ import type { ApiSelectProps } from '#/components/form-create/typing';
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
import { useUserStore } from '@vben/stores';
import { isEmpty } from '@vben/utils';
import {
@ -74,12 +75,46 @@ export function useApiSelect(option: ApiSelectProps) {
type: String,
default: 'id',
},
// 是否默认选中当前用户(仅用于 UserSelect
defaultCurrentUser: {
type: Boolean,
default: false,
},
},
setup(props) {
setup(props, { emit }) {
const attrs = useAttrs();
const options = ref<any[]>([]); // 下拉数据
const loading = ref(false); // 是否正在从远程获取数据
const queryParam = ref<any>(); // 当前输入的值
// 检查是否有有效的预设值
function hasValidPresetValue(): boolean {
const value = attrs.modelValue;
if (value === undefined || value === null || value === '') {
return false;
}
if (Array.isArray(value)) {
return value.length > 0;
}
return true;
}
// 设置默认当前用户
function setDefaultCurrentUser(): void {
if (option.name !== 'UserSelect' || !props.defaultCurrentUser) {
return;
}
if (hasValidPresetValue()) {
return;
}
const userStore = useUserStore();
const currentUserId = userStore.userInfo?.id;
if (currentUserId) {
const defaultValue = props.multiple ? [currentUserId] : currentUserId;
emit('update:modelValue', defaultValue);
}
}
const getOptions = async () => {
options.value = [];
// 接口选择器
@ -158,7 +193,8 @@ export function useApiSelect(option: ApiSelectProps) {
let parse: any = null;
if (props.parseFunc) {
// 解析字符串函数
// eslint-disable-next-line no-new-func
// oxlint-disable-next-line typescript/no-implied-eval
// oxlint-disable-next-line no-new-func, typescript/no-implied-eval
parse = new Function(`return ${props.parseFunc}`)();
}
return parse;
@ -199,6 +235,8 @@ export function useApiSelect(option: ApiSelectProps) {
onMounted(async () => {
await getOptions();
// 设置默认当前用户(仅用于 UserSelect
setDefaultCurrentUser();
});
const buildSelect = () => {

View File

@ -15,9 +15,8 @@ export function useImagesUpload() {
default: 5,
},
},
setup() {
// TODO: @puhui999@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
return (props: { maxNumber?: number; multiple?: boolean }) => (
setup(props) {
return () => (
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
);
},

View File

@ -11,8 +11,10 @@ import formCreate from '@form-create/ant-design-vue';
import { apiSelectRule } from '#/components/form-create/rules/data';
import {
useAreaSelectRule,
useDictSelectRule,
useEditorRule,
useIframeRule,
useSelectRule,
useUploadFileRule,
useUploadImageRule,
@ -160,6 +162,8 @@ export async function useFormCreateDesigner(designer: Ref) {
const uploadFileRule = useUploadFileRule();
const uploadImageRule = useUploadImageRule();
const uploadImagesRule = useUploadImagesRule();
const iframeRule = useIframeRule();
const areaSelectRule = useAreaSelectRule();
/** 构建表单组件 */
function buildFormComponents() {
@ -172,6 +176,8 @@ export async function useFormCreateDesigner(designer: Ref) {
uploadFileRule,
uploadImageRule,
uploadImagesRule,
iframeRule,
areaSelectRule,
];
components.forEach((component) => {
// 插入组件规则
@ -189,6 +195,14 @@ export async function useFormCreateDesigner(designer: Ref) {
name: 'UserSelect',
label: '用户选择器',
icon: 'icon-eye',
props: [
{
type: 'switch',
field: 'defaultCurrentUser',
title: '默认选中当前用户',
value: false,
},
],
});
const deptSelectRule = useSelectRule({
name: 'DeptSelect',
@ -205,6 +219,12 @@ export async function useFormCreateDesigner(designer: Ref) {
{ label: '部门名称', value: 'name' },
],
},
{
type: 'switch',
field: 'defaultCurrentDept',
title: '默认选中当前部门',
value: false,
},
],
});
const dictSelectRule = useDictSelectRule();

View File

@ -1,4 +1,3 @@
/* eslint-disable no-template-curly-in-string */
const selectRule = [
{
type: 'select',
@ -121,7 +120,7 @@ const apiSelectRule = [
field: 'data',
title: '请求参数 JSON 格式',
props: {
autoSize: true,
autoSize: true, // 特殊ele 里是 autosizeantd 里是 autoSize
type: 'textarea',
placeholder: '{"type": 1}',
},
@ -134,7 +133,7 @@ const apiSelectRule = [
type: 'input',
field: 'labelField',
title: 'label 属性',
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
info: `可以使用 el 表达式:\${},来实现复杂数据组合。如:\${nickname}-\${id}`,
props: {
placeholder: 'nickname',
},
@ -143,7 +142,7 @@ const apiSelectRule = [
type: 'input',
field: 'valueField',
title: 'value 属性',
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
info: `可以使用 el 表达式:\${},来实现复杂数据组合。如:\${nickname}-\${id}`,
props: {
placeholder: 'id',
},
@ -155,7 +154,7 @@ const apiSelectRule = [
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
(data: any)=>{ label: string; value: any }[]`,
props: {
autoSize: true,
autoSize: true, // 特殊ele 里是 autosizeantd 里是 autoSize
rows: { minRows: 2, maxRows: 6 },
type: 'textarea',
placeholder: `

View File

@ -1,5 +1,7 @@
export { useAreaSelectRule } from './use-area-select-rule';
export { useDictSelectRule } from './use-dict-select';
export { useEditorRule } from './use-editor-rule';
export { useIframeRule } from './use-iframe-rule';
export { useSelectRule } from './use-select-rule';
export { useUploadFileRule } from './use-upload-file-rule';
export { useUploadImageRule } from './use-upload-image-rule';

View File

@ -0,0 +1,77 @@
import { AreaLevelEnum } from '@vben/constants';
import {
localeProps,
makeRequiredRule,
} from '#/components/form-create/helpers';
/** 省市区选择器规则 */
export function useAreaSelectRule() {
const label = '省市区选择器';
const name = 'AreaSelect';
return {
icon: 'icon-location',
label,
name,
rule() {
return {
type: name,
field: `area_${Date.now()}`,
title: label,
info: '',
$required: false,
modelField: 'value', // 特殊ele 里是 model-valueantd 里是 value
};
},
props(_: any, { t }: any) {
return localeProps(t, `${name}.props`, [
makeRequiredRule(),
{
type: 'select',
field: 'level',
title: '选择层级',
value: AreaLevelEnum.DISTRICT,
options: [
{ label: '省', value: AreaLevelEnum.PROVINCE },
{ label: '省/市', value: AreaLevelEnum.CITY },
{ label: '省/市/区', value: AreaLevelEnum.DISTRICT },
],
info: '限制可选择的地区层级',
},
{
type: 'input',
field: 'placeholder',
title: '占位符',
value: '请选择省市区',
},
{
type: 'switch',
field: 'clearable',
title: '是否可清空',
value: true,
},
{
type: 'switch',
field: 'showAllLevels',
title: '显示完整路径',
value: true,
info: '输入框中是否显示选中值的完整路径',
},
{
type: 'input',
field: 'separator',
title: '分隔符',
value: '/',
info: '选项分隔符',
},
{
type: 'switch',
field: 'disabled',
title: '是否禁用',
value: false,
},
]);
},
};
}

View File

@ -39,7 +39,7 @@ export function useDictSelectRule() {
title: label,
info: '',
$required: false,
modelField: 'value',
modelField: 'value', // 特殊ele 里是 model-valueantd 里是 value
};
},
props(_: any, { t }: any) {

View File

@ -0,0 +1,77 @@
import { buildUUID } from '@vben/utils';
import {
localeProps,
makeRequiredRule,
} from '#/components/form-create/helpers';
/** iframe 组件规则 */
export function useIframeRule() {
const label = '网页 iframe';
const name = 'IframeComponent';
return {
icon: 'icon-link',
label,
name,
rule() {
return {
type: name,
field: buildUUID(),
title: label,
info: '',
$required: false,
modelField: 'value', // 特殊ele 里是 model-valueantd 里是 value
};
},
props(_: any, { t }: any) {
return localeProps(t, `${name}.props`, [
makeRequiredRule(),
{
type: 'input',
field: 'url',
title: 'URL 地址',
value: '',
info: '请输入完整的 HTTP 或 HTTPS 地址',
},
{
type: 'input',
field: 'height',
title: 'iframe 高度',
value: '500px',
info: '支持 px、%、vh 等单位',
},
{
type: 'input',
field: 'width',
title: 'iframe 宽度',
value: '100%',
info: '支持 px、%、vw 等单位',
},
{
type: 'select',
field: 'loading',
title: '加载方式',
value: 'lazy',
options: [
{ label: '懒加载', value: 'lazy' },
{ label: '立即加载', value: 'eager' },
],
},
{
type: 'switch',
field: 'allowfullscreen',
title: '允许全屏',
value: true,
},
{
type: 'input',
field: 'sandbox',
title: 'sandbox 属性',
value: '',
info: '安全沙箱限制allow-scripts allow-same-origin',
},
]);
},
};
}

View File

@ -23,13 +23,24 @@ export function useSelectRule(option: SelectRuleOption) {
name,
event: option.event,
rule() {
return {
// 构建基础规则
const baseRule: any = {
type: name,
field: buildUUID(),
title: label,
info: '',
$required: false,
};
// 将自定义 props 的默认值添加到 rule 的 props 中
if (option.props && option.props.length > 0) {
baseRule.props = {};
option.props.forEach((prop: any) => {
if (prop.field && prop.value !== undefined) {
baseRule.props[prop.field] = prop.value;
}
});
}
return baseRule;
},
props(_: any, { t }: any) {
if (!option.props) {

View File

@ -0,0 +1,3 @@
export { default as MapDialog } from './src/map-dialog.vue';
export { loadBaiduMapSdk } from './src/utils';

View File

@ -0,0 +1,287 @@
<!-- 地图选择弹窗组件基于百度地图 GL 实现 -->
<script setup lang="ts">
import { nextTick, reactive, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button, Form, Input, Select, Space } from 'ant-design-vue';
import { loadBaiduMapSdk } from './utils';
const emit = defineEmits<{
confirm: [
data: {
address: string;
latitude: string;
longitude: string;
},
];
}>();
const mapContainerRef = ref<HTMLElement>();
const state = reactive({
lonLat: '', // ","
address: '', //
loading: false, //
latitude: '', //
longitude: '', //
map: null as any, //
mapAddressOptions: [] as any[], //
mapMarker: null as any, //
geocoder: null as any, //
mapContainerReady: false, //
});
//
const initLongitude = ref<number | undefined>();
const initLatitude = ref<number | undefined>();
/** 弹窗打开动画完成后初始化地图 */
async function handleDialogOpened() {
//
state.mapContainerReady = true;
// DOM
await nextTick();
// SDK
await loadBaiduMapSdk();
initMapInstance();
}
/** 弹窗关闭后清理地图 */
function handleDialogClosed() {
//
if (state.map) {
state.map.destroy?.();
state.map = null;
}
state.mapMarker = null;
state.geocoder = null;
state.mapContainerReady = false;
}
/** 初始化地图实例 */
function initMapInstance() {
if (!mapContainerRef.value) {
return;
}
//
initMap();
initGeocoder();
//
state.map.addEventListener('click', (e: any) => {
const point = e.latlng;
state.lonLat = `${point.lng},${point.lat}`;
regeoCode(state.lonLat);
});
//
if (initLongitude.value && initLatitude.value) {
const lonLat = `${initLongitude.value},${initLatitude.value}`;
regeoCode(lonLat);
}
}
/** 初始化地图 */
function initMap() {
state.map = new window.BMapGL.Map(mapContainerRef.value);
state.map.centerAndZoom(new window.BMapGL.Point(116.404, 39.915), 11);
state.map.enableScrollWheelZoom();
state.map.disableDoubleClickZoom();
state.map.addControl(new window.BMapGL.NavigationControl());
state.map.addControl(new window.BMapGL.ScaleControl());
state.map.addControl(new window.BMapGL.ZoomControl());
}
/** 初始化地理编码器 */
function initGeocoder() {
state.geocoder = new window.BMapGL.Geocoder();
}
/** 搜索地址 */
function autoSearch(queryValue: string) {
if (!queryValue) {
state.mapAddressOptions = [];
return;
}
state.loading = true;
// noinspection JSUnusedGlobalSymbols
const localSearch = new window.BMapGL.LocalSearch(state.map, {
onSearchComplete: (results: any) => {
state.loading = false;
const temp: any[] = [];
if (results && results._pois) {
results._pois.forEach((p: any) => {
const point = p.point;
if (point && point.lng && point.lat) {
temp.push({
name: p.title,
value: `${point.lng},${point.lat}`,
});
}
});
}
state.mapAddressOptions = temp;
},
});
localSearch.search(queryValue);
}
/** 处理地址选择 */
function handleAddressSelect(value: string) {
if (value) {
regeoCode(value);
}
}
/** 添加标记点 */
function setMarker(lnglat: string[]) {
if (!lnglat) {
return;
}
if (state.mapMarker !== null) {
state.map.removeOverlay(state.mapMarker);
}
const point = new window.BMapGL.Point(lnglat[0], lnglat[1]);
state.mapMarker = new window.BMapGL.Marker(point);
state.map.addOverlay(state.mapMarker);
state.map.centerAndZoom(point, 16);
}
/** 经纬度转地址、添加标记点 */
function regeoCode(lonLat: string) {
if (!lonLat) {
return;
}
const lnglat = lonLat.split(',');
if (lnglat.length !== 2) {
return;
}
state.longitude = lnglat[0]!;
state.latitude = lnglat[1]!;
const point = new window.BMapGL.Point(lnglat[0], lnglat[1]);
state.map.centerAndZoom(point, 16);
setMarker(lnglat);
getAddress(lnglat);
}
/** 根据经纬度获取地址信息 */
function getAddress(lnglat: string[]) {
const point = new window.BMapGL.Point(lnglat[0], lnglat[1]);
state.geocoder.getLocation(point, (result: any) => {
if (result && result.address) {
state.address = result.address;
}
});
}
/** 确认选择 */
function handleConfirm() {
if (state.longitude && state.latitude) {
emit('confirm', {
longitude: state.longitude,
latitude: state.latitude,
address: state.address,
});
}
modalApi.close();
}
const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (isOpen) {
handleDialogOpened();
} else {
handleDialogClosed();
}
},
});
/** 打开弹窗 */
function open(longitude?: number, latitude?: number) {
initLongitude.value = longitude;
initLatitude.value = latitude;
state.longitude = longitude ? String(longitude) : '';
state.latitude = latitude ? String(latitude) : '';
state.address = '';
state.mapAddressOptions = [];
modalApi.open();
}
defineExpose({ open });
</script>
<template>
<Modal :footer="false" class="w-[700px]" title="百度地图">
<div class="w-full">
<!-- 第一行位置搜索 -->
<Form :label-col="{ span: 4 }">
<Form.Item label="定位位置">
<Select
v-model:value="state.address"
:filter-option="false"
:loading="state.loading"
:options="
state.mapAddressOptions.map((item) => ({
label: item.name,
value: item.value,
}))
"
allow-clear
class="w-full"
placeholder="可输入地址查询经纬度"
show-search
@search="autoSearch"
@select="handleAddressSelect"
/>
</Form.Item>
<!-- 第二行坐标显示 -->
<Form.Item label="当前坐标">
<Space>
<Input
:value="state.longitude"
addon-before="经度"
disabled
style="width: 180px"
/>
<Input
:value="state.latitude"
addon-before="纬度"
disabled
style="width: 180px"
/>
</Space>
</Form.Item>
</Form>
<!-- 第三行地图 -->
<div
v-if="state.mapContainerReady"
ref="mapContainerRef"
class="mt-[10px] h-[400px] w-full"
></div>
<div
v-else
class="mt-[10px] flex h-[400px] w-full items-center justify-center"
>
<span class="text-gray-400">地图加载中...</span>
</div>
</div>
<div class="mt-4 flex justify-end gap-2">
<Button type="primary" @click="handleConfirm"> </Button>
<Button @click="modalApi.close()"> </Button>
</div>
</Modal>
</template>

View File

@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/no-dynamic-delete */
/**
* SDK
*/
// 扩展 Window 接口以包含百度地图 GL API
declare global {
interface Window {
BMapGL: any;
}
}
// 全局回调名称
const CALLBACK_NAME = '__BAIDU_MAP_LOAD_CALLBACK__';
// SDK 加载状态
let loadPromise: null | Promise<void> = null;
/**
* GL SDK
* @param timeout 10000
* @returns Promise<void>
*/
export const loadBaiduMapSdk = (timeout = 10_000): Promise<void> => {
// 已加载完成
if (window.BMapGL) {
return Promise.resolve();
}
// 正在加载中,返回同一个 Promise
if (loadPromise) {
return loadPromise;
}
loadPromise = new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
loadPromise = null;
reject(new Error('百度地图 SDK 加载超时'));
}, timeout);
// 全局回调
(window as any)[CALLBACK_NAME] = () => {
clearTimeout(timeoutId);
delete (window as any)[CALLBACK_NAME];
resolve();
};
// 创建 script 标签
const script = document.createElement('script');
script.src = `https://api.map.baidu.com/api?v=1.0&type=webgl&ak=${
import.meta.env.VITE_BAIDU_MAP_KEY
}&callback=${CALLBACK_NAME}`;
script.addEventListener('onerror', () => {
clearTimeout(timeoutId);
loadPromise = null;
delete (window as any)[CALLBACK_NAME];
reject(new Error('百度地图 SDK 加载失败'));
});
document.body.append(script);
});
return loadPromise;
};

View File

@ -47,6 +47,7 @@ onMounted(async () => {
</script>
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<div ref="contentRef" class="markdown-view" v-html="renderedMarkdown"></div>
</template>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
// TODO @xingyu yudao-ui-admin-vue3/src/components/OperateLogV2/src/OperateLogV2.vue userTypeuserNameaction
import type { OperateLogProps } from './typing';
import { DICT_TYPE } from '@vben/constants';
@ -14,37 +13,46 @@ withDefaults(defineProps<OperateLogProps>(), {
logList: () => [],
});
/** 获得 userType 颜色 */
function getUserTypeColor(userType: number) {
const dict = getDictObj(DICT_TYPE.USER_TYPE, userType);
if (dict && dict.colorType) {
return `hsl(var(--${dict.colorType}))`;
switch (dict?.colorType) {
case 'danger': {
return '#F56C6C';
}
case 'info': {
return '#909399';
}
case 'success': {
return '#67C23A';
}
case 'warning': {
return '#E6A23C';
}
}
return 'hsl(var(--primary))';
return '#409EFF';
}
</script>
<template>
<div>
<div class="pt-5">
<Timeline>
<Timeline.Item
v-for="log in logList"
:key="log.id"
:color="getUserTypeColor(log.userType)"
>
<Timeline.Item v-for="log in logList" :key="log.id">
<template #dot>
<p
<span
:style="{ backgroundColor: getUserTypeColor(log.userType) }"
class="absolute left-1 top-0 flex h-5 w-5 items-center justify-center rounded-full text-xs text-white"
class="flex h-5 w-5 items-center justify-center rounded-full text-[10px] text-white"
>
{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}
</p>
</span>
</template>
<p class="ml-2">{{ formatDateTime(log.createTime) }}</p>
<p class="ml-2 mt-2">
<Tag :color="getUserTypeColor(log.userType)">
{{ log.userName }}
</Tag>
{{ log.action }}
</p>
<div class="ml-2 flex flex-wrap items-center gap-2 leading-[22px]">
<span class="w-[140px] shrink-0 text-[13px] text-gray-400">
{{ formatDateTime(log.createTime) }}
</span>
<Tag color="success" class="!mr-0">{{ log.userName }}</Tag>
<span>{{ log.action }}</span>
</div>
</Timeline.Item>
</Timeline>
</div>

View File

@ -146,6 +146,7 @@ async function handlePreview(file: UploadFile) {
async function handleRemove(file: UploadFile) {
if (fileList.value) {
const index = fileList.value.findIndex((item) => item.uid === file.uid);
// oxlint-disable-next-line no-unused-expressions
index !== -1 && fileList.value.splice(index, 1);
const value = getValue();
isInnerOperate.value = true;
@ -350,6 +351,8 @@ function getValue() {
<style>
.ant-upload-select-picture-card {
@apply flex items-center justify-center;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -1,7 +1,7 @@
import { initPreferences } from '@vben/preferences';
import { unmountGlobalLoading } from '@vben/utils';
import { overridesPreferences } from './preferences';
import { overridesPreferences, preferencesExtension } from './preferences';
/**
*
@ -15,6 +15,7 @@ async function initApplication() {
// app偏好设置初始化
await initPreferences({
extension: preferencesExtension,
namespace,
overrides: overridesPreferences,
});

View File

@ -34,7 +34,10 @@ import {
// ======================= 自定义组件 =======================
import { useApiSelect } from '#/components/form-create';
import AreaSelect from '#/components/form-create/components/area-select.vue';
import DeptSelect from '#/components/form-create/components/dept-select.vue';
import DictSelect from '#/components/form-create/components/dict-select.vue';
import IframeComponent from '#/components/form-create/components/iframe.vue';
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
import { Tinymce } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload';
@ -45,12 +48,6 @@ const UserSelect = useApiSelect({
valueField: 'id',
url: '/system/user/simple-list',
});
const DeptSelect = useApiSelect({
name: 'DeptSelect',
labelField: 'name',
valueField: 'id',
url: '/system/dept/simple-list',
});
const ApiSelect = useApiSelect({
name: 'ApiSelect',
});
@ -89,6 +86,8 @@ const components = [
Tinymce,
ImageUpload,
FileUpload,
IframeComponent,
AreaSelect,
];
// 参考 https://www.form-create.com/v3/ant-design-vue/auto-import 文档

View File

@ -1,4 +1,14 @@
import { defineOverridesPreferences } from '@vben/preferences';
import {
defineOverridesPreferences,
definePreferencesExtension,
} from '@vben/preferences';
interface WebAntdPreferencesExtension {
defaultTableSize: number;
enableFormFullscreen: boolean;
reportTitle: string;
tenantMode: 'multi' | 'single';
}
/**
* @description
@ -23,3 +33,52 @@ export const overridesPreferences = defineOverridesPreferences({
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
},
});
export const preferencesExtension =
definePreferencesExtension<WebAntdPreferencesExtension>({
tabLabel: 'preferences.antd.tabLabel',
title: 'preferences.antd.title',
fields: [
{
component: 'switch',
defaultValue: true,
key: 'enableFormFullscreen',
label: 'preferences.antd.fields.enableFormFullscreen.label',
tip: 'preferences.antd.fields.enableFormFullscreen.tip',
},
{
component: 'select',
defaultValue: 'single',
key: 'tenantMode',
label: 'preferences.antd.fields.tenantMode.label',
options: [
{
label: 'preferences.antd.fields.tenantMode.options.single.label',
value: 'single',
},
{
label: 'preferences.antd.fields.tenantMode.options.multi.label',
value: 'multi',
},
],
},
{
component: 'number',
componentProps: {
max: 200,
min: 10,
step: 10,
},
defaultValue: 20,
key: 'defaultTableSize',
label: 'preferences.antd.fields.defaultTableSize.label',
},
{
component: 'input',
defaultValue: '',
key: 'reportTitle',
label: 'preferences.antd.fields.reportTitle.label',
placeholder: 'preferences.antd.fields.reportTitle.placeholder',
},
],
});

View File

@ -109,6 +109,21 @@ const coreRoutes: RouteRecordRaw[] = [
},
],
},
/**
* bpm web-view
*/
{
component: () => import('#/views/bpm/form/mobile/index.vue'),
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
ignoreAccess: true,
title: '移动端流程表单展示',
},
name: 'BpmMobileFormPreview',
path: '/bpm/mobile/form-preview',
},
];
export { coreRoutes, fallbackNotFoundRoute };

View File

@ -83,6 +83,7 @@ export const useAuthStore = defineStore('auth', () => {
if (accessStore.loginExpired) {
accessStore.setLoginExpired(false);
} else {
// oxlint-disable-next-line no-unused-expressions
onSuccess
? await onSuccess?.()
: await router.push(
@ -132,6 +133,7 @@ export const useAuthStore = defineStore('auth', () => {
async function fetchUserInfo() {
// 加载
// eslint-disable-next-line no-useless-assignment
let authPermissionInfo: AuthPermissionInfo | null = null;
authPermissionInfo = await getAuthPermissionInfoApi();
// userStore

View File

@ -3,6 +3,9 @@ import type { Recordable } from '@vben/types';
export * from './rangePickerProps';
export * from './routerHelper';
// 从共享包导出 URL 工具函数
export { isUrl } from '@vben/utils';
/**
*
* @param {Array} ary

View File

@ -4,18 +4,19 @@ import type { SystemUserProfileApi } from '#/api/system/user/profile';
import { onMounted, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { Card, Tabs } from 'ant-design-vue';
import { getAuthPermissionInfoApi } from '#/api';
import { getUserProfile } from '#/api/system/user/profile';
import { useAuthStore } from '#/store';
import BaseInfo from './modules/base-info.vue';
import ProfileUser from './modules/profile-user.vue';
import ResetPwd from './modules/reset-pwd.vue';
import UserSocial from './modules/user-social.vue';
const authStore = useAuthStore();
const userStore = useUserStore();
const activeName = ref('basicInfo');
/** 加载个人信息 */
@ -30,7 +31,8 @@ async function refreshProfile() {
await loadProfile();
// store
await authStore.fetchUserInfo();
const authPermissionInfo = await getAuthPermissionInfoApi();
userStore.setUserInfo(authPermissionInfo.user);
}
/** 初始化 */

View File

@ -100,7 +100,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
toolbarConfig: {
enabled: false,
},
} as VxeTableGridOptions<SystemSocialUserApi.SocialUser>,
},
});
/** 解绑账号 */

View File

@ -1,14 +1,12 @@
<script setup lang="ts">
import type { VbenFormSchema } from '#/adapter/form';
import { computed, ref } from 'vue';
import { computed } from 'vue';
import { ProfilePasswordSetting, z } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const profilePasswordSettingRef = ref();
const formSchema = computed((): VbenFormSchema[] => {
return [
{
@ -58,7 +56,6 @@ function handleSubmit() {
</script>
<template>
<ProfilePasswordSetting
ref="profilePasswordSettingRef"
class="w-1/3"
:form-schema="formSchema"
@submit="handleSubmit"

View File

@ -571,6 +571,7 @@ onMounted(async () => {
size="small"
@click="openChatConversationUpdateForm"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="activeConversation?.modelName"></span>
<IconifyIcon icon="lucide:settings" class="ml-2 size-4" />
</Button>

View File

@ -21,6 +21,7 @@ const imageListRef = ref<any>(); // image 列表 ref
const dall3Ref = ref<any>(); // dall3(openai) ref
const midjourneyRef = ref<any>(); // midjourney ref
const stableDiffusionRef = ref<any>(); // stable diffusion ref
// @ts-expect-error: template ref is retained for future provider expansion
const commonRef = ref<any>(); // stable diffusion ref
const selectPlatform = ref('common'); //
@ -45,7 +46,9 @@ const platformOptions = [
const models = ref<AiModelModelApi.Model[]>([]); //
/** 绘画 start */
async function handleDrawStart() {}
function handleDrawStart() {
// drawing state is handled by child components
}
/** 绘画 complete */
async function handleDrawComplete() {

View File

@ -150,10 +150,12 @@ defineExpose({
ref="mdContainerRef"
class="wh-full overflow-y-auto"
>
<!-- eslint-disable vue/no-v-html -->
<div
class="flex flex-col items-center justify-center"
v-html="html"
></div>
<!-- eslint-enable vue/no-v-html -->
</div>
<div ref="mindMapRef" class="wh-full">
<svg

View File

@ -20,6 +20,7 @@ const currentSong = inject<any>('currentSong', {});
{{ currentSong.date }}
</div>
<Button size="small" shape="round" class="my-2">信息复用</Button>
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="text-xs" v-html="currentSong.lyric"></div>
</Card>
</template>

View File

@ -106,7 +106,9 @@ async function goRun() {
try {
convertedParams[paramKey] = convertParamValue(value, dataType);
} catch (error: any) {
throw new Error(`参数 ${paramKey} 转换失败: ${error.message}`);
throw new Error(`参数 ${paramKey} 转换失败: ${error.message}`, {
cause: error,
});
}
}
@ -175,7 +177,7 @@ function convertParamValue(value: string, dataType: string) {
try {
return JSON.parse(value);
} catch (error: any) {
throw new Error(`JSON格式错误: ${error.message}`);
throw new Error(`JSON格式错误: ${error.message}`, { cause: error });
}
}
default: {

View File

@ -22,7 +22,7 @@ import {
import { Button, ButtonGroup, message, Modal, Tooltip } from 'ant-design-vue';
//
// @ts-ignore
// @ts-expect-error: token simulation package does not ship compatible types
import tokenSimulation from 'bpmn-js-token-simulation';
import BpmnModeler from 'bpmn-js/lib/Modeler';
//
@ -132,6 +132,7 @@ const emit = defineEmits([
'element-click',
]);
// @ts-expect-error: file input ref is set imperatively by the template
const bpmnCanvas = ref();
const refFile = ref();
@ -178,6 +179,7 @@ const additionalModules = computed(() => {
) {
Modules.push(...(props.additionalModel as any[]));
} else {
// oxlint-disable-next-line no-unused-expressions
props.additionalModel && Modules.push(props.additionalModel);
}
@ -417,6 +419,7 @@ const processSimulation = () => {
// bpmnModeler.get('toggleMode', 'strict'),
// "bpmnModeler.get('toggleMode')",
// );
// oxlint-disable-next-line no-unused-expressions
props.simulation && bpmnModeler.get('toggleMode', 'strict').toggleMode();
};
const processRedo = () => {

View File

@ -7,7 +7,7 @@ import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
/**
* A provider for BPMN 2.0 elements context pad
*/
export default function ContextPadProvider(
function ContextPadProvider(
config,
injector,
eventBus,
@ -57,6 +57,8 @@ export default function ContextPadProvider(
});
}
export default ContextPadProvider;
ContextPadProvider.$inject = [
'config.contextPad',
'injector',

View File

@ -1,6 +1,6 @@
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
export default function CustomPalette(
function CustomPalette(
palette,
create,
elementFactory,
@ -24,11 +24,21 @@ export default function CustomPalette(
);
}
const F = function () {}; // 核心,利用空对象作为中介;
F.prototype = PaletteProvider.prototype; // 核心将父类的原型赋值给空对象F
CustomPalette.$inject = [
'palette',
'create',
'elementFactory',
'spaceTool',
'lassoTool',
'handTool',
'globalConnect',
'translate',
];
// 利用中介函数重写原型链方法
F.prototype.getPaletteEntries = function () {
CustomPalette.prototype = Object.create(PaletteProvider.prototype);
CustomPalette.prototype.constructor = CustomPalette;
CustomPalette.prototype.getPaletteEntries = function () {
const actions = {};
const create = this._create;
const elementFactory = this._elementFactory;
@ -94,8 +104,7 @@ F.prototype.getPaletteEntries = function () {
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: '激活抓手工具',
// title: translate("Activate the hand tool"),
title: translate('Activate the hand tool'),
action: {
click(event) {
handTool.activateHand(event);
@ -219,16 +228,4 @@ F.prototype.getPaletteEntries = function () {
return actions;
};
CustomPalette.$inject = [
'palette',
'create',
'elementFactory',
'spaceTool',
'lassoTool',
'handTool',
'globalConnect',
'translate',
];
CustomPalette.prototype = new F(); // 核心,将 F的实例赋值给子类
CustomPalette.prototype.constructor = CustomPalette; // 修复子类CustomPalette的构造器指向防止原型链的混乱
export default CustomPalette;

View File

@ -1,7 +1,7 @@
/**
* A palette provider for BPMN 2.0 elements.
*/
export default function PaletteProvider(
function PaletteProvider(
palette,
create,
elementFactory,
@ -23,6 +23,8 @@ export default function PaletteProvider(
palette.registerProvider(this);
}
export default PaletteProvider;
PaletteProvider.$inject = [
'palette',
'create',

View File

@ -1,4 +1,3 @@
/* eslint-disable no-template-curly-in-string */
/**
* This is a sample file that should be replaced with the actual translation.
*
@ -238,10 +237,8 @@ export default {
'Due Date': '到期时间',
'Follow Up Date': '跟踪日期',
Priority: '优先级',
'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
'跟踪日期必须符合EL表达式 ${someDate} ,或者一个ISO标准日期2015-06-26T09:54:00',
'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
'跟踪日期必须符合EL表达式 ${someDate} ,或者一个ISO标准日期2015-06-26T09:54:00',
[`The follow up date as an EL expression (e.g. \${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)`]: `跟踪日期必须符合EL表达式 \${someDate} ,或者一个ISO标准日期2015-06-26T09:54:00`,
[`The due date as an EL expression (e.g. \${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)`]: `跟踪日期必须符合EL表达式 \${someDate} ,或者一个ISO标准日期2015-06-26T09:54:00`,
Variables: '变量',
'Candidate Starter Configuration': '候选人起动器配置',
'Candidate Starter Groups': '候选人起动器组',

View File

@ -39,7 +39,7 @@ watch(
val +=
props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || '';
}
// @ts-ignore
// @ts-expect-error: async component registry is indexed dynamically
customConfigComponent.value = (
CustomConfigMap as Record<string, { component: Component }>
)[val]?.component;

View File

@ -16,7 +16,7 @@ import {
} from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import ProcessListenerSelectModal from '#/views/bpm/processListener/components/process-listener-select-modal.vue';
import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
import { createListenerObject, updateElementExtensions } from '../../utils';
import ListenerFieldModal from './ListenerFieldModal.vue';

View File

@ -16,7 +16,7 @@ import {
} from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import ProcessListenerSelectModal from '#/views/bpm/processListener/components/process-listener-select-modal.vue';
import { ProcessListenerSelectModal } from '#/views/bpm/processListener/components';
import { createListenerObject, updateElementExtensions } from '../../utils';
import ListenerFieldModal from './ListenerFieldModal.vue';

View File

@ -1,4 +1,3 @@
<!-- eslint-disable no-unused-vars -->
<script lang="ts" setup>
import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
@ -66,13 +65,13 @@ const bpmnElement = ref<any>(null);
const multiLoopInstance = ref<any>(null);
declare global {
interface Window {
// @ts-ignore
bpmnInstances?: () => any;
}
}
const bpmnInstances = () => (window as any)?.bpmnInstances;
// @ts-expect-error: retained for legacy multi-instance mode compatibility
// eslint-disable-next-line unused-imports/no-unused-vars
const getElementLoop = (businessObject: any): void => {
if (!businessObject.loopCharacteristics) {
@ -141,8 +140,7 @@ const changeLoopCharacteristicsType = (type: any): void => {
isSequential: true,
})
: bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
// eslint-disable-next-line no-template-curly-in-string
collection: '${coll_userList}',
collection: `\${coll_userList}`,
});
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
loopCharacteristics: toRaw(multiLoopInstance.value),
@ -233,7 +231,7 @@ const updateLoopAsync = (key: any): void => {
extensionElements: null,
};
} else {
// @ts-ignore
// @ts-expect-error: dynamic async flags are assigned by runtime key
asyncAttr[key] = loopInstanceForm.value[key];
}
bpmnInstances().modeling.updateModdleProperties(
@ -247,23 +245,23 @@ const changeConfig = (config: string): void => {
switch (config) {
case '会签': {
changeLoopCharacteristicsType('ParallelMultiInstance');
// eslint-disable-next-line no-template-curly-in-string
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
updateLoopCondition(`\${ nrOfCompletedInstances >= nrOfInstances }`);
break;
}
case '依次审批': {
changeLoopCharacteristicsType('SequentialMultiInstance');
updateLoopCardinality('1');
// eslint-disable-next-line no-template-curly-in-string
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
updateLoopCondition(`\${ nrOfCompletedInstances >= nrOfInstances }`);
break;
}
case '或签': {
changeLoopCharacteristicsType('ParallelMultiInstance');
// eslint-disable-next-line no-template-curly-in-string
updateLoopCondition('${ nrOfCompletedInstances > 0 }');
updateLoopCondition(`\${ nrOfCompletedInstances > 0 }`);
break;
}
@ -331,8 +329,8 @@ const updateLoopCharacteristics = (): void => {
if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
multiLoopInstance.value = bpmnInstances().moddle.create(
'bpmn:MultiInstanceLoopCharacteristics',
// eslint-disable-next-line no-template-curly-in-string
{ isSequential: false, collection: '${coll_userList}' },
{ isSequential: false, collection: `\${coll_userList}` },
);
multiLoopInstance.value.completionCondition =
bpmnInstances().moddle.create('bpmn:FormalExpression', {
@ -344,20 +342,19 @@ const updateLoopCharacteristics = (): void => {
if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
multiLoopInstance.value = bpmnInstances().moddle.create(
'bpmn:MultiInstanceLoopCharacteristics',
// eslint-disable-next-line no-template-curly-in-string
{ isSequential: false, collection: '${coll_userList}' },
{ isSequential: false, collection: `\${coll_userList}` },
);
multiLoopInstance.value.completionCondition =
bpmnInstances().moddle.create('bpmn:FormalExpression', {
// eslint-disable-next-line no-template-curly-in-string
body: '${ nrOfCompletedInstances > 0 }',
body: `\${ nrOfCompletedInstances > 0 }`,
});
}
if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
multiLoopInstance.value = bpmnInstances().moddle.create(
'bpmn:MultiInstanceLoopCharacteristics',
// eslint-disable-next-line no-template-curly-in-string
{ isSequential: true, collection: '${coll_userList}' },
{ isSequential: true, collection: `\${coll_userList}` },
);
multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
'bpmn:FormalExpression',
@ -367,8 +364,7 @@ const updateLoopCharacteristics = (): void => {
);
multiLoopInstance.value.completionCondition =
bpmnInstances().moddle.create('bpmn:FormalExpression', {
// eslint-disable-next-line no-template-curly-in-string
body: '${ nrOfCompletedInstances >= nrOfInstances }',
body: `\${ nrOfCompletedInstances >= nrOfInstances }`,
});
}
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {

View File

@ -53,7 +53,7 @@ watch(
() => props.type,
() => {
if (props.type) {
// @ts-ignore
// @ts-expect-error: installed task component map is indexed dynamically
witchTaskComponent.value = installedComponent[props.type].component;
}
},

View File

@ -65,7 +65,7 @@ const initCallActivity = () => {
//
Object.keys(formData.value).forEach((key: string) => {
// @ts-ignore
// @ts-expect-error: form state is updated through dynamic schema keys
formData.value[key] =
bpmnElement.value.businessObject[key] ??
formData.value[key as keyof FormData];
@ -183,6 +183,7 @@ const updateElementExtensions = () => {
watch(
() => props.id,
(val) => {
// oxlint-disable-next-line no-unused-expressions
val &&
val.length > 0 &&
nextTick(() => {

View File

@ -82,7 +82,6 @@ onMounted(() => {
bpmnRootElements.value
.filter((el: any) => el.$type === 'bpmn:Message')
.forEach((m: any) => {
// @ts-ignore
if (bpmnMessageRefsMap.value) {
bpmnMessageRefsMap.value[m.id] = m;
}

View File

@ -34,7 +34,6 @@ const bpmnInstances = () => (window as any)?.bpmnInstances;
const resetTaskForm = () => {
for (const key in defaultTaskForm.value) {
// @ts-ignore
scriptTaskForm.value[key] =
bpmnElement.value?.businessObject[
key as keyof typeof defaultTaskForm.value

View File

@ -1,3 +1,4 @@
<!-- eslint-disable unicorn/no-nested-ternary -->
<!-- eslint-disable prettier/prettier -->
<script lang="ts" setup>
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue';
@ -206,9 +207,9 @@ const updateHttpExtensions = (force = false) => {
const persisted = HTTP_BOOLEAN_FIELDS.has(name)
? String(!!rawValue)
: (rawValue === undefined
: rawValue === undefined
? ''
: rawValue.toString());
: rawValue.toString();
desiredEntries.push([name, persisted]);
});

View File

@ -43,7 +43,7 @@ import {
MULTI_LEVEL_DEPT,
} from '#/views/bpm/components/simple-process-design/consts';
import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers';
import ProcessExpressionSelectModal from '#/views/bpm/processExpression/components/process-expression-select-modal.vue';
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
defineOptions({ name: 'UserTask' });
const props = defineProps({
@ -70,6 +70,7 @@ const deptTreeOptions = ref<any>(); // 部门树
const postOptions = ref<SystemPostApi.Post[]>([]); //
const userOptions = ref<SystemUserApi.User[]>([]); //
const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); //
// @ts-expect-error: tree ref instance type is provided by the UI library at runtime
const treeRef = ref<any>();
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ);
@ -128,7 +129,7 @@ const resetTaskForm = () => {
// eslint-disable-next-line unicorn/prefer-switch
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
// input
// @ts-ignore
// @ts-expect-error: expression strategy stores a scalar in an array-shaped field
userTaskForm.value.candidateParam = [candidateParamStr];
} else if (
userTaskForm.value.candidateStrategy ===
@ -152,7 +153,7 @@ const resetTaskForm = () => {
userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
) {
// @ts-ignore
// @ts-expect-error: dynamic candidate param shape varies by strategy
userTaskForm.value.candidateParam = +candidateParamStr;
deptLevel.value = +candidateParamStr;
} else if (
@ -303,7 +304,7 @@ const openProcessExpressionDialog = async () => {
const selectProcessExpression = (
expression: BpmProcessExpressionApi.ProcessExpression,
) => {
// @ts-ignore
// @ts-expect-error: modal helper exposes runtime methods outside static typing
userTaskForm.value.candidateParam = [expression.expression];
updateElementTask();
};
@ -311,7 +312,7 @@ const selectProcessExpression = (
const handleFormUserChange = (e: any) => {
if (e === 'PROCESS_START_USER_ID') {
userTaskForm.value.candidateParam = [];
// @ts-ignore
// @ts-expect-error: modal helper exposes runtime methods outside static typing
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
}
updateElementTask();

View File

@ -72,7 +72,7 @@
.element-listener-item {
display: inline-grid;
grid-template-columns: 16px auto 32px 32px;
grid-column-gap: 8px;
column-gap: 8px;
width: 100%;
}

View File

@ -1,6 +1,6 @@
import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer';
export default function CustomRenderer(
function CustomRenderer(
config,
eventBus,
styles,
@ -19,12 +19,10 @@ export default function CustomRenderer(
2000,
);
this.handlers.label = function () {
return null;
};
this.handlers.label = () => null;
}
const F = function () {}; // 核心,利用空对象作为中介;
F.prototype = BpmnRenderer.prototype; // 核心将父类的原型赋值给空对象F
CustomRenderer.prototype = new F(); // 核心,将 F的实例赋值给子类
CustomRenderer.prototype.constructor = CustomRenderer; // 修复子类CustomRenderer的构造器指向防止原型链的混乱
CustomRenderer.prototype = Object.create(BpmnRenderer.prototype);
CustomRenderer.prototype.constructor = CustomRenderer;
export default CustomRenderer;

View File

@ -1,7 +1,8 @@
import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules';
// eslint-disable-next-line n/no-extraneous-import
import inherits from 'inherits';
export default function CustomRules(eventBus) {
function CustomRules(eventBus) {
BpmnRules.call(this, eventBus);
}
@ -14,3 +15,5 @@ CustomRules.prototype.canDrop = function () {
CustomRules.prototype.canMove = function () {
return false;
};
export default CustomRules;

View File

@ -1,4 +1,5 @@
function xmlStr2XmlObj(xmlStr) {
// eslint-disable-next-line no-useless-assignment
let xmlObj = {};
if (document.all) {
const xmlDom = new window.ActiveXObject('Microsoft.XMLDOM');

View File

@ -29,6 +29,7 @@ import {
import { getForm } from '#/api/bpm/form';
import { getModelList } from '#/api/bpm/model';
import { parseFormFields } from '#/components/form-create';
import {
CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE,
@ -42,12 +43,7 @@ import {
TIME_UNIT_TYPES,
TimeUnitType,
} from '../../consts';
import {
parseFormFields,
useFormFields,
useNodeName,
useWatchNode,
} from '../../helpers';
import { useFormFields, useNodeName, useWatchNode } from '../../helpers';
import { convertTimeUnit } from './utils';
defineOptions({ name: 'ChildProcessNodeConfig' });

View File

@ -158,7 +158,7 @@ function changeNodeName() {
defineExpose({ open }); // open
</script>
<template>
<Drawer class="w-1/3">
<Drawer class="w-2/5">
<template #title>
<div class="flex items-center">
<Input

View File

@ -71,6 +71,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
//
const currentNode = useWatchNode(props);
//
// @ts-expect-error: composable typing does not preserve this node schema exactly
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.TRIGGER_NODE);
//
@ -383,7 +384,7 @@ onMounted(() => {
});
</script>
<template>
<Drawer class="w-1/3">
<Drawer class="w-2/5">
<template #title>
<div class="config-header">
<Input

View File

@ -6,9 +6,9 @@ import type { ComponentPublicInstance, Ref } from 'vue';
import type { ButtonSetting, SimpleFlowNode } from '../../consts';
import type { UserTaskFormType } from '../../helpers';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { computed, nextTick, onMounted, reactive, ref, watchEffect } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import {
BpmModelFormType,
BpmNodeTypeEnum,
@ -39,6 +39,8 @@ import {
TypographyText,
} from 'ant-design-vue';
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
import {
APPROVE_METHODS,
APPROVE_TYPE,
@ -112,10 +114,20 @@ const [Drawer, drawerApi] = useVbenDrawer({
},
});
const [ExpressionSelectModal, expressionSelectModalApi] = useVbenModal({
connectedComponent: ProcessExpressionSelectModal,
destroyOnClose: true,
showConfirmButton: false,
});
//
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
watchEffect(() => {
void inputRef.value;
});
// Tab
const activeTabName = ref('user');
@ -218,9 +230,18 @@ function changeCandidateStrategy() {
configForm.value.deptLevel = 1;
configForm.value.formUser = '';
configForm.value.formDept = '';
configForm.value.expression = '';
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE;
}
function openExpressionSelect() {
expressionSelectModalApi.open();
}
function handleExpressionSelected(row: any) {
configForm.value.expression = row?.expression ?? '';
}
/** 审批方式改变 */
function approveMethodChanged() {
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS;
@ -843,7 +864,6 @@ onMounted(() => {
</SelectOption>
</Select>
</FormItem>
<!-- TODO @jason后续要支持选择已经存好的表达式 -->
<FormItem
v-if="
configForm.candidateStrategy === CandidateStrategy.EXPRESSION
@ -851,7 +871,15 @@ onMounted(() => {
label="流程表达式"
name="expression"
>
<Textarea v-model:value="configForm.expression" allow-clear />
<div class="flex gap-2">
<Textarea v-model:value="configForm.expression" :rows="2" />
<div class="flex flex-col gap-2">
<Button type="primary" @click="openExpressionSelect">
选择
</Button>
<Button @click="configForm.expression = ''">清空</Button>
</div>
</div>
</FormItem>
<!-- 多人审批/办理 方式 -->
<FormItem :label="`多人${nodeTypeName}方式`" name="approveMethod">
@ -1266,4 +1294,6 @@ onMounted(() => {
</TabPane>
</Tabs>
</Drawer>
<ExpressionSelectModal @select="handleExpressionSelected" />
</template>

View File

@ -25,12 +25,13 @@ const emits = defineEmits<{
}>();
//
const readonly = inject<Boolean>('readonly');
const readonly = inject<boolean>('readonly');
/** 监控节点的变化 */
const currentNode = useWatchNode(props);
/** 节点名称编辑 */
// @ts-expect-error: composable typing does not preserve this node schema exactly
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.CHILD_PROCESS_NODE,

View File

@ -27,10 +27,11 @@ const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined];
}>();
//
const readonly = inject<Boolean>('readonly');
const readonly = inject<boolean>('readonly');
//
const currentNode = useWatchNode(props);
//
// @ts-expect-error: composable typing does not preserve this node schema exactly
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.COPY_TASK_NODE,

View File

@ -25,10 +25,11 @@ const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined];
}>();
//
const readonly = inject<Boolean>('readonly');
const readonly = inject<boolean>('readonly');
//
const currentNode = useWatchNode(props);
//
// @ts-expect-error: composable typing does not preserve this node schema exactly
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.DELAY_TIMER_NODE,

View File

@ -20,7 +20,7 @@ const props = defineProps({
//
const currentNode = useWatchNode(props);
//
const readonly = inject<Boolean>('readonly');
const readonly = inject<boolean>('readonly');
const processInstance = inject<Ref<any>>('processInstance', ref({}));
const [Modal, modalApi] = useVbenModal({

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