461 Commits

Author SHA1 Message Date
YunaiV
5710761dbe feat(wms):调整 README.md 2026-05-16 15:09:05 +08:00
YunaiV
877ba2727f feat(wms):调整 README.md 2026-05-16 14:56:01 +08:00
YunaiV
70a639967c feat:更新 README.md
- 提交时不再用节点表单值覆盖 data.variables;与预览阶段使用同一份合并变量
- onChange 加 useDebounceFn(300ms) + 请求序号去重,handleAudit 提交前 await 最新一轮重算
- 切换任务时重置请求序号与 pending 重算
- 改用 form-create 官方 formData() 取节点表单当前值
- 节点表单初始化等 fApi 就绪后再计算下一节点(until + 1s 兜底)

同步至 web-antd / web-ele 两端
2026-05-16 14:44:01 +08:00
xingyu
59183029b6 !341 fix: 修复禁用的删除按钮仍然可以点击的问题
Merge pull request !341 from li_shifeng/fix-dept-delete
2026-05-14 08:29:04 +00:00
xingyu
e81ca2c13f !343 fix(@vben/web-antdv-next): 修复 InputNumber 组件宽度在表单中不占满的问题
Merge pull request !343 from XuZhiqiang/feat-antdv-next
2026-05-14 08:03:20 +00:00
XuZhiqiang
dcccef1c02 fix(@vben/web-antdv-next): 修复 InputNumber 组件宽度在表单中不占满的问题 2026-05-13 15:47:52 +08:00
XuZhiqiang
9a5bee4dce fix(@vben/web-antdv-next): adapter修正组件名称TextArea一致的大小写格式 2026-05-13 15:20:15 +08:00
xingyu
29665f02bf !342 feat(@vben/web-antdv-next): migrate ant-design-vue to antdv-next
Merge pull request !342 from XuZhiqiang/feat-antdv-next
2026-05-12 13:46:18 +00:00
XuZhiqiang
06f776d1ef feat(@vben/web-antdv-next): 替换 antdv-next 中不可用的 List 组件,手动迁移为 div 结构,后续组件库新增 Listy 组件在进行替换 2026-05-12 16:37:42 +08:00
XuZhiqiang
40f0ba71f5 feat(@vben/web-antdv-next): migrate ant-design-vue to antdv-next
Migration Summary: ant-design-vue → antdv-next
Core Changes
package.json - Replaced "ant-design-vue": "catalog:" with "antdv-next": "catalog:"

bootstrap.ts - Changed @vben/styles/antd to @vben/styles/antdv-next

adapter/component/index.ts - Major rewrite:

Removed dynamic defineAsyncComponent imports from ant-design-vue/es/...
Added static imports from antdv-next main entry
Renamed RangePicker → DateRangePicker, Textarea → TextArea
Defined local types for Rule, Locale, UploadRequestOption, FileType, Key
Bulk Import Replacements (100+ files)
from ant-design-vue → from antdv-next
from ant-design-vue/es/locale/... → from antdv-next/locale/...
from ant-design-vue/es/... → removed (use main entry)
from ant-design-vue/lib/... → removed (use main entry)
Component API Differences Handled
ant-design-vue	antdv-next	Files affected
Form.Item	FormItem	475 references
Tabs.TabPane	TabPane	240 references
Select.Option	SelectOption	151 references
Descriptions.Item	DescriptionsItem	2 references
Timeline.Item	TimelineItem	2 references
Radio.Group	RadioGroup	20 references
Collapse.Panel	CollapsePanel	9 references
Layout.Content/Sider/Header/Footer	LayoutContent/LayoutSider/...	14 references
Dropdown#overlay slot	Dropdown#popupRender	6 references
RangePicker	DateRangePicker	15+ references
Textarea	TextArea	37 references
ButtonGroup	Space (fallback)	12 references
Known Issues (requires manual attention)
List component - Not available in antdv-next. 4 files have TODO comments where List/List.Item/List.Item.Meta are used
@form-create/ant-design-vue - Kept as-is (compatible with antdv-next at runtime)
Type errors - ~366 type errors remain (vs 189 in web-antd), mostly pre-existing business logic issues and minor API differences
2026-05-12 15:30:08 +08:00
XuZhiqiang
0fced45a9c refactor(@vben/web-antdv-next): 根据web-antd初始化web-antdv-next 2026-05-12 12:14:32 +08:00
li_shifeng
2ea7da06c5 fix: 修复禁用的删除任然可以点击的问题 2026-05-11 14:13:23 +08:00
YunaiV
c164904a14 chore: 合并 github/master,引入 PR #259 BPMN 流程设计器审批节点自定义配置编辑后丢失修复 2026-05-04 00:36:22 +08:00
芋道源码
a0ceb45df9 Merge pull request #259 from lb1565387341/fix_bpmn_custom_user_config
fix: [bpm][antd&ele] 修复流程设计器自定义配置编辑后丢失的问题
2026-05-04 00:26:29 +08:00
YunaiV
c641542c71 fix(bpm):修正 BPM 流程实例审批弹窗网关分支重算的并发与提交问题
- 提交时不再用节点表单值覆盖 data.variables;与预览阶段使用同一份合并变量
- onChange 加 useDebounceFn(300ms) + 请求序号去重,handleAudit 提交前 await 最新一轮重算
- 切换任务时重置请求序号与 pending 重算
- 改用 form-create 官方 formData() 取节点表单当前值
- 节点表单初始化等 fApi 就绪后再计算下一节点(until + 1s 兜底)

