Compare commits

...

118 Commits

Author SHA1 Message Date
invalid w
fa5803f8c7 chore: release 2.10.1 2023-09-27 09:37:53 +08:00
smirkQAQ
aa6168fe20 fix: Fix context menu style not working (#3075) 2023-09-27 08:18:38 +08:00
invalid w
855a410557 style(Modal): modal全屏模式 ant 的modal-footer margin-top调整为0 (#3072) 2023-09-26 09:41:44 +08:00
invalid w
b1559e2cad fix(ApiSelect): ApiSelect首次选择值时仍然提示校验, fix #3065 (#3071) 2023-09-26 09:41:34 +08:00
invalid w
c5d24e07f0 Chore: 处理了Vben封装的Drawer,Modal组件的一些类型错误 (#3064)
* chore: rm unuse params

* chore(Modal): getCurrentInstance的uid类型为number

* chore(Drawer): 调整drawer组件的一些类型问题

* chore(Drawer): 移除多余的classname配置

* chore(Drawer): 修复getContainer和antd4 drawer组件类型不一致

* fix(AppSearchModal): 调整setRefs函数的类型
2023-09-26 09:41:25 +08:00
林飞
1cf2a81f2a feat(table): 表格拖拽列改变宽度
* feat(funcation): update 修复表格无法拖拽列改变宽度

* feat(function): add 完善可伸缩列

---------

Co-authored-by: gavin-james <meaganlindesy1258@gmail.com>
2023-09-22 10:35:30 +08:00
黄小民
5665fd62a9 fix: 英文版本时提示中的单词没有分开 (#3062) 2023-09-22 08:21:44 +08:00
James Zow
b7554fdb74 Create node.js.yml (#3060) 2023-09-21 17:00:21 +08:00
黄小民
3b62afe110 Update index.less (#3055)
处理 移动滚动条的时候,ant-select-dropdown 没有随form-item移动
2023-09-21 10:40:18 +08:00
invalid w
dbdd811705 feat(style): 使用Antd组件提供的切换主题功能 (#3051)
* perf(style): 使用antd4的暗黑主题

* refactor: 抽离切换暗黑主题模式
2023-09-21 10:40:09 +08:00
zmcode
a6b65b58a1 feat: 解决Form组件slot必须传递component字段才显示的问题 (#3049)
feat: 解决Form组件slot必须传递component字段才显示的问题
2023-09-21 10:39:44 +08:00
胤玄
556575f501 feat: update nginx.conf 使用固定编译入口可能导致浏览器使用缓存js文件,而此配置可以解决 (#3045) 2023-09-20 08:16:48 +08:00
invalid w
e4778757ad style(TableColumn): fix Column settings container is too wide #3043 (#3044) 2023-09-20 08:16:38 +08:00
leo
b43fe7adbc fix(axios): get等方法配置ignoreCancelToken无效 (#3040) 2023-09-18 10:04:24 +08:00
Li Kui
4aaddef06f fix: type check failed for prop 'onClick' (#3038) 2023-09-18 10:04:15 +08:00
alixhome
ce6b25d03b fix: Change role to toles for LoginResultModel (#3037) 2023-09-18 10:04:00 +08:00
open-carp
74a2f6209f fix: FormAction组件 绑定表单提交事件 (#3036) 2023-09-18 10:03:52 +08:00
luchanan
99ddc3598a perf: 指令权限v-auth支持并显示按钮权限 (#3035) 2023-09-17 12:08:17 +08:00
invalid w
d3fd22dbbd chore: 解决 ESLint no-undef 规则校验问题和 basicTable 组件的类型问题,替换popover组件的 visible 属性。 (#3033)
* chore: 关闭eslint的no-undef规则校验

* chore(VFormDesign): 替换表单设计组件的modal是否可见属性

* chore(BasicTable): emit传参类型问题

* chore(Table): 调整函数参数类型

* chore: 调整expandedRowKeys数据类型

* chore(Table): 完善TableRowSelection接口类型

* chore(Table): 完善useRowSelection 文件的类型问题

* chore(useColumns): key赋值的类型问题

* chore(useDataSource): setTableData的类型问题

* chore:  替换rowKeys类型为 (string|number)[]

* fix(edit-cell): 修复edit table Popover 传参问题

* fix(Table):  handleItem函数进行绑定key dataIndex可能为数组,造成类型错误提示
2023-09-16 20:32:33 +08:00
LanceJiang
bf2f6390ad feat: BasicTable 组件 HeaderCell 新增(类customRender) customHeaderRender 方法 自定义渲染支持 (#3030)
示例见: /comp/table/multipleHeader
2023-09-15 15:56:26 +08:00
Li Kui
bd024cc521 fix: isible deprecated warnings (#3029) 2023-09-15 15:56:17 +08:00
Li Kui
4ac08e5ae5 fix: type check failed for prop 'onClick' (#3028) 2023-09-15 15:56:06 +08:00
invalid w
b0c2ca5393 fix(Header): 顶部菜单混合模式下,头部导航栏样式错位 #3021 (#3024)
* fix(Header): 顶部菜单混合模式下,头部导航栏样式错位 #3021

antdv4的ant-layout-header css会携带父选择器 导致line-height失效

* chore: 移除无用css
2023-09-13 20:26:16 +08:00
invalid w
1ac8c56c6b fix(Modal): 全屏modal样式问题 #3019 (#3020) 2023-09-13 16:32:00 +08:00
LanceJiang
f4149c2f1d fix: 由于更新ant-vue 新版本到 4.0.2以上 tsx 渲染 FormItem 插入style 导致的出错 改换class (#3016) 2023-09-12 23:19:11 +08:00
Li Kui
3ed49c3f8f fix: arrow-left icon not centered (#3013) 2023-09-11 22:22:43 +08:00
invalid w
356f132610 style(Drawer): antdv4版本 drawer要使用rootClassName来来配置最外层元素样式 (#3012) 2023-09-11 17:54:40 +08:00
k1ngbanana
1c668f21bf feat: pageWrapper.vue加入sticky功能 (#3008) 2023-09-08 22:10:29 +08:00
k1ngbanana
a244dcd261 fix: 配置文件公共路径无效 (#3007) 2023-09-08 22:10:19 +08:00
invalid w
aaf2fde3cf antdv升级到4.x版本 (#3006)
* fix(ApiTreeSelect):  ApiTreeSelect组件首次调用会重复请求(#2940)

由于watchEffect,onMounted会立即执行,所以导致重复请求。
并且 watch函数和watchEffect监听的依赖重复了,故删去

* chore: update ant design npm version

* chore: 修改antd的css引用路径

* style(layout):  替换sider header组件传参属性,适配原有组件的样式

* style(Menu): 去除气泡卡片的背景

* chore: typo

* typo(Modal): 将visible替换成open

* typo:修改modal弹窗的绑定函数名称

* style(Modal): 为了解决直接引用 Modal 组件时关闭按钮样式的问题,增加一个名为 "vben-basic-modal" 的类名,以实现样式隔离。

* Update package.json

* typo(Drawer): Drawer 组件 visible 变为 open

* typo(Table): Table 组件 filterDropdownVisible 变为 filterDropdownOpen。

* typo(TreeSelect): 组件弹框的 classname API 统一为 popupClassName,dropdownClassName
2023-09-08 22:10:07 +08:00
invalid w
607a24632a fix(ApiTreeSelect): ApiTreeSelect组件首次调用会重复请求(#2940) (#3005)
由于watchEffect,onMounted会立即执行,所以导致重复请求。
并且 watch函数和watchEffect监听的依赖重复了,故删去
2023-09-07 21:47:55 +08:00
invalid w
89d7a19f3f feat(Form): 为fieldMapToTime的映射类型增加时间戳转换 (#2996)
为fieldMapToTime的第三个参数增加 timestamp 和 timestampStartDay 选项

timestamp的作用: 将映射的时间格式转为时间戳

timestampStartDay的作用: 将映射的时间格式转为当天0点开始的时间戳
2023-09-03 16:22:02 +08:00
invalid w
a9017da294 perf: 为createAsyncComponent函数增加类型推导 (#2967) (#2991) 2023-09-03 16:21:51 +08:00
Li Kui
1c1ce4b0e3 fix: VxeTable demo parameter typo& tabs close icon not centered (#2989)
* fix(msg): message icon not centered

* fix: VxeTable demo parameter typo

* fix: tabs close icon not centered
2023-08-31 12:49:21 +08:00
Li Kui
e31525a803 fix(msg): message icon not centered (#2982) 2023-08-26 07:17:06 +08:00
invalid w
7b26c5994c feat: 增加Prompt组件, 并且修复 #2976 (#2979)
* feat: 增加Prompt组件

类似于Element UI组件库的MessageBox Prompt组件

* fix: #2976

* refactor: 对appendSchemaByField函数的通用操作进行整理
2023-08-25 12:36:10 +08:00
supperchong
248f9d5e81 chore: format nginx.conf (#2971) 2023-08-19 08:41:04 +08:00
scottMan1001
a2a78f40da Feat/scott man (#2970)
* feat: esbuild增加不同开发模式下对cnosole debugger的处理

* fix: fix production spelling
2023-08-16 21:35:21 +08:00
Li Kui
2c74e790cb fix(Tree): beforeRightClick type (#2968) 2023-08-16 21:34:57 +08:00
舜岳
b660f96220 refactor: code optimization (#2961) 2023-08-13 09:14:38 +08:00
Aynakeya
5dd3babc4e update left/right icon not align to center (#2958) 2023-08-13 09:14:15 +08:00
invalid w
5425dc241f fix: #2767 #2884 #2868 (#2956)
* fix: #2767

* fix: #2884

* fix: #2868
2023-08-13 09:14:04 +08:00
Hong Sen Lv
44f2b1c644 fix: ts error (#2955)
Co-authored-by: lvhongsen <lvhongsen@tal.com>
2023-08-11 08:39:24 +08:00
billyshen26
573d395b69 fix: 解决ApiTreeSelect的params更新后fetch不执行 (#2954)
* fix: 解决ApiTreeSelect的params更新后fetch不执行

* feat: 优化ApiTreeSelect参数,可自定义value等

---------

Co-authored-by: Billy Shen <shenfangtao@imaodu.com>
2023-08-11 08:39:13 +08:00
jinmao88
df1fceb291 fix: ci ok 2023-08-10 16:24:33 +08:00
jinmao88
9650122736 fix: ci 2023-08-10 15:52:57 +08:00
jinmao88
c99ef68b7b fix: ci 2023-08-10 15:16:22 +08:00
jinmao88
a95ba47b74 fix: ci 2023-08-10 15:09:38 +08:00
jinmao88
3fd193eb8b fix: ci 2023-08-10 14:51:56 +08:00
jinmao88
6fbc552ed1 fix: ci 2023-08-10 11:52:14 +08:00
jinmao88
247665513a fix: ci 2023-08-10 11:26:42 +08:00
jinmao88
351e1e55c2 fix: ci #12 2023-08-10 11:22:39 +08:00
jinmao88
8a27f5f277 fix: ci #11 2023-08-10 11:14:34 +08:00
jinmao88
befb508f7a fix: ci #10 2023-08-10 11:03:00 +08:00
jinmao88
e6c820792a fix: ci #9 2023-08-10 11:00:49 +08:00
jinmao88
30ccbfa695 fix: ci #8 2023-08-10 10:59:37 +08:00
jinmao88
7e12259ec4 fix: ci #7 2023-08-10 10:58:30 +08:00
jinmao88
244eeb18aa fix: ci #6 2023-08-10 10:47:16 +08:00
jinmao88
1108d78a06 fix: ci #5 2023-08-10 10:26:44 +08:00
jinmao88
ebe2047ae0 fix: ci #4 2023-08-10 10:12:39 +08:00
jinmao88
c7f4e6a459 fix: ci #3 2023-08-10 10:09:38 +08:00
jinmao88
833b31129b fix: getAppEnvConfig 2023-08-10 09:11:56 +08:00
smilv
86d5752ed7 perf: useRefs函数接收泛型类型 (#2952) 2023-08-09 21:58:22 +08:00
jinmao88
9babbc43fc fix: ci #2 2023-08-09 14:09:35 +08:00
jinmao88
a2451be5bc fix: ci 2023-08-08 19:07:09 +08:00
invalid w
eea414e04b feat: 恢复暗黑主题 (#2947)
* feat: 恢复暗黑主题

* fix: 修复danger btn暗黑样式优先级
2023-08-07 18:51:30 +08:00
Cao Duc Anh
0bd98b3c27 chore: split function for easy maintain, understand and using (#2945) 2023-08-03 16:28:34 +08:00
LanceJiang
a065de4fbc feat: Form 自定义组件渲染 新增 opts: {disabled} 用于自定义渲染判断 示例: /comp/form/customerForm页面 (#2944)
* feat: Form 自定义组件渲染 示例: /comp/form/customerForm页面
1. 针对自定义渲染功能 FormSchema 中
render, renderColContent, renderComponentContent 新增 opts:{disabled} 扩展 帮助自定义渲染时做 条件判断、展示同步
渲染: ((renderCallbackParams) => any) ===> ((renderCallbackParams, opts) => any)
2. slot, colSlot 分别是 render, renderColContent 插槽,为方便插槽使用
slotFn 进行解构 #test={scope} scope==={...data, ...opts}

* feat: Form 自定义组件渲染 示例: /comp/form/customerForm页面

1. 针对自定义渲染功能 FormSchema 新增 [fields] 和 [defaultValueObj] 帮助
render, renderColContent 自定义渲染时 存在多个 表单字段操作(复合field 场景)
2023-08-03 11:12:19 +08:00
ludens blunt
fa5ecb090f fix(Dropdown): still pop when disabled is true; (#2935)
Co-authored-by: chenguangzhuang <chenguangzhuang@techpci.com>
2023-07-29 10:43:07 +08:00
YueCHEN195
4f9c711012 fix: 修复BasicTable组件开启可编辑行卡顿/卡死问题 (#2939) 2023-07-29 10:42:51 +08:00
lessroc
12924fb3fa perf: 解决提交代码时stylelint格式化less和scss的媒体查询为不支持的语法(Media Queries Level 4) (#2931) 2023-07-29 10:42:25 +08:00
DoverDee
16b4b6d57c fit: 增加CropperAvatar组件图片上传大小限制默认最大5M (#2928)
Co-authored-by: doverlee <doverlee@fox.mail.com>
2023-07-26 19:05:31 +08:00
沐枫
c28224f3f8 增加TimeRangePicker时间区间范围选择组件 (#2926)
* feat: 增加TimeRangePicker时间范围选择组件

* feat: 增加TimeRangePicker时间范围选择组件2
2023-07-24 13:23:59 +08:00
zhangshujun
3b0b8d0baa feat:不允许可重复添加同一field的表单项 (#2923) 2023-07-21 20:23:04 +08:00
明修
b30270a3fb fix: 修复部分图标未居中的问题 (#2917) 2023-07-20 14:44:22 +08:00
zhangshujun
cb64e5d24c From default value (#2913)
* feat: defaultValue赋值后不改动时间相关字段无法格式化问题

* feat: 更新处理方式

* feat: 去除未引用方法

---------

Co-authored-by: zhangshujun <zhangsj01@51cto.com>
2023-07-17 16:52:21 +08:00
LittleSaya
a4e70b9efe 通过把名称编码成hex,使项目能够在VITE_GLOB_APP_TITLE包含特殊字符时正常打包 (#2910)
* Update env.ts

使getVariableName能处理含有特殊字符的标题

* Update appConfig.ts

是getVariableName能处理包含特殊字符的标题的情况
2023-07-13 10:44:13 +08:00
scottMan1001
4e3e721650 feat: esbuild增加不同开发模式下对cnosole debugger的处理 (#2907) 2023-07-11 11:52:54 +08:00
xingyu
c6e135195a 修复合并代码导致的冲突 (#2905)
* fix: 合并代码导致的问题,升级vite 4.4.0

* fix: stylelint
2023-07-07 07:06:36 +08:00
xingyu
1262e13067 升级最新依赖 并修复eslint报错 (#2896)
* chore: update deps

* fix: import.meta.globEager 已过期

* docs: fix doc bugs

* fix: eslint

* fix: lint:prettier

* fix: stylelint

* chore: update deps

* fix: eslint

* refactor: accountdetail setup

* fix: 'Nullable' is not defined

* feat: remove vite-plugin-vue-setup-extend

* chore: remove unplugin-vue-define-options

* fix(component): pageWrapper

use setup

closed #2898

* refactor: PageFooter use setup

---------

Co-authored-by: jinmao88 <50581550+jinmao88@users.noreply.github.com>
2023-07-06 15:00:38 +08:00
胤玄
c659c14c5a Docker dynamic publish support. (#2903)
* feat: Docker support, including dynamic publish

* fix:  run container command
2023-07-06 13:55:41 +08:00
LanceJiang
5ad5c8cdc7 fix: 类似 /comp/table/formTable页面 rowSelection.selectedRowKeys 数据双向绑定时 clickToRowSelect RowClick 触发失效 (#2893) 2023-06-29 21:04:06 +08:00
Li Kui
27cb958c2e fix: Cropper typo (#2891) 2023-06-28 16:52:33 +08:00
前端爱码士
155ad45848 fix: 修复ts-config未匹配上的文件 (#2880) 2023-06-21 22:41:06 +08:00
Kirk Lin
7535db377f fix(utils): deepMerge failing to correctly merge basic data types (#2872) 2023-06-19 16:59:44 +08:00
jinmao88
a0fdceeae7 fix: 修复边栏无法打开问题,deepMerge函数有问题未修复 2023-06-17 09:44:06 +08:00
jinmao88
c6e5c0f5f1 perf: 组件修改为setup 2023-06-17 09:42:42 +08:00
jinmao88
ca997c15ca fix: 默认不清除ts内console.log() 2023-06-17 08:45:58 +08:00
tawen
4c381596a9 fix: 可编辑单元格editCellTable,当值为空时无法进行编辑 (#2867)
* fix: 修复 Cannot access 'pagewrapper' before initialization

* fix: 修复 角色管理 编辑角色时,角色状态不正确

* fix: 可编辑单元格editCellTable,当值为空时无法进行编辑

* fix: 可编辑单元格editCellTable,当值为空时无法进行编辑
2023-06-16 07:40:35 +08:00
tawen
7bcdb46148 fix: 修复当设置apiselect的immediate为false时,默认赋值后取值不正确传入默认options方案 (#2862) 2023-06-14 06:09:03 +08:00
tawen
d33ccd042f fix: 修复 角色管理 编辑角色时,角色状态不正确 (#2861)
* fix: 修复 Cannot access 'pagewrapper' before initialization

* fix: 修复 角色管理 编辑角色时,角色状态不正确
2023-06-14 06:08:51 +08:00
zty
08f479f3e1 RoleDrawe.vue 改造为setup,角色编辑时状态不正确 (#2857)
* chore: 改造为setup

* fix: 状态不一致
2023-06-14 06:08:39 +08:00
zuihou
c054d73fe6 feat: 保持 和 windi 一样的全局样式,减少升级带来的影响
close 2814
close 2818

https://github.com/vbenjs/vue-vben-admin/issues/2814
https://github.com/vbenjs/vue-vben-admin/issues/2818
2023-06-12 10:44:20 +08:00
Kirk Lin
1f287145f4 fix(deepMerge): 去掉合并错误的代码 (#2848) 2023-06-10 17:19:18 +08:00
Kirk Lin
9c43c74131 Revert "fix(deepMerge): fix recursive merge data without removing duplicate bugs (#2831)" (#2844)
This reverts commit 7ca007ecd5.

Co-authored-by: jinmao88 <50581550+jinmao88@users.noreply.github.com>
2023-06-10 09:23:39 +08:00
Kirk Lin
c516d39225 fix(deepMerge): the default merge strategy is to replace the array (#2843) 2023-06-10 09:21:20 +08:00
Norton
a1283c1322 fix:关闭其它页签需要使用fullPath来判断,更新缓存后需要同时更新localStorage,否则刷新页面页签会再次加载 (#2847)
* fixed:修复操作页签后 打开页面需要使用fullPath,否则可能会导致参数丢失引起数据加载异常。

* fix:关闭其它页签需要使用fullPath来判断,更新缓存后需要同时更新localStorage,否则刷新页面页签会再次加载

---------

Co-authored-by: lichi <lichi@ilinecn.com>
2023-06-10 09:12:20 +08:00
tawen
cc88e1a66c fix: 修复 Cannot access 'pagewrapper' before initialization (#2835) 2023-06-07 08:42:39 +08:00
jinmao88
6aa3f934d0 Revert "perf: 优化modal弹窗样式 (#2824)" (#2834)
This reverts commit 1e8fab3fe5.
2023-06-06 16:30:01 +08:00
luojz
7ca007ecd5 fix(deepMerge): fix recursive merge data without removing duplicate bugs (#2831)
Co-authored-by: luojingzhou <luojingzhou@kezaihui.com>
2023-06-06 14:15:44 +08:00
Kyun Wong
361a189f6a fix: 修复未传参(params)的时,redo失效的问题 (#2826)
Co-authored-by: kelvin <kyun.wang@jodoinc.com>
2023-06-05 11:05:10 +08:00
前端爱码士
19fd49e22d perf: 优化tree getIcon方法类型定义 (#2825) 2023-06-05 11:04:56 +08:00
前端爱码士
1e8fab3fe5 perf: 优化modal弹窗样式 (#2824) 2023-06-05 11:04:37 +08:00
Gary
7e0456cc6c 删除appConfigSrc多余符号 (#2813) 2023-05-31 19:39:40 +08:00
HUCHAOQI
c118e83a2b 修复axios中responseInterceptorsCatch的类型 (#2811)
* fix(table): 使用lodash 的merge来递归assign,优化在多对象嵌套情况下的结构

* fix(view): 修复登入页面点击其他登入方式后返回时视图异常的bug

* fix(util): 修复类型错误
2023-05-30 10:14:02 +08:00
Peng Peidong
31d44ad372 fix(api/demo): fix typo (#2808) 2023-05-30 10:13:53 +08:00
GauharChan
f810a0892d ci(fix): 修复更新lock文件导致github action失败 (#2802) 2023-05-25 14:18:06 +08:00
GauharChan
5de89b5ec5 perf(demo): 动态更换表格配置 (#2793) 2023-05-24 16:42:39 +08:00
xiaoWangSec
0347c83620 fix: 修复VITE_GLOB_APP_TITLE中不能存在-的问题#1522 (#2794)
* fix #1522

fix #1522

* fix #1522

fix #1522

* fix #1522

fix #1522
2023-05-24 16:42:30 +08:00
HUCHAOQI
eb0fdb2cfc fix(component): 修复keep-alive组件包裹的文件onActivited不生效 (#2785)
* fix(table): 使用lodash 的merge来递归assign,优化在多对象嵌套情况下的结构

* fix(component): 修复keep-alive组件包裹的文件onActivited不生效
2023-05-22 10:10:57 +08:00
HUCHAOQI
e154d1366c Fix/views login (#2789)
* fix(table): 使用lodash 的merge来递归assign,优化在多对象嵌套情况下的结构

* fix(view): 修复登入页面点击其他登入方式后返回时视图异常的bug
2023-05-22 10:10:37 +08:00
GauharChan
34237ef033 workflow: fix github action build error (#2773)
* workflow: fix github action build error

* workflow: pnpm/action-setup 不指定具体版本号
2023-05-19 17:08:57 +08:00
LanceJiang
b13c4a81fc fix: 由于 多路径字段获取值 (#2664) 处理 造成的 set value值为0||''时不成功的情况 (#2776) 2023-05-19 16:35:58 +08:00
GauharChan
c46b04d548 workflow: fix github action build error (#2752) 2023-05-10 13:59:53 +08:00
Kyun Wong
afacf68825 fix: 解决重定向路径 params 参数丢失问题 (#2753)
Co-authored-by: kelvin <kyun.wang@jodoinc.com>
2023-05-10 13:59:36 +08:00
Norton
60a3b6a9f9 fixed:修复操作页签后 打开页面需要使用fullPath,否则可能会导致参数丢失引起数据加载异常。 (#2768)
Co-authored-by: lichi <lichi@ilinecn.com>
2023-05-10 13:59:06 +08:00
GauharChan
b97d588392 refactor: 完善ColumnSetting的操作逻辑 (#2745) 2023-04-30 22:52:42 +08:00
前端爱码士
6e716c5607 fix: 解决打包报内存溢出问题 (#2697) 2023-04-10 16:57:49 +08:00
228 changed files with 7811 additions and 3410 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
dist/
.vscode/

22
.env.docker Normal file
View File

@@ -0,0 +1,22 @@
# Whether to open mock
VITE_USE_MOCK = false
# public path
VITE_PUBLIC_PATH = /
# timeout(seconds)
VITE_TIMEOUT = 15
# Delete console
VITE_DROP_CONSOLE = true
# Whether to enable gzip or brotli compression
# Optional: gzip | brotli | none
# If you need multiple forms, you can use `,` to separate
VITE_BUILD_COMPRESS = 'none'
VITE_GLOB_API_URL="__vg_base_url"
# File upload address optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/files/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

View File

@@ -1,4 +1,7 @@
module.exports = {
root: true,
extends: ['@vben'],
rules: {
'no-undef': 'off',
},
};

View File

@@ -60,6 +60,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
# - uses: NullVoxPopuli/action-setup-pnpm@v2
- name: Sed Config Base
shell: bash
run: |
@@ -67,22 +69,28 @@ jobs:
sed -i "s#VITE_DROP_CONSOLE\s*=.*#VITE_DROP_CONSOLE = true#g" ./.env.production
cat ./.env.production
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: use Node.js 16
uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '20.x'
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
# - name: Get yarn cache directory path
# id: yarn-cache-dir-path
# run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
#
# - name: Cache dependencies
# uses: actions/cache@v3
# with:
# path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
# key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
# restore-keys: |
# ${{ runner.os }}-yarn-
- name: Set SSH Environment
env:
@@ -100,8 +108,8 @@ jobs:
env:
NODE_OPTIONS: '--max_old_space_size=4096'
run: |
yarn install
yarn run build
pnpm install --no-frozen-lockfile
pnpm build
touch dist/.nojekyll
cp dist/index.html dist/404.html

36
.github/workflows/node.js.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [main, thin]
pull_request:
branches: [main, thin]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build

View File

@@ -8,3 +8,5 @@ node_modules
public
.npmrc
*-lock.yaml

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
# node 构建
FROM node:16-alpine as build-stage
# 署名
MAINTAINER Adoin 'adoin@qq.com'
WORKDIR /app
COPY . ./
# 设置 node 阿里镜像
RUN npm config set registry https://registry.npm.taobao.org
# 设置--max-old-space-size
ENV NODE_OPTIONS=--max-old-space-size=16384
# 设置阿里镜像、pnpm、依赖、编译
RUN npm install pnpm -g && \
pnpm install --frozen-lockfile && \
pnpm build:docker
# node部分结束
RUN echo "🎉 编 🎉 译 🎉 成 🎉 功 🎉"
# nginx 部署
FROM nginx:1.23.3-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html/dist
COPY --from=build-stage /app/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
## 将/usr/share/nginx/html/dist/assets/index.js 和/usr/share/nginx/html/dist/_app.config.js中的"$vg_base_url"替换为环境变量中的VG_BASE_URL,$vg_sub_domain 替换成VG_SUB_DOMAIN$vg_default_user替换成VG_DEFAULT_USER$vg_default_password替换成VG_DEFAULT_PASSWORD 而后启动nginx
CMD sed -i "s|__vg_base_url|$VG_BASE_URL|g" /usr/share/nginx/html/dist/assets/index.js && \
sed -i "s|__vg_base_url|$VG_BASE_URL|g" /usr/share/nginx/html/dist/_app.config.js && \
nginx -g 'daemon off;'
RUN echo "🎉 架 🎉 设 🎉 成 🎉 功 🎉"

View File

@@ -9,7 +9,7 @@
## Introduction
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite2`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite4`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
## Feature
@@ -54,7 +54,7 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript`
- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax
- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui basic use
- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui basic use
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax
## Install and use
@@ -86,6 +86,24 @@ pnpm serve
pnpm build
```
- docker
### The dockerFile is located in the project root directory and supports differential deployment
#### build image
```bash
docker build -t vue-vben-admin .
```
#### Environment variables are dynamically used to achieve differentiated container deployment. Different VG_BASE_URL environment variables point to different back-end service addresses. In the following example, http://localhost:3333 is used as the back-end service address and the container is mapped to port 6666
```bash
docker run --name vue-vben-admin -d -p 6666:80 -e VG_BASE_URL=http://localhost:3333 vue-vben-admin
```
Then you can navigate http://localhost:6666
## Change Log
[CHANGELOG](./CHANGELOG.zh_CN.md)

View File

@@ -9,7 +9,7 @@
## 简介
Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite4`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
## 特性
@@ -54,7 +54,7 @@ Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3
- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui 基本使用
- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装使用
@@ -86,6 +86,24 @@ pnpm serve
pnpm build
```
- docker
### dockerFile 位于项目根目录下 并且支持差异化部署
#### 构建镜像
```bash
docker build -t vue-vben-admin .
```
#### 动态使用环境变量实现容器差异化部署,通过不同的 VG_BASE_URL 环境变量,指向不同的后端服务地址,下面例子使用 http://localhost:3333 作为后端服务地址,并且将容器映射到 6666 端口
```bash
docker run --name vue-vben-admin -d -p 6666:80 -e VG_BASE_URL=http://localhost:3333 vue-vben-admin
```
而后可以打开 http://localhost:6666 访问
## 更新日志
[CHANGELOG](./CHANGELOG.zh_CN.md)

View File

@@ -11,9 +11,9 @@
},
"dependencies": {
"fs-extra": "^11.1.1",
"koa": "^2.14.1",
"koa": "^2.14.2",
"koa-body": "^6.0.1",
"koa-bodyparser": "^4.4.0",
"koa-bodyparser": "^4.4.1",
"koa-route": "^3.2.0",
"koa-router": "^12.0.0",
"koa-static": "^5.0.0",
@@ -24,13 +24,13 @@
"@types/koa": "^2.13.6",
"@types/koa-bodyparser": "^5.0.2",
"@types/koa-router": "^7.4.4",
"@types/node": "^18.15.11",
"@types/node": "^20.4.0",
"nodemon": "^2.0.22",
"pm2": "^5.3.0",
"rimraf": "^4.4.1",
"rimraf": "^5.0.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tsup": "^6.7.0",
"typescript": "^5.0.3"
"tsup": "^7.1.0",
"typescript": "^5.1.6"
}
}

View File

@@ -36,14 +36,14 @@
"stub": "pnpm unbuild --stub"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.10.0",
"vue-eslint-parser": "^9.1.1"
"eslint-plugin-vue": "^9.17.0",
"vue-eslint-parser": "^9.3.1"
}
}

View File

@@ -31,18 +31,18 @@
"stub": "pnpm unbuild --stub"
},
"devDependencies": {
"postcss": "^8.4.21",
"postcss": "^8.4.24",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.7",
"stylelint": "^15.4.0",
"prettier": "^2.8.8",
"stylelint": "^15.10.1",
"stylelint-config-property-sort-order-smacss": "^9.1.0",
"stylelint-config-recommended": "^11.0.0",
"stylelint-config-recommended-scss": "^9.0.1",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-recommended-scss": "^12.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^32.0.0",
"stylelint-config-standard-scss": "^7.0.1",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^10.0.0",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^3.0.0"
}

View File

@@ -22,6 +22,7 @@ export default {
},
],
rules: {
'media-feature-range-notation': null,
'selector-not-notation': null,
'import-notation': null,
'function-no-unknown': null,

View File

@@ -15,12 +15,11 @@
"files": [
"base.json",
"node.json",
"vue.json",
"vue-app.json",
"node-server.json"
],
"dependencies": {
"@types/node": "^18.15.11",
"unplugin-vue-define-options": "^1.3.3",
"vite": "^4.3.0-beta.2"
"@types/node": "^20.4.0",
"vite": "^4.4.0"
}
}

View File

@@ -6,6 +6,5 @@
"jsx": "preserve",
"lib": ["ESNext", "DOM"],
"noImplicitAny": false
}
}

View File

@@ -32,25 +32,24 @@
},
"dependencies": {
"@ant-design/colors": "^7.0.0",
"vite": "^4.3.0-beta.2"
"vite": "^4.4.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@vitejs/plugin-vue": "^4.1.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"ant-design-vue": "^3.2.17",
"dayjs": "^1.11.7",
"dotenv": "^16.0.3",
"ant-design-vue": "^3.2.20",
"dayjs": "^1.11.9",
"dotenv": "^16.3.1",
"fs-extra": "^11.1.1",
"less": "^4.1.3",
"picocolors": "^1.0.0",
"pkg-types": "^1.0.2",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.60.0",
"unocss": "^0.50.6",
"unplugin-vue-define-options": "^1.3.3",
"pkg-types": "^1.0.3",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"unocss": "^0.53.4",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-dts": "^2.2.0",
"vite-plugin-dts": "^3.1.0",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.9.2",

View File

@@ -21,7 +21,10 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
return defineConfig(async ({ command, mode }) => {
const root = process.cwd();
const isBuild = command === 'build';
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_ENABLE_ANALYZE } = loadEnv(mode, root);
const { VITE_PUBLIC_PATH, VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_ENABLE_ANALYZE } = loadEnv(
mode,
root,
);
const defineData = await createDefineData(root);
const plugins = await createPlugins({
@@ -35,6 +38,7 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
const pathResolve = (pathname: string) => resolve(root, '.', pathname);
const applicationConfig: UserConfig = {
base: VITE_PUBLIC_PATH,
resolve: {
alias: [
{
@@ -69,6 +73,8 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
cssTarget: 'chrome80',
rollupOptions: {
output: {
// 入口文件名
entryFileNames: 'assets/[name].js',
manualChunks: {
vue: ['vue', 'pinia', 'vue-router'],
antd: ['ant-design-vue', '@ant-design/icons-vue'],
@@ -87,7 +93,7 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
plugins,
};
const mergedConfig = mergeConfig(commonConfig, applicationConfig);
const mergedConfig = mergeConfig(commonConfig(mode), applicationConfig);
return mergeConfig(mergedConfig, overrides);
});

View File

@@ -2,12 +2,12 @@ import { presetTypography, presetUno } from 'unocss';
import UnoCSS from 'unocss/vite';
import { type UserConfig } from 'vite';
const commonConfig: UserConfig = {
const commonConfig: (mode: string) => UserConfig = (mode) => ({
server: {
host: true,
},
esbuild: {
drop: ['console', 'debugger'],
drop: mode === 'production' ? ['console', 'debugger'] : [],
},
build: {
reportCompressedSize: false,
@@ -22,6 +22,6 @@ const commonConfig: UserConfig = {
presets: [presetUno(), presetTypography()],
}),
],
};
});
export { commonConfig };

View File

@@ -14,7 +14,7 @@ interface DefineOptions {
function definePackageConfig(defineOptions: DefineOptions = {}) {
const { overrides = {} } = defineOptions;
const root = process.cwd();
return defineConfig(async () => {
return defineConfig(async ({ mode }) => {
const { dependencies = {}, peerDependencies = {} } = await readPackageJSON(root);
const packageConfig: UserConfig = {
build: {
@@ -33,7 +33,7 @@ function definePackageConfig(defineOptions: DefineOptions = {}) {
}),
],
};
const mergedConfig = mergeConfig(commonConfig, packageConfig);
const mergedConfig = mergeConfig(commonConfig(mode), packageConfig);
return mergeConfig(mergedConfig, overrides);
});

View File

@@ -27,8 +27,8 @@ async function createAppConfigPlugin({
return {
name: PLUGIN_NAME,
async configResolved(_config) {
let appTitle = _config?.env?.VITE_GLOB_APP_TITLE ?? '';
appTitle = appTitle.replace(/\s/g, '_');
const appTitle = _config?.env?.VITE_GLOB_APP_TITLE ?? '';
// appTitle = appTitle.replace(/\s/g, '_').replace(/-/g, '_');
publicPath = _config.base;
source = await getConfigSource(appTitle);
},
@@ -37,7 +37,7 @@ async function createAppConfigPlugin({
const appConfigSrc = `${
publicPath || '/'
}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}}`;
}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}`;
return {
html,
@@ -74,7 +74,15 @@ async function createAppConfigPlugin({
* @param env
*/
const getVariableName = (title: string) => {
return `__PRODUCTION__${title || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
function strToHex(str: string) {
const result: string[] = [];
for (let i = 0; i < str.length; ++i) {
const hex = str.charCodeAt(i).toString(16);
result.push(('000' + hex).slice(-4));
}
return result.join('').toUpperCase();
}
return `__PRODUCTION__${strToHex(title) || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
};
async function getConfigSource(appTitle: string) {

View File

@@ -1,7 +1,5 @@
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
// @ts-ignore: type unless
import DefineOptions from 'unplugin-vue-define-options/vite';
import { type PluginOption } from 'vite';
import purgeIcons from 'vite-plugin-purge-icons';
@@ -21,7 +19,7 @@ interface Options {
}
async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyze }: Options) {
const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx(), DefineOptions()];
const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx()];
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild });
vitePlugins.push(appConfigPlugin);

View File

@@ -16,14 +16,14 @@ import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
// return pre;
// }, [] as any[]);
const modules = import.meta.globEager('./**/*.ts');
const modules = import.meta.glob('./**/*.ts', { eager: true });
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...modules[key].default);
mockModules.push(...(modules as Recordable)[key].default);
});
/**

38
nginx.conf Normal file
View File

@@ -0,0 +1,38 @@
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
location / {
root /usr/share/nginx/html/dist;
try_files $uri $uri/ /index.html;
index index.html;
# Enable CORS
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_filename ~* ^.*?.(html|htm|js)$) {
add_header Cache-Control no-cache;
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin",
"version": "2.10.0",
"version": "2.10.1",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": {
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
@@ -17,10 +17,11 @@
},
"scripts": {
"bootstrap": "pnpm install",
"build": "cross-env NODE_ENV=production pnpm vite build",
"build:analyze": "pnpm vite build --mode analyze",
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build",
"build:analyze": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode analyze",
"build:docker": "vite build --mode docker",
"build:no-cache": "pnpm clean:cache && npm run build",
"build:test": "pnpm vite build --mode test",
"build:test": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode test",
"commit": "czg",
"dev": "pnpm vite",
"preinstall": "npx only-allow pnpm",
@@ -28,7 +29,7 @@
"lint": "turbo run lint",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write .",
"lint:stylelint": "stylelint \"**/*.{vue,css,less.scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/",
"lint:stylelint": "stylelint \"**/*.{vue,css,less,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/",
"prepare": "husky install",
"preview": "npm run build && vite preview",
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
@@ -67,20 +68,20 @@
},
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"@iconify/iconify": "^3.1.0",
"@logicflow/core": "^1.2.1",
"@logicflow/extension": "^1.2.1",
"@iconify/iconify": "^3.1.1",
"@logicflow/core": "^1.2.9",
"@logicflow/extension": "^1.2.9",
"@vben/hooks": "workspace:*",
"@vue/shared": "^3.2.47",
"@vueuse/core": "^9.13.0",
"@vueuse/shared": "^9.13.0",
"@zxcvbn-ts/core": "^2.2.1",
"ant-design-vue": "^3.2.17",
"axios": "^1.3.5",
"@vue/shared": "^3.3.4",
"@vueuse/core": "^10.2.1",
"@vueuse/shared": "^10.2.1",
"@zxcvbn-ts/core": "^3.0.2",
"ant-design-vue": "^4.0.2",
"axios": "^1.4.0",
"codemirror": "^5.65.12",
"cropperjs": "^1.5.13",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.7",
"dayjs": "^1.11.9",
"echarts": "^5.4.2",
"exceljs": "^4.3.0",
"intro.js": "^7.0.1",
@@ -88,63 +89,62 @@
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.1",
"pinia": "2.0.33",
"pinia": "2.1.4",
"print-js": "^1.6.0",
"qrcode": "^1.5.1",
"qs": "^6.11.1",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"resize-observer-polyfill": "^1.5.1",
"showdown": "^2.1.0",
"sortablejs": "^1.15.0",
"tinymce": "^5.10.7",
"vditor": "^3.9.1",
"vue": "^3.2.47",
"vditor": "^3.9.4",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vue-json-pretty": "^2.2.4",
"vue-router": "^4.1.6",
"vue-types": "^5.0.2",
"vue-router": "^4.2.3",
"vue-types": "^5.1.0",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.3.11",
"vxe-table": "^4.4.5",
"vxe-table-plugin-export-xlsx": "^3.0.4",
"xe-utils": "^3.5.7",
"xe-utils": "^3.5.11",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@commitlint/cli": "^17.5.1",
"@commitlint/config-conventional": "^17.4.4",
"@iconify/json": "^2.2.46",
"@commitlint/cli": "^17.6.6",
"@commitlint/config-conventional": "^17.6.6",
"@iconify/json": "^2.2.87",
"@purge-icons/generated": "^0.9.0",
"@types/codemirror": "^5.60.7",
"@types/codemirror": "^5.60.8",
"@types/crypto-js": "^4.1.1",
"@types/intro.js": "^5.1.1",
"@types/lodash-es": "^4.17.7",
"@types/mockjs": "^1.0.7",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.0",
"@types/qrcode": "^1.5.1",
"@types/qs": "^6.9.7",
"@types/showdown": "^2.0.0",
"@types/showdown": "^2.0.1",
"@types/sortablejs": "^1.15.1",
"@vben/eslint-config": "workspace:*",
"@vben/stylelint-config": "workspace:*",
"@vben/ts-config": "workspace:*",
"@vben/types": "workspace:*",
"@vben/vite-config": "workspace:*",
"@vue/compiler-sfc": "^3.2.47",
"@vue/test-utils": "^2.3.2",
"@vue/compiler-sfc": "^3.3.4",
"@vue/test-utils": "^2.4.0",
"cross-env": "^7.0.3",
"cz-git": "^1.6.1",
"czg": "^1.6.1",
"husky": "^8.0.3",
"lint-staged": "13.2.0",
"prettier": "^2.8.7",
"prettier-plugin-packagejson": "^2.4.3",
"rimraf": "^4.4.1",
"turbo": "^1.8.8",
"typescript": "^5.0.3",
"unbuild": "^1.2.0",
"unplugin-vue-define-options": "^1.3.3",
"vite": "^4.3.0-beta.2",
"lint-staged": "13.2.3",
"prettier": "^2.8.8",
"prettier-plugin-packagejson": "^2.4.4",
"rimraf": "^5.0.1",
"turbo": "^1.10.7",
"typescript": "^5.1.6",
"unbuild": "^1.2.1",
"vite": "^4.4.0",
"vite-plugin-mock": "^2.9.6",
"vue-tsc": "^1.2.0"
"vue-tsc": "^1.8.4"
},
"packageManager": "pnpm@8.1.0",
"engines": {

View File

@@ -29,8 +29,8 @@
"lint": "pnpm eslint ."
},
"dependencies": {
"@vueuse/core": "^9.13.0",
"vue": "^3.2.47"
"@vueuse/core": "^10.2.1",
"vue": "^3.3.4"
},
"devDependencies": {
"@vben/types": "workspace:*"

View File

@@ -1,18 +1,18 @@
import type { Ref } from 'vue';
import type { ComponentPublicInstance, Ref } from 'vue';
import { onBeforeUpdate, shallowRef } from 'vue';
function useRefs(): {
refs: Ref<HTMLElement[]>;
setRefs: (index: number) => (el: HTMLElement) => void;
function useRefs<T = HTMLElement>(): {
refs: Ref<T[]>;
setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void;
} {
const refs = shallowRef([]) as Ref<HTMLElement[]>;
const refs = shallowRef([]) as Ref<T[]>;
onBeforeUpdate(() => {
refs.value = [];
});
const setRefs = (index: number) => (el: HTMLElement) => {
refs.value[index] = el;
const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => {
refs.value[index] = el as T;
};
return {

5701
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<template>
<ConfigProvider :locale="getAntdLocale">
<ConfigProvider :locale="getAntdLocale" :theme="isDark ? darkTheme : {}">
<AppProvider>
<RouterView />
</AppProvider>
@@ -11,11 +11,15 @@
import { AppProvider } from '@/components/Application';
import { useTitle } from '@/hooks/web/useTitle';
import { useLocale } from '@/locales/useLocale';
import 'dayjs/locale/zh-cn';
import { useDarkModeTheme } from '@/hooks/setting/useDarkModeTheme';
// support Multi-language
const { getAntdLocale } = useLocale();
const { isDark, darkTheme } = useDarkModeTheme();
// Listening to page changes and dynamically changing site titles
useTitle();
</script>

View File

@@ -1,8 +1,8 @@
import { BasicFetchResult } from '/@/api/model/baseModel';
export interface DemoOptionsItem {
label: string;
value: string;
name: string;
id: string;
}
export interface selectParams {

View File

@@ -17,7 +17,7 @@ export interface RoleInfo {
export interface LoginResultModel {
userId: string | number;
token: string;
role: RoleInfo;
roles: RoleInfo[];
}
/**

View File

@@ -4,7 +4,7 @@ import appLogo from './src/AppLogo.vue';
import appProvider from './src/AppProvider.vue';
import appSearch from './src/search/AppSearch.vue';
import appLocalePicker from './src/AppLocalePicker.vue';
// import appDarkModeToggle from './src/AppDarkModeToggle.vue';
import appDarkModeToggle from './src/AppDarkModeToggle.vue';
export { useAppProviderContext } from './src/useAppContext';
@@ -12,4 +12,4 @@ export const AppLogo = withInstall(appLogo);
export const AppProvider = withInstall(appProvider);
export const AppSearch = withInstall(appSearch);
export const AppLocalePicker = withInstall(appLocalePicker);
// export const AppDarkModeToggle = withInstall(appDarkModeToggle);
export const AppDarkModeToggle = withInstall(appDarkModeToggle);

View File

@@ -22,5 +22,5 @@ export const buttonProps = {
* @default: 14
*/
iconSize: { type: Number, default: 14 },
onClick: { type: Function as PropType<(...args) => any>, default: null },
onClick: { type: [Function, Array] as PropType<(() => any) | (() => any)[]>, default: null },
};

View File

@@ -61,7 +61,7 @@
<CardMeta>
<template #title>
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
<TypographyParagraph :content="item.name" :ellipsis="{ tooltip: item.address }" />
</template>
<template #avatar>
<Avatar :src="item.avatar" />
@@ -93,7 +93,7 @@
const ListItem = List.Item;
const CardMeta = Card.Meta;
const TypographyText = Typography.Text;
const TypographyParagraph = Typography.Paragraph;
// 获取slider属性
const sliderProp = computed(() => useSlider(4));
// 组件接收参数

View File

@@ -59,12 +59,12 @@
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
return {
...styles,
position: 'absolute',
width: `${width}px`,
left: `${left + 1}px`,
top: `${top + 1}px`,
zIndex: 9999,
...styles, // Not the first, fix options.styles.width not working
};
});
@@ -127,11 +127,15 @@
}
const { items } = props;
return (
<div class={prefixCls}>
<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
<Menu
inlineIndent={12}
mode="vertical"
class={prefixCls}
ref={wrapRef}
style={unref(getStyle)}
>
{renderMenuItem(items)}
</Menu>
</div>
);
};
},
@@ -147,7 +151,7 @@
.item-style() {
li {
display: inline-block;
width: 100%;
width: 100% !important;
height: @default-height;
margin: 0 !important;
line-height: @default-height;
@@ -157,6 +161,8 @@
}
> div {
width: 100% !important;
height: 100% !important;
margin: 0 !important;
}
@@ -176,10 +182,12 @@
width: 156px;
margin: 0;
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 0.25rem;
border-radius: 8px;
background-clip: padding-box;
background-color: @component-background;
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 10%),
box-shadow:
0 2px 2px 0 rgb(0 0 0 / 14%),
0 3px 1px -2px rgb(0 0 0 / 10%),
0 1px 5px 0 rgb(0 0 0 / 6%);
list-style: none;
user-select: none;

View File

@@ -20,11 +20,12 @@
{{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button>
<CopperModal
<CropperModal
@register="register"
@upload-success="handleUploadSuccess"
:uploadApi="uploadApi"
:src="sourceValue"
:size="size"
/>
</div>
</template>
@@ -39,7 +40,7 @@
watch,
PropType,
} from 'vue';
import CopperModal from './CopperModal.vue';
import CropperModal from './CropperModal.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
@@ -54,11 +55,12 @@
btnProps: { type: Object as PropType<ButtonProps> },
btnText: { type: String, default: '' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
size: { type: Number, default: 5 },
};
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal, Icon },
components: { CropperModal, Icon },
props,
emits: ['update:value', 'change'],
setup(props, { emit, expose }) {

View File

@@ -130,13 +130,14 @@
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
},
src: { type: String },
size: { type: Number },
};
export default defineComponent({
name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
emits: ['uploadSuccess', 'uploadError', 'register'],
setup(props, { emit }) {
let filename = '';
const src = ref(props.src || '');
@@ -151,6 +152,10 @@
// Block upload
function handleBeforeUpload(file: File) {
if (props.size && file.size > 1024 * 1024 * props.size) {
emit('uploadError', { msg: t('component.cropper.imageTooBig') });
return;
}
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';

View File

@@ -1,5 +1,5 @@
<template>
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
<Drawer @close="onClose" v-bind="getBindValues">
<template #title v-if="!$slots.title">
<DrawerHeader
:title="getMergeProps.title"
@@ -58,9 +58,9 @@
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
inheritAttrs: false,
props: basicProps,
emits: ['visible-change', 'ok', 'close', 'register'],
emits: ['open-change', 'ok', 'close', 'register'],
setup(props, { emit }) {
const visibleRef = ref(false);
const openRef = ref(false);
const attrs = useAttrs();
const propsRef = ref<Partial<DrawerProps | null>>(null);
@@ -69,7 +69,7 @@
const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps as any,
emitVisible: undefined,
emitOpen: undefined,
};
const instance = getCurrentInstance();
@@ -85,7 +85,7 @@
placement: 'right',
...unref(attrs),
...unref(getMergeProps),
visible: unref(visibleRef),
open: unref(openRef),
};
opt.title = undefined;
const { isDetail, width, wrapClassName, getContainer } = opt;
@@ -94,7 +94,7 @@
opt.width = '100%';
}
const detailCls = `${prefixCls}__detail`;
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
opt.rootClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
if (!getContainer) {
// TODO type error?
@@ -135,19 +135,19 @@
});
watch(
() => props.visible,
() => props.open,
(newVal, oldVal) => {
if (newVal !== oldVal) visibleRef.value = newVal;
if (newVal !== oldVal) openRef.value = newVal;
},
{ deep: true },
);
watch(
() => visibleRef.value,
(visible) => {
() => openRef.value,
(open) => {
nextTick(() => {
emit('visible-change', visible);
instance && drawerInstance.emitVisible?.(visible, instance.uid);
emit('open-change', open);
instance && drawerInstance.emitOpen?.(open, instance.uid);
});
},
);
@@ -158,18 +158,18 @@
emit('close', e);
if (closeFunc && isFunction(closeFunc)) {
const res = await closeFunc();
visibleRef.value = !res;
openRef.value = !res;
return;
}
visibleRef.value = false;
openRef.value = false;
}
function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
if (Reflect.has(props, 'open')) {
openRef.value = !!props.open;
}
}

View File

@@ -30,7 +30,7 @@ export const basicProps = {
title: { type: String, default: '' },
loadingText: { type: String },
showDetailBack: { type: Boolean, default: true },
visible: { type: Boolean },
open: { type: Boolean },
loading: { type: Boolean },
maskClosable: { type: Boolean, default: true },
getContainer: {

View File

@@ -3,23 +3,23 @@ import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
import type { ScrollContainerOptions } from '/@/components/Container/index';
export interface DrawerInstance {
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
emitVisible?: (visible: boolean, uid: number) => void;
setDrawerProps: (props: Partial<DrawerProps>) => void;
emitOpen?: (open: boolean, uid: number) => void;
}
export interface ReturnMethods extends DrawerInstance {
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
openDrawer: <T = any>(open?: boolean, data?: T, openOnSet?: boolean) => void;
closeDrawer: () => void;
getVisible?: ComputedRef<boolean>;
getOpen?: ComputedRef<boolean>;
}
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
export type RegisterFn = (drawerInstance: DrawerInstance, uuid: number) => void;
export interface ReturnInnerMethods extends DrawerInstance {
closeDrawer: () => void;
changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void;
getVisible?: ComputedRef<boolean>;
getOpen?: ComputedRef<boolean>;
}
export type UseDrawerReturnType = [RegisterFn, ReturnMethods];
@@ -73,7 +73,7 @@ export interface DrawerProps extends DrawerFooterProps {
isDetail?: boolean;
loading?: boolean;
showDetailBack?: boolean;
visible?: boolean;
open?: boolean;
/**
* Built-in ScrollContainer component configuration
* @type ScrollContainerOptions
@@ -100,7 +100,7 @@ export interface DrawerProps extends DrawerFooterProps {
* @default 'body'
* @type any ( HTMLElement| () => HTMLElement | string)
*/
getContainer?: () => HTMLElement | string;
getContainer?: string | false | HTMLElement | (() => HTMLElement);
/**
* Whether to show mask or not.
@@ -134,6 +134,7 @@ export interface DrawerProps extends DrawerFooterProps {
*/
wrapClassName?: string;
class?: string;
rootClassName?: string;
/**
* Style of wrapper element which **contains mask** compare to `drawerStyle`
* @type object
@@ -179,7 +180,7 @@ export interface DrawerProps extends DrawerFooterProps {
* @type string
*/
placement?: 'top' | 'right' | 'bottom' | 'left';
afterVisibleChange?: (visible?: boolean) => void;
afterOpenChange?: (open?: boolean) => void;
keyboard?: boolean;
/**
* Specify a callback that will be called when a user clicks mask, close button or Cancel button.

View File

@@ -23,7 +23,7 @@ import { error } from '/@/utils/log';
const dataTransferRef = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});
const openData = reactive<{ [key: number]: boolean }>({});
/**
* @description: Applicable to separate drawer and call outside
@@ -34,9 +34,9 @@ export function useDrawer(): UseDrawerReturnType {
}
const drawer = ref<DrawerInstance | null>(null);
const loaded = ref<Nullable<boolean>>(false);
const uid = ref<string>('');
const uid = ref<number>(0);
function register(drawerInstance: DrawerInstance, uuid: string) {
function register(drawerInstance: DrawerInstance, uuid: number) {
isProdMode() &&
tryOnUnmounted(() => {
drawer.value = null;
@@ -51,8 +51,8 @@ export function useDrawer(): UseDrawerReturnType {
drawer.value = drawerInstance;
loaded.value = true;
drawerInstance.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible;
drawerInstance.emitOpen = (open: boolean, uid: number) => {
openData[uid] = open;
};
}
@@ -69,13 +69,13 @@ export function useDrawer(): UseDrawerReturnType {
getInstance()?.setDrawerProps(props);
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uid)];
getOpen: computed((): boolean => {
return openData[~~unref(uid)];
}),
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
openDrawer: <T = any>(open = true, data?: T, openOnSet = true): void => {
getInstance()?.setDrawerProps({
visible: visible,
open,
});
if (!data) return;
@@ -90,7 +90,7 @@ export function useDrawer(): UseDrawerReturnType {
}
},
closeDrawer: () => {
getInstance()?.setDrawerProps({ visible: false });
getInstance()?.setDrawerProps({ open: false });
},
};
@@ -100,7 +100,7 @@ export function useDrawer(): UseDrawerReturnType {
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
const currentInstance = getCurrentInstance();
const uidRef = ref<string>('');
const uidRef = ref<number>(0);
if (!getCurrentInstance()) {
throw new Error('useDrawerInner() can only be used inside setup() or functional components!');
@@ -115,7 +115,7 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
return instance;
};
const register = (modalInstance: DrawerInstance, uuid: string) => {
const register = (modalInstance: DrawerInstance, uuid: number) => {
isProdMode() &&
tryOnUnmounted(() => {
drawerInstanceRef.value = null;
@@ -145,12 +145,12 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
changeOkLoading: (loading = true) => {
getInstance()?.setDrawerProps({ confirmLoading: loading });
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
getOpen: computed((): boolean => {
return openData[~~unref(uidRef)];
}),
closeDrawer: () => {
getInstance()?.setDrawerProps({ visible: false });
getInstance()?.setDrawerProps({ open: false });
},
setDrawerProps: (props: Partial<DrawerProps>) => {

View File

@@ -14,6 +14,7 @@
<a-popconfirm
v-if="popconfirm && item.popConfirm"
v-bind="getPopConfirmAttrs(item.popConfirm)"
:disabled="item.disabled"
>
<template #icon v-if="item.popConfirm.icon">
<Icon :icon="item.popConfirm.icon" />

View File

@@ -117,15 +117,18 @@
const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
for (const schema of schemas) {
const { defaultValue, component, isHandleDateDefaultValue = true } = schema;
const { defaultValue, component, componentProps,isHandleDateDefaultValue = true } = schema;
// handle date type
if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
const valueFormat =componentProps ? componentProps['valueFormat'] : null;
if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue);
schema.defaultValue = valueFormat
? dateUtil(defaultValue).format(valueFormat)
: dateUtil(defaultValue);
} else {
const def: any[] = [];
defaultValue.forEach((item) => {
def.push(dateUtil(item));
def.push(valueFormat ? dateUtil(item).format(valueFormat) : dateUtil(item));
});
schema.defaultValue = def;
}

View File

@@ -65,6 +65,7 @@ componentMap.set('MonthPicker', DatePicker.MonthPicker);
componentMap.set('RangePicker', DatePicker.RangePicker);
componentMap.set('WeekPicker', DatePicker.WeekPicker);
componentMap.set('TimePicker', TimePicker);
componentMap.set('TimeRangePicker', TimePicker.TimeRangePicker);
componentMap.set('StrengthMeter', StrengthMeter);
componentMap.set('IconPicker', IconPicker);
componentMap.set('InputCountDown', CountdownInput);

View File

@@ -55,6 +55,7 @@
valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(false),
options: propTypes.array.def([]),
},
emits: ['options-change', 'change', 'update:value'],
setup(props, { emit }) {
@@ -71,7 +72,7 @@
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
return unref(options).reduce((prev, next: any) => {
let data = unref(options).reduce((prev, next: any) => {
if (next) {
const value = get(next, valueField);
prev.push({
@@ -82,6 +83,7 @@
}
return prev;
}, [] as OptionsItem[]);
return data.length > 0 ? data : props.options;
});
watchEffect(() => {
@@ -142,6 +144,7 @@
}
function handleChange(_, ...args) {
emit('change', args[0] ? args[0].value : undefined);
emitData.value = args;
}

View File

@@ -1,5 +1,5 @@
<template>
<a-tree-select v-bind="getAttrs" @change="handleChange">
<a-tree-select v-bind="getAttrs" @change="handleChange" :field-names="fieldNames">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
@@ -11,7 +11,16 @@
<script lang="ts">
import { type Recordable } from '@vben/types';
import { type PropType, computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import {
type PropType,
computed,
defineComponent,
watchEffect,
watch,
ref,
onMounted,
unref,
} from 'vue';
import { TreeSelect } from 'ant-design-vue';
import { isArray, isFunction } from '/@/utils/is';
import { get } from 'lodash-es';
@@ -26,6 +35,9 @@
params: { type: Object },
immediate: { type: Boolean, default: true },
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('title'),
valueField: propTypes.string.def('value'),
childrenField: propTypes.string.def('children'),
},
emits: ['options-change', 'change'],
setup(props, { attrs, emit }) {
@@ -38,6 +50,11 @@
...attrs,
};
});
const fieldNames = {
children: props.childrenField,
value: props.valueField,
label: props.labelField,
};
function handleChange(...args) {
emit('change', ...args);
@@ -64,7 +81,7 @@
async function fetch() {
const { api } = props;
if (!api || !isFunction(api)) return;
if (!api || !isFunction(api) || loading.value) return;
loading.value = true;
treeData.value = [];
let result;
@@ -82,7 +99,7 @@
isFirstLoaded.value = true;
emit('options-change', treeData.value);
}
return { getAttrs, loading, handleChange };
return { getAttrs, loading, handleChange, fieldNames };
},
});
</script>

View File

@@ -41,7 +41,6 @@
</template>
<script lang="ts">
import type { ColEx } from '../types/index';
//import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { defineComponent, computed, PropType } from 'vue';
import { Form, Col } from 'ant-design-vue';
import { Button, ButtonProps } from '/@/components/Button';

View File

@@ -159,7 +159,10 @@
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
? rulesMessageJoinLabel
: globalRulesMessageJoinLabel;
const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
const assertLabel = joinLabel ? label : '';
const defaultMsg = component
? createPlaceholderMessage(component) + assertLabel
: assertLabel;
function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg;
@@ -299,7 +302,7 @@
return <Comp {...compAttr} />;
}
const compSlot = isFunction(renderComponentContent)
? { ...renderComponentContent(unref(getValues)) }
? { ...renderComponentContent(unref(getValues), { disabled: unref(getDisable) }) }
: {
default: () => renderComponentContent,
};
@@ -333,7 +336,7 @@
const { itemProps, slot, render, field, suffix, component } = props.schema;
const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
const { colon } = props.formProps;
const opts = { disabled: unref(getDisable) };
if (component === 'Divider') {
return (
<Col span={24}>
@@ -343,9 +346,9 @@
} else {
const getContent = () => {
return slot
? getSlot(slots, slot, unref(getValues))
? getSlot(slots, slot, unref(getValues), opts)
: render
? render(unref(getValues))
? render(unref(getValues), opts)
: renderComponent();
};
@@ -382,8 +385,8 @@
}
return () => {
const { colProps = {}, colSlot, renderColContent, component } = props.schema;
if (!componentMap.has(component)) {
const { colProps = {}, colSlot, renderColContent, component, slot } = props.schema;
if (!componentMap.has(component) && !slot) {
return null;
}
@@ -391,12 +394,13 @@
const realColProps = { ...baseColProps, ...colProps };
const { isIfShow, isShow } = getShow();
const values = unref(getValues);
const opts = { disabled: unref(getDisable) };
const getContent = () => {
return colSlot
? getSlot(slots, colSlot, values)
? getSlot(slots, colSlot, values, opts)
: renderColContent
? renderColContent(values)
? renderColContent(values, opts)
: renderItem();
};

View File

@@ -78,7 +78,6 @@ export const NO_AUTO_LINK_COMPONENTS: ComponentType[] = [
'Upload',
'ApiTransfer',
'ApiTree',
'ApiSelect',
'ApiTreeSelect',
'ApiRadioGroup',
'ApiCascader',

View File

@@ -87,23 +87,31 @@ export function useFormEvents({
Object.keys(formModel).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key);
const isInput = schema?.component && defaultValueComponents.includes(schema.component);
const defaultValue = cloneDeep(defaultValueRef.value[key]);
formModel[key] = isInput ? defaultValue || '' : defaultValue;
const defaultValueObj = schema?.defaultValueObj;
const fieldKeys = Object.keys(defaultValueObj || {});
if (fieldKeys.length) {
fieldKeys.map((field) => {
formModel[field] = defaultValueObj![field];
});
}
formModel[key] = getDefaultValue(schema, defaultValueRef, key);
});
nextTick(() => clearValidate());
emit('reset', toRaw(formModel));
submitOnReset && handleSubmit();
}
// 获取表单fields
const getAllFields = () =>
unref(getSchema)
.map((item) => [...(item.fields || []), item.field])
.flat(1)
.filter(Boolean);
/**
* @description: Set form value
*/
async function setFieldsValue(values: Recordable): Promise<void> {
const fields = unref(getSchema)
.map((item) => item.field)
.filter(Boolean);
const fields = getAllFields();
// key 支持 a.b.c 的嵌套写法
const delimiter = '.';
@@ -113,7 +121,7 @@ export function useFormEvents({
fields.forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key);
let value = get(values, key);
const hasKey = !!get(values, key);
const hasKey = Reflect.has(values, key);
value = handleInputNumberValue(schema?.component, value);
const { componentProps } = schema || {};
@@ -210,21 +218,22 @@ export function useFormEvents({
first = false,
) {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
const addSchemaIds: string[] = Array.isArray(schema)
? schema.map((item) => item.field)
: [schema.field];
if (schemaList.find((item) => addSchemaIds.includes(item.field))) {
error('There are schemas that have already been added');
return;
}
const index = schemaList.findIndex((schema) => schema.field === prefixField);
const _schemaList = isObject(schema) ? [schema as FormSchema] : (schema as FormSchema[]);
if (!prefixField || index === -1 || first) {
first ? schemaList.unshift(..._schemaList) : schemaList.push(..._schemaList);
schemaRef.value = schemaList;
_setDefaultValue(schema);
return;
}
if (index !== -1) {
} else if (index !== -1) {
schemaList.splice(index + 1, 0, ..._schemaList);
}
_setDefaultValue(schema);
schemaRef.value = schemaList;
_setDefaultValue(schema);
}
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
@@ -334,8 +343,14 @@ export function useFormEvents({
return unref(formElRef)?.validateFields(nameList);
}
async function validate(nameList?: NamePath[] | undefined) {
return await unref(formElRef)?.validate(nameList);
async function validate(nameList?: NamePath[] | false | undefined) {
let _nameList: any;
if (nameList === undefined) {
_nameList = getAllFields();
} else {
_nameList = nameList === Array.isArray(nameList) ? nameList : undefined;
}
return await unref(formElRef)?.validate(_nameList);
}
async function clearValidate(name?: string | string[]) {
@@ -385,3 +400,29 @@ export function useFormEvents({
scrollToField,
};
}
function getDefaultValue(
schema: FormSchema | undefined,
defaultValueRef: UseFormActionContext['defaultValueRef'],
key: string,
) {
let defaultValue = cloneDeep(defaultValueRef.value[key]);
const isInput = checkIsInput(schema);
if (isInput) {
return defaultValue || '';
}
if (!defaultValue && schema && checkIsRangeSlider(schema)) {
defaultValue = [0, 0];
}
return defaultValue;
}
function checkIsRangeSlider(schema: FormSchema) {
if (schema.component === 'Slider' && schema.componentProps && schema.componentProps.range) {
return true;
}
}
function checkIsInput(schema?: FormSchema) {
return schema?.component && defaultValueComponents.includes(schema.component);
}

View File

@@ -115,19 +115,37 @@ export function useFormValues({
const [startTimeFormat, endTimeFormat] = Array.isArray(format) ? format : [format, format];
values[startTimeKey] = dateUtil(startTime).format(startTimeFormat);
values[endTimeKey] = dateUtil(endTime).format(endTimeFormat);
values[startTimeKey] = formatTime(startTime, startTimeFormat);
values[endTimeKey] = formatTime(endTime, endTimeFormat);
Reflect.deleteProperty(values, field);
}
return values;
}
function formatTime(time: string, format: string) {
if (format === 'timestamp') {
return dateUtil(time).unix();
} else if (format === 'timestampStartDay') {
return dateUtil(time).startOf('day').unix();
}
return dateUtil(time).format(format);
}
function initDefault() {
const schemas = unref(getSchema);
const obj: Recordable = {};
schemas.forEach((item) => {
const { defaultValue } = item;
const { defaultValue, defaultValueObj } = item;
const fieldKeys = Object.keys(defaultValueObj || {});
if (fieldKeys.length) {
fieldKeys.map((field) => {
obj[field] = defaultValueObj![field];
if (formModel[field] === undefined) {
formModel[field] = defaultValueObj![field];
}
});
}
if (!isNullOrUnDef(defaultValue)) {
obj[item.field] = defaultValue;

View File

@@ -39,7 +39,7 @@ export interface FormActionType {
first?: boolean | undefined,
) => Promise<void>;
validateFields: (nameList?: NamePath[]) => Promise<any>;
validate: (nameList?: NamePath[]) => Promise<any>;
validate: (nameList?: NamePath[] | false) => Promise<any>;
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
}
@@ -123,15 +123,21 @@ export interface FormProps {
transformDateFunc?: (date: any) => string;
colon?: boolean;
}
export type RenderOpts = {
disabled: boolean;
[key: string]: any;
};
export interface FormSchema {
// Field name
field: string;
// Extra Fields name[]
fields?: string[];
// Event name triggered by internal value change, default change
changeEvent?: string;
// Variable name bound to v-model Default value
valueField?: string;
// Label name
label: string | VNode;
label?: string | VNode;
// Auxiliary text
subLabel?: string;
// Help text on the right side of the text
@@ -175,6 +181,9 @@ export interface FormSchema {
// 默认值
defaultValue?: any;
// 额外默认值数组对象
defaultValueObj?: { [key: string]: any };
// 是否自动处理与时间相关组件的默认值
isHandleDateDefaultValue?: boolean;
@@ -188,13 +197,19 @@ export interface FormSchema {
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
// Render the content in the form-item tag
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
render?: (
renderCallbackParams: RenderCallbackParams,
opts: RenderOpts,
) => VNode | VNode[] | string;
// Rendering col content requires outer wrapper form-item
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
renderColContent?: (
renderCallbackParams: RenderCallbackParams,
opts: RenderOpts,
) => VNode | VNode[] | string;
renderComponentContent?:
| ((renderCallbackParams: RenderCallbackParams) => any)
| ((renderCallbackParams: RenderCallbackParams, opts: RenderOpts) => any)
| VNode
| VNode[]
| string;

View File

@@ -106,6 +106,7 @@ export type ComponentType =
| 'RangePicker'
| 'WeekPicker'
| 'TimePicker'
| 'TimeRangePicker'
| 'Switch'
| 'StrengthMeter'
| 'Upload'

View File

@@ -29,8 +29,8 @@
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,
.ant-menu-submenu-active,
.ant-menu-submenu-title:hover {
color: #fff;
background-color: @top-menu-active-bg-color !important;
color: #fff;
}
.ant-menu-item:hover,
@@ -68,7 +68,7 @@
}
.ant-menu-inline.ant-menu-sub {
box-shadow: unset !important;
transition: unset;
box-shadow: unset !important;
}
}

View File

@@ -34,9 +34,9 @@
:loading-tip="getProps.loadingTip"
:minHeight="getProps.minHeight"
:height="getWrapperHeight"
:visible="visibleRef"
:open="openRef"
:modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')"
v-bind="omit(getProps.wrapperProps, 'open', 'height', 'modalFooterHeight')"
@ext-height="handleExtHeight"
@height-change="handleHeightChange"
>
@@ -79,9 +79,9 @@
components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
inheritAttrs: false,
props: basicProps,
emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register', 'update:visible'],
emits: ['open-change', 'height-change', 'cancel', 'ok', 'register', 'update:open'],
setup(props, { emit, attrs }) {
const visibleRef = ref(false);
const openRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null);
const modalWrapperRef = ref<any>(null);
const { prefixCls } = useDesign('basic-modal');
@@ -90,7 +90,7 @@
const extHeightRef = ref(0);
const modalMethods: ModalMethods = {
setModalProps,
emitVisible: undefined,
emitOpen: undefined,
redoModalHeight: () => {
nextTick(() => {
if (unref(modalWrapperRef)) {
@@ -123,7 +123,7 @@
const getProps = computed((): Recordable => {
const opt = {
...unref(getMergeProps),
visible: unref(visibleRef),
open: unref(openRef),
okButtonProps: undefined,
cancelButtonProps: undefined,
title: undefined,
@@ -138,10 +138,10 @@
const attr = {
...attrs,
...unref(getMergeProps),
visible: unref(visibleRef),
open: unref(openRef),
};
attr['wrapClassName'] = `${attr?.['wrapClassName'] || ''} ${unref(getWrapClassName)}`;
attr['wrapClassName'] =
`${attr?.['wrapClassName'] || ''} ${unref(getWrapClassName)}` + 'vben-basic-modal-wrap';
if (unref(fullScreenRef)) {
return omit(attr, ['height', 'title']);
}
@@ -154,16 +154,16 @@
});
watchEffect(() => {
visibleRef.value = !!props.visible;
openRef.value = !!props.open;
fullScreenRef.value = !!props.defaultFullscreen;
});
watch(
() => unref(visibleRef),
() => unref(openRef),
(v) => {
emit('visible-change', v);
emit('update:visible', v);
instance && modalMethods.emitVisible?.(v, instance.uid);
emit('open-change', v);
emit('update:open', v);
instance && modalMethods.emitOpen?.(v, instance.uid);
nextTick(() => {
if (props.scrollTop && v && unref(modalWrapperRef)) {
(unref(modalWrapperRef) as any).scrollTop();
@@ -182,11 +182,11 @@
if ((e.target as HTMLElement)?.classList?.contains(prefixCls + '-close--custom')) return;
if (props.closeFunc && isFunction(props.closeFunc)) {
const isClose: boolean = await props.closeFunc();
visibleRef.value = !isClose;
openRef.value = !isClose;
return;
}
visibleRef.value = false;
openRef.value = false;
emit('cancel', e);
}
@@ -196,8 +196,8 @@
function setModalProps(props: Partial<ModalProps>): void {
// Keep the last setModalProps
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
if (Reflect.has(props, 'open')) {
openRef.value = !!props.open;
}
if (Reflect.has(props, 'defaultFullscreen')) {
fullScreenRef.value = !!props.defaultFullscreen;
@@ -230,7 +230,7 @@
fullScreenRef,
getMergeProps,
handleOk,
visibleRef,
openRef,
omit,
modalWrapperRef,
handleExtHeight,

View File

@@ -11,10 +11,10 @@ export default defineComponent({
props: basicProps as any,
emits: ['cancel'],
setup(props, { slots, emit }) {
const { visible, draggable, destroyOnClose } = toRefs(props);
const { open, draggable, destroyOnClose } = toRefs(props);
const attrs = useAttrs();
useModalDragMove({
visible,
open,
destroyOnClose,
draggable,
});

View File

@@ -32,7 +32,7 @@
minHeight: { type: Number, default: 200 },
height: { type: Number },
footerOffset: { type: Number, default: 0 },
visible: { type: Boolean },
open: { type: Boolean },
fullScreen: { type: Boolean },
loadingTip: { type: String },
};
@@ -53,7 +53,7 @@
let stopElResizeFn: AnyFunction = () => {};
useWindowSizeFn(setModalHeight.bind(null, false));
useWindowSizeFn(setModalHeight.bind(null));
useMutationObserver(
spinRef,
@@ -112,8 +112,8 @@
async function setModalHeight() {
// 解决在弹窗关闭的时候监听还存在,导致再次打开弹窗没有高度
// 加上这个,就必须在使用的时候传递父级的visible
if (!props.visible) return;
// 加上这个,就必须在使用的时候传递父级的open
if (!props.open) return;
const wrapperRefDom = unref(wrapperRef);
if (!wrapperRefDom) return;

View File

@@ -24,7 +24,7 @@ import { error } from '/@/utils/log';
const dataTransfer = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});
const openData = reactive<{ [key: number]: boolean }>({});
/**
* @description: Applicable to independent modal and call outside
@@ -32,9 +32,9 @@ const visibleData = reactive<{ [key: number]: boolean }>({});
export function useModal(): UseModalReturnType {
const modal = ref<Nullable<ModalMethods>>(null);
const loaded = ref<Nullable<boolean>>(false);
const uid = ref<string>('');
const uid = ref<number>(0);
function register(modalMethod: ModalMethods, uuid: string) {
function register(modalMethod: ModalMethods, uuid: number) {
if (!getCurrentInstance()) {
throw new Error('useModal() can only be used inside setup() or functional components!');
}
@@ -43,14 +43,14 @@ export function useModal(): UseModalReturnType {
onUnmounted(() => {
modal.value = null;
loaded.value = false;
dataTransfer[unref(uid)] = null;
dataTransfer[String(unref(uid))] = null;
});
if (unref(loaded) && isProdMode() && modalMethod === unref(modal)) return;
modal.value = modalMethod;
loaded.value = true;
modalMethod.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible;
modalMethod.emitOpen = (open: boolean, uid: number) => {
openData[uid] = open;
};
}
@@ -67,17 +67,17 @@ export function useModal(): UseModalReturnType {
getInstance()?.setModalProps(props);
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uid)];
getOpen: computed((): boolean => {
return openData[~~unref(uid)];
}),
redoModalHeight: () => {
getInstance()?.redoModalHeight?.();
},
openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => {
openModal: <T = any>(open = true, data?: T, openOnSet = true): void => {
getInstance()?.setModalProps({
visible: visible,
open,
});
if (!data) return;
@@ -94,7 +94,7 @@ export function useModal(): UseModalReturnType {
},
closeModal: () => {
getInstance()?.setModalProps({ visible: false });
getInstance()?.setModalProps({ open: false });
},
};
return [register, methods];
@@ -103,7 +103,7 @@ export function useModal(): UseModalReturnType {
export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
const modalInstanceRef = ref<Nullable<ModalMethods>>(null);
const currentInstance = getCurrentInstance();
const uidRef = ref<string>('');
const uidRef = ref<number>(0);
const getInstance = () => {
const instance = unref(modalInstanceRef);
@@ -113,7 +113,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
return instance;
};
const register = (modalInstance: ModalMethods, uuid: string) => {
const register = (modalInstance: ModalMethods, uuid: number) => {
isProdMode() &&
tryOnUnmounted(() => {
modalInstanceRef.value = null;
@@ -138,8 +138,8 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
changeLoading: (loading = true) => {
getInstance()?.setModalProps({ loading });
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
getOpen: computed((): boolean => {
return openData[~~unref(uidRef)];
}),
changeOkLoading: (loading = true) => {
@@ -147,7 +147,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
},
closeModal: () => {
getInstance()?.setModalProps({ visible: false });
getInstance()?.setModalProps({ open: false });
},
setModalProps: (props: Partial<ModalProps>) => {

View File

@@ -4,7 +4,7 @@ import { useTimeoutFn } from '@vben/hooks';
export interface UseModalDragMoveContext {
draggable: Ref<boolean>;
destroyOnClose: Ref<boolean | undefined> | undefined;
visible: Ref<boolean>;
open: Ref<boolean>;
}
export function useModalDragMove(context: UseModalDragMoveContext) {
@@ -97,7 +97,7 @@ export function useModalDragMove(context: UseModalDragMoveContext) {
};
watchEffect(() => {
if (!unref(context.visible) || !unref(context.draggable)) {
if (!unref(context.open) || !unref(context.draggable)) {
return;
}
useTimeoutFn(() => {

View File

@@ -1,11 +1,9 @@
/* stylelint-disable media-feature-range-notation */
.fullscreen-modal {
overflow: hidden;
.ant-modal {
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
inset: 0 !important;
width: 100% !important;
height: 100%;
@@ -13,9 +11,13 @@
height: 100%;
}
}
.ant-modal-footer {
margin-top: 0;
}
}
.ant-modal {
.vben-basic-modal-wrap .ant-modal {
width: 520px;
padding-bottom: 0;
@@ -53,6 +55,7 @@
}
&-content {
padding: 12px 8px !important;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
}
@@ -63,8 +66,10 @@
}
&-close {
font-weight: normal;
width: auto !important;
outline: none;
background: transparent !important;
font-weight: normal;
}
&-close-x {
@@ -110,16 +115,19 @@
.ant-modal-confirm .ant-modal-body {
padding: 24px !important;
}
@media screen and (max-height: 600px) {
.ant-modal {
top: 60px;
}
}
@media screen and (max-height: 540px) {
.ant-modal {
top: 30px;
}
}
@media screen and (max-height: 480px) {
.ant-modal {
top: 10px;

View File

@@ -6,7 +6,7 @@ import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export const modalProps = {
visible: { type: Boolean },
open: { type: Boolean },
scrollTop: { type: Boolean, default: true },
height: { type: Number },
minHeight: { type: Number },
@@ -73,7 +73,7 @@ export const basicProps = Object.assign({}, modalProps, {
title: { type: String },
visible: { type: Boolean },
open: { type: Boolean },
width: [String, Number] as PropType<string | number>,

View File

@@ -5,16 +5,16 @@ import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
*/
export interface ModalMethods {
setModalProps: (props: Partial<ModalProps>) => void;
emitVisible?: (visible: boolean, uid: number) => void;
emitOpen?: (open: boolean, uid: number) => void;
redoModalHeight?: () => void;
}
export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void;
export type RegisterFn = (modalMethods: ModalMethods, uuid: number) => void;
export interface ReturnMethods extends ModalMethods {
openModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void;
closeModal: () => void;
getVisible?: ComputedRef<boolean>;
getOpen?: ComputedRef<boolean>;
}
export type UseModalReturnType = [RegisterFn, ReturnMethods];
@@ -23,7 +23,7 @@ export interface ReturnInnerMethods extends ModalMethods {
closeModal: () => void;
changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void;
getVisible?: ComputedRef<boolean>;
getOpen?: ComputedRef<boolean>;
redoModalHeight: () => void;
}
@@ -40,7 +40,7 @@ export interface ModalProps {
// 是否可以进行全屏
canFullscreen?: boolean;
defaultFullscreen?: boolean;
visible?: boolean;
open?: boolean;
// 温馨提醒信息
helpMessage: string | string[];
@@ -203,7 +203,7 @@ export interface ModalWrapperProps {
modalFooterHeight: number;
minHeight: number;
height: number;
visible: boolean;
open: boolean;
fullScreen: boolean;
useWrapper: boolean;
}

View File

@@ -5,5 +5,3 @@ import pageWrapper from './src/PageWrapper.vue';
export const PageFooter = withInstall(pageFooter);
export const PageWrapper = withInstall(pageWrapper);
export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';

View File

@@ -9,20 +9,17 @@
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
defineOptions({
name: 'PageFooter',
inheritAttrs: false,
setup() {
});
const { prefixCls } = useDesign('page-footer');
const { getCalcContentWidth } = useMenuSetting();
return { prefixCls, getCalcContentWidth };
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-page-footer';

View File

@@ -4,6 +4,7 @@
:ghost="ghost"
:title="title"
v-bind="omit($attrs, 'class')"
:style="getHeaderStyle"
ref="headerRef"
v-if="getShowHeader"
>
@@ -32,16 +33,17 @@
</PageFooter>
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import {
CSSProperties,
PropType,
provide,
defineComponent,
computed,
watch,
ref,
unref,
useAttrs,
useSlots,
} from 'vue';
import PageFooter from './PageFooter.vue';
@@ -51,27 +53,34 @@
import { omit } from 'lodash-es';
import { PageHeader } from 'ant-design-vue';
import { useContentHeight } from '/@/hooks/web/useContentHeight';
import { PageWrapperFixedHeightKey } from '..';
import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
import { PageWrapperFixedHeightKey } from '/@/enums/pageEnum';
export default defineComponent({
defineOptions({
name: 'PageWrapper',
components: { PageFooter, PageHeader },
inheritAttrs: false,
props: {
});
const props = defineProps({
title: propTypes.string,
dense: propTypes.bool,
ghost: propTypes.bool,
headerSticky: propTypes.bool,
headerStyle: Object as PropType<CSSProperties>,
content: propTypes.string,
contentStyle: {
type: Object as PropType<CSSProperties>,
},
contentBackground: propTypes.bool,
contentFullHeight: propTypes.bool,
contentFullHeight: propTypes.bool.def(false),
contentClass: propTypes.string,
fixedHeight: propTypes.bool,
upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
},
setup(props, { slots, attrs }) {
});
const attrs = useAttrs();
const slots = useSlots();
const wrapperRef = ref(null);
const headerRef = ref(null);
const contentRef = ref(null);
@@ -107,6 +116,20 @@
];
});
const { headerHeightRef } = useLayoutHeight();
const getHeaderStyle = computed((): CSSProperties => {
const { headerSticky } = props;
if (!headerSticky) {
return {};
}
return {
position: 'sticky',
top: `${unref(headerHeightRef)}px`,
...props.headerStyle,
};
});
const getShowHeader = computed(
() => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
);
@@ -152,23 +175,6 @@
immediate: true,
},
);
return {
getContentStyle,
wrapperRef,
headerRef,
contentRef,
footerRef,
getClass,
getHeaderSlots,
prefixCls,
getShowHeader,
getShowFooter,
omit,
getContentClass,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-page-wrapper';

View File

@@ -0,0 +1,49 @@
<template>
<Modal
v-model:open="open"
:title="title"
@ok="handleSubmit"
:destroyOnClose="true"
:width="width || '500px'"
okText="确定"
cancelText="取消"
>
<div class="pt-5 pr-3px">
<BasicForm @register="register" />
</div>
</Modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Modal } from 'ant-design-vue';
import { FormSchema } from '/@/components/Form';
import { BasicForm, useForm } from '/@/components/Form/index';
const open = ref<boolean>(true);
const props = defineProps<{
title: string;
addFormSchemas: FormSchema[];
onOK?: Fn;
width?: string;
labelWidth?: number;
layout?: 'horizontal' | 'vertical' | 'inline';
}>();
const [register, { validate }] = useForm({
schemas: props.addFormSchemas,
showActionButtonGroup: false,
labelWidth: props.labelWidth || 80,
layout: props.layout || 'horizontal',
});
async function handleSubmit() {
const row = await validate();
if (props.onOK) {
await props.onOK(row.txt);
}
open.value = false;
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,39 @@
import { createVNode, VNode, defineComponent, h, render, reactive } from 'vue';
import { PromptProps, genFormSchemas } from './state';
import Dialog from './dialog.vue';
export function createPrompt(props: PromptProps) {
let vm: Nullable<VNode> = null;
const data = reactive({
...props,
addFormSchemas: genFormSchemas({
label: props.label,
required: props.required,
inputType: props.inputType,
defaultValue: props.defaultValue,
}),
});
const DialogWrap = defineComponent({
render() {
return h(Dialog, { ...data } as any);
},
});
vm = createVNode(DialogWrap);
render(vm, document.createElement('div'));
function close() {
if (vm?.el && vm.el.parentNode) {
vm.el.parentNode.removeChild(vm.el);
}
}
return {
vm,
close,
get $el() {
return vm?.el as HTMLElement;
},
};
}

View File

@@ -0,0 +1,69 @@
import { FormSchema } from '/@/components/Form';
type InputType = 'InputTextArea' | 'InputNumber' | 'Input';
export interface PromptProps {
title: string;
label?: string;
required?: boolean;
onOK?: Fn;
inputType?: InputType;
labelWidth?: number;
width?: string;
layout?: 'horizontal' | 'vertical' | 'inline';
defaultValue?: string | number;
}
interface genFormSchemasProps {
label?: string;
required?: boolean;
inputType?: InputType;
defaultValue?: string | number;
}
const inputTypeMap: {
[key in InputType]: {
colProps: { span: number; offset?: number };
componentProps: FormSchema['componentProps'];
};
} = {
InputTextArea: {
colProps: { span: 23 },
componentProps: {
placeholder: '请输入内容',
autoSize: { minRows: 2, maxRows: 6 },
maxlength: 255,
showCount: true,
},
},
InputNumber: {
colProps: { span: 20, offset: 2 },
componentProps: {
placeholder: '请输入数字',
min: 0,
},
},
Input: {
colProps: { span: 20, offset: 2 },
componentProps: {
placeholder: '请输入内容',
min: 0,
},
},
};
export function genFormSchemas({
label = '备注信息',
required = true,
inputType = 'InputTextArea',
defaultValue = '',
}: genFormSchemasProps) {
const formSchema: FormSchema = {
field: 'txt',
component: inputType,
label,
defaultValue,
required: Boolean(required),
...inputTypeMap[inputType],
};
return [formSchema];
}

View File

@@ -20,9 +20,10 @@
placement="right"
:overlayClassName="`${prefixCls}-menu-popover`"
v-else
:visible="getIsOpend"
@visible-change="handleVisibleChange"
:open="getIsOpend"
@on-open-change="handleVisibleChange"
:overlayStyle="getOverlayStyle"
:overlayInnerStyle="{ padding: 0 }"
:align="{ offset: [0, 0] }"
>
<div :class="getSubClass" v-bind="getEvents(false)">

View File

@@ -7,13 +7,13 @@
.light-border {
&::after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: block;
width: 2px;
content: '';
background-color: @primary-color;
}
}
@@ -37,16 +37,16 @@
position: relative;
z-index: 1;
padding: 12px 20px;
transition: all @transition-time @ease-in-out;
color: @menu-dark-subsidiary-color;
cursor: pointer;
transition: all @transition-time @ease-in-out;
&-icon {
position: absolute;
top: 50%;
right: 18px;
transition: transform @transition-time @ease-in-out;
transform: translateY(-50%) rotate(-90deg);
transition: transform @transition-time @ease-in-out;
}
}
@@ -61,8 +61,8 @@
}
&-selected {
color: #fff;
background-color: @primary-color !important;
color: #fff;
}
}
}
@@ -78,8 +78,8 @@
&-selected {
z-index: 2;
color: @primary-color;
background-color: fade(@primary-color, 10);
color: @primary-color;
.light-border();
}
@@ -91,15 +91,15 @@
.content();
.content() {
.@{menu-prefix-cls} {
position: relative;
display: block;
position: relative;
width: 100%;
padding: 0;
margin: 0;
font-size: @font-size-base;
color: @text-color-base;
list-style: none;
padding: 0;
outline: none;
color: @text-color-base;
font-size: @font-size-base;
list-style: none;
// .collapse-transition {
// transition: @transition-time height ease-in-out, @transition-time padding-top ease-in-out,
@@ -125,15 +125,15 @@
}
&-item {
display: flex;
position: relative;
z-index: 1;
display: flex;
align-items: center;
font-size: @font-size-base;
outline: none;
color: inherit;
font-size: @font-size-base;
list-style: none;
cursor: pointer;
outline: none;
&:hover,
&:active {
@@ -218,8 +218,8 @@
&-light&-vertical &-item {
&-active:not(.@{menu-prefix-cls}-submenu) {
z-index: 2;
color: @primary-color;
background-color: fade(@primary-color, 10);
color: @primary-color;
.light-border();
}
@@ -239,12 +239,12 @@
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
content: '';
background-color: @primary-color;
}
}
@@ -254,8 +254,8 @@
&-dark&-vertical &-submenu-title {
color: @menu-dark-subsidiary-color;
&-active:not(.@{menu-prefix-cls}-submenu) {
color: #fff !important;
background-color: @primary-color !important;
color: #fff !important;
}
&:hover {
@@ -267,16 +267,16 @@
> li.@{menu-prefix-cls}-item-active,
.@{menu-prefix-cls}-submenu-active {
position: relative;
color: #fff !important;
background-color: @sider-dark-darken-bg-color !important;
color: #fff !important;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
content: '';
background-color: @primary-color;
}
@@ -289,8 +289,8 @@
&-dark&-vertical &-submenu &-item {
&-active,
&-active:hover {
color: #fff;
border-right: none;
color: #fff;
}
}

View File

@@ -28,22 +28,22 @@
.@{simple-prefix-cls} {
&-sub-title {
overflow: hidden;
transition: all 0.3s;
text-overflow: ellipsis;
white-space: nowrap;
transition: all 0.3s;
}
&-tag {
display: inline-block;
position: absolute;
top: calc(50% - 8px);
right: 30px;
display: inline-block;
padding: 2px 3px;
margin-right: 4px;
padding: 2px 3px;
border-radius: 2px;
color: #fff;
font-size: 10px;
line-height: 14px;
color: #fff;
border-radius: 2px;
&--collapse {
top: 6px !important;

View File

@@ -21,12 +21,15 @@
:rowClassName="getRowClassName"
v-show="getEmptyDataIsShowTable"
@change="handleTableChange"
@resizeColumn="setColumnWidth"
>
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #headerCell="{ column }">
<slot name="headerCell" v-bind="{ column }">
<HeaderCell :column="column" />
</slot>
</template>
<!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data">
@@ -49,7 +52,7 @@
import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect } from 'vue';
import { Table } from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { PageWrapperFixedHeightKey } from '/@/components/Page';
import { PageWrapperFixedHeightKey } from '/@/enums/pageEnum';
import HeaderCell from './components/HeaderCell.vue';
import { InnerHandlers } from './types/table';
@@ -74,15 +77,7 @@
import { isFunction } from '/@/utils/is';
import { warn } from '/@/utils/log';
export default defineComponent({
name: 'BasicTable',
components: {
Table,
BasicForm,
HeaderCell,
},
props: basicProps,
emits: [
const events = [
'fetch-success',
'fetch-error',
'selection-change',
@@ -99,7 +94,17 @@
'expanded-rows-change',
'change',
'columns-change',
],
];
export default defineComponent({
name: 'BasicTable',
components: {
Table,
BasicForm,
HeaderCell,
},
props: basicProps,
emits: events,
setup(props, { attrs, emit, slots, expose }) {
const tableElRef = ref(null);
const tableData = ref([]);
@@ -184,6 +189,8 @@
getViewColumns,
getColumns,
setCacheColumnsByField,
setCacheColumns,
setColumnWidth,
setColumns,
getColumnsRef,
getCacheColumns,
@@ -323,6 +330,7 @@
getSize: () => {
return unref(getBindValues).size as SizeType;
},
setCacheColumns,
};
createTableContext({ ...tableAction, wrapRef, getBindValues });
@@ -339,6 +347,7 @@
handleSearchInfoChange,
getEmptyDataIsShowTable,
handleTableChange,
setColumnWidth,
getRowClassName,
wrapRef,
tableAction,

View File

@@ -1,5 +1,5 @@
<template>
<span>
<span class="edit-header-cell">
<slot></slot>
{{ title }}
<FormOutlined />

View File

@@ -1,11 +1,4 @@
<template>
<EditTableHeaderCell v-if="getIsEdit">
{{ getTitle }}
</EditTableHeaderCell>
<span v-else>{{ getTitle }}</span>
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
</template>
<script lang="ts">
<script lang="tsx">
import type { PropType } from 'vue';
import type { BasicColumn } from '../types/table';
import { defineComponent, computed } from 'vue';
@@ -29,10 +22,29 @@
const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => props.column?.customTitle || props.column?.title);
const getTitle = computed(() => {
const column = props.column;
if (typeof column.customHeaderRender === 'function') {
return column.customHeaderRender(props.column);
}
return props.column?.customTitle || props.column?.title;
});
const getHelpMessage = computed(() => props.column?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
return () => {
return (
<div>
{getIsEdit.value ? (
<EditTableHeaderCell>{getTitle.value}</EditTableHeaderCell>
) : (
<span class="default-header-cell">{getTitle.value}</span>
)}
{getHelpMessage.value && (
<BasicHelp text={getHelpMessage.value} class={`${prefixCls}__help`} />
)}
</div>
);
};
},
});
</script>

View File

@@ -33,7 +33,7 @@ export const CellComponent: FunctionalComponent = (
Popover,
{
overlayClassName: 'edit-cell-rule-popover',
visible: !!popoverVisible,
open: !!popoverVisible,
...(getPopupContainer ? { getPopupContainer } : {}),
},
{

View File

@@ -483,6 +483,7 @@
}
.@{prefix-cls} {
position: relative;
min-height: 24px; //设置高度让其始终可被hover
&__wrapper {
display: flex;

View File

@@ -1,6 +1,6 @@
import type { BasicColumn } from '/@/components/Table/src/types/table';
import { h, Ref } from 'vue';
import { h, Ref, toRaw } from 'vue';
import EditableCell from './EditableCell.vue';
import { isArray } from '/@/utils/is';
@@ -13,7 +13,7 @@ interface Params {
export function renderEditCell(column: BasicColumn) {
return ({ text: value, record, index }: Params) => {
record.onValid = async () => {
toRaw(record).onValid = async () => {
if (isArray(record?.validCbs)) {
const validFns = (record?.validCbs || []).map((fn) => fn());
const res = await Promise.all(validFns);
@@ -23,7 +23,7 @@ export function renderEditCell(column: BasicColumn) {
}
};
record.onEdit = async (edit: boolean, submit = false) => {
toRaw(record).onEdit = async (edit: boolean, submit = false) => {
if (!submit) {
record.editable = edit;
}

View File

@@ -6,7 +6,7 @@
<Popover
placement="bottomLeft"
trigger="click"
@visible-change="handleVisibleChange"
@open-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer"
>
@@ -99,7 +99,7 @@
</Tooltip>
</template>
<script lang="ts">
import type { BasicColumn, ColumnChangeParam } from '../../types/table';
import type { BasicColumn, BasicTableProps, ColumnChangeParam } from '../../types/table';
import {
defineComponent,
ref,
@@ -159,6 +159,10 @@
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
let inited = false;
// 是否当前的setColums触发的
let isSetColumnsFromThis = false;
// 是否当前组件触发的setProps
let isSetPropsFromThis = false;
const cachePlainOptions = ref<Options[]>([]);
const plainOptions = ref<Options[] | any>([]);
@@ -172,7 +176,8 @@
checkedList: [],
defaultCheckList: [],
});
/** 缓存初始化props */
let cacheTableProps: Partial<BasicTableProps<any>> = {};
const checkIndex = ref(false);
const checkSelect = ref(false);
@@ -185,7 +190,9 @@
watchEffect(() => {
const columns = table.getColumns();
setTimeout(() => {
if (columns.length && !state.isInit) {
if (isSetColumnsFromThis) {
isSetColumnsFromThis = false;
} else if (columns.length) {
init();
}
}, 0);
@@ -193,6 +200,11 @@
watchEffect(() => {
const values = unref(getValues);
if (isSetPropsFromThis) {
isSetPropsFromThis = false;
} else {
cacheTableProps = cloneDeep(values);
}
checkIndex.value = !!values.showIndexColumn;
checkSelect.value = !!values.rowSelection;
});
@@ -209,8 +221,17 @@
return ret;
}
function init() {
const columns = getColumns();
async function init(isReset = false) {
// Sortablejs存在bug不知道在哪个步骤中会向el append了一个childNode因此这里先清空childNode
// 有可能复现上述问题的操作:拖拽一个元素,快速的上下移动,最后放到最后的位置中松手
plainOptions.value = [];
const columnListEl = unref(columnListRef);
if (columnListEl && (columnListEl as any).$el) {
const el = (columnListEl as any).$el as Element;
Array.from(el.children).forEach((item) => el.removeChild(item));
}
await nextTick();
const columns = isReset ? cloneDeep(cachePlainOptions.value) : getColumns();
const checkList = table
.getColumns({ ignoreAction: true, ignoreIndex: true })
@@ -221,31 +242,25 @@
return item.dataIndex || item.title;
})
.filter(Boolean) as string[];
if (!plainOptions.value.length) {
plainOptions.value = columns;
plainSortOptions.value = columns;
cachePlainOptions.value = columns;
// 更新缓存配置
table.setCacheColumns?.(columns);
!isReset && (cachePlainOptions.value = cloneDeep(columns));
state.defaultCheckList = checkList;
} else {
// const fixedColumns = columns.filter((item) =>
// Reflect.has(item, 'fixed')
// ) as BasicColumn[];
unref(plainOptions).forEach((item: BasicColumn) => {
const findItem = columns.find((col: BasicColumn) => col.dataIndex === item.dataIndex);
if (findItem) {
item.fixed = findItem.fixed;
}
});
}
state.isInit = true;
state.checkedList = checkList;
// 是否列展示全选
state.checkAll = checkList.length === columns.length;
inited = false;
handleVisibleChange();
}
// checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainOptions.value.map((item) => item.value);
const checkList = plainSortOptions.value.map((item) => item.value);
plainSortOptions.value.forEach(
(item) => ((item as BasicColumn).defaultHidden = !e.target.checked),
);
if (e.target.checked) {
state.checkedList = checkList;
setColumns(checkList);
@@ -270,6 +285,9 @@
checkedList.sort((prev, next) => {
return sortList.indexOf(prev) - sortList.indexOf(next);
});
unref(plainSortOptions).forEach((item) => {
(item as BasicColumn).defaultHidden = !checkedList.includes(item.value);
});
setColumns(checkedList);
}
@@ -277,11 +295,14 @@
let sortableOrder: string[] = [];
// reset columns
function reset() {
state.checkedList = [...state.defaultCheckList];
state.checkAll = true;
plainOptions.value = unref(cachePlainOptions);
plainSortOptions.value = unref(cachePlainOptions);
setColumns(table.getCacheColumns());
setColumns(cachePlainOptions.value);
init(true);
checkIndex.value = !!cacheTableProps.showIndexColumn;
checkSelect.value = !!cacheTableProps.rowSelection;
table.setProps({
showIndexColumn: checkIndex.value,
rowSelection: checkSelect.value ? defaultRowSelection : undefined,
});
sortable.sort(sortableOrder);
}
@@ -316,12 +337,7 @@
}
plainSortOptions.value = columns;
setColumns(
columns
.map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)),
);
setColumns(columns.filter((item) => state.checkedList.includes(item.value)));
},
});
// 记录原始order 序列
@@ -332,6 +348,8 @@
// Control whether the serial number column is displayed
function handleIndexCheckChange(e: CheckboxChangeEvent) {
isSetPropsFromThis = true;
isSetColumnsFromThis = true;
table.setProps({
showIndexColumn: e.target.checked,
});
@@ -339,6 +357,8 @@
// Control whether the check box is displayed
function handleSelectCheckChange(e: CheckboxChangeEvent) {
isSetPropsFromThis = true;
isSetColumnsFromThis = true;
table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined,
});
@@ -360,11 +380,14 @@
if (isFixed && !item.width) {
item.width = 100;
}
updateSortOption(item);
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
setColumns(columns);
}
function setColumns(columns: BasicColumn[] | string[]) {
isSetPropsFromThis = true;
isSetColumnsFromThis = true;
table.setColumns(columns);
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const visible =
@@ -384,6 +407,14 @@
: getParentContainer();
}
function updateSortOption(column: BasicColumn) {
plainSortOptions.value.forEach((item) => {
if (item.value === column.dataIndex) {
Object.assign(item, column);
}
});
}
return {
t,
...toRefs(state),
@@ -471,6 +502,7 @@
}
.ant-checkbox-group {
display: inline-block;
width: 100%;
min-width: 260px;
// flex-wrap: wrap;

View File

@@ -15,7 +15,7 @@ function handleItem(item: BasicColumn, ellipsis: boolean) {
item.align = item.align || DEFAULT_ALIGN;
if (ellipsis) {
if (!key) {
item.key = dataIndex;
item.key = typeof dataIndex == 'object' ? dataIndex.join('-') : dataIndex;
}
if (!isBoolean(item.ellipsis)) {
Object.assign(item, {
@@ -260,14 +260,26 @@ export function useColumns(
function getCacheColumns() {
return cacheColumns;
}
function setCacheColumns(columns: BasicColumn[]) {
if (!isArray(columns)) return;
cacheColumns = columns.filter((item) => !item.flag);
}
/**
* 拖拽列宽修改列的宽度
*/
function setColumnWidth(w: number, col: BasicColumn) {
col.width = w;
}
return {
getColumnsRef,
getCacheColumns,
getColumns,
setColumns,
setColumnWidth,
getViewColumns,
setCacheColumnsByField,
setCacheColumns,
};
}

View File

@@ -42,7 +42,7 @@ export function useCustomRow(
if (!rowSelection || !clickToRowSelect) return;
const keys = getSelectRowKeys() || [];
const key = getKey(record, rowKey, unref(getAutoCreateKey));
if (!key) return;
if (key === null) return;
const isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) {
@@ -55,7 +55,8 @@ export function useCustomRow(
const checkBox = tr.querySelector('input[type=checkbox]');
if (!checkBox || checkBox.hasAttribute('disabled')) return;
if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]);
keys.push(key);
setSelectedRowKeys(keys);
return;
}
const keyIndex = keys.findIndex((item) => item === key);

View File

@@ -208,7 +208,7 @@ export function useDataSource(
function insertTableDataRecord(
record: Recordable | Recordable[],
index: number,
index?: number,
): Recordable[] | undefined {
// if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length;
@@ -349,7 +349,7 @@ export function useDataSource(
}
function setTableData<T = Recordable>(values: T[]) {
dataSourceRef.value = values;
dataSourceRef.value = values as Recordable[];
}
function getDataSource<T = Recordable>() {

View File

@@ -4,13 +4,14 @@ import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from '
import { ROW_KEY } from '../const';
import { omit } from 'lodash-es';
import { findNodeAll } from '/@/utils/helper/treeHelper';
import type { Key } from 'ant-design-vue/lib/table/interface';
export function useRowSelection(
propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>,
emit: EmitType,
) {
const selectedRowKeysRef = ref<string[]>([]);
const selectedRowKeysRef = ref<Key[]>([]);
const selectedRowRef = ref<Recordable[]>([]);
const getRowSelectionRef = computed((): TableRowSelection | null => {
@@ -21,7 +22,7 @@ export function useRowSelection(
return {
selectedRowKeys: unref(selectedRowKeysRef),
onChange: (selectedRowKeys: string[]) => {
onChange: (selectedRowKeys: Key[]) => {
setSelectedRowKeys(selectedRowKeys);
},
...omit(rowSelection, ['onChange']),
@@ -30,7 +31,7 @@ export function useRowSelection(
watch(
() => unref(propsRef).rowSelection?.selectedRowKeys,
(v: string[]) => {
(v?: Key[]) => {
setSelectedRowKeys(v);
},
);
@@ -62,8 +63,8 @@ export function useRowSelection(
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys;
function setSelectedRowKeys(rowKeys?: Key[]) {
selectedRowKeysRef.value = rowKeys || [];
const allSelectedRows = findNodeAll(
toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
(item) => rowKeys?.includes(item[unref(getRowKey) as string]),
@@ -72,7 +73,7 @@ export function useRowSelection(
},
);
const trueSelectedRows: any[] = [];
rowKeys?.forEach((key: string) => {
rowKeys?.forEach((key: Key) => {
const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key);
found && trueSelectedRows.push(found);
});

View File

@@ -7,6 +7,7 @@ import { getDynamicProps } from '/@/utils';
import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import type { Key } from 'ant-design-vue/lib/table/interface';
type Props = Partial<DynamicProps<BasicTableProps>>;
@@ -92,7 +93,7 @@ export function useTable(tableProps?: Props): [
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return toRaw(columns);
},
setColumns: (columns: BasicColumn[]) => {
setColumns: (columns: BasicColumn[] | string[]) => {
getTableInstance().setColumns(columns);
},
setTableData: (values: any[]) => {
@@ -113,7 +114,7 @@ export function useTable(tableProps?: Props): [
clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys();
},
setSelectedRowKeys: (keys: string[] | number[]) => {
setSelectedRowKeys: (keys: (string | number)[]) => {
getTableInstance().setSelectedRowKeys(keys);
},
getPaginationRef: () => {
@@ -155,7 +156,7 @@ export function useTable(tableProps?: Props): [
expandAll: () => {
getTableInstance().expandAll();
},
expandRows: (keys: string[]) => {
expandRows: (keys: Key[]) => {
getTableInstance().expandRows(keys);
},
collapseAll: () => {

View File

@@ -8,7 +8,7 @@ export function useTableExpand(
tableData: Ref<Recordable[]>,
emit: EmitType,
) {
const expandedRowKeys = ref<string[]>([]);
const expandedRowKeys = ref<(string | number)[]>([]);
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
@@ -37,7 +37,7 @@ export function useTableExpand(
expandedRowKeys.value = keys;
}
function expandRows(keys: string[]) {
function expandRows(keys: (string | number)[]) {
// use row ID expands the specified table row
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return;

View File

@@ -53,22 +53,7 @@ export function useTableScroll(
let footerEl: HTMLElement | null;
let bodyEl: HTMLElement | null;
async function calcTableHeight() {
const { resizeHeightOffset, pagination, maxHeight, isCanResizeParent, useSearchForm } =
unref(propsRef);
const tableData = unref(getDataSourceRef);
const table = unref(tableElRef);
if (!table) return;
const tableEl: Element = table.$el;
if (!tableEl) return;
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return;
}
function handleScrollBar(bodyEl: HTMLElement, tableEl: Element) {
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
@@ -85,20 +70,10 @@ export function useTableScroll(
} else {
!tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
}
}
bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return;
await nextTick();
// Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
const headEl = tableEl.querySelector('.ant-table-thead ');
if (!headEl) return;
// Table height from bottom height-custom offset
let paddingHeight = 32;
function caclPaginationHeight(tableEl: Element): number {
const { pagination } = unref(propsRef);
// Pager height
let paginationHeight = 2;
if (!isBoolean(pagination)) {
@@ -113,7 +88,11 @@ export function useTableScroll(
} else {
paginationHeight = -8;
}
return paginationHeight;
}
function caclFooterHeight(tableEl: Element): number {
const { pagination } = unref(propsRef);
let footerHeight = 0;
if (!isBoolean(pagination)) {
if (!footerEl) {
@@ -123,12 +102,21 @@ export function useTableScroll(
footerHeight += offsetHeight || 0;
}
}
return footerHeight;
}
function calcHeaderHeight(headEl: Element): number {
let headerHeight = 0;
if (headEl) {
headerHeight = (headEl as HTMLElement).offsetHeight;
}
return headerHeight;
}
function calcBottomAndPaddingHeight(tableEl: Element, headEl: Element) {
const { pagination, isCanResizeParent, useSearchForm } = unref(propsRef);
// Table height from bottom height-custom offset
let paddingHeight = 30;
let bottomIncludeBody = 0;
if (unref(wrapRef) && isCanResizeParent) {
const tablePadding = 12;
@@ -158,6 +146,45 @@ export function useTableScroll(
bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody;
}
return {
paddingHeight,
bottomIncludeBody,
};
}
async function calcTableHeight() {
const { resizeHeightOffset, maxHeight } = unref(propsRef);
const tableData = unref(getDataSourceRef);
const table = unref(tableElRef);
if (!table) return;
const tableEl: Element = table.$el;
if (!tableEl) return;
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return;
}
handleScrollBar(bodyEl, tableEl);
bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return;
await nextTick();
// Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
const headEl = tableEl.querySelector('.ant-table-thead ');
if (!headEl) return;
const paginationHeight = caclPaginationHeight(tableEl);
const footerHeight = caclFooterHeight(tableEl);
const headerHeight = calcHeaderHeight(headEl);
const { paddingHeight, bottomIncludeBody } = calcBottomAndPaddingHeight(tableEl, headEl);
let height =
bottomIncludeBody -
(resizeHeightOffset || 0) -

View File

@@ -80,7 +80,7 @@ export interface ColumnProps<T> {
* Whether filterDropdown is visible
* @type boolean
*/
filterDropdownVisible?: boolean;
filterDropdownOpen?: boolean;
/**
* Whether the dataSource is filtered

View File

@@ -1,7 +1,10 @@
import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type {
TableRowSelection as ITableRowSelection,
Key,
} from 'ant-design-vue/lib/table/interface';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import { ComponentType } from './componentType';
@@ -19,7 +22,7 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
* Callback executed when selected rows change
* @type Function
*/
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
onChange?: (selectedRowKeys: Key[], selectedRows: T[]) => any;
/**
* Callback executed when select/deselect one row
@@ -37,7 +40,7 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
* Callback executed when row selection is inverted
* @type Function
*/
onSelectInvert?: (selectedRows: string[] | number[]) => any;
onSelectInvert?: (selectedRows: Key[]) => any;
}
export interface TableCustomRecord<T> {
@@ -88,7 +91,7 @@ export interface TableActionType {
getSelectRows: <T = Recordable>() => T[];
clearSelectedRowKeys: () => void;
expandAll: () => void;
expandRows: (keys: string[] | number[]) => void;
expandRows: (keys: (string | number)[]) => void;
collapseAll: () => void;
scrollTo: (pos: string) => void; // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[];
@@ -106,7 +109,7 @@ export interface TableActionType {
setLoading: (loading: boolean) => void;
setProps: (props: Partial<BasicTableProps>) => void;
redoHeight: () => void;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
setSelectedRowKeys: (rowKeys: Key[]) => void;
getPaginationRef: () => PaginationProps | boolean;
getSize: () => SizeType;
getRowSelection: () => TableRowSelection<Recordable>;
@@ -116,6 +119,7 @@ export interface TableActionType {
setShowPagination: (show: boolean) => Promise<void>;
getShowPagination: () => boolean;
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
setCacheColumns?: (columns: BasicColumn[]) => void;
}
export interface FetchSetting {
@@ -429,6 +433,8 @@ export interface BasicColumn extends ColumnProps<Recordable> {
slots?: Recordable;
// 自定义header渲染
customHeaderRender?: (column: BasicColumn) => string | VNodeChild | JSX.Element;
// Whether to hide the column by default, it can be displayed in the column configuration
defaultHidden?: boolean;

View File

@@ -129,7 +129,7 @@
getSelectedNode,
} = useTree(treeDataRef, getFieldNames);
function getIcon(params: Recordable, icon?: string) {
function getIcon(params: TreeItem, icon?: string) {
if (!icon) {
if (props.renderIcon && isFunction(props.renderIcon)) {
return props.renderIcon(params);

View File

@@ -105,7 +105,7 @@ export const treeProps = buildProps({
},
beforeRightClick: {
type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>,
type: Function as PropType<(...arg: any) => Promise<ContextMenuItem[] | ContextMenuOptions>>,
default: undefined,
},

View File

@@ -17,8 +17,8 @@
}
&__title {
position: relative;
display: flex;
position: relative;
align-items: center;
width: 100%;
padding-right: 10px;
@@ -35,15 +35,15 @@
}
&__actions {
display: flex;
position: absolute;
//top: 2px;
right: 3px;
display: flex;
}
&__action {
margin-left: 4px;
visibility: hidden;
margin-left: 4px;
}
&-header {

View File

@@ -1,3 +1,4 @@
/* stylelint-disable scss/percent-placeholder-pattern */
%ResetBorder {
border: 0;
box-shadow: none;
@@ -13,11 +14,13 @@
width: 100%;
}
}
.vxe-form {
.vxe-form--item-content {
@extend %CompWidth;
}
}
.vxe-table--filter-antd-wrapper {
& > .ant-input,
& > .ant-input-number,
@@ -26,16 +29,20 @@
width: 180px;
}
}
.vxe-cell,
.vxe-tree-cell {
@extend %CompWidth;
& > .ant-rate {
vertical-align: bottom;
.anticon-star {
display: block;
}
}
}
.col--valid-error {
& > .vxe-cell,
& > .vxe-tree-cell {
@@ -50,55 +57,67 @@
}
}
}
.vxe-table.cell--highlight {
.vxe-cell,
.vxe-tree-cell {
& > .ant-input,
& > .ant-input-number {
padding: 0;
@extend %ResetBorder;
padding: 0;
}
& > .ant-select {
.ant-input {
padding: 0;
@extend %ResetBorder;
}
}
& > .ant-input-number {
.ant-input-number-input {
padding: 0;
}
.ant-input-number-handler-wrap,
.ant-input-number-handler-down {
@extend %ResetBorder;
}
}
& > .ant-select {
.ant-select-selection {
@extend %ResetBorder;
.ant-select-selection__rendered {
margin: 0;
}
}
}
& > .ant-input-number {
.ant-input-number-input {
padding: 0;
}
.ant-input-number-handler-wrap,
.ant-input-number-handler-down {
@extend %ResetBorder;
}
}
& > .ant-cascader-picker {
.ant-input {
@extend %ResetBorder;
}
.ant-cascader-picker-label {
padding: 0;
}
}
& > .ant-calendar-picker {
.ant-calendar-picker-input {
padding: 0;
@extend %ResetBorder;
padding: 0;
}
}
& > .ant-time-picker {
.ant-time-picker-input {
padding: 0;
@extend %ResetBorder;
padding: 0;
}
}
}

View File

@@ -1,6 +1,6 @@
@import './common.scss';
@import './variable.scss';
@import './scrollbar.scss';
@import './toolbar.scss';
@import './component.scss';
@import 'vxe-table/styles/index.scss';
@import './common';
@import './variable';
@import './scrollbar';
@import './toolbar';
@import './component';
@import 'vxe-table/styles/index';

View File

@@ -3,22 +3,27 @@
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background-color: #ffffff;
background-color: #fff;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.1);
border-radius: 5px;
border: 1px solid #f1f1f1;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 5px;
background-color: rgb(0 0 0 / 10%);
box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
}
::-webkit-scrollbar-thumb:hover {
background-color: #a8a8a8;
}
::-webkit-scrollbar-thumb:active {
background-color: #a8a8a8;
}
::-webkit-scrollbar-corner {
background-color: #ffffff;
background-color: #fff;
}
}

View File

@@ -5,6 +5,7 @@
.vxe-toolbar .vxe-tools--wrapper,
.vxe-toolbar .vxe-tools--operate button:first-child {
margin: 0;
margin-left: 10px;
}
.vxe-toolbar .vxe-tools--wrapper,
@@ -13,11 +14,6 @@
border-radius: 0 !important;
}
.vxe-toolbar .vxe-tools--wrapper,
.vxe-toolbar .vxe-tools--operate button:first-child {
margin-left: 10px;
}
.vxe-toolbar .vxe-tools--wrapper,
.vxe-toolbar .vxe-tools--operate .vxe-custom--wrapper {
margin-left: 1px;

View File

@@ -1,6 +1,6 @@
$vxe-primary-color: rgb(9, 96, 189) !default;
$vxe-table-row-current-background-color: rgba(9, 96, 189, 0.3);
$vxe-table-row-hover-current-background-color: rgba(9, 96, 189, 0.2);
$vxe-table-column-hover-background-color: rgba(9, 96, 189, 0.3);
$vxe-table-column-current-background-color: rgba(9, 96, 189, 0.2);
$vxe-primary-color: rgb(9 96 189) !default;
$vxe-table-row-current-background-color: rgb(9 96 189 / 30%);
$vxe-table-row-hover-current-background-color: rgb(9 96 189 / 20%);
$vxe-table-column-hover-background-color: rgb(9 96 189 / 30%);
$vxe-table-column-current-background-color: rgb(9 96 189 / 20%);
$vxe-table-validate-error-color: #f56c6c;

View File

@@ -8,10 +8,6 @@
}
}
span.anticon:not(.app-iconify) {
vertical-align: 0.125em !important;
}
.ant-back-top {
right: 20px;
bottom: 20px;

View File

@@ -4,8 +4,8 @@
.ant-input {
&-number,
&-number-group-wrapper {
min-width: 110px;
width: 100% !important;
min-width: 110px;
max-width: 100%;
}
}

View File

@@ -16,9 +16,9 @@ html[data-theme='dark'] {
}
.ant-pagination-item-active {
background-color: @primary-color !important;
border: none;
border-radius: none !important;
background-color: @primary-color !important;
a {
color: @white !important;
@@ -32,9 +32,9 @@ html[data-theme='dark'] {
&.mini {
.ant-pagination-prev,
.ant-pagination-next {
font-size: 12px;
color: @text-color-base;
border: 1px solid;
color: @text-color-base;
font-size: 12px;
}
.ant-pagination-prev:hover,
@@ -50,9 +50,9 @@ html[data-theme='dark'] {
.ant-pagination-next,
.ant-pagination-item {
margin: 0 4px !important;
background-color: #f4f4f5 !important;
border: none;
border-radius: none !important;
background-color: #f4f4f5 !important;
a {
margin-top: 1px;
@@ -65,9 +65,9 @@ html[data-theme='dark'] {
}
.ant-pagination-item-active {
background-color: @primary-color !important;
border: none;
border-radius: none !important;
background-color: @primary-color !important;
a {
color: @white !important;

View File

@@ -44,10 +44,6 @@
.ant-table {
.ant-table-content {
.ant-table-scroll {
.ant-table-hide-scrollbar {
//overflow-x: auto !important;
}
.ant-table-body {
overflow: auto !important;
}

1415
src/design/dark.less Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
@import 'ant/index.less';
@import './theme.less';
@import './entry.css';
@import './dark.less';
input:-webkit-autofill {
box-shadow: 0 0 0 1000px white inset !important;
@@ -24,6 +25,7 @@ body {
height: 100%;
overflow: visible;
overflow-x: hidden;
position: relative;
&.color-weak {
filter: invert(80%);
@@ -43,3 +45,10 @@ svg,
span {
outline: none;
}
// 保持 和 windi 一样的全局样式,减少升级带来的影响
ul {
margin: 0;
padding: 0;
list-style: none;
}

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