Compare commits

..

181 Commits

Author SHA1 Message Date
vben
195ceec9b4 chore: release 5.5.3 2025-01-21 22:07:55 +08:00
Netfan
5bd73867b6 feat: auto fetch icon list in iconPicker (#5446)
* feat: auto fetch icon list in iconPicker

* fix: add timeout controller for fetching

* feat: add pending controller

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

* docs: typo

* fix: test unit

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

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

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

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

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

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

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

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

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

* fix: header-mixed layout update

* feat: layout preference update

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

* fix: typo
2024-12-28 16:16:48 +08:00
Netfan
15fe82c62f chore: update deps 2024-12-28 16:15:39 +08:00
Netfan
cb5ecf4a8a chore: add apiSelect remote search demo (#5246) 2024-12-26 19:23:59 +08:00
Netfan
68a7e790d8 fix: grid form submit button locale switch (#5205) 2024-12-21 15:36:48 +08:00
Vben
24a4935e85 fix: build error (#5199) 2024-12-20 22:41:38 +08:00
Netfan
9a660827a6 fix: vxeGrid top padding (#5193) 2024-12-20 14:47:33 +08:00
Netfan
a44ff73dd3 fix: grid tools in toolbar config not working as expected (#5190) 2024-12-19 21:34:47 +08:00
Netfan
acd87b2250 feat: add resizable and ColPage component (#5188)
* feat: add component resizable

* feat: component `ColPage` with demo
2024-12-19 20:37:42 +08:00
Netfan
1853ba1d60 fix: sidebar header height (#5183) 2024-12-19 11:45:32 +08:00
OldDriver
85cbb3b842 fix: remove the overlap caused by border-b (#5160) 2024-12-19 09:36:36 +08:00
booshaw
968c44572a docs: fix typos (#5169) 2024-12-19 09:35:25 +08:00
Journey
3201b843a8 fix: resolve eslint errors as well as TS type errors (#5172)
* fix: remove TypeScript error suppression for missing types in Vue ESLint config

* fix: enhance application configuration with CSS options type support
2024-12-19 09:34:42 +08:00
Netfan
db5b727300 feat: page content class override (#5179) 2024-12-18 22:53:05 +08:00
Netfan
10b3a16f79 fix: sidebar style on focus (#5178) 2024-12-18 22:52:40 +08:00
Netfan
a97c998be5 fix: user homePath no effect sometimes (#5166) 2024-12-17 21:39:12 +08:00
Netfan
b22d900e27 feat: form compact mode support (#5165) 2024-12-17 20:51:17 +08:00
Netfan
181e38733c fix: form auto submit no effect when showDefaultActions is false (#5163) 2024-12-17 20:15:09 +08:00
Netfan
4fe44611d3 docs: fix docs-link and add EllipsisText docs (#5158)
* docs: fix docs-link and add `EllipsisText` docs

* fix: ellipsisText docs link
2024-12-17 14:41:03 +08:00
Netfan
593916d6aa feat: form colon support (#5156) 2024-12-16 22:37:29 +08:00
Netfan
38805a0e1f feat: improve code login demo (#5154)
* feat: add some method in formApi

* fix: VbenPinInput style with small screen

* chore: improve code login demo
2024-12-16 20:48:51 +08:00
Netfan
0f756503ff feat: add demo for modify menu badge data 2024-12-16 12:45:07 +08:00
Netfan
f6faeb034e feat: autoActivateChild support more layout mode (#5148) 2024-12-16 04:54:32 +08:00
Netfan
2efb5b71c3 feat: auto activate subMenu on select root menu (#5147)
* feat: auto activate subMenu on click root menu

* fix: prop name fixed

* chore: test and docs update
2024-12-16 02:57:50 +08:00
Netfan
22c1f86ca1 fix: disabledOnChangeListener not work in form (#5146) 2024-12-16 00:57:10 +08:00
Netfan
ce4af37fd8 fix: login expired modal z-index (#5145)
* fix: login expired modal z-index

* feat: support custom z-index
2024-12-15 23:25:40 +08:00
Netfan
f446cbf9e5 feat: user-dropdown support hover trigger (#5143)
* feat: user-dropdown support `hover` trigger

* fix: modified type declaration
2024-12-15 18:24:22 +08:00
Netfan
7581fb381f fix: pinInput value synchronous update (#5142) 2024-12-15 14:26:42 +08:00
Netfan
bedf19993d fix: vxeGrid default sort data no effect in first query (#5141)
* fix: vxeGrid default sort data no effect in first query

* fix: query params lost
2024-12-15 12:52:56 +08:00
Netfan
e558087bcf fix: vscode debug profile (#5140) 2024-12-15 12:44:33 +08:00
Netfan
698daf46c7 fix: form component events bind (#5137)
* fix: from component events bind

* chore: update docs

* chore: default value and docs sync
2024-12-14 17:42:13 +08:00
Netfan
0410f1e1be fix: element plus validate failed style (#5130)
* fix: element plus validate failed style

* fix: element plus textarea style
2024-12-13 15:33:30 +08:00
Netfan
7fbf7b189a feat: tabbar support mouse wheel vertical (#5129)
* feat: tabbar support mouse wheel

* docs: add tabbar wheelable tips

* chore: resolve vitest test
2024-12-13 14:45:06 +08:00
Netfan
be208fe915 fix: form support disabledOnInputListener (#5127)
* fix: form support `disabledOnInputListener`

* chore: docs update
2024-12-13 11:18:45 +08:00
Netfan
1d3729aa24 fix: form submission not appropriate (#5126) 2024-12-13 10:56:24 +08:00
vben
cbca9ffd95 chore: release 5.5.1 2024-12-12 22:47:11 +08:00
Netfan
ed465d2b5b feat: table search form visible control (#5121)
* feat: table search form visible control

* chore: fix docs and demo

* chore: type error fixed
2024-12-12 22:28:03 +08:00
Netfan
d308da6ba1 fix: resolve table toolbar error (#5109) 2024-12-11 15:44:45 +08:00
Netfan
7c4dfdc1c2 feat: form support reverse action buttons (#5108)
* feat: form support reverse action buttons

* fix: submit button class
2024-12-11 15:29:25 +08:00
Netfan
991ada31ba chore: update deps (#5107) 2024-12-11 15:09:38 +08:00
Netfan
43adc943b9 docs: fix typos (#5105) 2024-12-11 14:48:08 +08:00
Netfan
4a20156f3d fix: table auto height (#5101) 2024-12-11 13:46:52 +08:00
Netfan
eec6f41f6a refactor: ApiComponent with docs (#5099)
* refactor:  `ApiComponent` with docs

* docs: remove invalid docs

* docs: remove duplicate prop docs

* docs: update `ApiComponent` docs
2024-12-11 10:45:04 +08:00
Arthur Darkstone
2cc918f79d feat: replace ElSelect with ElSelectV2 in component adapter for butter performance (#5085) 2024-12-11 09:57:45 +08:00
Netfan
07b1ad121c chore: remove redundant test code (#5094) 2024-12-10 17:58:57 +08:00
Netfan
e419b03cab feat: modal&drawer support appendToMain and zIndex (#5092)
* feat: modal/drawer support append to main content

* feat: modal zIndex support

* fix: drawer prop define

* chore: type

* fix: modal/drawer position fixed while append to body

* docs: typo

* chore: add full-width drawer in content area

* chore: remove unnecessary class
2024-12-10 17:37:06 +08:00
Netfan
018ddc75c6 feat: add default placeholder for ApiSelect (#5078) 2024-12-09 14:03:46 +08:00
Netfan
d085736bac feat: improve ApiSelect component (#5075)
* feat: improve `ApiSelect` component

* chore: `ApiSelect` props name changed
2024-12-09 12:47:33 +08:00
Netfan
305549e7f2 feat: improve element plus form component (#5072) 2024-12-08 19:29:49 +08:00
Netfan
958c8b4f21 feat: imporve naive form component (#5071) 2024-12-08 19:23:46 +08:00
Netfan
373766691f chore: remove useless fixedHeader prop for Page (#5069) 2024-12-07 23:26:47 +08:00
Netfan
bac0275624 chore: page prop type check (#5067) 2024-12-07 12:15:51 +08:00
Netfan
0fc0f13064 fix: layout overflow style (#5066) 2024-12-07 12:05:03 +08:00
Netfan
b75a8e6a2b fix: form setValues not support dayjs and Date value (#5064)
* fix: setFormValues not support  `dayjs object` value

* fix: setFormValues not support `Date` value

* chore: remove console log
2024-12-07 11:09:55 +08:00
Netfan
68ab73bdb5 fix: range picker props fixed for element-plus (#5042) 2024-12-07 11:09:33 +08:00
Netfan
4c1fc4a11e fix: validate message not display, fix #5034 (#5038) 2024-12-07 11:02:59 +08:00
Netfan
03f166f8a4 fix: form prop handleValuesChange no effect (#5060) 2024-12-07 11:02:14 +08:00
Netfan
d42daf9ce0 fix: modal radius not follow preferences (#5063) 2024-12-07 11:00:53 +08:00
Netfan
d1862fba27 fix: replace input component in IconPicker (#5047)
* fix: replace input component in `IconPicker`

* chore: fixed IconPicker demo
2024-12-06 13:46:52 +08:00
Netfan
f0db3d6b79 chore: codeowners update (#5048) 2024-12-06 13:46:32 +08:00
Netfan
21d37a1be0 fix: dialog and drawer footer gap in small screen (#5025) 2024-12-05 11:24:09 +08:00
huangxiaomin
fe236ea929 feat: add submitOnChange props to vben form (#5032) 2024-12-05 11:23:21 +08:00
huangxiaomin
05b4b61c6e fix: select Long option style problem (#5030) 2024-12-05 11:22:35 +08:00
vben
7ab00250bf chore: release 5.5.0 2024-12-04 22:57:27 +08:00
Vben
9896a67c21 feat: add api-select component (#5024) 2024-12-04 22:56:29 +08:00
Netfan
db38ef522f fix: Page header class in fixed mode (#5023) 2024-12-04 22:56:06 +08:00
Netfan
845f2a2abd fix: header left padding fixed (#5007) 2024-12-04 21:43:54 +08:00
Netfan
9b73792dc9 fix: extra menu title follow locale change (#5006) 2024-12-04 21:43:29 +08:00
Netfan
fccbe44cf7 feat: v-loading support for element plus (#5008) 2024-12-04 21:42:48 +08:00
Netfan
e23486dbc6 feat: form component IconPicker (#5005) 2024-12-04 21:42:21 +08:00
Netfan
935df713f3 fix: app config support .env.local (#5012) 2024-12-04 21:41:22 +08:00
Netfan
17c7ce8f21 feat: improve page component (#5013)
* feat: `page` component support fixed header

* docs: `page`  component documentation

* docs: Improve `props` types of `page`

* docs: improve `fixedHeader` description of `page`

* fix: `page` header border color with fixedHeader

* feat: add `headerClass` for `Page`
2024-12-04 21:40:41 +08:00
vben
24b9aa44d2 chore: Revert "fix: form 表单不支持field.xxx.xx格式的defaultValue配置 (#4965)"
This reverts commliit 12f216c0e7.
2024-12-02 00:47:06 +08:00
Vben
014e6d38a0 chore: update deps (#4993) 2024-12-01 21:53:52 +08:00
leizhiyou
12f216c0e7 fix: form 表单不支持field.xxx.xx格式的defaultValue配置 (#4965)
* fix: form 表单不支持field.xxx.xx格式的defaultValue配置

* chore: 修复代码规范问题
2024-12-01 21:48:54 +08:00
Netfan
ae3f7cb909 fix: mixed menu layout in full content mode (#4990) 2024-12-01 21:37:36 +08:00
Netfan
32117b73aa docs: add form slots docs (#4992) 2024-12-01 21:37:19 +08:00
huangfe1
e8992a1d16 chore: update modal.vue (#4987)
loading时候 子组件禁用点击事件

Co-authored-by: Vben <ann.vben@gmail.com>
2024-11-30 11:18:22 +08:00
Svend
3c4af23edf fix: 修复 Form Api 根据字段名移除表单项,字段取反了的问题 (#4971) 2024-11-30 10:58:17 +08:00
LinaBell
e3a93970f4 fix: when VxeTable toolbarConfig.refresh is enabled, it will carry incorrect parameters (#4980) 2024-11-30 10:57:23 +08:00
richex-cn
7b9866158b chore: update deprecated document link in .github/ISSUE_TEMPLATE (#4986) 2024-11-30 10:56:42 +08:00
Netfan
3fb286b552 fix: element hover style in dark theme (#4983) 2024-11-30 10:55:29 +08:00
Netfan
253abc5ef1 chore: tailwind css icon example (#4969) 2024-11-28 15:10:14 +08:00
Jeff
5f55799572 fix: button-control page mistake (#4963)
* fix: button-control page mistake

按钮控制展示逻辑错误

* fix: button-control.vue button text
2024-11-28 10:01:26 +08:00
vince
54a9ff088f feat: upgrade vite version to 6.0.0 (#4961)
* chore: upgrade vite version to 6.0.0

* chore: update lock
2024-11-27 15:52:25 +08:00
Netfan
73502677ff feat: add placement for Drawer (#4958) 2024-11-27 11:29:25 +08:00
Netfan
dedba18553 feat: add confirmDisabled for Dialog (#4959) 2024-11-27 11:28:49 +08:00
huangxiaomin
f85badf482 fix: the route path did not synchronize with the page (#4947) 2024-11-25 15:07:52 +08:00
眼圈发黑
12f25cf3a2 style: typo (#4948) 2024-11-25 15:07:16 +08:00
vben
c8dd9bbf0b chore: release 5.4.8 2024-11-24 22:00:41 +08:00
Vben
3587ec54eb fix: supplement datepicker component (#4943)
* fix: supplement datepicker component

* chore: typo
2024-11-24 21:56:41 +08:00
Vben
dbcb7138f2 fix: resolve issue with Upload component not working correctly inside Form (#4916) 2024-11-17 21:37:37 +08:00
ryomahan
fe58af2e78 fix: form-api.setValues can't resolve nested fields (#4915)
fix #4912
2024-11-17 21:04:35 +08:00
huangxiaomin
94c68c966e fix: fieldMappingTime data error when clear inputvalue (#4906) 2024-11-17 21:04:04 +08:00
Arthur Darkstone
77083abcc5 feat: add 3 resize examples (#4907) 2024-11-17 21:01:32 +08:00
Netfan
1302092798 fix: dialog opened/closed event triggered incorrectly,fixed #4902 (#4908) 2024-11-17 20:55:19 +08:00
Mintnoii
ec53bf8084 docs: optimize the introduction in both Chinese and English (#4913)
* 优化简介中文文档

1. 优化文案及病句
2. 统一格式

* Optimize the introduction English document

1. Optimize copywriting and sentences
2. Uniform format
2024-11-17 20:54:28 +08:00
Netfan
b87d41bada fix: adjust useWatermark logic (#4896) 2024-11-15 14:06:13 +08:00
vben
788a29a8cb chore: release v5.4.7 2024-11-14 22:15:46 +08:00
zyy
3bd5ef4523 fix(@vben/common-ui): pagination current page error (#4893) 2024-11-14 22:11:32 +08:00
Vben
86e52ce58a fix: resolve onChange issue in form component (#4890) 2024-11-13 22:53:09 +08:00
Vben
9ddaba5333 fix: correct grid styling issue (#4889) 2024-11-13 22:28:30 +08:00
Vben
5b079471b9 fix: resolve issue with grid reload parameter not working (#4888) 2024-11-13 22:27:50 +08:00
Arthur Darkstone
8cc73cf59c feat: add reize components & demo (#4862)
* feat: resize component

* chore: change positon of resize components

* feat: add resize demo

* chore: resize demo completed

* chore: fix display number

* chore: add infer comment

* fix: move reszie demo to examples

* fix: fix icon & removed scss
2024-11-13 15:43:17 +08:00
vince
a89711610d chore: update eslint configuration (#4872) 2024-11-12 13:42:32 +08:00
vince
67c2b13713 fix: drawer console warning (#4871) 2024-11-12 13:34:47 +08:00
Netfan
1ff1e4a8d7 fix: form enter event handling, fixed #4865 (#4867) 2024-11-12 13:20:48 +08:00
Arthur Darkstone
ea8af98324 docs: add route config desc (#4857) 2024-11-11 19:22:39 +08:00
Arthur Darkstone
dc15accd04 fix: clipboard demo not working with a-input (#4856) 2024-11-11 14:50:30 +08:00
vben
94efcec7da chore: release v5.4.6 2024-11-10 11:50:46 +08:00
Vben
a3d0d2ed34 feat: added file download examples (#4853) 2024-11-10 11:50:06 +08:00
Vben
90dc00b168 fix: unable to preventDefault inside passive event listener invocation (#4852) 2024-11-10 10:26:35 +08:00
Vben
ba36ce8836 feat: pinInput supports disabled props (#4851)
* feat: pinInput supports disabled props
2024-11-10 10:09:06 +08:00
vben
57d5a919d2 chore: release v5.4.5 2024-11-09 21:06:10 +08:00
Vben
546c0928fb fix: form data that is not submitted by the form should not be carried when switching paging (#4847) 2024-11-09 21:04:58 +08:00
Vben
5e44aa9283 fix: drawer header is missing (#4846) 2024-11-09 20:00:09 +08:00
Vben
26bec4222f fix: pages cannot be quickly moved back by hand gestures (#4845) 2024-11-09 16:32:55 +08:00
Vben
4005023fd4 fix: drawer component header does not take effect (#4844) 2024-11-09 15:53:17 +08:00
Vben
8617d3dd1e perf: formApi added validateAndSubmitForm & filedMapToTime renamed fieldMappingTime (#4842)
* perf: formApi added validateAndSubmitForm & filedMapToTime renamed fieldMappingTime
2024-11-09 15:00:59 +08:00
huangxiaomin
632081e828 feat: add icon-picker component (#4832)
* feat: add icon-picker component

* fix: resolve conversations

* refactor: resort @vben/hooks
2024-11-09 14:10:17 +08:00
huangxiaomin
6b9acf09dc feat: add fieldMapToTime prop to FormRenderProps (#4838) 2024-11-09 14:08:46 +08:00
Vben
2c6edafeb2 fix: when opening the tool separately, there is no need to pass the toolbar-tools slot (#4841) 2024-11-09 14:07:41 +08:00
Vben
9cf0573921 perf: optimize local startup speed and add header Class configuration to drawer (#4840) 2024-11-09 11:12:30 +08:00
Xiaoyu
da7d61b160 feat: add click-to-click event support to the WorkenchProject and WorkenchQuickNav components (#4831)
* feat(@vben/common-ui): add click event emission to WorkbenchProject and WorkbenchQuickNav components

* feat: add navigation and project link functionality to dashboard workspace

* feat: add URL property to WorkbenchProjectItem and WorkbenchQuickNavItem for enhanced navigation

---------

Co-authored-by: XiaoyuDing <xiaoyuding@keymedbio.com>
2024-11-09 10:26:58 +08:00
Vben
8f1e397077 fix: when the form is destroyed, the form parameters still exist (#4834)
* fix: when the form is destroyed, the form parameters still exist

* chore: update deps
2024-11-07 22:36:26 +08:00
Arthur Darkstone
dcdebaf7ca docs: remove unnecessary commas (#4833) 2024-11-07 21:55:20 +08:00
Vben
4e88ef0840 perf: improve the usage documentation of vben-vxe-table (#4829)
* perf: improve the usage documentation of vben-vxe-table
2024-11-06 23:03:33 +08:00
Arthur Darkstone
33ce4d3cf3 docs: add vxe-table doc (#4807)
* docs: init vxe-table demos

* style: fix vxe-table index.scss import error

* docs: fix vxe-table style & theme toggle problem

* docs: add rest demos

* docs: add vxe-table demo desc

* fix: add maximumFileSizeToCacheInBytes to fix build error

* fix: fix vxe-table set-theme build error

* docs: fix vitepress ssr render problem

* docs: add some tips for vitepress compatibility
2024-11-06 21:44:02 +08:00
zhaoweijie
6b54cb7563 chore: update vscode settings to configure stylelint for .vue files (#4821)
Co-authored-by: zhaoweijie <zhaoweijie1108@qq.com>
2024-11-06 21:34:06 +08:00
567 changed files with 15208 additions and 6333 deletions

18
.github/CODEOWNERS vendored
View File

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

View File

@@ -62,7 +62,7 @@ body:
description: Before submitting the issue, please make sure you do the following
# description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
options:
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
- label: Read the [docs](https://doc.vben.pro/)
required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true

View File

@@ -62,7 +62,7 @@ body:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
- label: Read the [docs](https://doc.vben.pro/)
required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true

View File

@@ -153,3 +153,20 @@ jobs:
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
local-dir: ./apps/web-naive/dist/
rerun-on-failure:
name: Rerun on failure
needs:
- deploy-playground-ftp
- deploy-docs-ftp
- deploy-antd-ftp
- deploy-ele-ftp
- deploy-naive-ftp
if: failure() && fromJSON(github.run_attempt) < 10
runs-on: ubuntu-latest
steps:
- name: Retry ${{ fromJSON(github.run_attempt) }} of 10
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: gh workflow run rerun.yml -F run_id=${{ github.run_id }}

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

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

View File

@@ -1,4 +1,10 @@
export default {
'*.md': ['prettier --cache --ignore-unknown --write'],
'*.vue': [
'prettier --write',
'eslint --cache --fix',
'stylelint --fix --allow-empty-input',
],
'*.{js,jsx,ts,tsx}': [
'prettier --cache --ignore-unknown --write',
'eslint --cache --fix',
@@ -7,14 +13,8 @@ export default {
'prettier --cache --ignore-unknown --write',
'stylelint --fix --allow-empty-input',
],
'*.md': ['prettier --cache --ignore-unknown --write'],
'*.vue': [
'prettier --write',
'eslint --cache --fix',
'stylelint --fix --allow-empty-input',
],
'package.json': ['prettier --cache --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write--parser json',
],
'package.json': ['prettier --cache --write'],
};

8
.vscode/launch.json vendored
View File

@@ -9,7 +9,7 @@
"url": "http://localhost:5555",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}/playground"
},
{
"type": "chrome",
@@ -18,7 +18,7 @@
"url": "http://localhost:5666",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}/apps/web-antd"
},
{
"type": "chrome",
@@ -27,7 +27,7 @@
"url": "http://localhost:5777",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}/apps/web-ele"
},
{
"type": "chrome",
@@ -36,7 +36,7 @@
"url": "http://localhost:5888",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}/apps/web-naive"
}
]
}

View File

@@ -160,6 +160,7 @@
"stylelint.enable": true,
"stylelint.packageManager": "pnpm",
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
"stylelint.customSyntax": "postcss-html",
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
"typescript.inlayHints.enumMemberValues.enabled": true,
@@ -221,5 +222,6 @@
"commentTranslate.hover.enabled": false,
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"oxc.enable": false
}

View File

@@ -43,6 +43,31 @@ export default eventHandler(async (event) => {
await sleep(600);
const { page, pageSize } = getQuery(event);
return usePageResponseSuccess(page as string, pageSize as string, mockData);
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
const listData = structuredClone(mockData);
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
listData.sort((a, b) => {
if (sortOrder === 'asc') {
if (sortBy === 'price') {
return (
Number.parseFloat(a[sortBy as string]) -
Number.parseFloat(b[sortBy as string])
);
} else {
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
}
} else {
if (sortBy === 'price') {
return (
Number.parseFloat(b[sortBy as string]) -
Number.parseFloat(a[sortBy as string])
);
} else {
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
}
}
});
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});

View File

@@ -4,6 +4,7 @@ export interface UserInfo {
realName: string;
roles: string[];
username: string;
homePath?: string;
}
export const MOCK_USERS: UserInfo[] = [
@@ -20,6 +21,7 @@ export const MOCK_USERS: UserInfo[] = [
realName: 'Admin',
roles: ['admin'],
username: 'admin',
homePath: '/workspace',
},
{
id: 2,
@@ -27,6 +29,7 @@ export const MOCK_USERS: UserInfo[] = [
realName: 'Jack',
roles: ['user'],
username: 'jack',
homePath: '/analytics',
},
];
@@ -50,7 +53,6 @@ export const MOCK_CODES = [
const dashboardMenus = [
{
component: 'BasicLayout',
meta: {
order: -1,
title: 'page.dashboard.title',
@@ -113,7 +115,6 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
return [
{
component: 'BasicLayout',
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,

View File

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

View File

@@ -3,12 +3,13 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -48,12 +49,15 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
@@ -77,7 +81,38 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
AutoComplete,
Checkbox,
CheckboxGroup,
@@ -87,6 +122,13 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs },
slots,
);
},
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),

View File

@@ -1,12 +1,13 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
defaultResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
} from '@vben/request';
@@ -20,8 +21,9 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) {
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({
...options,
baseURL,
});
@@ -69,19 +71,14 @@ function createRequestClient(baseURL: string) {
},
});
// response数据解构
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw Object.assign({}, response, { response });
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({
codeField: 'code',
dataField: 'data',
successCode: 0,
}),
);
// token过期的处理
client.addResponseInterceptor(
@@ -109,6 +106,8 @@ function createRequestClient(baseURL: string) {
return client;
}
export const requestClient = createRequestClient(apiURL);
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@@ -1,6 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -18,6 +19,15 @@ async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
// });
// // 设置抽屉的默认配置
// setDefaultDrawerProps({
// zIndex: 1020,
// });
const app = createApp(App);
// 国际化 i18n 配置
@@ -29,6 +39,9 @@ async function bootstrap(namespace: string) {
// 安装权限指令
registerAccessDirective(app);
// 初始化 tippy
initTippy(app);
// 配置路由及路由守卫
app.use(router);

View File

@@ -1,7 +1,9 @@
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { Locale } from 'ant-design-vue/es/locale';
import type { App } from 'vue';
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import { ref } from 'vue';
import {

View File

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
if (coreRouteNames.includes(to.name as string)) {
if (to.path === LOGIN_PATH && accessStore.accessToken) {
return decodeURIComponent(
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
(to.query?.redirect as string) ||
userStore.userInfo?.homePath ||
DEFAULT_HOME_PATH,
);
}
return true;
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
query:
to.fullPath === DEFAULT_HOME_PATH
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
const redirectPath = (from.query.redirect ??
(to.path === DEFAULT_HOME_PATH
? userInfo.homePath || DEFAULT_HOME_PATH
: to.fullPath)) as string;
return {
...router.resolve(decodeURIComponent(redirectPath)),

View File

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
/** 基本路由,这些路由是必须存在的 */
const coreRoutes: RouteRecordRaw[] = [
/**
* 根路由
* 使用基础布局作为所有页面的父级容器子级就不必配置BasicLayout。
* 此路由必须存在,且不应修改
*/
{
component: BasicLayout,
meta: {
hideInBreadcrumb: true,
title: 'Root',
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
children: [],
},
{
component: AuthPageLayout,

View File

@@ -17,12 +17,12 @@ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
/** 不需要权限的菜单列表(会显示在菜单中) */
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由+静态路由组成 */
/** 路由列表,由基本路由、外部路由和404兜底路由组成
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
@@ -32,5 +32,6 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

View File

@@ -1,18 +1,16 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'lucide:layout-dashboard',
order: -1,
title: $t('page.dashboard.title'),
},
name: 'Dashboard',
path: '/',
path: '/dashboard',
children: [
{
name: 'Analytics',

View File

@@ -1,11 +1,9 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,

View File

@@ -8,30 +8,20 @@ import {
VBEN_NAIVE_PREVIEW_URL,
} from '@vben/constants';
import { BasicLayout, IFrameView } from '#/layouts';
import { IFrameView } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
order: 9998,
title: $t('demos.vben.title'),
},
name: 'VbenProject',
path: '/vben-admin',
children: [
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
},
},
{
name: 'VbenDocument',
path: '/vben-admin/document',
@@ -76,6 +66,16 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
order: 9999,
},
},
];
export default routes;

View File

@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => {
return [
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{
component: 'VbenPinInput',
componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => {
const text =
countdown > 0
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
},
];
});

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -15,10 +15,10 @@ import {
} from '@vben/icons';
import AnalyticsTrends from './analytics-trends.vue';
import AnalyticsVisits from './analytics-visits.vue';
import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue';
import AnalyticsVisits from './analytics-visits.vue';
const overviewItems: AnalysisOverviewItem[] = [
{

View File

@@ -7,6 +7,7 @@ import type {
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
@@ -18,11 +19,15 @@ import {
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">

View File

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

View File

@@ -3,24 +3,30 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
ElButton,
ElCheckbox,
ElCheckboxButton,
ElCheckboxGroup,
ElDatePicker,
ElDivider,
ElInput,
ElInputNumber,
ElNotification,
ElRadio,
ElRadioButton,
ElRadioGroup,
ElSelect,
ElSelectV2,
ElSpace,
ElSwitch,
ElTimePicker,
@@ -40,10 +46,13 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@@ -60,11 +69,59 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: ElSelectV2,
loadingSlot: 'loading',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: ElTreeSelect,
props: { label: 'label', children: 'children' },
nodeKey: 'value',
loadingSlot: 'loading',
optionsPropName: 'data',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
CheckboxGroup: (props, { attrs, slots }) => {
let defaultSlot;
if (Reflect.has(slots, 'default')) {
defaultSlot = slots.default;
} else {
const { options, isButton } = attrs;
if (Array.isArray(options)) {
defaultSlot = () =>
options.map((option) =>
h(isButton ? ElCheckboxButton : ElCheckbox, option),
);
}
}
return h(
ElCheckboxGroup,
{ ...props, ...attrs },
{ ...slots, default: defaultSlot },
);
},
// 自定义默认按钮
DefaulButton: (props, { attrs, slots }) => {
DefaultButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
},
// 自定义主要按钮
@@ -72,13 +129,87 @@ async function initComponentAdapter() {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{
iconSlot: 'append',
modelValueProp: 'model-value',
inputComponent: ElInput,
...props,
...attrs,
},
slots,
);
},
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Select: withDefaultPlaceholder(ElSelect, 'select'),
RadioGroup: (props, { attrs, slots }) => {
let defaultSlot;
if (Reflect.has(slots, 'default')) {
defaultSlot = slots.default;
} else {
const { options } = attrs;
if (Array.isArray(options)) {
defaultSlot = () =>
options.map((option) =>
h(attrs.isButton ? ElRadioButton : ElRadio, option),
);
}
}
return h(
ElRadioGroup,
{ ...props, ...attrs },
{ ...slots, default: defaultSlot },
);
},
Select: (props, { attrs, slots }) => {
return h(ElSelectV2, { ...props, attrs }, slots);
},
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
TimePicker: (props, { attrs, slots }) => {
const { name, id, isRange } = props;
const extraProps: Recordable<any> = {};
if (isRange) {
if (name && !Array.isArray(name)) {
extraProps.name = [name, `${name}_end`];
}
if (id && !Array.isArray(id)) {
extraProps.id = [id, `${id}_end`];
}
}
return h(
ElTimePicker,
{
...props,
...attrs,
...extraProps,
},
slots,
);
},
DatePicker: (props, { attrs, slots }) => {
const { name, id, type } = props;
const extraProps: Recordable<any> = {};
if (type && type.includes('range')) {
if (name && !Array.isArray(name)) {
extraProps.name = [name, `${name}_end`];
}
if (id && !Array.isArray(id)) {
extraProps.id = [id, `${id}_end`];
}
}
return h(
ElDatePicker,
{
...props,
...attrs,
...extraProps,
},
slots,
);
},
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
};

View File

@@ -12,6 +12,7 @@ setupVbenForm<ComponentType>({
config: {
modelPropNameMap: {
Upload: 'fileList',
CheckboxGroup: 'model-value',
},
},
defineRules: {

View File

@@ -1,12 +1,13 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
defaultResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
} from '@vben/request';
@@ -20,8 +21,9 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) {
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({
...options,
baseURL,
});
@@ -69,18 +71,14 @@ function createRequestClient(baseURL: string) {
},
});
// response数据解构
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw Object.assign({}, response, { response });
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({
codeField: 'code',
dataField: 'data',
successCode: 0,
}),
);
// token过期的处理
client.addResponseInterceptor(
@@ -108,6 +106,8 @@ function createRequestClient(baseURL: string) {
return client;
}
export const requestClient = createRequestClient(apiURL);
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@@ -1,12 +1,14 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/ele';
import { useTitle } from '@vueuse/core';
import { ElLoading } from 'element-plus';
import { $t, setupI18n } from '#/locales';
@@ -17,8 +19,19 @@ import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
// });
// // 设置抽屉的默认配置
// setDefaultDrawerProps({
// zIndex: 2000,
// });
const app = createApp(App);
// 注册Element Plus提供的v-loading指令
app.directive('loading', ElLoading.directive);
// 国际化 i18n 配置
await setupI18n(app);
@@ -28,6 +41,9 @@ async function bootstrap(namespace: string) {
// 安装权限指令
registerAccessDirective(app);
// 初始化 tippy
initTippy(app);
// 配置路由及路由守卫
app.use(router);

View File

@@ -1,7 +1,9 @@
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { Language } from 'element-plus/es/locale';
import type { App } from 'vue';
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import { ref } from 'vue';
import {

View File

@@ -1,6 +1,7 @@
{
"title": "Demos",
"elementPlus": "Element Plus",
"form": "Form",
"vben": {
"title": "Project",
"about": "About",

View File

@@ -1,6 +1,7 @@
{
"title": "演示",
"elementPlus": "Element Plus",
"form": "表单演示",
"vben": {
"title": "项目",
"about": "关于",

View File

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
if (coreRouteNames.includes(to.name as string)) {
if (to.path === LOGIN_PATH && accessStore.accessToken) {
return decodeURIComponent(
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
(to.query?.redirect as string) ||
userStore.userInfo?.homePath ||
DEFAULT_HOME_PATH,
);
}
return true;
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
query:
to.fullPath === DEFAULT_HOME_PATH
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
const redirectPath = (from.query.redirect ??
(to.path === DEFAULT_HOME_PATH
? userInfo.homePath || DEFAULT_HOME_PATH
: to.fullPath)) as string;
return {
...router.resolve(decodeURIComponent(redirectPath)),

View File

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
/** 基本路由,这些路由是必须存在的 */
const coreRoutes: RouteRecordRaw[] = [
/**
* 根路由
* 使用基础布局作为所有页面的父级容器子级就不必配置BasicLayout。
* 此路由必须存在,且不应修改
*/
{
component: BasicLayout,
meta: {
hideInBreadcrumb: true,
title: 'Root',
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
children: [],
},
{
component: AuthPageLayout,

View File

@@ -17,12 +17,12 @@ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
/** 不需要权限的菜单列表(会显示在菜单中) */
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由+静态路由组成 */
/** 路由列表,由基本路由、外部路由和404兜底路由组成
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
@@ -32,5 +32,6 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

View File

@@ -1,18 +1,16 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'lucide:layout-dashboard',
order: -1,
title: $t('page.dashboard.title'),
},
name: 'Dashboard',
path: '/',
path: '/dashboard',
children: [
{
name: 'Analytics',

View File

@@ -1,11 +1,9 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
@@ -23,6 +21,14 @@ const routes: RouteRecordRaw[] = [
path: '/demos/element',
component: () => import('#/views/demos/element/index.vue'),
},
{
meta: {
title: $t('demos.form'),
},
name: 'BasicForm',
path: '/demos/form',
component: () => import('#/views/demos/form/basic.vue'),
},
],
},
];

View File

@@ -9,30 +9,20 @@ import {
} from '@vben/constants';
import { SvgAntdvLogoIcon } from '@vben/icons';
import { BasicLayout, IFrameView } from '#/layouts';
import { IFrameView } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
order: 9998,
title: $t('demos.vben.title'),
},
name: 'VbenProject',
path: '/vben-admin',
children: [
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
},
},
{
name: 'VbenDocument',
path: '/vben-admin/document',
@@ -77,6 +67,16 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
order: 9999,
},
},
];
export default routes;

View File

@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => {
return [
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{
component: 'VbenPinInput',
componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => {
const text =
countdown > 0
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
},
];
});

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -15,10 +15,10 @@ import {
} from '@vben/icons';
import AnalyticsTrends from './analytics-trends.vue';
import AnalyticsVisits from './analytics-visits.vue';
import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue';
import AnalyticsVisits from './analytics-visits.vue';
const overviewItems: AnalysisOverviewItem[] = [
{

View File

@@ -7,6 +7,7 @@ import type {
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
@@ -18,11 +19,15 @@ import {
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import {
@@ -6,6 +8,7 @@ import {
ElCard,
ElMessage,
ElNotification,
ElSegmented,
ElSpace,
ElTable,
} from 'element-plus';
@@ -47,6 +50,10 @@ const tableData = [
{ prop1: '5', prop2: 'E' },
{ prop1: '6', prop2: 'F' },
];
const segmentedValue = ref('Mon');
const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
</script>
<template>
@@ -54,41 +61,57 @@ const tableData = [
description="支持多语言,主题功能集成切换等"
title="Element Plus组件使用演示"
>
<ElCard class="mb-5">
<template #header> 按钮 </template>
<ElSpace>
<ElButton text>Text</ElButton>
<ElButton>Default</ElButton>
<ElButton type="primary"> Primary </ElButton>
<ElButton type="info"> Info </ElButton>
<ElButton type="success"> Success </ElButton>
<ElButton type="warning"> Warning </ElButton>
<ElButton type="danger"> Error </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<template #header> Message </template>
<ElSpace>
<ElButton type="info" @click="info"> 信息 </ElButton>
<ElButton type="danger" @click="error"> 错误 </ElButton>
<ElButton type="warning" @click="warning"> 警告 </ElButton>
<ElButton type="success" @click="success"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<template #header> Notification </template>
<ElSpace>
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
<ElButton type="danger" @click="notify('error')"> 错误 </ElButton>
<ElButton type="warning" @click="notify('warning')"> 警告 </ElButton>
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<ElTable :data="tableData" stripe>
<ElTable.TableColumn label="测试列1" prop="prop1" />
<ElTable.TableColumn label="测试列2" prop="prop2" />
</ElTable>
</ElCard>
<div class="flex flex-wrap gap-5">
<ElCard class="mb-5 w-auto">
<template #header> 按钮 </template>
<ElSpace>
<ElButton text>Text</ElButton>
<ElButton>Default</ElButton>
<ElButton type="primary"> Primary </ElButton>
<ElButton type="info"> Info </ElButton>
<ElButton type="success"> Success </ElButton>
<ElButton type="warning"> Warning </ElButton>
<ElButton type="danger"> Error </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> Message </template>
<ElSpace>
<ElButton type="info" @click="info"> 信息 </ElButton>
<ElButton type="danger" @click="error"> 错误 </ElButton>
<ElButton type="warning" @click="warning"> 警告 </ElButton>
<ElButton type="success" @click="success"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> Notification </template>
<ElSpace>
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
<ElButton type="danger" @click="notify('error')"> 错误 </ElButton>
<ElButton type="warning" @click="notify('warning')"> 警告 </ElButton>
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5 w-auto">
<template #header> Segmented </template>
<ElSegmented
v-model="segmentedValue"
:options="segmentedOptions"
size="large"
/>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> V-Loading </template>
<div class="flex size-72 items-center justify-center" v-loading="true">
一些演示的内容
</div>
</ElCard>
<ElCard class="mb-5 w-80">
<ElTable :data="tableData" stripe>
<ElTable.TableColumn label="测试列1" prop="prop1" />
<ElTable.TableColumn label="测试列2" prop="prop2" />
</ElTable>
</ElCard>
</div>
</Page>
</template>

View File

@@ -0,0 +1,181 @@
<script lang="ts" setup>
import { h } from 'vue';
import { Page } from '@vben/common-ui';
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { getAllMenusApi } from '#/api';
const [Form, formApi] = useVbenForm({
commonConfig: {
// 所有表单项
componentProps: {
class: 'w-full',
},
},
layout: 'horizontal',
// 大屏一行显示3个中屏一行显示2个小屏一行显示1个
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
handleSubmit: (values) => {
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
},
schema: [
{
// 组件需要在 #/adapter.ts内注册并加上类型
component: 'ApiSelect',
// 对应组件的参数
componentProps: {
// 菜单接口转options格式
afterFetch: (data: { name: string; path: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.path,
}));
},
// 菜单接口
api: getAllMenusApi,
},
// 字段名
fieldName: 'api',
// 界面显示的label
label: 'ApiSelect',
},
{
component: 'ApiTreeSelect',
// 对应组件的参数
componentProps: {
// 菜单接口
api: getAllMenusApi,
childrenField: 'children',
// 菜单接口转options格式
labelField: 'name',
valueField: 'path',
},
// 字段名
fieldName: 'apiTree',
// 界面显示的label
label: 'ApiTreeSelect',
},
{
component: 'Input',
fieldName: 'string',
label: 'String',
},
{
component: 'InputNumber',
fieldName: 'number',
label: 'Number',
},
{
component: 'RadioGroup',
fieldName: 'radio',
label: 'Radio',
componentProps: {
options: [
{ value: 'A', label: 'A' },
{ value: 'B', label: 'B' },
{ value: 'C', label: 'C' },
{ value: 'D', label: 'D' },
{ value: 'E', label: 'E' },
],
},
},
{
component: 'RadioGroup',
fieldName: 'radioButton',
label: 'RadioButton',
componentProps: {
isButton: true,
options: ['A', 'B', 'C', 'D', 'E', 'F'].map((v) => ({
value: v,
label: `选项${v}`,
})),
},
},
{
component: 'CheckboxGroup',
fieldName: 'checkbox',
label: 'Checkbox',
componentProps: {
options: ['A', 'B', 'C'].map((v) => ({ value: v, label: `选项${v}` })),
},
},
{
component: 'CheckboxGroup',
fieldName: 'checkbox1',
label: 'Checkbox1',
renderComponentContent: () => {
return {
default: () => {
return ['A', 'B', 'C', 'D'].map((v) =>
h(ElCheckbox, { label: v, value: v }),
);
},
};
},
},
{
component: 'CheckboxGroup',
fieldName: 'checkbotton',
label: 'CheckBotton',
componentProps: {
isButton: true,
options: [
{ value: 'A', label: '选项A' },
{ value: 'B', label: '选项B' },
{ value: 'C', label: '选项C' },
],
},
},
{
component: 'DatePicker',
fieldName: 'date',
label: 'Date',
},
{
component: 'Select',
fieldName: 'select',
label: 'Select',
componentProps: {
filterable: true,
options: [
{ value: 'A', label: '选项A' },
{ value: 'B', label: '选项B' },
{ value: 'C', label: '选项C' },
],
},
},
],
});
function setFormValues() {
formApi.setValues({
string: 'string',
number: 123,
radio: 'B',
radioButton: 'C',
checkbox: ['A', 'C'],
checkbotton: ['B', 'C'],
checkbox1: ['A', 'B'],
date: new Date(),
select: 'B',
});
}
</script>
<template>
<Page
description="我们重新包装了CheckboxGroup、RadioGroup、Select可以通过options属性传入选项属性数组以自动生成选项"
title="表单演示"
>
<ElCard>
<template #header>
<div class="flex items-center">
<span class="flex-auto">基础表单演示</span>
<ElButton type="primary" @click="setFormValues">设置表单值</ElButton>
</div>
</template>
<Form />
</ElCard>
</Page>
</template>

View File

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

View File

@@ -3,12 +3,13 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -19,6 +20,8 @@ import {
NDivider,
NInput,
NInputNumber,
NRadio,
NRadioButton,
NRadioGroup,
NSelect,
NSpace,
@@ -42,10 +45,13 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@@ -63,8 +69,54 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: NSelect,
modelPropName: 'value',
},
slots,
);
},
ApiTreeSelect: (props, { attrs, slots }) => {
return h(
ApiComponent,
{
placeholder: $t('ui.placeholder.select'),
...props,
...attrs,
component: NTreeSelect,
nodeKey: 'value',
loadingSlot: 'arrow',
keyField: 'value',
modelPropName: 'value',
optionsPropName: 'options',
visibleEvent: 'onVisibleChange',
},
slots,
);
},
Checkbox: NCheckbox,
CheckboxGroup: NCheckboxGroup,
CheckboxGroup: (props, { attrs, slots }) => {
let defaultSlot;
if (Reflect.has(slots, 'default')) {
defaultSlot = slots.default;
} else {
const { options } = attrs;
if (Array.isArray(options)) {
defaultSlot = () => options.map((option) => h(NCheckbox, option));
}
}
return h(
NCheckboxGroup,
{ ...props, ...attrs },
{ default: defaultSlot },
);
},
DatePicker: NDatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
@@ -75,9 +127,37 @@ async function initComponentAdapter() {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'suffix', inputComponent: NInput, ...props, ...attrs },
slots,
);
},
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,
RadioGroup: (props, { attrs, slots }) => {
let defaultSlot;
if (Reflect.has(slots, 'default')) {
defaultSlot = slots.default;
} else {
const { options } = attrs;
if (Array.isArray(options)) {
defaultSlot = () =>
options.map((option) =>
h(attrs.isButton ? NRadioButton : NRadio, option),
);
}
}
const groupRender = h(
NRadioGroup,
{ ...props, ...attrs },
{ default: defaultSlot },
);
return attrs.isButton
? h(NSpace, { vertical: true }, () => groupRender)
: groupRender;
},
Select: withDefaultPlaceholder(NSelect, 'select'),
Space: NSpace,
Switch: NSwitch,

View File

@@ -10,8 +10,6 @@ import { $t } from '@vben/locales';
setupVbenForm<ComponentType>({
config: {
// naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true,
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null,
baseModelPropName: 'value',

View File

@@ -1,12 +1,13 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
defaultResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
} from '@vben/request';
@@ -19,8 +20,9 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) {
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({
...options,
baseURL,
});
@@ -68,18 +70,14 @@ function createRequestClient(baseURL: string) {
},
});
// response数据解构
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw Object.assign({}, response, { response });
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({
codeField: 'code',
dataField: 'data',
successCode: 0,
}),
);
// token过期的处理
client.addResponseInterceptor(
@@ -107,6 +105,8 @@ function createRequestClient(baseURL: string) {
return client;
}
export const requestClient = createRequestClient(apiURL);
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@@ -1,9 +1,11 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/naive';
import { useTitle } from '@vueuse/core';
@@ -16,6 +18,16 @@ import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
initComponentAdapter();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
// });
// // 设置抽屉的默认配置
// setDefaultDrawerProps({
// // zIndex: 2000,
// });
const app = createApp(App);
// 国际化 i18n 配置
@@ -27,6 +39,9 @@ async function bootstrap(namespace: string) {
// 安装权限指令
registerAccessDirective(app);
// 初始化 tippy
initTippy(app);
// 配置路由及路由守卫
app.use(router);

View File

@@ -1,7 +1,7 @@
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { App } from 'vue';
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import {
$t,
setupI18n as coreSetup,

View File

@@ -2,6 +2,7 @@
"title": "Demos",
"naive": "Naive UI",
"table": "Table",
"form": "Form",
"vben": {
"title": "Project",
"about": "About",

View File

@@ -2,6 +2,7 @@
"title": "演示",
"naive": "Naive UI",
"table": "Table",
"form": "表单",
"vben": {
"title": "项目",
"about": "关于",

View File

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides

View File

@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
if (coreRouteNames.includes(to.name as string)) {
if (to.path === LOGIN_PATH && accessStore.accessToken) {
return decodeURIComponent(
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
(to.query?.redirect as string) ||
userStore.userInfo?.homePath ||
DEFAULT_HOME_PATH,
);
}
return true;
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
query:
to.fullPath === DEFAULT_HOME_PATH
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
@@ -101,7 +106,10 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
const redirectPath = (from.query.redirect ??
(to.path === DEFAULT_HOME_PATH
? userInfo.homePath || DEFAULT_HOME_PATH
: to.fullPath)) as string;
return {
...router.resolve(decodeURIComponent(redirectPath)),

View File

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
/** 基本路由,这些路由是必须存在的 */
const coreRoutes: RouteRecordRaw[] = [
/**
* 根路由
* 使用基础布局作为所有页面的父级容器子级就不必配置BasicLayout。
* 此路由必须存在,且不应修改
*/
{
component: BasicLayout,
meta: {
hideInBreadcrumb: true,
title: 'Root',
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
children: [],
},
{
component: AuthPageLayout,

View File

@@ -17,12 +17,12 @@ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
/** 不需要权限的菜单列表(会显示在菜单中) */
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由+静态路由组成 */
/** 路由列表,由基本路由、外部路由和404兜底路由组成
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [
...coreRoutes,
...externalRoutes,
@@ -32,5 +32,6 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

View File

@@ -1,18 +1,16 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'lucide:layout-dashboard',
order: -1,
title: $t('page.dashboard.title'),
},
name: 'Dashboard',
path: '/',
path: '/dashboard',
children: [
{
name: 'Analytics',

View File

@@ -1,11 +1,9 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
@@ -31,6 +29,14 @@ const routes: RouteRecordRaw[] = [
path: '/demos/table',
component: () => import('#/views/demos/table/index.vue'),
},
{
meta: {
title: $t('demos.form'),
},
name: 'Form',
path: '/demos/form',
component: () => import('#/views/demos/form/basic.vue'),
},
],
},
];

View File

@@ -9,30 +9,20 @@ import {
} from '@vben/constants';
import { SvgAntdvLogoIcon } from '@vben/icons';
import { BasicLayout, IFrameView } from '#/layouts';
import { IFrameView } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
order: 9998,
title: $t('demos.vben.title'),
},
name: 'VbenProject',
path: '/vben-admin',
children: [
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
},
},
{
name: 'VbenDocument',
path: '/vben-admin/document',
@@ -77,6 +67,16 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
order: 9999,
},
},
];
export default routes;

View File

@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => {
return [
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
{
component: 'VbenPinInput',
componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => {
const text =
countdown > 0
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
},
];
});

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,9 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -15,10 +15,10 @@ import {
} from '@vben/icons';
import AnalyticsTrends from './analytics-trends.vue';
import AnalyticsVisits from './analytics-visits.vue';
import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue';
import AnalyticsVisits from './analytics-visits.vue';
const overviewItems: AnalysisOverviewItem[] = [
{

View File

@@ -7,6 +7,7 @@ import type {
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
@@ -18,11 +19,15 @@ import {
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
@@ -31,6 +36,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
@@ -39,6 +45,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
@@ -47,6 +54,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
@@ -55,6 +63,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
@@ -63,6 +72,7 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
@@ -71,39 +81,47 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
@@ -195,6 +213,24 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
@@ -210,7 +246,7 @@ const trendItems: WorkbenchTrendItem[] = [
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
@@ -218,6 +254,7 @@ const trendItems: WorkbenchTrendItem[] = [
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">

View File

@@ -0,0 +1,159 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { NButton, NCard, useMessage } from 'naive-ui';
import { useVbenForm } from '#/adapter/form';
import { getAllMenusApi } from '#/api';
const message = useMessage();
const [Form, formApi] = useVbenForm({
commonConfig: {
// 所有表单项
componentProps: {
class: 'w-full',
},
},
layout: 'horizontal',
// 大屏一行显示3个中屏一行显示2个小屏一行显示1个
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
handleSubmit: (values) => {
message.success(`表单数据:${JSON.stringify(values)}`);
},
schema: [
{
// 组件需要在 #/adapter.ts内注册并加上类型
component: 'ApiSelect',
// 对应组件的参数
componentProps: {
// 菜单接口转options格式
afterFetch: (data: { name: string; path: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.path,
}));
},
// 菜单接口
api: getAllMenusApi,
},
// 字段名
fieldName: 'api',
// 界面显示的label
label: 'ApiSelect',
rules: 'required',
},
{
component: 'ApiTreeSelect',
// 对应组件的参数
componentProps: {
// 菜单接口
api: getAllMenusApi,
childrenField: 'children',
// 菜单接口转options格式
labelField: 'name',
valueField: 'path',
},
// 字段名
fieldName: 'apiTree',
// 界面显示的label
label: 'ApiTreeSelect',
rules: 'required',
},
{
component: 'Input',
fieldName: 'string',
label: 'String',
rules: 'required',
},
{
component: 'InputNumber',
fieldName: 'number',
label: 'Number',
rules: 'required',
},
{
component: 'RadioGroup',
fieldName: 'radio',
label: 'Radio',
componentProps: {
options: [
{ value: 'A', label: 'A' },
{ value: 'B', label: 'B' },
{ value: 'C', label: 'C' },
{ value: 'D', label: 'D' },
{ value: 'E', label: 'E' },
],
},
rules: 'selectRequired',
},
{
component: 'RadioGroup',
fieldName: 'radioButton',
label: 'RadioButton',
componentProps: {
isButton: true,
class: 'flex flex-wrap', // 如果选项过多可以添加class来自动折叠
options: [
{ value: 'A', label: '选项A' },
{ value: 'B', label: '选项B' },
{ value: 'C', label: '选项C' },
{ value: 'D', label: '选项D' },
{ value: 'E', label: '选项E' },
],
},
rules: 'selectRequired',
},
{
component: 'CheckboxGroup',
fieldName: 'checkbox',
label: 'Checkbox',
componentProps: {
options: [
{ value: 'A', label: '选项A' },
{ value: 'B', label: '选项B' },
{ value: 'C', label: '选项C' },
],
},
rules: 'selectRequired',
},
{
component: 'DatePicker',
fieldName: 'date',
label: 'Date',
rules: 'required',
},
{
component: 'Input',
fieldName: 'textArea',
label: 'TextArea',
componentProps: {
type: 'textarea',
},
rules: 'required',
},
],
});
function setFormValues() {
formApi.setValues({
string: 'string',
number: 123,
radio: 'B',
radioButton: 'C',
checkbox: ['A', 'C'],
date: Date.now(),
});
}
</script>
<template>
<Page
description="表单适配器重新包装了CheckboxGroup和RadioGroup可以通过options属性传递选项数据选项数据将作为子组件的属性"
title="表单演示"
>
<NCard title="基础表单">
<template #header-extra>
<NButton type="primary" @click="setFormValues">设置表单值</NButton>
</template>
<Form />
</NCard>
</Page>
</template>

View File

@@ -1,7 +1,8 @@
<script lang="ts" setup>
import type { NotificationType } from 'naive-ui';
import { Page } from '@vben/common-ui';
import { type NotificationType } from 'naive-ui';
import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
const notification = useNotification();

View File

@@ -4,53 +4,55 @@
"language": "en,en-US",
"allowCompoundWords": true,
"words": [
"clsx",
"esno",
"demi",
"unref",
"taze",
"acmr",
"antd",
"lucide",
"antdv",
"astro",
"brotli",
"clsx",
"defu",
"demi",
"echarts",
"ependencies",
"esno",
"etag",
"execa",
"iconify",
"iconoir",
"intlify",
"lockb",
"lucide",
"minh",
"minw",
"mkdist",
"mockjs",
"vitejs",
"naiveui",
"nocheck",
"noopener",
"noreferrer",
"nprogress",
"nuxt",
"pinia",
"prefixs",
"publint",
"qrcode",
"shadcn",
"sonner",
"sortablejs",
"styl",
"taze",
"ui-kit",
"uicons",
"unplugin",
"unref",
"vben",
"vbenjs",
"vueuse",
"yxxx",
"nuxt",
"lockb",
"astro",
"ui-kit",
"styl",
"vnode",
"nocheck",
"prefixs",
"vitepress",
"antdv",
"ependencies",
"vite",
"echarts",
"sortablejs",
"etag",
"naiveui",
"uicons",
"iconoir"
"vitejs",
"vitepress",
"vnode",
"vueuse",
"yxxx"
],
"ignorePaths": [
"**/node_modules/**",

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { SetupContext } from 'vue';
import { computed, ref, useSlots } from 'vue';
import { VbenTooltip } from '@vben-core/shadcn-ui';
@@ -25,7 +27,7 @@ const props = withDefaults(
const open = ref(false);
const slots = useSlots();
const slots: SetupContext['slots'] = useSlots();
const tabs = computed(() => {
return props.files.map((file) => {

View File

@@ -1,4 +1,6 @@
import { type DefaultTheme, defineConfig } from 'vitepress';
import type { DefaultTheme } from 'vitepress';
import { defineConfig } from 'vitepress';
import { version } from '../../../package.json';
@@ -221,9 +223,9 @@ function nav(): DefaultTheme.NavItem[] {
link: '/commercial/community',
text: '👨‍👦‍👦 Community',
},
{
link: '/friend-links/',
text: '🤝 Friend Links',
},
// {
// link: '/friend-links/',
// text: '🤝 Friend Links',
// },
];
}

View File

@@ -3,7 +3,10 @@ import type { HeadConfig } from 'vitepress';
import { resolve } from 'node:path';
import { viteArchiverPlugin } from '@vben/vite-config';
import {
viteArchiverPlugin,
viteVxeTableImportsPlugin,
} from '@vben/vite-config';
import {
GitChangelog,
@@ -59,6 +62,11 @@ export const shared = defineConfig({
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
],
},
preprocessorOptions: {
scss: {
api: 'modern',
},
},
},
json: {
stringify: true,
@@ -85,6 +93,7 @@ export const shared = defineConfig({
GitChangelogMarkdownSection(),
viteArchiverPlugin({ outputDir: '.vitepress' }),
groupIconVitePlugin(),
await viteVxeTableImportsPlugin(),
],
server: {
fs: {
@@ -93,6 +102,7 @@ export const shared = defineConfig({
host: true,
port: 6173,
},
ssr: {
external: ['@vue/repl'],
},
@@ -156,6 +166,7 @@ function pwa(): PwaOptions {
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
},
};
}

View File

@@ -1,4 +1,6 @@
import { type DefaultTheme, defineConfig } from 'vitepress';
import type { DefaultTheme } from 'vitepress';
import { defineConfig } from 'vitepress';
import { version } from '../../../package.json';
@@ -124,7 +126,7 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
return [
{
link: 'community',
text: '社区',
text: '交流群',
},
{
link: 'technical-support',
@@ -148,10 +150,24 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
},
],
},
{
collapsed: false,
text: '布局组件',
items: [
{
link: 'layout-ui/page',
text: 'Page 页面',
},
],
},
{
collapsed: false,
text: '通用组件',
items: [
{
link: 'common-ui/vben-api-component',
text: 'ApiComponent Api组件包装器',
},
{
link: 'common-ui/vben-modal',
text: 'Modal 模态框',
@@ -172,6 +188,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
link: 'common-ui/vben-count-to-animator',
text: 'CountToAnimator 数字动画',
},
{
link: 'common-ui/vben-ellipsis-text',
text: 'EllipsisText 省略文本',
},
],
},
];
@@ -266,7 +286,7 @@ function nav(): DefaultTheme.NavItem[] {
},
{
link: '/commercial/community',
text: '👨‍👦‍👦 社区',
text: '👨‍👦‍👦 交流群',
// items: [
// {
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',
@@ -282,10 +302,10 @@ function nav(): DefaultTheme.NavItem[] {
// },
// ],
},
{
link: '/friend-links/',
text: '🤝 友情链接',
},
// {
// link: '/friend-links/',
// text: '🤝 友情链接',
// },
];
}

View File

@@ -10,7 +10,6 @@ import {
// import { useAntdDesignTokens } from '@vben/hooks';
// import { initPreferences } from '@vben/preferences';
import { ConfigProvider, theme } from 'ant-design-vue';
import mediumZoom from 'medium-zoom';
import { useRoute } from 'vitepress';

View File

@@ -15,11 +15,12 @@ import 'virtual:group-icons.css';
import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
export default {
enhanceApp(ctx: EnhanceAppContext) {
async enhanceApp(ctx: EnhanceAppContext) {
const { app } = ctx;
app.component('VbenContributors', VbenContributors);
app.component('DemoPreview', DemoPreview);
app.use(NolebaseGitChangelogPlugin);
// 百度统计
initHmPlugin();
},

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.4.4",
"version": "5.5.3",
"private": true,
"scripts": {
"build": "vitepress build",
@@ -8,12 +8,16 @@
"docs:preview": "vitepress preview"
},
"imports": {
"#/*": "./src/_env/*"
"#/*": {
"node": "./src/_env/node/*",
"default": "./src/_env/*"
}
},
"dependencies": {
"@vben-core/shadcn-ui": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/styles": "workspace:*",
"ant-design-vue": "catalog:",
"lucide-vue-next": "catalog:",

View File

@@ -3,9 +3,10 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';

View File

@@ -14,8 +14,6 @@ initComponentAdapter();
setupVbenForm<ComponentType>({
config: {
baseModelPropName: 'value',
// naive-ui组件不接受onChang事件所以需要禁用
disabledOnChangeListener: true,
// naive-ui组件的空值为null,不能是undefined否则重置表单时不生效
emptyStateValue: null,
modelPropNameMap: {

View File

@@ -1 +0,0 @@
export * from './form';

View File

@@ -0,0 +1,70 @@
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
if (!import.meta.env.SSR) {
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
columnConfig: {
resizable: true,
},
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
minHeight: 180,
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
}
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';

View File

@@ -0,0 +1,4 @@
export const useVbenForm = () => {};
export const z = {};
export type VbenFormSchema = any;
export type VbenFormProps = any;

View File

@@ -0,0 +1,3 @@
export type * from '@vben/plugins/vxe-table';
export const useVbenVxeGrid = () => {};

View File

@@ -20,7 +20,10 @@
::: tip
因为微信群人数有限制,加微信群前,你可以通过[赞助](../sponsor/personal.md)任意金额,主动发送截图给作者,备注`加入微信群`即可。
因为微信群人数有限制,加微信群要求:
- 通过[赞助](../sponsor/personal.md)任意金额。
- 发送赞助`截图`,备注`加入微信群`即可。
:::

View File

@@ -0,0 +1,149 @@
---
outline: deep
---
# Vben ApiComponent Api组件包装器
框架提供的API“包装器”它一般不独立使用主要用于包装其它组件为目标组件提供自动获取远程数据的能力但仍然保持了目标组件的原始用法。
::: info 写在前面
我们在各个应用的组件适配器中使用ApiComponent包装了Select、TreeSelect组件使得这些组件可以自动获取远程数据并生成选项。其它类似的组件比如Cascader如有需要也可以参考示例代码自行进行包装。
:::
## 基础用法
通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch``afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField``labelField`等来从数据中提取value和label如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。
::: details 包装级联选择器,点击下拉时开始加载远程数据
```vue
<script lang="ts" setup>
import { ApiComponent } from '@vben/common-ui';
import { Cascader } from 'ant-design-vue';
const treeData: Record<string, any> = [
{
label: '浙江',
value: 'zhejiang',
children: [
{
value: 'hangzhou',
label: '杭州',
children: [
{
value: 'xihu',
label: '西湖',
},
{
value: 'sudi',
label: '苏堤',
},
],
},
{
value: 'jiaxing',
label: '嘉兴',
children: [
{
value: 'wuzhen',
label: '乌镇',
},
{
value: 'meihuazhou',
label: '梅花洲',
},
],
},
{
value: 'zhoushan',
label: '舟山',
children: [
{
value: 'putuoshan',
label: '普陀山',
},
{
value: 'taohuadao',
label: '桃花岛',
},
],
},
],
},
{
label: '江苏',
value: 'jiangsu',
children: [
{
value: 'nanjing',
label: '南京',
children: [
{
value: 'zhonghuamen',
label: '中华门',
},
{
value: 'zijinshan',
label: '紫金山',
},
{
value: 'yuhuatai',
label: '雨花台',
},
],
},
],
},
];
/**
* 模拟请求接口
*/
function fetchApi(): Promise<Record<string, any>> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(treeData);
}, 1000);
});
}
</script>
<template>
<ApiComponent
:api="fetchApi"
:component="Cascader"
:immediate="false"
children-field="children"
loading-slot="suffixIcon"
visible-event="onDropdownVisibleChange"
/>
</template>
```
:::
## API
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| modelValue(v-model) | 当前值 | `any` | - |
| component | 欲包装的组件(以下称为目标组件) | `Component` | - |
| numberToString | 是否将value从数字转为string | `boolean` | `false` |
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - |
| params | 传递给api的参数 | `Record<string, any>` | - |
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - |
| labelField | label字段名 | `string` | `label` |
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` |
| valueField | value字段名 | `string` | `value` |
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` |
| modelPropName | 目标组件的双向绑定属性名默认为modelValue。部分组件可能为value | `string` | `modelValue` |
| immediate | 是否立即调用api | `boolean` | `true` |
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` |
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - |
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - |
| options | 直接传入选项数据也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |

View File

@@ -54,6 +54,8 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
- `VbenDrawer` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
:::
@@ -74,12 +76,16 @@ const [Drawer, drawerApi] = useVbenDrawer({
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
| isOpen | 弹窗打开状态 | `boolean` | `false` |
| loading | 弹窗加载状态 | `boolean` | `false` |
| closable | 显示关闭按钮 | `boolean` | `true` |
| closeIconPlacement | 关闭按钮位置 | `'left'\|'right'` | `right` |
| modal | 显示遮罩 | `boolean` | `true` |
| header | 显示header | `boolean` | `true` |
| footer | 显示footer | `boolean\|slot` | `true` |
@@ -88,23 +94,34 @@ const [Drawer, drawerApi] = useVbenDrawer({
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |
| headerClass | modal顶部区域的class | `string` | - |
| zIndex | 抽屉的ZIndex层级 | `number` | `1000` |
| overlayBlur | 遮罩模糊度 | `number` | - |
::: info appendToMain
`appendToMain`可以指定将抽屉挂载到内容区域打开抽屉时内容区域以外的部分标签栏、导航菜单等等不会被遮挡。默认情况下抽屉会挂载到body上。但是挂载到内容区域时作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。
:::
### Event
以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。
| 事件名 | 描述 | 类型 |
| --- | --- | --- |
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` |
| onCancel | 点击取消按钮触发 | `()=>void` |
| onConfirm | 点击确认按钮触发 | `()=>void` |
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` |
| 事件名 | 描述 | 类型 | 版本限制 |
| --- | --- | --- | --- |
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- |
| onCancel | 点击取消按钮触发 | `()=>void` | --- |
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 |
| onConfirm | 点击确认按钮触发 | `()=>void` | --- |
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | --- |
| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.5.2 |
### Slots
@@ -115,14 +132,16 @@ const [Drawer, drawerApi] = useVbenDrawer({
| default | 默认插槽 - 弹窗内容 |
| prepend-footer | 取消按钮左侧 |
| append-footer | 取消按钮右侧 |
| close-icon | 关闭按钮图标 |
| extra | 额外内容(标题右侧) |
### modalApi
### drawerApi
| 事件名 | 描述 | 类型 |
| 方法 | 描述 | 类型 |
| --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `setState(props) \| setState((prev)=>(props))` |
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>drawerApi` |
| open | 打开弹窗 | `()=>void` |
| close | 关闭弹窗 | `()=>void` |
| setData | 设置共享数据 | `<T>(data:T)=>void` |
| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` |
| getData | 获取共享数据 | `<T>()=>T` |
| useStore | 获取可响应式状态 | - |

View File

@@ -0,0 +1,56 @@
---
outline: deep
---
# Vben EllipsisText 省略文本
框架提供的文本展示组件可配置超长省略、tooltip提示、展开收起等功能。
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
## 基础用法
通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。
<DemoPreview dir="demos/vben-ellipsis-text/line" />
## 可折叠的文本块
通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。
<DemoPreview dir="demos/vben-ellipsis-text/expand" />
## 自定义提示浮层
通过名为`tooltip`的插槽定制提示信息。
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
## API
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| expand | 支持点击展开或收起 | `boolean` | `false` |
| line | 文本最大行数 | `number` | `1` |
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
| tooltip | 启用文本提示 | `boolean` | `true` |
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
| tooltipColor | 提示文本的颜色 | `string` | - |
| tooltipFontSize | 提示文本的大小 | `string` | - |
| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - |
| tooltipOverlayStyle | 提示框内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |
### Events
| 事件名 | 描述 | 类型 |
| ------------ | ------------ | -------------------------- |
| expandChange | 展开状态改变 | `(isExpand:boolean)=>void` |
### Slots
| 插槽名 | 描述 |
| ------- | -------------------------------- |
| tooltip | 启用文本提示时,用来定制提示内容 |

View File

@@ -87,7 +87,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -149,6 +149,7 @@ export type ComponentType =
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'IconPicker';
| BaseFormComponentType;
async function initComponentAdapter() {
@@ -166,6 +167,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
@@ -280,10 +282,13 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| 方法名 | 描述 | 类型 |
| --- | --- | --- |
| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` |
| resetForm | 重置表单 | `()=>Promise<void>` |
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
| validate | 表单校验 | `()=>Promise<void>` |
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` |
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` |
| resetValidate | 重置表单校验 | `()=>Promise<void>` |
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` |
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` |
@@ -303,15 +308,25 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| actionWrapperClass | 表单操作区域class | `any` | - |
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>,) => void` | - |
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
| collapsed | 是否折叠,在`是否展开,在showCollapseButton=true`时生效 | `boolean` | `false` |
| collapsed | 是否折叠,在`showCollapseButton``true`时生效 | `boolean` | `false` |
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
| fieldMappingTime | 用于将表单内的数组值值映射成 2 个字段 | `[string, [string, string],Nullable<string>\|[string,string]\|((any,string)=>any)?][]` | - |
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
| schema | 表单项的每一项配置 | `FormSchema` | - |
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
::: tip fieldMappingTime
此属性用于将表单内的数组值映射成 2 个字段它应当传入一个数组数组的每一项是一个映射规则规则的第一个成员是一个字符串表示需要映射的字段名第二个成员是一个数组表示映射后的字段名第三个成员是一个可选的格式掩码用于格式化日期时间字段也可以提供一个格式化函数参数分别为当前值和当前字段名返回格式化后的值。如果明确地将格式掩码设为null则原值映射而不进行格式化适用于非日期时间字段。例如`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]``timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime``endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
:::
### TS 类型说明
@@ -320,7 +335,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
```ts
export interface ActionButtonOptions {
/** 样式 */
class?: any;
class?: ClassType;
/** 是否禁用 */
disabled?: boolean;
/** 是否加载中 */
@@ -348,10 +363,21 @@ export interface FormCommonConfig {
* 所有表单项的props
*/
componentProps?: ComponentProps;
/**
* 是否紧凑模式(移除表单底部为显示校验错误信息所预留的空间)。
* 在有设置校验规则的场景下建议不要将其设置为true
* 默认为false。但用作表格的搜索表单时默认为true
* @default false
*/
compact?: boolean;
/**
* 所有表单项的控件样式
*/
controlClass?: string;
/**
* 在表单项的Label后显示一个冒号
*/
colon?: boolean;
/**
* 所有表单项的禁用状态
* @default false
@@ -386,6 +412,11 @@ export interface FormCommonConfig {
* 所有表单项的label宽度
*/
labelWidth?: number;
/**
* 所有表单项的model属性名。使用自定义组件时可通过此配置指定组件的model属性名。已经在modelPropNameMap中注册的组件不受此配置影响
* @default "modelValue"
*/
modelPropName?: string;
/**
* 所有表单项的wrapper样式
*/
@@ -411,13 +442,13 @@ export interface FormSchema<
dependencies?: FormItemDependencies;
/** 描述 */
description?: string;
/** 字段名 */
/** 字段名,也作为自定义插槽的名称 */
fieldName: string;
/** 帮助信息 */
help?: string;
/** 表单项 */
label?: string;
// 自定义组件内部渲染
/** 自定义组件内部渲染 */
renderComponentContent?: RenderComponentContentType;
/** 字段规则 */
rules?: FormSchemaRuleType;
@@ -434,7 +465,7 @@ export interface FormSchema<
```ts
dependencies: {
// 只有当 name 字段值变时,才会触发联动
// 触发字段。只有这些字段值变时,联动才会触发
triggerFields: ['name'],
// 动态判断当前字段是否需要显示,不显示则直接销毁
if(values,formApi){},
@@ -455,11 +486,11 @@ dependencies: {
### 表单校验
表单联动需要通过 schema 内的 `rules` 属性进行配置。
表单校验需要通过 schema 内的 `rules` 属性进行配置。
rules的值可以是一个字符串也可以是一个zod的schema。
rules的值可以是字符串(预定义的校验规则名称)也可以是一个zod的schema。
#### 字符串
#### 预定义的校验规则
```ts
// 表示字段必填默认会根据适配器的required进行国际化
@@ -485,11 +516,16 @@ import { z } from '#/adapter/form';
rules: z.string().min(1, { message: '请输入字符串' });
}
// 可选,并且携带默认值
// 可选(可以是undefined)并且携带默认值。注意zod的optional不包括空字符串''
{
rules: z.string().default('默认值').optional(),
}
// 可以是空字符串、undefined或者一个邮箱地址
{
rules: z.union(z.string().email().optional(), z.literal(""))
}
// 复杂校验
{
z.string().min(1, { message: "请输入" })
@@ -498,3 +534,20 @@ import { z } from '#/adapter/form';
});
}
```
## Slots
可以使用以下插槽在表单中插入自定义的内容
| 插槽名 | 描述 |
| ------------- | ------------------ |
| reset-before | 重置按钮之前的位置 |
| submit-before | 提交按钮之前的位置 |
| expand-before | 展开按钮之前的位置 |
| expand-after | 展开按钮之后的位置 |
::: tip 字段插槽
除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。
:::

View File

@@ -60,6 +60,8 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
:::
@@ -80,6 +82,9 @@ const [Modal, modalApi] = useVbenModal({
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域默认挂载到body | `boolean` | `false` |
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
@@ -93,18 +98,28 @@ const [Modal, modalApi] = useVbenModal({
| modal | 显示遮罩 | `boolean` | `true` |
| header | 显示header | `boolean` | `true` |
| footer | 显示footer | `boolean\|slot` | `true` |
| confirmDisabled | 禁用确认按钮 | `boolean` | `false` |
| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |
| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |
| headerClass | modal顶部区域的class | `string` | - |
| bordered | 是否显示border | `boolean` | `false` |
| zIndex | 弹窗的ZIndex层级 | `number` | `1000` |
| overlayBlur | 遮罩模糊度 | `number` | - |
| submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` |
::: info appendToMain
`appendToMain`可以指定将弹窗挂载到内容区域打开这种弹窗时内容区域以外的部分标签栏、导航菜单等等不会被遮挡。默认情况下弹窗会挂载到body上。但是挂载到内容区域时作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。
:::
### Event
@@ -112,7 +127,7 @@ const [Modal, modalApi] = useVbenModal({
| 事件名 | 描述 | 类型 | 版本号 |
| --- | --- | --- | --- |
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | |
| onBeforeClose | 关闭前触发,返回 `false`或者被`reject`则禁止关闭 | `()=>Promise<boolean>\|boolean` | >5.5.2支持Promise |
| onCancel | 点击取消按钮触发 | `()=>void` | |
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.4.3 |
| onConfirm | 点击确认按钮触发 | `()=>void` | |
@@ -131,11 +146,18 @@ const [Modal, modalApi] = useVbenModal({
### modalApi
| 事件名 | 描述 | 类型 |
| --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `setState(props) \| setState((prev)=>(props))` |
| open | 打开弹窗 | `()=>void` |
| close | 关闭弹窗 | `()=>void` |
| setData | 设置共享数据 | `<T>(data:T)=>void` |
| getData | 获取共享数据 | `<T>()=>T` |
| useStore | 获取可响应式状态 | - |
| 方法 | 描述 | 类型 | 版本 |
| --- | --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>modalApi` | - |
| open | 打开弹窗 | `()=>void` | - |
| close | 关闭弹窗 | `()=>void` | - |
| setData | 设置共享数据 | `<T>(data:T)=>modalApi` | - |
| getData | 获取共享数据 | `<T>()=>T` | - |
| useStore | 获取可响应式状态 | - | - |
| lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 |
::: info lock
`lock`方法用于锁定当前弹窗的状态一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时弹窗的确认按钮会变为loading状态同时禁用确认按钮、隐藏关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。
:::

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