同步至 web-antd / web-ele 两端
2026-05-03 16:35:03 +08:00
YunaiV
a3d8e4bfc1 feat: 添加包含和不包含条件选项到常量定义 2026-05-03 11:04:58 +08:00
YunaiV
e385823d46 fix: 修复 Vben5.0 form-create 多图上传校验拒绝 png/jpeg/gif,isImage 兼容 MIME 与扩展名两种 accept 写法 2026-05-02 22:56:38 +08:00
YunaiV
897220e19a fix: 修复 Vben5.0 download 接口 token 过期不触发刷新,导出/下载文件变成「账号未登录」JSON;web-antd / web-ele / web-naive / web-tdesign 加 Blob 业务错误嗅探拦截器 2026-05-02 20:36:00 +08:00
YunaiV
b293e112c6 fix: 修复 MALL 商品保存时 SKU 价格被反复 *100 的漂移 2026-05-02 20:23:43 +08:00
YunaiV
627e31f1b0 fix: 修复 Vben5.0 CRM 合同配置 / 客户公海规则配置表单 label 错用 labelClass: 'w-100',Tailwind v4 动态间距下被解析为 400px 撑爆 w-1/4 容器,挤掉 RadioGroup 输入区,改用 labelWidth: 120 2026-05-02 19:44:21 +08:00
YunaiV
8020b4b743 fix: 修复 MALL 商品列表/选择器「价格」列展示原始的「分」(web-antd / web-ele)
商品列表 [mall/product/spu/data.ts] 与商品选择器 [mall/product/spu/components/spu-select-data.ts]
的「价格」列原先 formatter: 'formatAmount2',只做了小数格式化、漏了「分转元」,导致
19900 直接显示成 19900.00(应为 199.00 元)。同文件的 marketPrice / costPrice 已正确使用
fenToYuan,唯独 price 漏了。

顺手将 spu/data.ts 的 price / marketPrice / costPrice 三列从手写闭包统一切到已注册的
formatFenToYuanAmount formatter,单位「元」从 cell 后缀挪进列标题(如「价格(元)」),
减少 8 处闭包并复用平台统一的 null/NaN 处理。
2026-05-02 19:38:50 +08:00
YunaiV
228c5463da fix: 修复 IoT 物模型表单 Form.Item 嵌套字段 name 误用点号字符串,事件类型等校验始终失败 / resetFields 写错路径 2026-05-02 19:27:35 +08:00
YunaiV
50ee691191 fix: 修复 web-ele 下 ApiSelect / ApiTreeSelect 误用 antd 的 fieldNames 写法导致下拉无内容
element-plus 适配器走 ApiComponent,识别的是 labelField / valueField / childrenField;
而 fieldNames 是 antd 风格写法,从 web-antd 复制过来未做适配,导致内部数据无法被映射成
{ label, value, children },下拉树/列表显示为空。

涉及:
- CRM 客户 / 联系人 / 线索 新增表单的「地址」树
- CRM 商机状态「应用部门」、产品「产品类型」树
- ERP 销售出库的 客户 / 销售人员 / 结算账户 / 产品 / 创建人 下拉
2026-05-02 18:55:48 +08:00
YunaiV
eda6ffaf1e fix: 修复 web-ele 下 ApiSelect / ApiTreeSelect 误用 antd 的 fieldNames 写法导致下拉无内容
element-plus 适配器走 ApiComponent,识别的是 labelField / valueField / childrenField;
而 fieldNames 是 antd 风格写法,从 web-antd 复制过来未做适配,导致内部数据无法被映射成
{ label, value, children },下拉树/列表显示为空。

