Compare commits

..

147 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
vben
a969d2091a chore: release 2.10.0, 2.x 版本不再更新,如有任何pr,请提交到 next分支 2023-04-08 00:08:22 +08:00
vben
e285947716 fix: cross-env script 2023-04-08 00:04:56 +08:00
zhenbintuo
cfbd5e9851 修复insertNodesByKey方法批量添加异常问题 (#2695)
当批量添加节点parentKey为空时,未赋值treeDataRef导致添加异常
2023-04-08 00:03:11 +08:00
kissGoodBye
122db78e84 feat(directives/ellipsis): add ellipsis directive (#2688) 2023-04-07 09:22:51 +08:00
vben
5c69b3d5a8 chore: 删除无用文件 2023-04-07 00:15:06 +08:00
vben
335f30c887 chore: 优化 useScrollTo、useWindowSizeFn 2023-04-07 00:12:26 +08:00
vben
6a9bd686d5 chore: remove LazyContainer 2023-04-06 23:49:20 +08:00
vben
6890dd7201 perf: 新包使用更严格的eslint 2023-04-06 23:28:37 +08:00
vben
762e5dee14 fix: 修复循环依赖问题 2023-04-06 23:16:15 +08:00
vben
fa18365c21 chore: 移除 useTimeoutFn文件,使用 vueuse 2023-04-06 22:49:16 +08:00
vben
feadf64ee3 fix: Icon加载问题 2023-04-06 22:47:39 +08:00
vben
553ee9c7ae chore: revert fix(Icon): Cannot access Icon before initialization #2680 (#2683) 2023-04-06 22:44:26 +08:00
vben
9f8e010534 feat: 重构 useRefs 2023-04-06 22:17:18 +08:00
vben
14ba72dd1c fix: 主题样式设置无效,close #2685 2023-04-06 22:12:17 +08:00
前端爱码士
be0d35394c fix: 解决项目配置/界面功能/自动锁屏&菜单展开宽度样式变形问题 (#2681) 2023-04-06 22:03:26 +08:00
Kirk Lin
b14a15e66b refactor: axios Canceler use AbortController (#2676) 2023-04-06 22:03:12 +08:00
Kirk Lin
ae5f5cb13c feat: sync mitt (#2677) 2023-04-06 22:03:00 +08:00
Kirk Lin
ccaa84c305 fix: axios type (#2678)
* fix: axios type

* fix: axios type
2023-04-06 22:02:42 +08:00
Kirk Lin
357beabaeb fix: an object literal cannot have multiple properties with the same name. (#2682) 2023-04-06 22:02:23 +08:00
xingyu
7469312ffc fix(Icon): Cannot access Icon before initialization #2680 (#2683)
* fix(Icon): Cannot access Icon before initialization

* fix(comps): import eslint

* fix(Icon): Cannot access Icon before initialization

* fix(comps): import eslint
2023-04-06 22:02:02 +08:00
oooplz
401fcaf325 fix: 空数组覆盖有效数组 (#2687) 2023-04-06 22:01:25 +08:00
vben
5e8ef2f64f feat: 添加 defineOptions 2023-04-06 00:08:17 +08:00
vben
8e5a6b7ce5 perf: 修复部分 eslint 错误 2023-04-05 22:47:14 +08:00
vben
279977b817 feat: 逐步抽离部分包到packages 2023-04-05 22:29:16 +08:00
vben
a5ed79fc94 Merge branch 'main' of https://github.com/anncwb/vue-vben-admin 2023-04-05 21:25:37 +08:00
Cherelle Spencer
660024c6f3 perf: tsconfig 同步vite配置修改, 增加路径映射 (#2675)
Co-authored-by: 苗大 <caoshengmiao@hypergryph.com>
2023-04-05 20:34:53 +08:00
vben
d25df8321e chore: windcss -> unocss 2023-04-05 18:17:55 +08:00
luoawai
6b30c9f7bb fix(useFormItem):修复ApiSelect的onChange事件获取不到第二个参数(#2592) (#2674) 2023-04-05 15:58:43 +08:00
vben
aedb8e53aa perf: 优化env字段 2023-04-05 15:58:03 +08:00
408 changed files with 9709 additions and 4642 deletions

3
.dockerignore Normal file
View File

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

3
.env
View File

@@ -1,5 +1,2 @@
# spa-title
VITE_GLOB_APP_TITLE = Vben Admin
# spa shortname
VITE_GLOB_APP_SHORT_NAME = vue_vben_admin

View File

@@ -4,10 +4,6 @@ VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api

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

View File

@@ -104,7 +104,6 @@
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"vben",
"windi",
"browserslist",
"tailwindcss",
"esnext",

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

@@ -1,107 +0,0 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, ''));
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '');
/** @type {import('cz-git').UserConfig} */
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
prompt: {
/** @use `yarn commit :f` */
alias: {
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
b: 'build: bump dependencies',
c: 'chore: update config',
},
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
defaultScope: scopeComplete,
scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
allowCustomIssuePrefixs: false,
// English
typesAppend: [
{ value: 'wip', name: 'wip: work in process' },
{ value: 'workflow', name: 'workflow: workflow improvements' },
{ value: 'types', name: 'types: type definition file changes' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
};

View File

@@ -1,15 +0,0 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

View File

@@ -1,4 +1,4 @@
module.exports = {
root: true,
extends: ['@vben'],
extends: ['@vben/eslint-config/strict'],
};

View File

@@ -1,10 +0,0 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc

View File

@@ -1,19 +0,0 @@
module.exports = {
printWidth: 100,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
trailingComma: 'all',
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
plugins: ['prettier-plugin-packagejson'],
overrides: [
{
files: '.*rc',
options: {
parser: 'json',
},
},
],
};

View File

@@ -1,2 +0,0 @@
dist
public

View File

@@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

View File

@@ -2,7 +2,7 @@ import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
entries: ['src/index', 'src/strict'],
declaration: true,
rollup: {
emitCJS: true,

View File

@@ -17,6 +17,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./strict": {
"types": "./dist/strict.d.ts",
"import": "./dist/strict.mjs",
"require": "./dist/strict.cjs"
}
},
"main": "./dist/index.cjs",
@@ -31,12 +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-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.10.0",
"vue-eslint-parser": "^9.1.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.17.0",
"vue-eslint-parser": "^9.3.1"
}
}

View File

@@ -17,7 +17,7 @@ export default {
createDefaultProgram: false,
extraFileExtensions: ['.vue'],
},
plugins: ['vue', '@typescript-eslint'],
plugins: ['vue', '@typescript-eslint', 'import'],
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
@@ -25,21 +25,15 @@ export default {
'plugin:prettier/recommended',
],
rules: {
'no-unused-vars': 'off',
'no-case-declarations': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'space-before-function-paren': 'off',
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
@@ -47,15 +41,19 @@ export default {
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'space-before-function-paren': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
@@ -78,5 +76,16 @@ export default {
},
],
'vue/multi-word-component-names': 'off',
// 'sort-imports': [
// 'error',
// {
// ignoreCase: true,
// ignoreDeclarationSort: false,
// ignoreMemberSort: false,
// memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
// allowSeparatedGroups: false,
// },
// ],
},
globals: { defineOptions: 'readonly' },
};

View File

@@ -0,0 +1,57 @@
export default {
extends: ['@vben'],
plugins: ['simple-import-sort'],
rules: {
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': 'allow-with-description',
'ts-check': false,
},
],
/**
* 【强制】关键字前后有一个空格
* @link https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/keyword-spacing.md
*/
'keyword-spacing': 'off',
'@typescript-eslint/keyword-spacing': [
'error',
{
before: true,
after: true,
overrides: {
return: { after: true },
throw: { after: true },
case: { after: true },
},
},
],
/**
* 禁止出现空函数,普通函数(非 async/await/generator、箭头函数、类上的方法除外
* @link https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-function.md
*/
'no-empty-function': 'off',
'@typescript-eslint/no-empty-function': [
'error',
{
allow: ['arrowFunctions', 'functions', 'methods'],
},
],
/**
* 优先使用 interface 而不是 type 定义对象类型
* @link https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md
*/
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'vue/attributes-order': 'error',
'vue/require-default-prop': 'error',
},
};

View File

@@ -1,107 +0,0 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, ''));
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '');
/** @type {import('cz-git').UserConfig} */
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
prompt: {
/** @use `yarn commit :f` */
alias: {
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
b: 'build: bump dependencies',
c: 'chore: update config',
},
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
defaultScope: scopeComplete,
scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
allowCustomIssuePrefixs: false,
// English
typesAppend: [
{ value: 'wip', name: 'wip: work in process' },
{ value: 'workflow', name: 'workflow: workflow improvements' },
{ value: 'types', name: 'types: type definition file changes' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
};

View File

@@ -1,15 +0,0 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

View File

@@ -1,4 +1,4 @@
module.exports = {
root: true,
extends: ['@vben'],
extends: ['@vben/eslint-config/strict'],
};

View File

@@ -1,10 +0,0 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc

View File

@@ -1,19 +0,0 @@
module.exports = {
printWidth: 100,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
trailingComma: 'all',
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
plugins: ['prettier-plugin-packagejson'],
overrides: [
{
files: '.*rc',
options: {
parser: 'json',
},
},
],
};

View File

@@ -1,2 +0,0 @@
dist
public

View File

@@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

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,9 @@ export default {
},
],
rules: {
'media-feature-range-notation': null,
'selector-not-notation': null,
'import-notation': null,
'function-no-unknown': null,
'selector-class-pattern': null,
'selector-pseudo-class-no-unknown': [

View File

@@ -15,10 +15,11 @@
"files": [
"base.json",
"node.json",
"vue.json",
"vue-app.json",
"node-server.json"
],
"dependencies": {
"@types/node": "^18.15.11"
"@types/node": "^20.4.0",
"vite": "^4.4.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Vue Library",
"display": "Vue Application",
"extends": "./base.json",
"compilerOptions": {
"jsx": "preserve",

View File

@@ -1,107 +0,0 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, ''));
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '');
/** @type {import('cz-git').UserConfig} */
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
prompt: {
/** @use `yarn commit :f` */
alias: {
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
b: 'build: bump dependencies',
c: 'chore: update config',
},
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
defaultScope: scopeComplete,
scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
allowCustomIssuePrefixs: false,
// English
typesAppend: [
{ value: 'wip', name: 'wip: work in process' },
{ value: 'workflow', name: 'workflow: workflow improvements' },
{ value: 'types', name: 'types: type definition file changes' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
};

View File

@@ -1,15 +0,0 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

View File

@@ -1,4 +1,4 @@
module.exports = {
root: true,
extends: ['@vben'],
extends: ['@vben/eslint-config/strict'],
};

View File

@@ -1,10 +0,0 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc

View File

@@ -1,19 +0,0 @@
module.exports = {
printWidth: 100,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
trailingComma: 'all',
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
plugins: ['prettier-plugin-packagejson'],
overrides: [
{
files: '.*rc',
options: {
parser: 'json',
},
},
],
};

View File

@@ -1,2 +0,0 @@
dist
public

View File

@@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

View File

@@ -31,24 +31,28 @@
"stub": "pnpm unbuild --stub"
},
"dependencies": {
"vite": "^4.3.0-beta.1"
"@ant-design/colors": "^7.0.0",
"vite": "^4.4.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"ant-design-vue": "^3.2.16",
"dayjs": "^1.11.7",
"dotenv": "^16.0.3",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"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",
"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": "^3.1.0",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.9.2",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-windicss": "^1.8.10"
"vite-plugin-svg-icons": "^2.0.1"
}
}

View File

@@ -1,14 +1,18 @@
import { type UserConfig, defineConfig, mergeConfig, loadEnv } from 'vite';
import { resolve } from 'node:path';
import dayjs from 'dayjs';
import { readPackageJSON } from 'pkg-types';
import { defineConfig, loadEnv, mergeConfig, type UserConfig } from 'vite';
import { createPlugins } from '../plugins';
import { generateModifyVars } from '../utils/modifyVars';
import { commonConfig } from './common';
import { createPlugins } from '../plugins';
import dayjs from 'dayjs';
interface DefineOptions {
overrides?: UserConfig;
options?: {};
options?: {
//
};
}
function defineApplicationConfig(defineOptions: DefineOptions = {}) {
@@ -17,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({
@@ -31,13 +38,7 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
const pathResolve = (pathname: string) => resolve(root, '.', pathname);
const applicationConfig: UserConfig = {
optimizeDeps: {
include: [
'@iconify/iconify',
'ant-design-vue/es/locale/zh_CN',
'ant-design-vue/es/locale/en_US',
],
},
base: VITE_PUBLIC_PATH,
resolve: {
alias: [
{
@@ -72,9 +73,11 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
cssTarget: 'chrome80',
rollupOptions: {
output: {
// 入口文件名
entryFileNames: 'assets/[name].js',
manualChunks: {
vue: ['vue', 'pinia', 'vue-router'],
antdv: ['ant-design-vue', '@ant-design/icons-vue'],
antd: ['ant-design-vue', '@ant-design/icons-vue'],
},
},
},
@@ -90,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

@@ -1,11 +1,13 @@
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,
@@ -15,6 +17,11 @@ const commonConfig: UserConfig = {
maxParallelFileOps: 3,
},
},
};
plugins: [
UnoCSS({
presets: [presetUno(), presetTypography()],
}),
],
});
export { commonConfig };

View File

@@ -1,5 +1,42 @@
function definePackageConfig() {
// TODO:
import { readPackageJSON } from 'pkg-types';
import { defineConfig, mergeConfig, type UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import { commonConfig } from './common';
interface DefineOptions {
overrides?: UserConfig;
options?: {
//
};
}
function definePackageConfig(defineOptions: DefineOptions = {}) {
const { overrides = {} } = defineOptions;
const root = process.cwd();
return defineConfig(async ({ mode }) => {
const { dependencies = {}, peerDependencies = {} } = await readPackageJSON(root);
const packageConfig: UserConfig = {
build: {
lib: {
entry: 'src/index.ts',
formats: ['es'],
fileName: () => 'index.mjs',
},
rollupOptions: {
external: [...Object.keys(dependencies), ...Object.keys(peerDependencies)],
},
},
plugins: [
dts({
logLevel: 'error',
}),
],
};
const mergedConfig = mergeConfig(commonConfig(mode), packageConfig);
return mergeConfig(mergedConfig, overrides);
});
}
export { definePackageConfig };

View File

@@ -1 +1,2 @@
export * from './config/application';
export * from './config/package';

View File

@@ -1,8 +1,9 @@
import colors from 'picocolors';
import { readPackageJSON } from 'pkg-types';
import { type PluginOption } from 'vite';
import { getEnvConfig } from '../utils/env';
import { createContentHash } from '../utils/hash';
import { readPackageJSON } from 'pkg-types';
import colors from 'picocolors';
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js';
const PLUGIN_NAME = 'app-config';
@@ -26,7 +27,8 @@ async function createAppConfigPlugin({
return {
name: PLUGIN_NAME,
async configResolved(_config) {
const appTitle = _config?.env?.VITE_GLOB_APP_SHORT_NAME ?? '';
const appTitle = _config?.env?.VITE_GLOB_APP_TITLE ?? '';
// appTitle = appTitle.replace(/\s/g, '_').replace(/-/g, '_');
publicPath = _config.base;
source = await getConfigSource(appTitle);
},
@@ -35,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,
@@ -72,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,14 +1,14 @@
import { type PluginOption } from 'vite';
import { configHtmlPlugin } from './html';
import { configMockPlugin } from './mock';
import { configCompressPlugin } from './compress';
import { configVisualizerConfig } from './visualizer';
import { configSvgIconsPlugin } from './svgSprite';
import { createAppConfigPlugin } from './appConfig';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { type PluginOption } from 'vite';
import purgeIcons from 'vite-plugin-purge-icons';
import windiCSS from 'vite-plugin-windicss';
import { createAppConfigPlugin } from './appConfig';
import { configCompressPlugin } from './compress';
import { configHtmlPlugin } from './html';
import { configMockPlugin } from './mock';
import { configSvgIconsPlugin } from './svgSprite';
import { configVisualizerConfig } from './visualizer';
interface Options {
isBuild: boolean;
@@ -24,9 +24,6 @@ async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyz
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild });
vitePlugins.push(appConfigPlugin);
// vite-plugin-windicss
vitePlugins.push(windiCSS());
// vite-plugin-html
vitePlugins.push(configHtmlPlugin({ isBuild }));

View File

@@ -3,9 +3,10 @@
* https://github.com/anncwb/vite-plugin-svg-icons
*/
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import { resolve } from 'node:path';
import type { PluginOption } from 'vite';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
export function configSvgIconsPlugin({ isBuild }: { isBuild: boolean }) {
const svgIconsPlugin = createSvgIconsPlugin({

View File

@@ -1,8 +1,8 @@
/**
* Package file volume analysis
*/
import { type PluginOption } from 'vite';
import visualizer from 'rollup-plugin-visualizer';
import { type PluginOption } from 'vite';
export function configVisualizerConfig() {
return visualizer({

View File

@@ -1,6 +1,7 @@
import { join } from 'node:path';
import dotenv from 'dotenv';
import { readFile } from 'fs-extra';
import { join } from 'node:path';
/**
* 获取当前环境下生效的配置文件名

View File

@@ -1,6 +1,7 @@
import { generate } from '@ant-design/colors';
import { resolve } from 'node:path';
// @ts-ignore
import { generate } from '@ant-design/colors';
// @ts-ignore: typo
import { getThemeVariables } from 'ant-design-vue/dist/theme';
const primaryColor = '#0960bd';

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.9.1",
"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": "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",
@@ -66,22 +67,21 @@
}
},
"dependencies": {
"@ant-design/colors": "^7.0.0",
"@ant-design/icons-vue": "^6.1.0",
"@iconify/iconify": "^3.1.0",
"@logicflow/core": "^1.2.1",
"@logicflow/extension": "^1.2.1",
"@vue/runtime-core": "^3.2.47",
"@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.4",
"@iconify/iconify": "^3.1.1",
"@logicflow/core": "^1.2.9",
"@logicflow/extension": "^1.2.9",
"@vben/hooks": "workspace:*",
"@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",
@@ -89,62 +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",
"@iconify/json": "^2.2.45",
"@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:*",
"@vitejs/plugin-vue": "^4.1.0",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@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",
"vite": "^4.3.0-beta.1",
"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

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/eslint-config/strict'],
};

View File

@@ -0,0 +1,10 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@@ -0,0 +1,38 @@
{
"name": "@vben/hooks",
"version": "1.0.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": {
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "packages/hooks"
},
"license": "MIT",
"sideEffects": false,
"exports": {
".": {
"default": "./src/index.ts"
}
},
"main": "./src/index.ts",
"module": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"//build": "pnpm unbuild",
"//stub": "pnpm unbuild --stub",
"clean": "pnpm rimraf .turbo node_modules dist",
"lint": "pnpm eslint ."
},
"dependencies": {
"@vueuse/core": "^10.2.1",
"vue": "^3.3.4"
},
"devDependencies": {
"@vben/types": "workspace:*"
}
}

View File

@@ -0,0 +1,6 @@
export * from './onMountedOrActivated';
export * from './useAttrs';
export * from './useRefs';
export * from './useScrollTo';
export * from './useWindowSizeFn';
export { useTimeoutFn } from '@vueuse/core';

View File

@@ -0,0 +1,25 @@
import { type AnyFunction } from '@vben/types';
import { nextTick, onActivated, onMounted } from 'vue';
/**
* 在 OnMounted 或者 OnActivated 时触发
* @param hook 任何函数(包括异步函数)
*/
function onMountedOrActivated(hook: AnyFunction) {
let mounted: boolean;
onMounted(() => {
hook();
nextTick(() => {
mounted = true;
});
});
onActivated(() => {
if (mounted) {
hook();
}
});
}
export { onMountedOrActivated };

View File

@@ -1,6 +1,7 @@
import { type Recordable } from '@vben/types';
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
import type { Ref } from 'vue';
interface Params {
interface UseAttrsOptions {
excludeListeners?: boolean;
excludeKeys?: string[];
excludeDefaultKeys?: boolean;
@@ -9,15 +10,15 @@ interface Params {
const DEFAULT_EXCLUDE_KEYS = ['class', 'style'];
const LISTENER_PREFIX = /^on[A-Z]/;
export function entries<T>(obj: Recordable<T>): [string, T][] {
function entries<T>(obj: Recordable<T>): [string, T][] {
return Object.keys(obj).map((key: string) => [key, obj[key]]);
}
export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
function useAttrs(options: UseAttrsOptions = {}): Recordable<any> {
const instance = getCurrentInstance();
if (!instance) return {};
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params;
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = options;
const attrs = shallowRef({});
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []);
@@ -31,10 +32,12 @@ export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
}
return acm;
}, {} as Recordable);
}, {} as Recordable<any>);
attrs.value = res;
});
return attrs;
}
export { useAttrs, type UseAttrsOptions };

View File

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

View File

@@ -1,35 +1,34 @@
import { isFunction, isUnDef } from '/@/utils/is';
import { ref, unref } from 'vue';
import { shallowRef, unref } from 'vue';
export interface ScrollToParams {
interface UseScrollToOptions {
el: any;
to: number;
duration?: number;
callback?: () => any;
}
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
function easeInOutQuad(t: number, b: number, c: number, d: number) {
t /= d / 2;
if (t < 1) {
return (c / 2) * t * t + b;
}
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
};
const move = (el: HTMLElement, amount: number) => {
}
function move(el: HTMLElement, amount: number) {
el.scrollTop = amount;
};
}
const position = (el: HTMLElement) => {
return el.scrollTop;
};
export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams) {
const isActiveRef = ref(false);
function useScrollTo({ el, to, duration = 500, callback }: UseScrollToOptions) {
const isActiveRef = shallowRef(false);
const start = position(el);
const change = to - start;
const increment = 20;
let currentTime = 0;
duration = isUnDef(duration) ? 500 : duration;
const animateScroll = function () {
if (!unref(isActiveRef)) {
@@ -41,7 +40,7 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
if (currentTime < duration && unref(isActiveRef)) {
requestAnimationFrame(animateScroll);
} else {
if (callback && isFunction(callback)) {
if (callback && typeof callback === 'function') {
callback();
}
}
@@ -57,3 +56,5 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
return { start: run, stop };
}
export { useScrollTo, type UseScrollToOptions };

View File

@@ -1,12 +1,15 @@
import { type AnyFunction } from '@vben/types';
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core';
interface WindowSizeOptions {
interface UseWindowSizeOptions {
wait?: number;
once?: boolean;
immediate?: boolean;
listenerOptions?: AddEventListenerOptions | boolean;
}
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) {
const { wait = 150, immediate } = options;
let handler = () => {
fn();
};
@@ -14,7 +17,7 @@ export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOp
handler = handleSize;
const start = () => {
if (options && options.immediate) {
if (immediate) {
handler();
}
window.addEventListener('resize', handler);
@@ -31,5 +34,7 @@ export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOp
tryOnUnmounted(() => {
stop();
});
return [start, stop];
return { start, stop };
}
export { useWindowSizeFn, type UseWindowSizeOptions };

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/vue-app.json",
"include": ["src"]
}

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/eslint-config/strict'],
};

View File

@@ -0,0 +1,10 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@@ -0,0 +1,31 @@
{
"name": "@vben/types",
"version": "1.0.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": {
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "packages/types"
},
"license": "MIT",
"sideEffects": false,
"exports": {
".": {
"default": "./src/index.ts"
}
},
"main": "./src/index.ts",
"module": "./src/index.ts",
"files": [
"dist"
],
"scripts": {
"//build": "pnpm unbuild",
"//stub": "pnpm unbuild --stub",
"clean": "pnpm rimraf .turbo node_modules dist",
"lint": "pnpm eslint ."
}
}

View File

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

View File

@@ -0,0 +1,58 @@
/**
* 任意类型的异步函数
*/
type AnyPromiseFunction = (...arg: any[]) => PromiseLike<any>;
/**
* 任意类型的普通函数
*/
type AnyNormalFunction = (...arg: any[]) => any;
/**
* 任意类型的函数
*/
type AnyFunction = AnyNormalFunction | AnyPromiseFunction;
/**
* T | null 包装
*/
type Nullable<T> = T | null;
/**
* T | Not null 包装
*/
type NonNullable<T> = T extends null | undefined ? never : T;
/**
* 字符串类型对象
*/
type Recordable<T> = Record<string, T>;
/**
* 字符串类型对象(只读)
*/
interface ReadonlyRecordable<T = any> {
readonly [key: string]: T;
}
/**
* setTimeout 返回值类型
*/
type TimeoutHandle = ReturnType<typeof setTimeout>;
/**
* setInterval 返回值类型
*/
type IntervalHandle = ReturnType<typeof setInterval>;
export {
type AnyFunction,
type AnyNormalFunction,
type AnyPromiseFunction,
type IntervalHandle,
type NonNullable,
type Nullable,
type ReadonlyRecordable,
type Recordable,
type TimeoutHandle,
};

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/vue-app.json",
"include": ["src"]
}

6120
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>
@@ -8,14 +8,18 @@
<script lang="ts" setup>
import { ConfigProvider } from 'ant-design-vue';
import { AppProvider } from '/@/components/Application';
import { useTitle } from '/@/hooks/web/useTitle';
import { useLocale } from '/@/locales/useLocale';
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

@@ -1,5 +1,6 @@
import { defHttp } from '/@/utils/http/axios';
import { DemoOptionsItem, selectParams } from './model/optionsModel';
enum Api {
OPTIONS_LIST = '/select/getDemoOptions',
}

View File

@@ -1,4 +1,5 @@
import type { RouteMeta } from 'vue-router';
export interface RouteItem {
path: string;
component: any;

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,7 +22,7 @@
import type { DropMenu } from '/@/components/Dropdown';
import { ref, watchEffect, unref, computed } from 'vue';
import { Dropdown } from '/@/components/Dropdown';
import { Icon } from '/@/components/Icon';
import Icon from '@/components/Icon/Icon.vue';
import { useLocale } from '/@/locales/useLocale';
import { localeList } from '/@/settings/localeSetting';

View File

@@ -14,6 +14,7 @@
import AppSearchKeyItem from './AppSearchKeyItem.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
const { prefixCls } = useDesign('app-search-footer');
const { t } = useI18n();
</script>

View File

@@ -4,7 +4,8 @@
</span>
</template>
<script lang="ts" setup>
import { Icon } from '/@/components/Icon';
import Icon from '@/components/Icon/Icon.vue';
defineProps({
icon: String,
});

View File

@@ -61,11 +61,11 @@
import { computed, unref, ref, watch, nextTick } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import AppSearchFooter from './AppSearchFooter.vue';
import Icon from '/@/components/Icon';
import Icon from '@/components/Icon/Icon.vue';
// @ts-ignore
import vClickOutside from '/@/directives/clickOutside';
import { useDesign } from '/@/hooks/web/useDesign';
import { useRefs } from '/@/hooks/core/useRefs';
import { useRefs } from '@vben/hooks';
import { useMenuSearch } from './useMenuSearch';
import { useI18n } from '/@/hooks/web/useI18n';
import { useAppInject } from '/@/hooks/web/useAppInject';
@@ -81,7 +81,7 @@
const { t } = useI18n();
const { prefixCls } = useDesign('app-search-modal');
const [refs, setRefs] = useRefs();
const { refs, setRefs } = useRefs();
const { getIsMobile } = useAppInject();
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } =

View File

@@ -1,10 +1,11 @@
import type { Menu } from '/@/router/types';
import { type Menu } from '/@/router/types';
import { type AnyFunction } from '@vben/types';
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
import { getMenus } from '/@/router/menus';
import { cloneDeep } from 'lodash-es';
import { filter, forEach } from '/@/utils/helper/treeHelper';
import { useGo } from '/@/hooks/web/usePage';
import { useScrollTo } from '/@/hooks/event/useScrollTo';
import { useScrollTo } from '@vben/hooks';
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
import { useI18n } from '/@/hooks/web/useI18n';
@@ -26,7 +27,7 @@ function createSearchReg(key: string) {
return new RegExp(str);
}
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref, emit: AnyFunction) {
const searchResult = ref<SearchResult[]>([]);
const keyword = ref('');
const activeIndex = ref(-1);

View File

@@ -9,7 +9,7 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { Icon } from '/@/components/Icon';
import Icon from '@/components/Icon/Icon.vue';
import { useDesign } from '/@/hooks/web/useDesign';
const props = defineProps({

View File

@@ -8,20 +8,18 @@
</Button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { Button } from 'ant-design-vue';
export default defineComponent({
import { computed, unref } from 'vue';
import Icon from '@/components/Icon/Icon.vue';
import { buttonProps } from './props';
import { useAttrs } from '@vben/hooks';
defineOptions({
name: 'AButton',
extends: Button,
inheritAttrs: false,
});
</script>
<script lang="ts" setup>
import { computed, unref } from 'vue';
import Icon from '/@/components/Icon/src/Icon.vue';
import { buttonProps } from './props';
import { useAttrs } from '/@/hooks/core/useAttrs';
const props = defineProps(buttonProps);
// get component class

View File

@@ -4,7 +4,7 @@
import { Popconfirm } from 'ant-design-vue';
import { extendSlots } from '/@/utils/helper/tsxHelper';
import { omit } from 'lodash-es';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { useAttrs } from '@vben/hooks';
import { useI18n } from '/@/hooks/web/useI18n';
const props = {
@@ -39,7 +39,7 @@
return () => {
const bindValues = omit(unref(getBindValues), 'icon');
const btnBind = omit(bindValues, 'title') as Recordable;
const btnBind = omit(bindValues, 'title') as any;
if (btnBind.disabled) btnBind.color = '';
const Button = h(BasicButton, btnBind, extendSlots(slots));

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" />
@@ -90,9 +90,10 @@
import { Button } from '/@/components/Button';
import { isFunction } from '/@/utils/is';
import { useSlider, grid } from './data';
const ListItem = List.Item;
const CardMeta = Card.Meta;
const TypographyText = Typography.Text;
const TypographyParagraph = Typography.Paragraph;
// 获取slider属性
const sliderProp = computed(() => useSlider(4));
// 组件接收参数

View File

@@ -6,8 +6,9 @@
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { onClickOutside } from '@vueuse/core';
const emit = defineEmits(['mounted', 'clickOutside']);
const wrap = ref<ElRef>(null);
const wrap = ref(null);
onClickOutside(wrap, () => {
emit('clickOutside');

View File

@@ -3,10 +3,20 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue';
import {
type PropType,
ref,
onMounted,
onUnmounted,
watchEffect,
watch,
unref,
nextTick,
} from 'vue';
import type { Nullable } from '@vben/types';
import { useWindowSizeFn } from '@vben/hooks';
import { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '/@/store/modules/app';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import CodeMirror from 'codemirror';
import { MODE } from './../typing';
// css

View File

@@ -1,10 +1,8 @@
import { withInstall } from '/@/utils';
import collapseContainer from './src/collapse/CollapseContainer.vue';
import scrollContainer from './src/ScrollContainer.vue';
import lazyContainer from './src/LazyContainer.vue';
export const CollapseContainer = withInstall(collapseContainer);
export const ScrollContainer = withInstall(scrollContainer);
export const LazyContainer = withInstall(lazyContainer);
export * from './src/typing';

View File

@@ -1,145 +0,0 @@
<template>
<transition-group
class="h-full w-full"
v-bind="$attrs"
ref="elRef"
:name="transitionName"
:tag="tag"
mode="out-in"
>
<div key="component" v-if="isInit">
<slot :loading="loading"></slot>
</div>
<div key="skeleton" v-else>
<slot name="skeleton" v-if="$slots.skeleton"></slot>
<Skeleton v-else />
</div>
</transition-group>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
import { Skeleton } from 'ant-design-vue';
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
interface State {
isInit: boolean;
loading: boolean;
intersectionObserverInstance: IntersectionObserver | null;
}
const props = {
/**
* Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
*/
timeout: { type: Number },
/**
* The viewport where the component is located.
* If the component is scrolling in the page container, the viewport is the container
*/
viewport: {
type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>,
default: () => null,
},
/**
* Preload threshold, css unit
*/
threshold: { type: String, default: '0px' },
/**
* The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
*/
direction: {
type: String,
default: 'vertical',
validator: (v) => ['vertical', 'horizontal'].includes(v),
},
/**
* The label name of the outer container that wraps the component
*/
tag: { type: String, default: 'div' },
maxWaitingTime: { type: Number, default: 80 },
/**
* transition name
*/
transitionName: { type: String, default: 'lazy-container' },
};
export default defineComponent({
name: 'LazyContainer',
components: { Skeleton },
inheritAttrs: false,
props,
emits: ['init'],
setup(props, { emit }) {
const elRef = ref();
const state = reactive<State>({
isInit: false,
loading: false,
intersectionObserverInstance: null,
});
onMounted(() => {
immediateInit();
initIntersectionObserver();
});
// If there is a set delay time, it will be executed immediately
function immediateInit() {
const { timeout } = props;
timeout &&
useTimeoutFn(() => {
init();
}, timeout);
}
function init() {
state.loading = true;
useTimeoutFn(() => {
if (state.isInit) return;
state.isInit = true;
emit('init');
}, props.maxWaitingTime || 80);
}
function initIntersectionObserver() {
const { timeout, direction, threshold } = props;
if (timeout) return;
// According to the scrolling direction to construct the viewport margin, used to load in advance
let rootMargin = '0px';
switch (direction) {
case 'vertical':
rootMargin = `${threshold} 0px`;
break;
case 'horizontal':
rootMargin = `0px ${threshold}`;
break;
}
try {
const { stop, observer } = useIntersectionObserver({
rootMargin,
target: toRef(elRef.value, '$el'),
onIntersect: (entries: any[]) => {
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio;
if (isIntersecting) {
init();
if (observer) {
stop();
}
}
},
root: toRef(props, 'viewport'),
});
} catch (e) {
init();
}
}
return {
elRef,
...toRefs(state),
};
},
});
</script>

View File

@@ -7,7 +7,8 @@
<script lang="ts">
import { defineComponent, ref, unref, nextTick } from 'vue';
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar';
import { useScrollTo } from '/@/hooks/event/useScrollTo';
import { useScrollTo } from '@vben/hooks';
import { type Nullable } from '@vben/types';
export default defineComponent({
name: 'ScrollContainer',

View File

@@ -2,10 +2,10 @@
import { ref, unref, defineComponent, type PropType, type ExtractPropTypes } from 'vue';
import { isNil } from 'lodash-es';
import { Skeleton } from 'ant-design-vue';
import { useTimeoutFn } from '@vben/hooks';
import { CollapseTransition } from '/@/components/Transition';
import CollapseHeader from './CollapseHeader.vue';
import { triggerWindowResize } from '/@/utils/event';
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useDesign } from '/@/hooks/web/useDesign';
const collapseContainerProps = {

View File

@@ -2,7 +2,7 @@
import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
import type { FunctionalComponent, CSSProperties, PropType } from 'vue';
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import Icon from '/@/components/Icon';
import Icon from '@/components/Icon/Icon.vue';
import { Menu, Divider } from 'ant-design-vue';
const prefixCls = 'context-menu';
@@ -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)}>
{renderMenuItem(items)}
</Menu>
</div>
<Menu
inlineIndent={12}
mode="vertical"
class={prefixCls}
ref={wrapRef}
style={unref(getStyle)}
>
{renderMenuItem(items)}
</Menu>
);
};
},
@@ -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,13 +40,13 @@
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';
import { useI18n } from '/@/hooks/web/useI18n';
import type { ButtonProps } from '/@/components/Button';
import Icon from '/@/components/Icon';
import Icon from '@/components/Icon/Icon.vue';
const props = {
width: { type: [String, Number], default: '200px' },
@@ -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

@@ -1,284 +1,289 @@
<template>
<BasicModal
v-bind="$attrs"
@register="register"
:title="t('component.cropper.modalTitle')"
width="800px"
:canFullscreen="false"
@ok="handleOk"
:okText="t('component.cropper.okText')"
>
<div :class="prefixCls">
<div :class="`${prefixCls}-left`">
<div :class="`${prefixCls}-cropper`">
<CropperImage
v-if="src"
:src="src"
height="300px"
:circled="circled"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>
<div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Tooltip>
</Upload>
<Space>
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('reset')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', -45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', 45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleX')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleY')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', 0.1)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', -0.1)"
/>
</Tooltip>
</Space>
</div>
</div>
<div :class="`${prefixCls}-right`">
<div :class="`${prefixCls}-preview`">
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
</div>
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
<Avatar :src="previewSource" size="large" />
<Avatar :src="previewSource" :size="48" />
<Avatar :src="previewSource" :size="64" />
<Avatar :src="previewSource" :size="80" />
</div>
</template>
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing';
import { defineComponent, ref, PropType } from 'vue';
import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n';
type apiFunParams = { file: Blob; name: string; filename: string };
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
},
src: { type: String },
};
export default defineComponent({
name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = '';
const src = ref(props.src || '');
const previewSource = ref('');
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { prefixCls } = useDesign('cropper-am');
const [register, { closeModal, setModalProps }] = useModalInner();
const { t } = useI18n();
// Block upload
function handleBeforeUpload(file: File) {
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
};
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance;
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
cropper?.value?.[event]?.(arg);
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value);
try {
setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.url });
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-am';
.@{prefix-cls} {
display: flex;
&-left,
&-right {
height: 340px;
}
&-left {
width: 55%;
}
&-right {
width: 45%;
}
&-cropper {
height: 300px;
background: #eee;
background-image: linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
),
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
);
background-position: 0 0, 12px 12px;
background-size: 24px 24px;
}
&-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
}
&-preview {
width: 220px;
height: 220px;
margin: 0 auto;
overflow: hidden;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
&-group {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid @border-color-base;
}
}
</style>
<template>
<BasicModal
v-bind="$attrs"
@register="register"
:title="t('component.cropper.modalTitle')"
width="800px"
:canFullscreen="false"
@ok="handleOk"
:okText="t('component.cropper.okText')"
>
<div :class="prefixCls">
<div :class="`${prefixCls}-left`">
<div :class="`${prefixCls}-cropper`">
<CropperImage
v-if="src"
:src="src"
height="300px"
:circled="circled"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>
<div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Tooltip>
</Upload>
<Space>
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('reset')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', -45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', 45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleX')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleY')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', 0.1)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', -0.1)"
/>
</Tooltip>
</Space>
</div>
</div>
<div :class="`${prefixCls}-right`">
<div :class="`${prefixCls}-preview`">
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
</div>
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
<Avatar :src="previewSource" size="large" />
<Avatar :src="previewSource" :size="48" />
<Avatar :src="previewSource" :size="64" />
<Avatar :src="previewSource" :size="80" />
</div>
</template>
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing';
import { defineComponent, ref, PropType } from 'vue';
import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n';
type apiFunParams = { file: Blob; name: string; filename: string };
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
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', 'uploadError', 'register'],
setup(props, { emit }) {
let filename = '';
const src = ref(props.src || '');
const previewSource = ref('');
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { prefixCls } = useDesign('cropper-am');
const [register, { closeModal, setModalProps }] = useModalInner();
const { t } = useI18n();
// 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 = '';
previewSource.value = '';
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
};
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance;
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
cropper?.value?.[event]?.(arg);
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value);
try {
setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.url });
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-am';
.@{prefix-cls} {
display: flex;
&-left,
&-right {
height: 340px;
}
&-left {
width: 55%;
}
&-right {
width: 45%;
}
&-cropper {
height: 300px;
background: #eee;
background-image: linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
),
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
);
background-position: 0 0, 12px 12px;
background-size: 24px 24px;
}
&-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
}
&-preview {
width: 220px;
height: 220px;
margin: 0 auto;
overflow: hidden;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
&-group {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid @border-color-base;
}
}
</style>

View File

@@ -1,16 +1,23 @@
<script lang="tsx">
import type { DescriptionProps, DescInstance, DescItem } from './typing';
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
import type { CSSProperties } from 'vue';
import type { CollapseContainerOptions } from '/@/components/Container/index';
import { defineComponent, computed, ref, unref, toRefs } from 'vue';
import {
type CSSProperties,
type PropType,
defineComponent,
computed,
ref,
unref,
toRefs,
} from 'vue';
import { get } from 'lodash-es';
import { Descriptions } from 'ant-design-vue';
import { CollapseContainer } from '/@/components/Container/index';
import { useDesign } from '/@/hooks/web/useDesign';
import { isFunction } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { useAttrs } from '@vben/hooks';
const props = {
useCollapse: { type: Boolean, default: true },
@@ -22,7 +29,7 @@
},
bordered: { type: Boolean, default: true },
column: {
type: [Number, Object] as PropType<number | Recordable>,
type: [Number, Object],
default: () => {
return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
},
@@ -52,7 +59,7 @@
const getMergeProps = computed(() => {
return {
...props,
...(unref(propsRef) as Recordable),
...(unref(propsRef) as any),
} as DescriptionProps;
});
@@ -89,7 +96,10 @@
*/
function setDescProps(descProps: Partial<DescriptionProps>): void {
// Keep the last setDrawerProps
propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable;
propsRef.value = {
...(unref(propsRef) as Record<string, any>),
...descProps,
} as Record<string, any>;
}
// Prevent line breaks
@@ -121,6 +131,7 @@
return null;
}
const getField = get(_data, field);
// eslint-disable-next-line
if (getField && !toRefs(_data).hasOwnProperty(field)) {
return isFunction(render) ? render('', _data) : '';
}

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"
@@ -52,15 +52,15 @@
import { ScrollContainer } from '/@/components/Container';
import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { useAttrs } from '@vben/hooks';
export default defineComponent({
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);
@@ -68,8 +68,8 @@
const { prefixVar, prefixCls } = useDesign('basic-drawer');
const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps,
emitVisible: undefined,
setDrawerProps: setDrawerProps as any,
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;
}
}

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