涉及:
- CRM 客户 / 联系人 / 线索 新增表单的「地址」树
- CRM 商机状态「应用部门」、产品「产品类型」树
- ERP 销售出库的 客户 / 销售人员 / 结算账户 / 产品 / 创建人 下拉
2026-05-02 18:53:11 +08:00
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
liubei
e136679934 fix: [bpm][antd&ele] 修复流程设计器自定义配置编辑后丢失的问题 2026-02-09 15:35:53 +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
2601 changed files with 280792 additions and 14673 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: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 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 插件

9
.vscode/launch.json vendored
View File

@@ -2,6 +2,15 @@
"$schema": "https://json.schemastore.org/launchsettings.json",
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"name": "vben admin antd dev",
"request": "launch",
"url": "http://localhost:5999",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-antdv-next"
},
{
"type": "chrome",
"name": "vben admin antd dev",

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
}

234
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 |
## 🔥 后端架构
@@ -84,31 +84,31 @@
- 通用模块(必选):系统功能、基础设施
- 通用模块(可选):工作流程、支付系统、数据报表、会员中心
- 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
- 业务系统(按需):Mall 电子商城、OA 办公自动化、ERP 企业资源计划系统、WMS 仓库管理系统、CRM 客户关系管理、CMS 内容管理系统、MES 执行制造系统、AI 大模型平台、IoT 物联网系统、IM 即时通讯系统、Mobile 手机移动端、Report 数据大屏
### 系统功能
| | 功能 | 描述 |
|----|-------|---------------------------------|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 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,18 +219,44 @@
![功能图](/.gitee/image/common/mall-preview.png)
### 会员中心
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 会员管理 | 会员是 C 端的消费者,该功能用于会员的搜索与管理 |
| 🚀 | 会员标签 | 对会员的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 会员等级 | 对会员的等级、成长值进行管理,可用于订单折扣等会员权益 |
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
### ERP 系统
演示地址:<https://doc.iocoder.cn/erp-preview/>
![功能图](/.gitee/image/common/erp-feature.png)
### WMS 系统
演示地址:<https://doc.iocoder.cn/wms-preview/>
![功能图](/.gitee/image/common/wms-feature.png)
![预览图](/.gitee/image/common/wms-preview.png)
### CRM 系统
演示地址:<https://doc.iocoder.cn/crm-preview/>
![功能图](/.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 +264,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-form、vben-modal、vben-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,13 +119,58 @@ 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;
},
});
// add by 芋艿:对应 https://t.zsxq.com/SHqWw 反馈
// 处理 Blob 响应中的业务错误(如 401后端把「账号未登录」包成 HTTP 200 + body {code: 401, msg: ...}
// download 强制 responseType: 'blob' 后被 axios 包成 application/json 的 BlobdefaultResponseInterceptor 走
// responseReturn === 'body' 分支直接返回,绕过了 authenticateResponseInterceptor 的 401 token 刷新;
// 这里把这种 Blob 解析回 JSON再以 axios 风格抛出,让后续拦截器接管
client.addResponseInterceptor({
fulfilled: async (response) => {
const blob = response.data;
if (!(blob instanceof Blob)) {
return response;
}
// Blob.type 在部分环境可能为空或大小写不一,叠加 response header 一起判断更稳
const blobType = (blob.type || '').toLowerCase();
const headerType = String(
response.headers?.['content-type'] ??
response.headers?.['Content-Type'] ??
'',
).toLowerCase();
if (
!blobType.includes('application/json') &&
!headerType.includes('application/json')
) {
return response;
}
let parsed: any;
try {
parsed = JSON.parse(await blob.text());
} catch {
return response;
}
if (parsed && parsed.code !== undefined && parsed.code !== 0) {
response.data = parsed;
throw Object.assign(new Error(parsed.msg ?? 'Request failed'), {
config: response.config,
response,
data: parsed,
isAxiosError: true,
});
}
return response;
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({

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

@@ -6,19 +6,36 @@ export function useImagesUpload() {
return defineComponent({
name: 'ImagesUpload',
props: {
multiple: {
accept: {
type: Array,
default: () => ['image/jpeg', 'image/png', 'image/gif'],
},
disabled: {
type: Boolean,
default: true,
default: false,
},
maxNumber: {
type: Number,
default: 5,
},
maxSize: {
type: Number,
default: 5,
},
multiple: {
type: Boolean,
default: true,
},
},
setup() {
// TODO: @puhui999@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
return (props: { maxNumber?: number; multiple?: boolean }) => (
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
setup(props) {
return () => (
<ImageUpload
accept={props.accept as string[]}
disabled={props.disabled}
maxNumber={props.maxNumber}
maxSize={props.maxSize}
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

@@ -26,7 +26,7 @@ export function useUploadFileRule() {
makeRequiredRule(),
{
type: 'select',
field: 'fileType',
field: 'accept',
title: '文件类型',
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
options: [
@@ -40,12 +40,6 @@ export function useUploadFileRule() {
mode: 'multiple',
},
},
{
type: 'switch',
field: 'autoUpload',
title: '是否在选取文件后立即进行上传',
value: true,
},
{
type: 'switch',
field: 'drag',
@@ -54,23 +48,23 @@ export function useUploadFileRule() {
},
{
type: 'switch',
field: 'isShowTip',
field: 'showDescription',
title: '是否显示提示',
value: true,
},
{
type: 'inputNumber',
field: 'fileSize',
field: 'maxSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 },
},
{
type: 'inputNumber',
field: 'limit',
field: 'maxNumber',
title: '数量限制',
value: 5,
props: { min: 0 },
props: { min: 1 },
},
{
type: 'switch',

View File

@@ -24,15 +24,9 @@ export function useUploadImageRule() {
props(_: any, { t }: any) {
return localeProps(t, `${name}.props`, [
makeRequiredRule(),
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false,
},
{
type: 'select',
field: 'fileType',
field: 'accept',
title: '图片类型限制',
value: ['image/jpeg', 'image/png', 'image/gif'],
options: [
@@ -52,40 +46,16 @@ export function useUploadImageRule() {
},
{
type: 'inputNumber',
field: 'fileSize',
field: 'maxSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 },
},
{
type: 'input',
field: 'height',
title: '组件高度',
value: '150px',
},
{
type: 'input',
field: 'width',
title: '组件宽度',
value: '150px',
},
{
type: 'input',
field: 'borderradius',
title: '组件边框圆角',
value: '8px',
},
{
type: 'switch',
field: 'disabled',
title: '是否显示删除按钮',
value: true,
},
{
type: 'switch',
field: 'showBtnText',
title: '是否显示按钮文字',
value: true,
title: '是否禁用',
value: false,
},
]);
},

View File

@@ -24,15 +24,9 @@ export function useUploadImagesRule() {
props(_: any, { t }: any) {
return localeProps(t, `${name}.props`, [
makeRequiredRule(),
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false,
},
{
type: 'select',
field: 'fileType',
field: 'accept',
title: '图片类型限制',
value: ['image/jpeg', 'image/png', 'image/gif'],
options: [
@@ -48,40 +42,27 @@ export function useUploadImagesRule() {
],
props: {
mode: 'multiple',
maxNumber: 5,
},
},
{
type: 'inputNumber',
field: 'fileSize',
field: 'maxSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 },
},
{
type: 'inputNumber',
field: 'limit',
field: 'maxNumber',
title: '数量限制',
value: 5,
props: { min: 0 },
props: { min: 1 },
},
{
type: 'input',
field: 'height',
title: '组件高度',
value: '150px',
},
{
type: 'input',
field: 'width',
title: '组件宽度',
value: '150px',
},
{
type: 'input',
field: 'borderradius',
title: '组件边框圆角',
value: '8px',
type: 'switch',
field: 'disabled',
title: '是否禁用',
value: false,
},
]);
},

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 这种一行时间、userType、userName、action
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

@@ -175,7 +175,7 @@ const resetCustomConfigList = () => {
approveType.value =
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:ApproveType`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
value: ApproveType.USER,
});
@@ -184,7 +184,7 @@ const resetCustomConfigList = () => {
assignStartUserHandlerTypeEl.value =
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
value: 1,
});
@@ -194,13 +194,13 @@ const resetCustomConfigList = () => {
rejectHandlerTypeEl.value =
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
returnNodeIdEl.value =
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
value: '',
});
@@ -210,7 +210,7 @@ const resetCustomConfigList = () => {
assignEmptyHandlerTypeEl.value =
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
value: 1,
});
@@ -218,7 +218,7 @@ const resetCustomConfigList = () => {
assignEmptyUserIdsEl.value =
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
value: '',
});

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,7 @@
import BpmnRules from 'bpmn-js/lib/features/rules/BpmnRules';
import inherits from 'inherits';
export default function CustomRules(eventBus) {
function CustomRules(eventBus) {
BpmnRules.call(this, eventBus);
}
@@ -14,3 +14,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' });

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