Compare commits

..

44 Commits

Author SHA1 Message Date
invalid w
2dcd733e16 chore: release 2.11.5 2024-05-31 09:57:10 +08:00
xachary
d5fed8a47c perf(menu->search): highlight match chars when search menu (#3880)
* fix: state mutations in computed getters should be avoided

* fix: type about getDataSourceRef

* perf(menu search): highlight match chars when search menu
2024-05-31 09:55:27 +08:00
xachary
a6086f4cc8 chore(build): keep same hash for the same files (#3878)
* fix: state mutations in computed getters should be avoided

* fix: type about getDataSourceRef

* chore(build): keep same hash for the same files
2024-05-31 09:54:41 +08:00
Electrolux
d9cdf3f034 chore(upload): use uid && fix handleDelete (#3872)
* chore(upload): 重构组件,添加key作为标识

* fix(upload): 显式传入handleDelete

* update case
2024-05-31 09:54:05 +08:00
Electrolux
0bc01d8528 feat(form): add valueFormat for schema (#3873) 2024-05-31 09:53:50 +08:00
Jun
5d36b1a560 fix: 修复表单重置后,页面变化了,但是由于异步问题导致表单内部的状态没有及时同步 (#3882) 2024-05-29 21:56:56 +08:00
No name
c89417f523 fix(loading): useLoading is not working as expected (#3877)
* fix(loading): useLoading is not working as expected

* chore: remove comment

---------

Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
2024-05-28 09:14:55 +08:00
Electrolux
dcba0ca837 perf(form->util): duplicate judge (#3865) 2024-05-23 21:59:44 +08:00
Arlo
e69dd1e223 feat: add devtools plugin instead of inspector plugin (#3856) 2024-05-23 08:25:43 +08:00
xachary
fee808198e fix(useDataSource): state mutations in computed getters should be avoided (#3859)
* fix: state mutations in computed getters should be avoided

* fix: type about getDataSourceRef
2024-05-23 08:20:41 +08:00
zhang
cfdb09fe5b style: format code style (#3857)
* first commit

* style: format code style

* style: format code
2024-05-22 22:40:56 +08:00
Electrolux
821238ea04 update case (#3858) 2024-05-22 22:40:23 +08:00
wangjinchuan108
bcd98ee067 fix(Loading): 处理v-loading指令和useLoading的内存泄露 (#3861)
Co-authored-by: jinchuanwang <1085296443@qq.com>
2024-05-22 22:39:51 +08:00
No name
144cdd4680 fix(Application): search menu now correctly lowercases input keys (#3842) 2024-05-14 14:07:57 +08:00
Kyun Wong
1fee161786 fix: 路由参数存在非英文字符的情况下 会生成一个新的tab 而非返回原有tab (#3832) 2024-05-14 10:52:44 +08:00
Electrolux
265627fcc8 feat(BasicForm): add prefix slot for schema (#3840) 2024-05-13 18:29:42 +08:00
李吉磊(Leo)
223562eab5 chore(docs): update git url (#3839) 2024-05-13 10:54:32 +08:00
zhang
22052f10f9 fix(BasicForm): when value is 0 or false resetFields is not work (#3828)
* fix(BasicForm): when value is 0 or false resetFields is not work

* fix(BasicForm): when value is 0 or false resetFields is not work
2024-05-11 06:56:36 +08:00
zzy-jonay
478802b426 fix(BasicForm): 修复FormSchema中使用ifShow隐藏字段时,默认表单查询重置按钮位置偏移量计算问题 (#3830) 2024-05-10 17:51:50 +08:00
tors
5a2d74249e feat(Demo): add ScreenShot page demo (#3826) 2024-05-10 17:30:52 +08:00
Electrolux
0ee721183b feat(codeEditor): add type and config && add use case (#3829)
* chore(codeEditor): add type and config && add use case

* type(codeEditor): perf the config

* fix annotate

* fix annotate
2024-05-10 16:37:22 +08:00
zhang
b66a0def98 fix(Breadcrumb): if hideBreadcrumb is true and hidden (#3821) 2024-05-09 09:16:20 +08:00
tors
679a23332f chore: Rename the default export name (#3820) 2024-05-09 09:15:58 +08:00
Electrolux
7538c57db7 fix(BasicForm): setFieldsValue not work in form when use date comp (#3819)
* fix(BasicForm): setFieldsValue not work in form when use date comp

* fix(BasicForm): add lost time component
2024-05-09 09:12:36 +08:00
Electrolux
88e77dbf99 perf(BasicForm): fix invaild defaultValue && split-setdefault-setvalue (#3815) 2024-05-08 14:05:54 +08:00
Electrolux
4348d21da8 fix(imgUpload): disabled not effect in the form (#3809) 2024-05-06 14:57:26 +08:00
invalid w
d7783baad4 chore: release 2.11.4 2024-05-06 09:17:36 +08:00
zhang
2af93c9f53 fix(breadcrumb): if hideChildrenInmenu is true & hidden dropdown-menu (#3807) 2024-05-06 08:47:02 +08:00
zhang
ef5285385f chore: format code style (#3808)
* first commit

* style: format code style

* style: format code style
2024-05-06 08:46:25 +08:00
Electrolux
ba5b8f8506 perf(util): remove handleInputNumberValue (#3806)
* perf(util): remove handleInputNumberValue

* fix: remove unuse fn
2024-05-02 03:22:53 +08:00
Song XinXin
08a1f7b682 fix(BasicForm): 修复 SetFieldsValue 设置值时,会将 Number 类型的值转为 string (#3802) 2024-04-30 07:02:35 +08:00
Electrolux
06018add79 fix(imgupload): resultField causing with error display && setField uncertain (#3798)
* fix(imgupload): resultField causing with error display

* perf(imgUpload):judge about getValue

* fix(imgUpload): set string but return uncertain
2024-04-30 06:54:55 +08:00
Electrolux
29ef0d3915 feat(upload->previewColumns): Adapt functions && chore upload demo (#3799)
* feat(upload->previewColumns): add function capability with delete fileList

* chore(upload->demo): split file && add previewColumns fn demo

* type(upload): optional  handleRemove

* type(upload): remove  optional  handleRemove

* feat(upload->previewCol): add enhanceing  handleAdd && handleDownload

* chore(upload->preview): remove  previewColumns
2024-04-30 06:54:30 +08:00
xingyu
17f5b1b210 chore(deps): bump deps version (#3790)
resolve #3793
2024-04-26 08:44:18 +08:00
kazusa
338d077ab3 fix(docker): update node version of dockerfile (#3788)
node version 18 is required for pnpm.
2024-04-25 09:09:03 +08:00
invalid w
641d0b38ba chore(doc): update changelog 2024-04-24 18:07:14 +08:00
invalid w
fa4bfc4d50 chore: relese 2.11.3 2024-04-24 18:01:47 +08:00
Electrolux
7ae2ec03a7 feat(BasicForm->Components): add beforeFetch & afterFetch to apicomp && perf the code (#3786)
* fix(apitree): fix the error way to use resultfield

* feat: add before & after Fetch fn in apiComp
2024-04-24 18:00:13 +08:00
xingyu
2f655c2127 fix(deps): lock vue version to 3.4.23 (#3785)
closed #3783
2024-04-24 10:41:52 +08:00
leelaa
1da97c3ff8 chore(github): update bug issue doc (#3781)
project's monorepo is organized using pnpm workspace, it cannot be run with npm or yarn
2024-04-23 17:54:27 +08:00
Electrolux
69a6e9023e fix(upload): disabled prop not effect to upload in the form (#3780) 2024-04-23 16:12:07 +08:00
xingyu
504cf725f1 chore(deps): bump deps version (#3776) 2024-04-23 12:21:29 +08:00
invalid w
3623ea7f7b chore(CI): automatically generate changelog via GitHub (#3778) 2024-04-23 12:18:04 +08:00
invalid w
49c4dc646a feat(demo): use Tour component replace dirverjs (#3777)
* feat(demo): use Tour component replace dirverjs

* chore(deps): remove dirverjs package
2024-04-23 12:05:40 +08:00
77 changed files with 2087 additions and 1441 deletions

View File

@@ -36,4 +36,4 @@ Please describe the steps of the problem in detail to ensure that we can restore
- Operating System:
- Node version:
- Package manager (npm/yarn/pnpm) and version:
- pnpm version:

View File

@@ -25,4 +25,4 @@ assignees: ''
- 操作系统:
- Node 版本:
- 包管理器 (npm/yarn/pnpm) 及其版本:
- pnpm 版本:

22
.github/workflows/release-tag.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Create Release Tag
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create Release for Tag
id: release_tag
uses: ncipollo/release-action@v1
with:
generateReleaseNotes: 'true'
body: |
> Please refer to [CHANGELOG.md](https://github.com/anncwb/vue-vben-admin/blob/main/CHANGELOG.md) for details.

View File

@@ -1,24 +0,0 @@
name: Create Release
on:
push:
tags:
- v*
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Create Release for Tag
id: release_tag
uses: yyx990803/release-tag@master
env:
GITHUB_TOKEN: ${{ secrets.OPER_TOKEN }}
with:
tag_name: ${{ github.ref }}
body: |
Please refer to [CHANGELOG.md](https://github.com/anncwb/vue-vben-admin/blob/main/CHANGELOG.md) for details.

View File

@@ -1,3 +1,62 @@
## [2.11.5](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.4...v2.11.5) (2024-05-31)
### Bug Fixes
- 路由参数存在非英文字符的情况下 会生成一个新的tab 而非返回原有tab ([#3832](https://github.com/vbenjs/vue-vben-admin/issues/3832)) ([1fee161](https://github.com/vbenjs/vue-vben-admin/commit/1fee161786fee79a06fb3b308374db098abd461a))
- 修复表单重置后,页面变化了,但是由于异步问题导致表单内部的状态没有及时同步 ([#3882](https://github.com/vbenjs/vue-vben-admin/issues/3882)) ([5d36b1a](https://github.com/vbenjs/vue-vben-admin/commit/5d36b1a5604f72921bf09c16a78e5e287a1ba9d0))
- **Application:** search menu now correctly lowercases input keys ([#3842](https://github.com/vbenjs/vue-vben-admin/issues/3842)) ([144cdd4](https://github.com/vbenjs/vue-vben-admin/commit/144cdd468092f9314abf766ffc2eccc66c00f08a))
- **BasicForm:** 修复FormSchema中使用ifShow隐藏字段时默认表单查询重置按钮位置偏移量计算问题 ([#3830](https://github.com/vbenjs/vue-vben-admin/issues/3830)) ([478802b](https://github.com/vbenjs/vue-vben-admin/commit/478802b42625f74e8e8f817dea343b0692e3d024))
- **BasicForm:** setFieldsValue not work in form when use date comp ([#3819](https://github.com/vbenjs/vue-vben-admin/issues/3819)) ([7538c57](https://github.com/vbenjs/vue-vben-admin/commit/7538c57db755f221d201e3fdb5052ecf1f42fd9a))
- **BasicForm:** when value is 0 or false resetFields is not work ([#3828](https://github.com/vbenjs/vue-vben-admin/issues/3828)) ([22052f1](https://github.com/vbenjs/vue-vben-admin/commit/22052f10f9264008dcd25f6efdd53d679585407a))
- **Breadcrumb:** if hideBreadcrumb is true and hidden ([#3821](https://github.com/vbenjs/vue-vben-admin/issues/3821)) ([b66a0de](https://github.com/vbenjs/vue-vben-admin/commit/b66a0def98018fc1504573692a9b7a83b5c2d483))
- **imgUpload:** disabled not effect in the form ([#3809](https://github.com/vbenjs/vue-vben-admin/issues/3809)) ([4348d21](https://github.com/vbenjs/vue-vben-admin/commit/4348d21da80a1e54c94fb9528617b29090701080))
- **Loading:** 处理v-loading指令和useLoading的内存泄露 ([#3861](https://github.com/vbenjs/vue-vben-admin/issues/3861)) ([bcd98ee](https://github.com/vbenjs/vue-vben-admin/commit/bcd98ee0672047aafebdd504ef0ecc630b5068da))
- **loading:** useLoading is not working as expected ([#3877](https://github.com/vbenjs/vue-vben-admin/issues/3877)) ([c89417f](https://github.com/vbenjs/vue-vben-admin/commit/c89417f523bf4c7e2329d943796c3dd92c066d3a))
- **useDataSource:** state mutations in computed getters should be avoided ([#3859](https://github.com/vbenjs/vue-vben-admin/issues/3859)) ([fee8081](https://github.com/vbenjs/vue-vben-admin/commit/fee808198e5c2038424a5f5b1da41b11d16d1508))
### Features
- add devtools plugin instead of inspector plugin ([#3856](https://github.com/vbenjs/vue-vben-admin/issues/3856)) ([e69dd1e](https://github.com/vbenjs/vue-vben-admin/commit/e69dd1e223a2e817805739f11823092992d14ae1))
- **BasicForm:** add prefix slot for schema ([#3840](https://github.com/vbenjs/vue-vben-admin/issues/3840)) ([265627f](https://github.com/vbenjs/vue-vben-admin/commit/265627fcc8a197861aea2f04641f457548180177))
- **codeEditor:** add type and config && add use case ([#3829](https://github.com/vbenjs/vue-vben-admin/issues/3829)) ([0ee7211](https://github.com/vbenjs/vue-vben-admin/commit/0ee721183bf52c4d1239a7966cc76f2150b43b13))
- **Demo:** add ScreenShot page demo ([#3826](https://github.com/vbenjs/vue-vben-admin/issues/3826)) ([5a2d742](https://github.com/vbenjs/vue-vben-admin/commit/5a2d74249e9e9a9631f3f43e2249b7ccc13f6da4))
- **form:** add valueFormat for schema ([#3873](https://github.com/vbenjs/vue-vben-admin/issues/3873)) ([0bc01d8](https://github.com/vbenjs/vue-vben-admin/commit/0bc01d8528fc11904628974799ab44180e3e3314))
### Performance Improvements
- **BasicForm:** fix invaild defaultValue && split-setdefault-setvalue ([#3815](https://github.com/vbenjs/vue-vben-admin/issues/3815)) ([88e77db](https://github.com/vbenjs/vue-vben-admin/commit/88e77dbf994f594bc31189b896deab4be18351b8))
- **form->util:** duplicate judge ([#3865](https://github.com/vbenjs/vue-vben-admin/issues/3865)) ([dcba0ca](https://github.com/vbenjs/vue-vben-admin/commit/dcba0ca837a0f72beaa09ff394fa9b6ff465dec1))
- **menu->search:** highlight match chars when search menu ([#3880](https://github.com/vbenjs/vue-vben-admin/issues/3880)) ([d5fed8a](https://github.com/vbenjs/vue-vben-admin/commit/d5fed8a47c031f6f228ca41ed29c0f9aaf05f623))
## [2.11.4](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.3...v2.11.4) (2024-05-06)
### Bug Fixes
- **BasicForm:** 修复 SetFieldsValue 设置值时,会将 Number 类型的值转为 string ([#3802](https://github.com/vbenjs/vue-vben-admin/issues/3802)) ([08a1f7b](https://github.com/vbenjs/vue-vben-admin/commit/08a1f7b682114bdf758ea9dcd4b84daea0d5f196))
- **breadcrumb:** if hideChildrenInmenu is true & hidden dropdown-menu ([#3807](https://github.com/vbenjs/vue-vben-admin/issues/3807)) ([2af93c9](https://github.com/vbenjs/vue-vben-admin/commit/2af93c9f5307ffdbb29653303177b2c8631e789a))
- **docker:** update node version of dockerfile ([#3788](https://github.com/vbenjs/vue-vben-admin/issues/3788)) ([338d077](https://github.com/vbenjs/vue-vben-admin/commit/338d077ab3669ef116e7406c586fe2cb59952022))
- **imgupload:** resultField causing with error display && setField uncertain ([#3798](https://github.com/vbenjs/vue-vben-admin/issues/3798)) ([06018ad](https://github.com/vbenjs/vue-vben-admin/commit/06018add798a6c23ebbfa91a3f4d625c2a57e458))
### Features
- **upload->previewColumns:** Adapt functions && chore upload demo ([#3799](https://github.com/vbenjs/vue-vben-admin/issues/3799)) ([29ef0d3](https://github.com/vbenjs/vue-vben-admin/commit/29ef0d39157957146015e1b914c26d2b6d1bf25e))
### Performance Improvements
- **util:** remove handleInputNumberValue ([#3806](https://github.com/vbenjs/vue-vben-admin/issues/3806)) ([ba5b8f8](https://github.com/vbenjs/vue-vben-admin/commit/ba5b8f8506bb9d052ce2705653f255f0401963e8))
## [2.11.3](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.2...v2.11.3) (2024-04-24)
### Bug Fixes
- **deps:** lock vue version to 3.4.23 ([#3785](https://github.com/vbenjs/vue-vben-admin/issues/3785)) ([2f655c2](https://github.com/vbenjs/vue-vben-admin/commit/2f655c2127753c0cde1cb29834314e961ce0930e)), closes [#3783](https://github.com/vbenjs/vue-vben-admin/issues/3783)
- **upload:** disabled prop not effect to upload in the form ([#3780](https://github.com/vbenjs/vue-vben-admin/issues/3780)) ([69a6e90](https://github.com/vbenjs/vue-vben-admin/commit/69a6e9023ef80a5504178d0887119f8e7bbd5113))
### Features
- **BasicForm->Components:** add beforeFetch & afterFetch to apicomp && perf the code ([#3786](https://github.com/vbenjs/vue-vben-admin/issues/3786)) ([7ae2ec0](https://github.com/vbenjs/vue-vben-admin/commit/7ae2ec03a773c2223feabbd1e341e90f012f8b7e))
- **demo:** use Tour component replace dirverjs ([#3777](https://github.com/vbenjs/vue-vben-admin/issues/3777)) ([49c4dc6](https://github.com/vbenjs/vue-vben-admin/commit/49c4dc646a9d123527577f02ee0d92865da9988e))
## [2.11.2](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.1...v2.11.2) (2024-04-23)
### Bug Fixes

View File

@@ -1,5 +1,5 @@
# node 构建
FROM node:16-alpine as build-stage
FROM node:18-alpine as build-stage
# 署名
MAINTAINER Adoin 'adoin@qq.com'
WORKDIR /app

View File

@@ -62,7 +62,7 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
- Get the project code
```bash
git clone https://github.com/anncwb/vue-vben-admin.git
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- Install dependencies

View File

@@ -62,7 +62,7 @@ Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3
- 获取项目代码
```bash
git clone https://github.com/anncwb/vue-vben-admin.git
git clone https://github.com/vbenjs/vue-vben-admin.git
```
- 安装依赖

View File

@@ -37,7 +37,7 @@
"postcss-less": "^6.0.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.2.5",
"stylelint": "^16.3.1",
"stylelint": "^16.4.0",
"stylelint-config-property-sort-order-smacss": "^10.0.0",
"stylelint-config-recommended-scss": "^14.0.0",
"stylelint-config-recommended-vue": "^1.5.0",

View File

@@ -21,6 +21,6 @@
],
"dependencies": {
"@types/node": "^20.12.7",
"vite": "^5.2.9"
"vite": "^5.2.10"
}
}

View File

@@ -33,24 +33,24 @@
},
"dependencies": {
"@ant-design/colors": "^7.0.2",
"vite": "^5.2.9"
"vite": "^5.2.10"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"ant-design-vue": "^4.1.2",
"ant-design-vue": "^4.2.1",
"dayjs": "^1.11.10",
"dotenv": "^16.4.5",
"fs-extra": "^11.2.0",
"less": "^4.2.0",
"picocolors": "^1.0.0",
"pkg-types": "^1.0.3",
"pkg-types": "^1.1.0",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.75.0",
"unocss": "0.59.4",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-dts": "^3.8.3",
"vite-plugin-dts": "^3.9.0",
"vite-plugin-html": "^3.2.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.10.0",

View File

@@ -36,7 +36,7 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
});
const pathResolve = (pathname: string) => resolve(root, '.', pathname);
const timestamp = new Date().getTime();
const applicationConfig: UserConfig = {
base: VITE_PUBLIC_PATH,
resolve: {
@@ -63,8 +63,8 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
cssTarget: 'chrome80',
rollupOptions: {
output: {
// 入口文件名
entryFileNames: `assets/entry/[name]-[hash]-${timestamp}.js`,
// 入口文件名(不能变,否则所有打包的 js hash 值全变了)
entryFileNames: 'index.js',
manualChunks: {
vue: ['vue', 'pinia', 'vue-router'],
antd: ['ant-design-vue', '@ant-design/icons-vue'],

View File

@@ -2,6 +2,7 @@ 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 DevTools from 'vite-plugin-vue-devtools';
import { createAppConfigPlugin } from './appConfig';
import { configCompressPlugin } from './compress';
@@ -24,6 +25,8 @@ async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyz
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild });
vitePlugins.push(appConfigPlugin);
vitePlugins.push(DevTools());
// vite-plugin-html
vitePlugins.push(configHtmlPlugin({ isBuild }));

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin",
"version": "2.11.2",
"version": "2.11.5",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": {
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
@@ -74,18 +74,18 @@
"@logicflow/core": "^1.2.26",
"@logicflow/extension": "^1.2.26",
"@vben/hooks": "workspace:*",
"@vue/shared": "^3.4.23",
"@vue/shared": "^3.4.25",
"@vueuse/core": "^10.9.0",
"@zxcvbn-ts/core": "^3.0.4",
"ant-design-vue": "^4.1.2",
"ant-design-vue": "^4.2.1",
"axios": "^1.6.8",
"codemirror": "^5.65.16",
"cropperjs": "^1.6.1",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"exceljs": "^4.4.0",
"html2canvas": "^1.4.1",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
@@ -101,21 +101,21 @@
"tinymce": "^5.10.9",
"unocss": "^0.59.4",
"vditor": "^3.10.4",
"vue": "^3.4.23",
"vue-i18n": "^9.13.0",
"vue": "^3.4.25",
"vue-i18n": "^9.13.1",
"vue-json-pretty": "^2.4.0",
"vue-router": "^4.3.2",
"vue-types": "^5.1.1",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.5.22",
"vxe-table": "^4.6.3",
"vxe-table-plugin-export-xlsx": "^4.0.1",
"xe-utils": "^3.5.24",
"xe-utils": "^3.5.25",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@commitlint/cli": "^19.2.2",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@iconify/json": "^2.2.202",
"@iconify/json": "^2.2.203",
"@purge-icons/generated": "^0.10.0",
"@types/codemirror": "^5.60.15",
"@types/crypto-js": "^4.2.2",
@@ -131,7 +131,7 @@
"@vben/ts-config": "workspace:*",
"@vben/types": "workspace:*",
"@vben/vite-config": "workspace:*",
"@vue/compiler-sfc": "^3.4.23",
"@vue/compiler-sfc": "^3.4.25",
"@vue/test-utils": "^2.4.5",
"conventional-changelog-cli": "^4.1.0",
"cross-env": "^7.0.3",
@@ -145,10 +145,10 @@
"turbo": "^1.13.2",
"typescript": "^5.4.5",
"unbuild": "^2.0.0",
"vite": "^5.2.9",
"vite": "^5.2.10",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-vue-inspector": "^5.0.0",
"vue-tsc": "^2.0.13"
"vite-plugin-vue-devtools": "^7.2.0",
"vue-tsc": "^2.0.14"
},
"packageManager": "pnpm@9.0.4",
"engines": {

View File

@@ -32,7 +32,7 @@
"dependencies": {
"@vueuse/core": "^10.9.0",
"lodash-es": "^4.17.21",
"vue": "^3.4.23"
"vue": "^3.4.25"
},
"devDependencies": {
"@vben/types": "workspace:*"

876
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,14 @@
<Icon :icon="item.icon || 'mdi:form-select'" :size="20" />
</div>
<div :class="`${prefixCls}-list__item-text`">
{{ item.name }}
<!-- 搜索结果包含的字符着色 -->
<span
v-for="(each, i) in item.chars"
:key="i"
:class="{ highlight: each.highlight }"
>
{{ each.char }}
</span>
</div>
<div :class="`${prefixCls}-list__item-enter`">
<Icon icon="ant-design:enter-outlined" :size="20" />
@@ -254,6 +261,13 @@
&-text {
flex: 1;
// 搜索结果包含的字符着色
& > span {
&.highlight {
color: lighten(@primary-color, 20%);
}
}
}
&-enter {

View File

@@ -13,6 +13,8 @@ export interface SearchResult {
name: string;
path: string;
icon?: string;
// 搜索结果包含的字符着色
chars: { char: string; highlight: boolean }[];
}
// Translate special characters
@@ -49,7 +51,7 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref, emit: A
function search(e: ChangeEvent) {
e?.stopPropagation();
const key = e.target.value;
keyword.value = key.trim();
keyword.value = key.trim().toLowerCase();
if (!key) {
searchResult.value = [];
return;
@@ -68,11 +70,85 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref, emit: A
const { name, path, icon, children, hideMenu, meta } = item;
if (
!hideMenu &&
reg.test(name?.toLowerCase()) &&
reg.test(name?.toLowerCase() ?? '') &&
(!children?.length || meta?.hideChildrenInMenu)
) {
const chars: { char: string; highlight: boolean }[] = [];
// 显示字符串
const label = (parent?.name ? `${parent.name} > ${name}` : name) ?? '';
const labelChars = label.split('');
let labelPointer = 0;
const keywordChars = keyword.value.split('');
const keywordLength = keywordChars.length;
let keywordPointer = 0;
// 用于查找完整关键词的匹配
let includePointer = 0;
// 优先查找完整关键词的匹配
if (label.toLowerCase().includes(keyword.value.toLowerCase())) {
while (includePointer < labelChars.length) {
if (
label.toLowerCase().slice(includePointer, includePointer + keywordLength) ===
keyword.value.toLowerCase()
) {
chars.push(
...label
.substring(labelPointer, includePointer)
.split('')
.map((v) => ({
char: v,
highlight: false,
})),
);
chars.push(
...label
.slice(includePointer, includePointer + keywordLength)
.split('')
.map((v) => ({
char: v,
highlight: true,
})),
);
includePointer += keywordLength;
labelPointer = includePointer;
} else {
includePointer++;
}
}
}
// 查找满足关键词顺序的匹配
while (labelPointer < labelChars.length) {
keywordPointer = 0;
while (keywordPointer < keywordChars.length) {
if (keywordChars[keywordPointer] !== void 0 && labelChars[labelPointer] !== void 0) {
if (
keywordChars[keywordPointer].toLowerCase() ===
labelChars[labelPointer].toLowerCase()
) {
chars.push({
char: labelChars[labelPointer],
highlight: true,
});
keywordPointer++;
} else {
chars.push({
char: labelChars[labelPointer],
highlight: false,
});
}
} else {
keywordPointer++;
}
labelPointer++;
}
}
ret.push({
name: parent?.name ? `${parent.name} > ${name}` : name,
name: label,
chars,
path,
icon,
});
@@ -81,7 +157,36 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref, emit: A
ret.push(...handlerSearchResult(children, reg, item));
}
});
return ret;
// 排序
return ret.sort((a, b) => {
if (
a.name.toLowerCase().includes(keyword.value.toLowerCase()) &&
b.name.toLowerCase().includes(keyword.value.toLowerCase())
) {
// 两者都存在完整关键词的匹配
// 匹配数量
const ca =
a.name.toLowerCase().match(new RegExp(keyword.value.toLowerCase(), 'g'))?.length ?? 0;
const cb =
b.name.toLowerCase().match(new RegExp(keyword.value.toLowerCase(), 'g'))?.length ?? 0;
// 匹配数量越多的优先显示,数量相同的按字符串排序
return ca === cb ? a.name.toLowerCase().localeCompare(b.name.toLowerCase()) : cb - ca;
} else {
if (a.name.toLowerCase().includes(keyword.value.toLowerCase())) {
// 完整关键词的匹配优先
return -1;
} else if (b.name.toLowerCase().includes(keyword.value.toLowerCase())) {
// 完整关键词的匹配优先
return 1;
} else {
// 按字符串排序
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
}
});
}
// Activate when the mouse moves to a certain line

View File

@@ -6,6 +6,7 @@
:mode="mode"
:readonly="readonly"
:bordered="bordered"
:config="config"
/>
</div>
</template>
@@ -14,6 +15,7 @@
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
import { isString } from '@/utils/is';
import { MODE } from './typing';
import type { EditorConfiguration } from 'codemirror';
const props = defineProps({
value: { type: [Object, String] as PropType<Record<string, any> | string> },
@@ -28,6 +30,7 @@
readonly: { type: Boolean },
autoFormat: { type: Boolean, default: true },
bordered: { type: Boolean, default: false },
config: { type: Object as PropType<EditorConfiguration>, default: () => {} },
});
const emit = defineEmits(['change', 'update:value', 'format-error']);

View File

@@ -22,15 +22,22 @@
import { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '@/store/modules/app';
import CodeMirror from 'codemirror';
import { MODE } from './../typing';
import type { EditorConfiguration } from 'codemirror';
import { MODE, parserDynamicImport } from './../typing';
// css
import './codemirror.css';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css';
// modes
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
// 代码段折叠功能
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/fold/markdown-fold';
import 'codemirror/addon/fold/xml-fold';
import 'codemirror/addon/fold/indent-fold';
const props = defineProps({
mode: {
@@ -44,6 +51,7 @@
value: { type: String, default: '' },
readonly: { type: Boolean, default: false },
bordered: { type: Boolean, default: false },
config: { type: Object as PropType<EditorConfiguration>, default: () => {} },
});
const emit = defineEmits(['change']);
@@ -66,7 +74,8 @@
{ flush: 'post' },
);
watchEffect(() => {
watchEffect(async () => {
await parserDynamicImport(props.mode)();
editor?.setOption('mode', props.mode);
});
@@ -96,7 +105,7 @@
autoCloseBrackets: true,
autoCloseTags: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers'],
gutters: ['CodeMirror-lint-markers', 'CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
};
editor = CodeMirror(el.value!, {
@@ -108,6 +117,7 @@
lineWrapping: true,
lineNumbers: true,
...addonOptions,
...props.config,
});
editor?.setValue(props.value);
setTheme();

View File

@@ -1,529 +0,0 @@
/* BASICS */
.CodeMirror {
--base: #545281;
--comment: hsl(210deg 25% 60%);
--keyword: #af4ab1;
--variable: #0055d1;
--function: #c25205;
--string: #2ba46d;
--number: #c25205;
--tags: #d00;
--qualifier: #ff6032;
--important: var(--string);
position: relative;
height: auto;
height: 100%;
overflow: hidden;
font-family: var(--font-code);
background: white;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
min-height: 1px; /* prevents collapsing before first draw */
padding: 4px 0; /* Vertical padding around content */
cursor: text;
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap;
background-color: transparent;
border-right: 1px solid #ddd;
}
.CodeMirror-linenumber {
min-width: 20px;
padding: 0 3px 0 5px;
color: var(--comment);
text-align: right;
white-space: nowrap;
opacity: 0.6;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* FOLD GUTTER */
.CodeMirror-foldmarker {
font-family: arial;
line-height: 0.3;
color: #414141;
text-shadow:
#f96 1px 1px 2px,
#f96 -1px -1px 2px,
#f96 1px -1px 2px,
#f96 -1px 1px 2px;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: 0.7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open::after,
.CodeMirror-foldgutter-folded::after {
position: relative;
top: -0.1em;
display: inline-block;
font-size: 0.8em;
content: '>';
opacity: 0.8;
transform: rotate(90deg);
transition: transform 0.2s;
}
.CodeMirror-foldgutter-folded::after {
transform: none;
}
/* CURSOR */
.CodeMirror-cursor {
position: absolute;
width: 0;
pointer-events: none;
border-right: none;
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
background: #7e7;
border: 0 !important;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgb(20 255 20 / 50%);
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
background-color: #7e7;
border: 0;
animation: blink 1.06s steps(1) infinite;
}
@keyframes blink {
50% {
background-color: transparent;
}
}
@keyframes blink {
50% {
background-color: transparent;
}
}
@keyframes blink {
50% {
background-color: transparent;
}
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-rulers {
position: absolute;
top: -50px;
right: 0;
bottom: -20px;
left: 0;
overflow: hidden;
}
.CodeMirror-ruler {
position: absolute;
top: 0;
bottom: 0;
border-left: 1px solid #ccc;
}
/* DEFAULT THEME */
.cm-s-default.CodeMirror {
background-color: transparent;
}
.cm-s-default .cm-header {
color: blue;
}
.cm-s-default .cm-quote {
color: #090;
}
.cm-negative {
color: #d44;
}
.cm-positive {
color: #292;
}
.cm-header,
.cm-strong {
font-weight: bold;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-s-default .cm-atom,
.cm-s-default .cm-def,
.cm-s-default .cm-property,
.cm-s-default .cm-variable-2,
.cm-s-default .cm-variable-3,
.cm-s-default .cm-punctuation {
color: var(--base);
}
.cm-s-default .cm-hr,
.cm-s-default .cm-comment {
color: var(--comment);
}
.cm-s-default .cm-attribute,
.cm-s-default .cm-keyword {
color: var(--keyword);
}
.cm-s-default .cm-variable {
color: var(--variable);
}
.cm-s-default .cm-bracket,
.cm-s-default .cm-tag {
color: var(--tags);
}
.cm-s-default .cm-number {
color: var(--number);
}
.cm-s-default .cm-string,
.cm-s-default .cm-string-2 {
color: var(--string);
}
.cm-s-default .cm-type {
color: #085;
}
.cm-s-default .cm-meta {
color: #555;
}
.cm-s-default .cm-qualifier {
color: var(--qualifier);
}
.cm-s-default .cm-builtin {
color: #7539ff;
}
.cm-s-default .cm-link {
color: var(--flash);
}
.cm-s-default .cm-error {
color: #ff008c;
}
.cm-invalidchar {
color: #ff008c;
}
.CodeMirror-composing {
border-bottom: 2px solid;
}
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {
color: #0b0;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: #a22;
}
.CodeMirror-matchingtag {
background: rgb(255 150 0 / 30%);
}
.CodeMirror-activeline-background {
background: #e8f2ff;
}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror-scroll {
position: relative;
height: 100%;
padding-bottom: 30px;
margin-right: -30px;
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px;
overflow: scroll !important; /* Things will break if this is overridden */
outline: none; /* Prevent dragging from highlighting the element */
}
.CodeMirror-sizer {
position: relative;
margin-bottom: 20px !important;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
top: 0;
right: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-x: scroll;
overflow-y: hidden;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-gutter-filler {
bottom: 0;
left: 0;
}
.CodeMirror-gutter {
display: inline-block;
height: 100%;
margin-bottom: -30px;
white-space: normal;
vertical-align: top;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0;
bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
z-index: 4;
cursor: default;
}
.CodeMirror-gutter-wrapper ::selection {
background-color: transparent;
}
.CodeMirrorwrapper ::selection {
background-color: transparent;
}
.CodeMirror pre {
position: relative;
z-index: 2;
padding: 0 4px; /* Horizontal padding of content */
margin: 0;
overflow: visible;
font-family: inherit;
font-size: inherit;
line-height: inherit;
color: inherit;
word-wrap: normal;
white-space: pre;
background: transparent;
border-width: 0;
/* Reset some styles that the rest of the page might have set */
border-radius: 0;
-webkit-tap-highlight-color: transparent;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-break: normal;
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-linebackground {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-rtl pre {
direction: rtl;
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
position: relative;
z-index: 3;
visibility: hidden;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-focused .CodeMirror-selected {
background: #d7d4f0;
}
.CodeMirror-crosshair {
cursor: crosshair;
}
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #d7d4f0;
}
.cm-searching {
background-color: #ffa;
background-color: rgb(255 255 0 / 40%);
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack::after {
content: '';
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}

View File

@@ -1,5 +1,247 @@
export enum MODE {
JSON = 'application/json',
HTML = 'htmlmixed',
JS = 'javascript',
APL = 'apl',
ASCIIARMOR = 'asciiarmor',
ASTERISK = 'asterisk',
BRAINFUCK = 'brainfuck',
CLIKE = 'clike',
CLOJURE = 'clojure',
CMAKE = 'cmake',
COBOL = 'cobol',
COFFEESCRIPT = 'coffeescript',
COMMONLISP = 'commonlisp',
CRYSTAL = 'crystal',
CSS = 'css',
CYPHER = 'cypher',
D = 'd',
DART = 'dart',
DIFF = 'diff',
DJANGO = 'django',
DOCKERFILE = 'dockerfile',
DTD = 'dtd',
DYLAN = 'dylan',
EBNF = 'ebnf',
ECL = 'ecl',
EIFFEL = 'eiffel',
ELM = 'elm',
ERLANG = 'erlang',
FACTOR = 'factor',
FCL = 'fcl',
FORTH = 'forth',
FORTRAN = 'fortran',
GAS = 'gas',
GFM = 'gfm',
GHERKIN = 'gherkin',
GO = 'go',
GROOVY = 'groovy',
HAML = 'haml',
HANDLEBARS = 'handlebars',
HASKELL = 'haskell',
HAXE = 'haxe',
HTMLEMBEDDED = 'htmlembedded',
HTMLMIXED = 'htmlmixed',
HTTP = 'http',
IDL = 'idl',
JAVASCRIPT = 'javascript',
JINJA2 = 'jinja2',
JSX = 'jsx',
JULIA = 'julia',
LIVESCRIPT = 'livescript',
LUA = 'lua',
MARKDOWN = 'markdown',
MATHEMATICA = 'mathematica',
MBOX = 'mbox',
MIRC = 'mirc',
MLLIKE = 'mllike',
MODELICA = 'modelica',
MSCGEN = 'mscgen',
MUMPS = 'mumps',
NGINX = 'nginx',
NSIS = 'nsis',
NTRIPLES = 'ntriples',
OCTAVE = 'octave',
OZ = 'oz',
PASCAL = 'pascal',
PEGJS = 'pegjs',
PERL = 'perl',
PHP = 'php',
PIG = 'pig',
POWERSHELL = 'powershell',
PROPERTIES = 'properties',
PROTOBUF = 'protobuf',
PUG = 'pug',
PUPPET = 'puppet',
PYTHON = 'python',
Q = 'q',
R = 'r',
RPM = 'rpm',
RST = 'rst',
RUBY = 'ruby',
RUST = 'rust',
SAS = 'sas',
SASS = 'sass',
SCHEME = 'scheme',
SHELL = 'shell',
SIEVE = 'sieve',
SLIM = 'slim',
SMALLTALK = 'smalltalk',
SMARTY = 'smarty',
SOLR = 'solr',
SOY = 'soy',
SPARQL = 'sparql',
SPREADSHEET = 'spreadsheet',
SQL = 'sql',
STEX = 'stex',
STYLUS = 'stylus',
SWIFT = 'swift',
TCL = 'tcl',
TEXTILE = 'textile',
TIDDLYWIKI = 'tiddlywiki',
TIKI = 'tiki',
TOML = 'toml',
TORNADO = 'tornado',
TROFF = 'troff',
TTCN = 'ttcn',
TURTLE = 'turtle',
TWIG = 'twig',
VB = 'vb',
VBSCRIPT = 'vbscript',
VELOCITY = 'velocity',
VERILOG = 'verilog',
VHDL = 'vhdl',
VUE = 'vue',
WAST = 'wast',
WEBIDL = 'webidl',
XML = 'xml',
XQUERY = 'xquery',
YACAS = 'yacas',
YAML = 'yaml',
Z80 = 'z80',
}
/**
* @description: DynamicImport codemirror
*/
export function parserDynamicImport(str: MODE): () => Promise<any> {
const dynamicArray = {
// adapt before demo
'application/json': async () => await import('codemirror/mode/javascript/javascript'),
apl: async () => await import('codemirror/mode/apl/apl'),
asciiarmor: async () => await import('codemirror/mode/asciiarmor/asciiarmor'),
asterisk: async () => await import('codemirror/mode/asterisk/asterisk'),
brainfuck: async () => await import('codemirror/mode/brainfuck/brainfuck'),
clike: async () => await import('codemirror/mode/clike/clike'),
clojure: async () => await import('codemirror/mode/clojure/clojure'),
cmake: async () => await import('codemirror/mode/cmake/cmake'),
cobol: async () => await import('codemirror/mode/cobol/cobol'),
coffeescript: async () => await import('codemirror/mode/coffeescript/coffeescript'),
commonlisp: async () => await import('codemirror/mode/commonlisp/commonlisp'),
crystal: async () => await import('codemirror/mode/crystal/crystal'),
css: async () => await import('codemirror/mode/css/css'),
cypher: async () => await import('codemirror/mode/cypher/cypher'),
d: async () => await import('codemirror/mode/d/d'),
dart: async () => await import('codemirror/mode/dart/dart'),
diff: async () => await import('codemirror/mode/diff/diff'),
django: async () => await import('codemirror/mode/django/django'),
dockerfile: async () => await import('codemirror/mode/dockerfile/dockerfile'),
dtd: async () => await import('codemirror/mode/dtd/dtd'),
dylan: async () => await import('codemirror/mode/dylan/dylan'),
ebnf: async () => await import('codemirror/mode/ebnf/ebnf'),
ecl: async () => await import('codemirror/mode/ecl/ecl'),
eiffel: async () => await import('codemirror/mode/eiffel/eiffel'),
elm: async () => await import('codemirror/mode/elm/elm'),
erlang: async () => await import('codemirror/mode/erlang/erlang'),
factor: async () => await import('codemirror/mode/factor/factor'),
fcl: async () => await import('codemirror/mode/fcl/fcl'),
forth: async () => await import('codemirror/mode/forth/forth'),
fortran: async () => await import('codemirror/mode/fortran/fortran'),
gas: async () => await import('codemirror/mode/gas/gas'),
gfm: async () => await import('codemirror/mode/gfm/gfm'),
gherkin: async () => await import('codemirror/mode/gherkin/gherkin'),
go: async () => await import('codemirror/mode/go/go'),
groovy: async () => await import('codemirror/mode/groovy/groovy'),
haml: async () => await import('codemirror/mode/haml/haml'),
handlebars: async () => await import('codemirror/mode/handlebars/handlebars'),
haskell: async () => await import('codemirror/mode/haskell/haskell'),
haxe: async () => await import('codemirror/mode/haxe/haxe'),
htmlembedded: async () => await import('codemirror/mode/htmlembedded/htmlembedded'),
htmlmixed: async () => await import('codemirror/mode/htmlmixed/htmlmixed'),
http: async () => await import('codemirror/mode/http/http'),
idl: async () => await import('codemirror/mode/idl/idl'),
javascript: async () => await import('codemirror/mode/javascript/javascript'),
jinja2: async () => await import('codemirror/mode/jinja2/jinja2'),
jsx: async () => await import('codemirror/mode/jsx/jsx'),
julia: async () => await import('codemirror/mode/julia/julia'),
livescript: async () => await import('codemirror/mode/livescript/livescript'),
lua: async () => await import('codemirror/mode/lua/lua'),
markdown: async () => await import('codemirror/mode/markdown/markdown'),
mathematica: async () => await import('codemirror/mode/mathematica/mathematica'),
mbox: async () => await import('codemirror/mode/mbox/mbox'),
mirc: async () => await import('codemirror/mode/mirc/mirc'),
mllike: async () => await import('codemirror/mode/mllike/mllike'),
modelica: async () => await import('codemirror/mode/modelica/modelica'),
mscgen: async () => await import('codemirror/mode/mscgen/mscgen'),
mumps: async () => await import('codemirror/mode/mumps/mumps'),
nginx: async () => await import('codemirror/mode/nginx/nginx'),
nsis: async () => await import('codemirror/mode/nsis/nsis'),
ntriples: async () => await import('codemirror/mode/ntriples/ntriples'),
octave: async () => await import('codemirror/mode/octave/octave'),
oz: async () => await import('codemirror/mode/oz/oz'),
pascal: async () => await import('codemirror/mode/pascal/pascal'),
pegjs: async () => await import('codemirror/mode/pegjs/pegjs'),
perl: async () => await import('codemirror/mode/perl/perl'),
php: async () => await import('codemirror/mode/php/php'),
pig: async () => await import('codemirror/mode/pig/pig'),
powershell: async () => await import('codemirror/mode/powershell/powershell'),
properties: async () => await import('codemirror/mode/properties/properties'),
protobuf: async () => await import('codemirror/mode/protobuf/protobuf'),
pug: async () => await import('codemirror/mode/pug/pug'),
puppet: async () => await import('codemirror/mode/puppet/puppet'),
python: async () => await import('codemirror/mode/python/python'),
q: async () => await import('codemirror/mode/q/q'),
r: async () => await import('codemirror/mode/r/r'),
rpm: async () => await import('codemirror/mode/rpm/rpm'),
rst: async () => await import('codemirror/mode/rst/rst'),
ruby: async () => await import('codemirror/mode/ruby/ruby'),
rust: async () => await import('codemirror/mode/rust/rust'),
sas: async () => await import('codemirror/mode/sas/sas'),
sass: async () => await import('codemirror/mode/sass/sass'),
scheme: async () => await import('codemirror/mode/scheme/scheme'),
shell: async () => await import('codemirror/mode/shell/shell'),
sieve: async () => await import('codemirror/mode/sieve/sieve'),
slim: async () => await import('codemirror/mode/slim/slim'),
smalltalk: async () => await import('codemirror/mode/smalltalk/smalltalk'),
smarty: async () => await import('codemirror/mode/smarty/smarty'),
solr: async () => await import('codemirror/mode/solr/solr'),
soy: async () => await import('codemirror/mode/soy/soy'),
sparql: async () => await import('codemirror/mode/sparql/sparql'),
spreadsheet: async () => await import('codemirror/mode/spreadsheet/spreadsheet'),
sql: async () => await import('codemirror/mode/sql/sql'),
stex: async () => await import('codemirror/mode/stex/stex'),
stylus: async () => await import('codemirror/mode/stylus/stylus'),
swift: async () => await import('codemirror/mode/swift/swift'),
tcl: async () => await import('codemirror/mode/tcl/tcl'),
textile: async () => await import('codemirror/mode/textile/textile'),
tiddlywiki: async () => await import('codemirror/mode/tiddlywiki/tiddlywiki'),
tiki: async () => await import('codemirror/mode/tiki/tiki'),
toml: async () => await import('codemirror/mode/toml/toml'),
tornado: async () => await import('codemirror/mode/tornado/tornado'),
troff: async () => await import('codemirror/mode/troff/troff'),
ttcn: async () => await import('codemirror/mode/ttcn/ttcn'),
turtle: async () => await import('codemirror/mode/turtle/turtle'),
twig: async () => await import('codemirror/mode/twig/twig'),
vb: async () => await import('codemirror/mode/vb/vb'),
vbscript: async () => await import('codemirror/mode/vbscript/vbscript'),
velocity: async () => await import('codemirror/mode/velocity/velocity'),
verilog: async () => await import('codemirror/mode/verilog/verilog'),
vhdl: async () => await import('codemirror/mode/vhdl/vhdl'),
vue: async () => await import('codemirror/mode/vue/vue'),
wast: async () => await import('codemirror/mode/wast/wast'),
webidl: async () => await import('codemirror/mode/webidl/webidl'),
xml: async () => await import('codemirror/mode/xml/xml'),
xquery: async () => await import('codemirror/mode/xquery/xquery'),
yacas: async () => await import('codemirror/mode/yacas/yacas'),
yaml: async () => await import('codemirror/mode/yaml/yaml'),
z80: async () => await import('codemirror/mode/z80/z80'),
};
return dynamicArray[str];
}

View File

@@ -64,6 +64,7 @@
import { useDesign } from '@/hooks/web/useDesign';
import { cloneDeep } from 'lodash-es';
import { TableActionType } from '@/components/Table';
import { isFunction } from '@/utils/is';
defineOptions({ name: 'BasicForm' });
@@ -130,6 +131,9 @@
component,
componentProps = {},
isHandleDateDefaultValue = true,
field,
isHandleDefaultValue = true,
valueFormat,
} = schema;
// handle date type
if (
@@ -161,6 +165,21 @@
schema.defaultValue = def;
}
}
// handle schema.valueFormat
if (
isHandleDefaultValue &&
defaultValue &&
component &&
isFunction(valueFormat)
) {
schema.defaultValue = valueFormat({
value: defaultValue,
schema,
model: formModel,
field,
});
}
}
if (unref(getProps).showAdvancedButton) {
return schemas.filter(
@@ -207,6 +226,7 @@
removeSchemaByField,
resetFields,
scrollToField,
resetDefaultField,
} = useFormEvents({
emit,
getProps,
@@ -305,6 +325,7 @@
validate,
submit: handleSubmit,
scrollToField: scrollToField,
resetDefaultField,
};
const getFormActionBindProps = computed(
@@ -337,11 +358,20 @@
// margin-bottom: 20px;
// }
&.suffix-item {
&.suffix-item,
&.prefix-item {
.ant-form-item-children {
display: flex;
}
.prefix {
display: inline-flex;
align-items: center;
margin-top: 1px;
padding-right: 6px;
line-height: 1;
}
.suffix {
display: inline-flex;
align-items: center;

View File

@@ -69,6 +69,14 @@
displayRenderArray: {
type: Array,
},
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
});
const emit = defineEmits(['change', 'defaultChange']);
@@ -112,19 +120,25 @@
}, [] as Option[]);
}
async function initialFetch() {
const api = props.api;
async function fetch() {
let { api, beforeFetch, initFetchParams, afterFetch, resultField } = props;
if (!api || !isFunction(api)) return;
apiData.value = [];
loading.value = true;
try {
const res = await api(props.initFetchParams);
if (beforeFetch && isFunction(beforeFetch)) {
initFetchParams = (await beforeFetch(initFetchParams)) || initFetchParams;
}
let res = await api(initFetchParams);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
if (Array.isArray(res)) {
apiData.value = res;
return;
}
if (props.resultField) {
apiData.value = get(res, props.resultField) || [];
if (resultField) {
apiData.value = get(res, resultField) || [];
}
} catch (error) {
console.warn(error);
@@ -136,20 +150,26 @@
const loadData: CascaderProps['loadData'] = async (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const api = props.api;
let { api, beforeFetch, afterFetch, resultField, apiParamKey } = props;
if (!api || !isFunction(api)) return;
try {
const res = await api({
[props.apiParamKey]: Reflect.get(targetOption, 'value'),
});
let param = {
[apiParamKey]: Reflect.get(targetOption, 'value'),
};
if (beforeFetch && isFunction(beforeFetch)) {
param = (await beforeFetch(param)) || param;
}
let res = await api(param);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
if (Array.isArray(res)) {
const children = generatorOptions(res);
targetOption.children = children;
return;
}
if (props.resultField) {
const children = generatorOptions(get(res, props.resultField) || []);
if (resultField) {
const children = generatorOptions(get(res, resultField) || []);
targetOption.children = children;
}
} catch (e) {
@@ -162,7 +182,7 @@
watch(
() => props.immediate,
() => {
props.immediate && initialFetch();
props.immediate && fetch();
},
{
immediate: true,
@@ -172,7 +192,7 @@
watch(
() => props.initFetchParams,
() => {
!unref(isFirstLoad) && initialFetch();
!unref(isFirstLoad) && fetch();
},
{ deep: true },
);

View File

@@ -57,6 +57,14 @@
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true),
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
});
const emit = defineEmits(['options-change', 'change', 'update:value']);
@@ -95,19 +103,25 @@
);
async function fetch() {
const api = props.api;
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api)) return;
options.value = [];
try {
loading.value = true;
const res = await api(props.params);
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
if (Array.isArray(res)) {
options.value = res;
emitChange();
return;
}
if (props.resultField) {
options.value = get(res, props.resultField) || [];
if (resultField) {
options.value = get(res, resultField) || [];
}
emitChange();
} catch (error) {

View File

@@ -54,6 +54,14 @@
type: Array<OptionsItem>,
default: [],
},
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
});
const emit = defineEmits(['options-change', 'change', 'update:value']);
@@ -103,20 +111,26 @@
);
async function fetch() {
const api = props.api;
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api) || loading.value) return;
optionsRef.value = [];
try {
loading.value = true;
const res = await api(props.params);
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
isFirstLoaded.value = true;
if (Array.isArray(res)) {
optionsRef.value = res;
emitChange();
return;
}
if (props.resultField) {
optionsRef.value = get(res, props.resultField) || [];
if (resultField) {
optionsRef.value = get(res, resultField) || [];
}
emitChange();
} catch (error) {

View File

@@ -32,7 +32,14 @@
dataSource: { type: Array as PropType<Array<TransferItem>> },
immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(false),
afterFetch: { type: Function },
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('title'),
valueField: propTypes.string.def('key'),
@@ -98,23 +105,29 @@
);
async function fetch() {
const api = props.api;
let { api, beforeFetch, afterFetch, params, resultField, dataSource } = props;
if (!api || !isFunction(api)) {
if (Array.isArray(props.dataSource)) {
_dataSource.value = props.dataSource;
if (Array.isArray(dataSource)) {
_dataSource.value = dataSource;
}
return;
}
_dataSource.value = [];
try {
const res = await api(props.params);
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
if (Array.isArray(res)) {
_dataSource.value = res;
emitChange();
return;
}
if (props.resultField) {
_dataSource.value = get(res, props.resultField) || [];
if (resultField) {
_dataSource.value = get(res, resultField) || [];
}
emitChange();
} catch (error) {

View File

@@ -7,10 +7,10 @@
</template>
<script lang="ts" setup>
import { type Recordable, type AnyFunction } from '@vben/types';
import { type Recordable } from '@vben/types';
import { type PropType, computed, watch, ref, onMounted, unref, useAttrs } from 'vue';
import { Tree, TreeProps } from 'ant-design-vue';
import { isArray, isFunction } from '@/utils/is';
import { isFunction } from '@/utils/is';
import { get } from 'lodash-es';
import { DataNode } from 'ant-design-vue/es/tree';
import { useRuleFormItem } from '@/hooks/component/useFormItem';
@@ -22,7 +22,14 @@
params: { type: Object },
immediate: { type: Boolean, default: true },
resultField: { type: String, default: '' },
afterFetch: { type: Function as PropType<AnyFunction> },
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
value: {
type: Array as PropType<TreeProps['selectedKeys']>,
},
@@ -72,25 +79,28 @@
});
async function fetch() {
const { api, afterFetch } = props;
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api)) return;
loading.value = true;
treeData.value = [];
let result;
let res;
try {
result = await api(props.params);
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
} catch (e) {
console.error(e);
}
if (afterFetch && isFunction(afterFetch)) {
result = afterFetch(result);
}
loading.value = false;
if (!result) return;
if (!isArray(result)) {
result = get(result, props.resultField);
if (!res) return;
if (resultField) {
res = get(res, resultField) || [];
}
treeData.value = (result as (Recordable & { key: string | number })[]) || [];
treeData.value = (res as (Recordable & { key: string | number })[]) || [];
isFirstLoaded.value = true;
emit('options-change', treeData.value);
}

View File

@@ -34,6 +34,14 @@
labelField: propTypes.string.def('title'),
valueField: propTypes.string.def('value'),
childrenField: propTypes.string.def('children'),
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
});
const emit = defineEmits(['options-change', 'change', 'load-data']);
@@ -88,22 +96,28 @@
}
async function fetch() {
const { api } = props;
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api) || loading.value) return;
loading.value = true;
treeData.value = [];
let result;
let res;
try {
result = await api(props.params);
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
} catch (e) {
console.error(e);
}
loading.value = false;
if (!result) return;
if (!isArray(result)) {
result = get(result, props.resultField);
if (!res) return;
if (resultField) {
res = get(res, resultField) || [];
}
treeData.value = (result as Recordable<any>[]) || [];
treeData.value = (res as Recordable<any>[]) || [];
isFirstLoaded.value = true;
emit('options-change', treeData.value);
}

View File

@@ -277,6 +277,7 @@
field,
changeEvent = 'change',
valueField,
valueFormat,
} = props.schema;
const isCheck = component && ['Switch', 'Checkbox'].includes(component);
@@ -286,9 +287,12 @@
const on = {
[eventKey]: (...args: Nullable<Recordable<any>>[]) => {
const [e] = args;
const target = e ? e.target : null;
const value = target ? (isCheck ? target.checked : target.value) : e;
let value = target ? (isCheck ? target.checked : target.value) : e;
if(isFunction(valueFormat)){
value = valueFormat({...unref(getValues),value});
}
props.setFormModel(field, value, props.schema);
if (propsData[eventKey]) {
@@ -367,7 +371,7 @@
}
function renderItem() {
const { itemProps, slot, render, field, suffix, component } = props.schema;
const { itemProps, slot, render, field, suffix, component, prefix } = props.schema;
const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
const { colon } = props.formProps;
const opts = { disabled: unref(getDisable), readonly: unref(getReadonly) };
@@ -383,7 +387,10 @@
labelCol={labelCol}
wrapperCol={wrapperCol}
name={field}
class={{ 'suffix-item': !!suffix }}
class={{
'suffix-item': !!suffix,
'prefix-item': !!prefix,
}}
>
<BasicTitle {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</BasicTitle>
</Form.Item>
@@ -400,6 +407,8 @@
const showSuffix = !!suffix;
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
const showPrefix = !!prefix;
const getPrefix = isFunction(prefix) ? prefix(unref(getValues)) : prefix;
// TODO 自定义组件验证会出现问题因此这里框架默认将自定义组件设置手动触发验证如果其他组件还有此问题请手动设置autoLink=false
if (component && NO_AUTO_LINK_COMPONENTS.includes(component)) {
props.schema &&
@@ -413,7 +422,10 @@
<Form.Item
name={field}
colon={colon}
class={{ 'suffix-item': showSuffix }}
class={{
'suffix-item': showSuffix,
'prefix-item': showPrefix,
}}
{...(itemProps as Recordable<any>)}
label={renderLabelHelpMessage()}
rules={handleRules()}
@@ -421,6 +433,7 @@
wrapperCol={wrapperCol}
>
<div style="display:flex">
{showPrefix && <span class="prefix">{getPrefix}</span>}
<div style="flex:1;">{getContent()}</div>
{showSuffix && <span class="suffix">{getSuffix}</span>}
</div>

View File

@@ -2,7 +2,7 @@ import type { Rule as ValidationRule } from 'ant-design-vue/lib/form/interface';
import type { ComponentType } from './types';
import { useI18n } from '@/hooks/web/useI18n';
import { dateUtil } from '@/utils/dateUtil';
import { isNumber, isObject } from '@/utils/is';
import { isObject } from '@/utils/is';
const { t } = useI18n();
@@ -32,7 +32,7 @@ export function createPlaceholderMessage(component: ComponentType) {
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'];
function genType() {
return [...DATE_TYPE, 'RangePicker'];
return [...DATE_TYPE, 'RangePicker',"TimeRangePicker"];
}
export function setComponentRuleType(
@@ -45,7 +45,7 @@ export function setComponentRuleType(
}
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) {
rule.type = valueFormat ? 'string' : 'object';
} else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) {
} else if (['RangePicker', 'Upload', 'CheckboxGroup'].includes(component)) {
rule.type = 'array';
} else if (['InputNumber'].includes(component)) {
rule.type = 'number';
@@ -69,14 +69,6 @@ export const defaultValueComponents = [
'InputTextArea',
];
export function handleInputNumberValue(component?: ComponentType, val?: any) {
if (!component) return val;
if (defaultValueComponents.includes(component)) {
return val && isNumber(val) ? `${val}` : val;
}
return val;
}
/**
* 时间字段
*/

View File

@@ -120,24 +120,18 @@ export default function ({
const { baseColProps = {} } = unref(getProps);
for (const schema of unref(getSchema)) {
const { show, colProps } = schema;
const { show, ifShow, colProps } = schema;
const renderCallbackParams = {
schema: schema,
model: formModel,
field: schema.field,
values: { ...unref(defaultValueRef), ...formModel },
};
let isShow = true;
if (isBoolean(show)) {
isShow = show;
}
if (isFunction(show)) {
isShow = show({
schema: schema,
model: formModel,
field: schema.field,
values: {
...unref(defaultValueRef),
...formModel,
},
});
}
isShow && isBoolean(ifShow) && (isShow = ifShow);
isShow && isFunction(ifShow) && (isShow = ifShow(renderCallbackParams));
isShow && isBoolean(show) && (isShow = show);
isShow && isFunction(show) && (isShow = show(renderCallbackParams));
if (isShow && (colProps || baseColProps)) {
const { itemColSum: sum, isAdvanced } = getAdvanced(

View File

@@ -78,9 +78,13 @@ export function useForm(props?: Props): UseFormReturnType {
form.clearValidate(name);
},
resetFields: async () => {
getForm().then(async (form) => {
await form.resetFields();
resetFields: () => {
// 修复表单重置后,页面变化了,但是由于异步问题导致表单内部的状态没有及时同步
return new Promise((resolve) => {
getForm().then(async (form) => {
await form.resetFields();
resolve();
});
});
},
@@ -121,6 +125,9 @@ export function useForm(props?: Props): UseFormReturnType {
const form = await getForm();
return form.validateFields(nameList);
},
resetDefaultField: async (nameList?: NamePath[]) => {
unref(formRef)?.resetDefaultField(nameList);
},
};
return [register, methods];

View File

@@ -2,16 +2,11 @@ import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchemaInner as FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw, nextTick } from 'vue';
import { isArray, isFunction, isObject, isString, isDef, isNil } from '@/utils/is';
import { isArray, isFunction, isObject, isString, isNil } from '@/utils/is';
import { deepMerge } from '@/utils';
import {
dateItemType,
handleInputNumberValue,
defaultValueComponents,
isIncludeSimpleComponents,
} from '../helper';
import { dateItemType, defaultValueComponents, isIncludeSimpleComponents } from '../helper';
import { dateUtil } from '@/utils/dateUtil';
import { cloneDeep, has, uniqBy, get } from 'lodash-es';
import { cloneDeep, has, uniqBy, get, set } from 'lodash-es';
import { error } from '@/utils/log';
interface UseFormActionContext {
@@ -25,6 +20,23 @@ interface UseFormActionContext {
handleFormValues: Fn;
}
function tryConstructArray(field: string, values: Recordable = {}): any[] | undefined {
const pattern = /^\[(.+)\]$/;
if (pattern.test(field)) {
const match = field.match(pattern);
if (match && match[1]) {
const keys = match[1].split(',');
if (!keys.length) {
return undefined;
}
const result = [];
keys.forEach((k, index) => {
set(result, index, values[k.trim()]);
});
return result.filter(Boolean).length ? result : undefined;
}
}
}
export function useFormEvents({
emit,
getProps,
@@ -77,10 +89,8 @@ export function useFormEvents({
const validKeys: string[] = [];
fields.forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key);
let value = get(values, key);
const value = get(values, key);
const hasKey = has(values, key);
value = handleInputNumberValue(schema?.component, value);
const { componentProps } = schema || {};
let _props = componentProps as any;
if (typeof componentProps === 'function') {
@@ -90,37 +100,66 @@ export function useFormEvents({
});
}
const constructValue = get(value, key);
let constructValue;
const setDateFieldValue = (v) => {
return v ? (_props?.valueFormat ? v : dateUtil(v)) : null;
};
// 0| '' is allow
if (hasKey || !!constructValue) {
const fieldValue = constructValue || value;
// time type
if (itemIsDateType(key)) {
// Adapt date component
if (itemIsDateComponent(schema?.component)) {
constructValue = tryConstructArray(key, values);
if (constructValue) {
const fieldValue = constructValue || value;
if (Array.isArray(fieldValue)) {
const arr: any[] = [];
for (const ele of fieldValue) {
arr.push(setDateFieldValue(ele));
}
unref(formModel)[key] = arr;
validKeys.push(key);
} else {
unref(formModel)[key] = setDateFieldValue(fieldValue);
validKeys.push(key);
}
} else {
unref(formModel)[key] = fieldValue;
}
}
// Adapt common component
if (hasKey) {
constructValue = get(value, key);
const fieldValue = constructValue || value;
unref(formModel)[key] = fieldValue;
if (_props?.onChange) {
_props?.onChange(fieldValue);
}
validKeys.push(key);
} else {
// key not exist
if (isDef(get(defaultValueRef.value, key))) {
unref(formModel)[key] = cloneDeep(unref(get(defaultValueRef.value, key)));
}
// refer:https://github.com/vbenjs/vue-vben-admin/issues/3795
}
});
validateFields(validKeys).catch((_) => {});
}
/**
* @description: Set form default value
*/
function resetDefaultField(nameList?: NamePath[]) {
if (!Array.isArray(nameList)) {
return;
}
if (Array.isArray(nameList) && nameList.length === 0) {
return;
}
const validKeys: string[] = [];
const keys = Object.keys(unref(formModel));
if (!keys) {
return;
}
nameList.forEach((key: any) => {
if (keys.includes(key)) {
validKeys.push(key);
unref(formModel)[key] = cloneDeep(unref(get(defaultValueRef.value, key)));
}
});
validateFields(validKeys).catch((_) => {});
@@ -135,9 +174,9 @@ export function useFormEvents({
return;
}
let fieldList: string[] = isString(fields) ? [fields] : fields;
let fieldList = (isString(fields) ? [fields] : fields) as string[];
if (isString(fields)) {
fieldList = [fields];
fieldList = [fields as string];
}
for (const field of fieldList) {
_removeSchemaByField(field, schemaList);
@@ -280,10 +319,8 @@ export function useFormEvents({
/**
* @description: Is it time
*/
function itemIsDateType(key: string) {
return unref(getSchema).some((item) => {
return item.field === key && item.component ? dateItemType.includes(item.component) : false;
});
function itemIsDateComponent(key: string) {
return dateItemType.includes(key);
}
async function validateFields(nameList?: NamePath[] | undefined) {
@@ -366,6 +403,7 @@ export function useFormEvents({
resetFields,
setFieldsValue,
scrollToField,
resetDefaultField,
};
}
@@ -377,7 +415,7 @@ function getDefaultValue(
let defaultValue = cloneDeep(defaultValueRef.value[key]);
const isInput = checkIsInput(schema);
if (isInput) {
return defaultValue || undefined;
return !isNil(defaultValue) ? defaultValue : undefined;
}
if (!defaultValue && schema && checkIsRangeSlider(schema)) {
defaultValue = [0, 0];

View File

@@ -135,7 +135,7 @@ export function useFormValues({
const schemas = unref(getSchema);
const obj: Recordable = {};
schemas.forEach((item) => {
const { defaultValue, defaultValueObj } = item;
const { defaultValue, defaultValueObj, componentProps = {} } = item;
const fieldKeys = Object.keys(defaultValueObj || {});
if (fieldKeys.length) {
fieldKeys.forEach((field) => {
@@ -152,6 +152,12 @@ export function useFormValues({
formModel[item.field] = defaultValue;
}
}
if (!isNil(componentProps?.defaultValue)) {
obj[item.field] = componentProps?.defaultValue;
if (formModel[item.field] === undefined) {
formModel[item.field] = componentProps?.defaultValue;
}
}
});
defaultValueRef.value = cloneDeep(obj);
}

View File

@@ -41,6 +41,7 @@ export interface FormActionType {
validateFields: (nameList?: NamePath[]) => Promise<any>;
validate: <T = Recordable>(nameList?: NamePath[] | false) => Promise<T>;
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
resetDefaultField: (name?: NamePath[]) => void;
}
export type RegisterFn = (formInstance: FormActionType) => void;
@@ -166,8 +167,16 @@ interface BaseFormSchema<T extends ComponentType = any> {
// Required
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
suffix?:
| string
| number
| VNode
| ((renderCallbackParams: RenderCallbackParams) => string | VNode | number);
prefix?:
| string
| number
| VNode
| ((renderCallbackParams: RenderCallbackParams) => string | VNode | number);
// Validation rules
rules?: Rule[];
// Check whether the information is added to the label
@@ -188,6 +197,9 @@ interface BaseFormSchema<T extends ComponentType = any> {
// 是否自动处理与时间相关组件的默认值
isHandleDateDefaultValue?: boolean;
// 是否使用valueFormat自动处理默认值
isHandleDefaultValue?: boolean;
isAdvanced?: boolean;
// Matching details components
@@ -223,6 +235,8 @@ interface BaseFormSchema<T extends ComponentType = any> {
dynamicReadonly?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
valueFormat?: (arg: Partial<RenderCallbackParams> & { value: any }) => any;
}
export interface ComponentFormSchema<T extends ComponentType = any> extends BaseFormSchema<T> {
// render component

View File

@@ -19,13 +19,13 @@ export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElemen
vm = createVNode(LoadingWrap);
let container: Nullable<HTMLElement> = null;
if (wait) {
// TODO fix https://github.com/anncwb/vue-vben-admin/issues/438
setTimeout(() => {
render(vm, document.createElement('div'));
render(vm, (container = document.createElement('div')));
}, 0);
} else {
render(vm, document.createElement('div'));
render(vm, (container = document.createElement('div')));
}
function close() {
@@ -41,6 +41,11 @@ export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElemen
target.appendChild(vm.el as HTMLElement);
}
function destory() {
container && render(null, container);
container = vm = null;
}
if (target) {
open(target);
}
@@ -48,6 +53,7 @@ export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElemen
vm,
close,
open,
destory,
setTip: (tip: string) => {
data.tip = tip;
},

View File

@@ -1,7 +1,8 @@
import { unref } from 'vue';
import type { Ref } from 'vue';
import { tryOnUnmounted } from '@vueuse/core';
import { createLoading } from './createLoading';
import type { LoadingProps } from './typing';
import type { Ref } from 'vue';
export interface UseLoadingOptions {
target?: any;
@@ -45,5 +46,9 @@ export function useLoading(
instance.setTip(tip);
};
tryOnUnmounted(() => {
instance.destory();
});
return [open, close, setTip];
}

View File

@@ -32,13 +32,9 @@
<HeaderCell :column="column" />
</slot>
</template>
<!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data">
<slot name="bodyCell" v-bind="data || {}"></slot>
</template>
<!-- <template #[`header-${column.dataIndex}`] v-for="(column, index) in columns" :key="index">-->
<!-- <HeaderCell :column="column" />-->
<!-- </template>-->
</Table>
</div>
</template>

View File

@@ -369,11 +369,9 @@
if (!props.record.editValueRefs) props.record.editValueRefs = {};
props.record.editValueRefs[props.column.dataIndex as any] = currentValueRef;
}
/* eslint-disable */
props.record.onCancelEdit = () => {
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
};
/* eslint-disable */
props.record.onSubmitEdit = async () => {
if (isArray(props.record?.submitCbs)) {
if (!props.record?.onValid?.()) return;
@@ -508,7 +506,7 @@
}
.@{prefix-cls} {
position: relative;
min-height: 24px; //设置高度让其始终可被hover
min-height: 24px; // 设置高度让其始终可被hover
&__wrapper {
display: flex;

View File

@@ -114,32 +114,40 @@ export function useDataSource(
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
return unref(dataSourceRef);
}
if (unref(getAutoCreateKey)) {
const firstItem = dataSource[0];
const lastItem = dataSource[dataSource.length - 1];
const getDataSourceRef: Ref<Recordable<any>[]> = ref([]);
if (firstItem && lastItem) {
if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
const data = cloneDeep(unref(dataSourceRef));
data.forEach((item) => {
if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID();
}
if (item.children && item.children.length) {
setTableKey(item.children);
}
});
dataSourceRef.value = data;
watch(
() => dataSourceRef.value,
() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
getDataSourceRef.value = unref(dataSourceRef);
}
if (unref(getAutoCreateKey)) {
const firstItem = dataSource[0];
const lastItem = dataSource[dataSource.length - 1];
if (firstItem && lastItem) {
if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
const data = cloneDeep(unref(dataSourceRef));
data.forEach((item) => {
if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID();
}
if (item.children && item.children.length) {
setTableKey(item.children);
}
});
dataSourceRef.value = data;
}
}
}
}
return unref(dataSourceRef);
});
getDataSourceRef.value = unref(dataSourceRef);
},
{
deep: true,
},
);
async function updateTableData(index: number, key: Key, value: any) {
const record = dataSourceRef.value[index];
@@ -351,7 +359,7 @@ export function useDataSource(
});
return {
getDataSourceRef,
getDataSourceRef: computed(() => getDataSourceRef.value),
getDataSource,
getRawDataSource,
searchInfoRef,

View File

@@ -1,7 +1,12 @@
<template>
<div>
<Space>
<a-button type="primary" @click="openUploadModal" preIcon="carbon:cloud-upload">
<a-button
type="primary"
@click="openUploadModal"
preIcon="carbon:cloud-upload"
:disabled="disabled"
>
{{ t('component.upload.upload') }}
</a-button>
<Tooltip placement="bottom" v-if="showPreview">
@@ -31,6 +36,7 @@
<UploadPreviewModal
:value="fileList"
:max-number="bindValue.maxNumber"
@register="registerPreviewModal"
@list-change="handlePreviewChange"
@delete="handlePreviewDelete"
@@ -48,10 +54,11 @@
import { uploadContainerProps } from './props';
import { omit } from 'lodash-es';
import { useI18n } from '@/hooks/web/useI18n';
import { isArray } from '@/utils/is';
import { isArray, isObject, isString } from '@/utils/is';
import UploadModal from './components/UploadModal.vue';
import UploadPreviewModal from './components/UploadPreviewModal.vue';
import { BaseFileItem } from './types/typing';
import { buildUUID } from '@/utils/uuid';
defineOptions({ name: 'BasicUpload' });
const props = defineProps(uploadContainerProps);
@@ -66,7 +73,7 @@
// 预览modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const fileList = ref<string[]>([]);
const fileList = ref<BaseFileItem[] | any[]>([]);
const showPreview = computed(() => {
const { emptyHidePreview } = props;
@@ -78,27 +85,64 @@
const value = { ...attrs, ...props };
return omit(value, 'onChange');
});
function getValue(valueKey="url") {
const list = (fileList.value || []).map((item: any) => {
return item[valueKey];
});
return list;
}
function genFileListByUrls(urls: string[]) {
const list = urls.map((e) => {
return {
uid: buildUUID(),
url: e,
};
});
return list;
}
watch(
() => props.value,
(value = []) => {
fileList.value = isArray(value) ? value : [];
(v = []) => {
let values: string[] = [];
if (v) {
if (isArray(v)) {
values = v;
} else if (typeof v == 'string') {
values.push(v);
}
fileList.value = values.map((item,i) => {
if (item && isString(item)) {
return {
uid: buildUUID(),
url: item,
};
} else if (item && isObject(item)) {
return item;
} else {
return;
}
}) as any;
}
emit('update:value', values);
emit('change', values);
},
{ immediate: true },
);
// 上传modal保存操作
function handleChange(urls: string[]) {
fileList.value = [...unref(fileList), ...(urls || [])];
emit('update:value', fileList.value);
emit('change', fileList.value);
function handleChange(urls: string[],valueKey:string) {
fileList.value = [...unref(fileList), ...(genFileListByUrls(urls) || [])];
const values = getValue(valueKey);
emit('update:value', values);
emit('change', values);
}
// 预览modal保存操作
function handlePreviewChange(urls: string[]) {
fileList.value = [...(urls || [])];
emit('update:value', fileList.value);
emit('change', fileList.value);
function handlePreviewChange(fileItems: string[],valueKey:string) {
fileList.value = [...(fileItems || [])];
const values = getValue(valueKey);
emit('update:value', values);
emit('change', values);
}
function handleDelete(record: Recordable<any>) {

View File

@@ -9,6 +9,7 @@
:maxCount="maxNumber"
:before-upload="beforeUpload"
:custom-request="customRequest"
:disabled="disabled"
@preview="handlePreview"
@remove="handleRemove"
>
@@ -70,8 +71,8 @@
isInnerOperate.value = false;
return;
}
let value: string[] = [];
if (v) {
let value: string[] = [];
if (isArray(v)) {
value = v;
} else {
@@ -92,6 +93,8 @@
}
}) as UploadProps['fileList'];
}
emit('update:value', value);
emit('change', value);
},
{
immediate: true,
@@ -157,21 +160,22 @@
};
async function customRequest(info: UploadRequestOption<any>) {
const { api } = props;
const { api, uploadParams = {}, name, filename, resultField } = props;
if (!api || !isFunction(api)) {
return warn('upload api must exist and be a function');
}
try {
const res = await props.api?.({
const res = await api?.({
data: {
...(props.uploadParams || {}),
...uploadParams,
},
file: info.file,
name: props.name,
filename: props.filename,
name: name,
filename: filename,
});
if (props.resultField) {
info.onSuccess!(res);
let result = get(res, resultField);
info.onSuccess!(result);
} else {
// 不传入 resultField 的情况
info.onSuccess!(res.data);
@@ -190,12 +194,12 @@
const list = (fileList.value || [])
.filter((item) => item?.status === UploadResultStatus.DONE)
.map((item: any) => {
if (props.resultField) {
return get(item?.response, props.resultField);
if (item?.response && props?.resultField) {
return item?.response;
}
return item?.url || item?.response?.url;
});
return props.multiple ? list : list.length > 0 ? list[0] : '';
return list;
}
</script>

View File

@@ -71,7 +71,7 @@
const props = defineProps({
...basicProps,
previewFileList: {
type: Array as PropType<string[]>,
type: Array as PropType<string[] | any[]>,
default: () => [],
},
});

View File

@@ -14,13 +14,16 @@
import { watch, ref } from 'vue';
import FileList from './FileList.vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { previewProps } from '../props';
import { FileBasicColumn, PreviewFileItem } from '../types/typing';
import { handleFnKey, previewProps } from '../props';
import { BaseFileItem, FileBasicColumn, PreviewFileItem } from '../types/typing';
import { downloadByUrl } from '@/utils/file/download';
import { createPreviewColumns, createPreviewActionColumn } from './data';
import { useI18n } from '@/hooks/web/useI18n';
import { isArray } from '@/utils/is';
import { isArray, isFunction } from '@/utils/is';
import { BasicColumn } from '@/components/Table';
import { useMessage } from '@/hooks/web/useMessage';
import { buildUUID } from '@/utils/uuid';
const { createMessage } = useMessage();
const props = defineProps(previewProps);
@@ -32,13 +35,15 @@
const [register] = useModalInner();
const { t } = useI18n();
const fileListRef = ref<PreviewFileItem[] | Array<any>>([]);
const fileListRef = ref<BaseFileItem[] | Array<any>>([]);
watch(
() => props.previewColumns,
() => {
if (props.previewColumns.length) {
if (Array.isArray(props.previewColumns) && props.previewColumns.length) {
columns = props.previewColumns;
actionColumn = null;
} else if (isFunction(props.previewColumns)) {
columns = props.previewColumns({ handleRemove, handleAdd });
} else {
columns = createPreviewColumns();
actionColumn = createPreviewActionColumn({ handleRemove, handleDownload });
@@ -59,14 +64,15 @@
fileListRef.value = value
.filter((item) => !!item)
.map((item) => {
if (typeof item != 'string') {
console.error('return value should be string');
if (typeof item != 'object') {
console.error('return value should be object');
return;
}
return {
url: item,
type: item.split('.').pop() || '',
name: item.split('/').pop() || '',
uid: item?.uid,
url: item?.url,
type: item?.url?.split('.').pop() || '',
name: item?.url?.split('/').pop() || '',
};
});
},
@@ -74,18 +80,27 @@
);
// 删除
function handleRemove(record: PreviewFileItem) {
const index = fileListRef.value.findIndex((item) => item.url === record.url);
function handleRemove(obj: Record<handleFnKey, any>) {
let { record = {}, valueKey = 'url', uidKey = 'uid' } = obj;
const index = fileListRef.value.findIndex((item) => item[uidKey] === record[uidKey]);
if (index !== -1) {
const removed = fileListRef.value.splice(index, 1);
emit('delete', removed[0].url);
emit(
'list-change',
fileListRef.value.map((item) => item.url),
);
emit('delete', removed[0][uidKey]);
emit('list-change', fileListRef.value, valueKey);
}
}
// 添加
function handleAdd(obj: Record<handleFnKey, any>) {
let { record = {}, valueKey = 'url', uidKey = 'uid' } = obj;
const { maxNumber } = props;
if (fileListRef.value.length + fileListRef.value.length > maxNumber) {
return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
}
record[uidKey] = record[uidKey] ?? buildUUID();
record[valueKey] = record[valueKey];
fileListRef.value = [...fileListRef.value, record];
emit('list-change', fileListRef.value, valueKey);
}
// 下载
function handleDownload(record: PreviewFileItem) {
const { url = '' } = record;

View File

@@ -5,6 +5,7 @@ import { Progress, Tag } from 'ant-design-vue';
import TableAction from '@/components/Table/src/components/TableAction.vue';
import ThumbUrl from './ThumbUrl.vue';
import { useI18n } from '@/hooks/web/useI18n';
import { previewColumnsFnType } from '../props';
const { t } = useI18n();
@@ -81,7 +82,11 @@ export function createActionColumn(handleRemove: Function): FileBasicColumn {
{
label: t('component.upload.del'),
color: 'error',
onClick: handleRemove.bind(null, record),
onClick: handleRemove.bind(null, {
record,
uidKey: 'uid',
valueKey: 'url',
}),
},
];
return <TableAction actions={actions} outside={true} />;
@@ -112,7 +117,7 @@ export function createPreviewActionColumn({
handleRemove,
handleDownload,
}: {
handleRemove: Fn;
handleRemove: previewColumnsFnType['handleRemove'];
handleDownload: Fn;
}): BasicColumn {
return {
@@ -125,7 +130,11 @@ export function createPreviewActionColumn({
{
label: t('component.upload.del'),
color: 'error',
onClick: handleRemove.bind(null, record),
onClick: handleRemove.bind(null, {
record,
uidKey: 'uid',
valueKey: 'url',
}),
},
{
label: t('component.upload.download'),

View File

@@ -1,5 +1,5 @@
import type { PropType } from 'vue';
import { FileBasicColumn } from './types/typing';
import { BaseFileItem, FileBasicColumn } from './types/typing';
import type { Options } from 'sortablejs';
@@ -14,15 +14,20 @@ type SortableOptions = Merge<
// ...可扩展
}
>;
export type handleFnKey = "record" | "valueKey" | "uidKey"
export type previewColumnsFnType = {
handleRemove: (record: Record<handleFnKey, any>) => any;
handleAdd: (record: Record<handleFnKey, any>) => any;
};
export const previewType = {
previewColumns: {
type: Array as PropType<BasicColumn[] | FileBasicColumn[]>,
default: [],
type: [Array, Function] as PropType<
BasicColumn[] | ((arg: previewColumnsFnType) => BasicColumn[])
>,
required: false,
},
beforePreviewData: {
type: Function as PropType<(arg: string[]) => Recordable<any>>,
type: Function as PropType<(arg: BaseFileItem[] | any) => Recordable<any>>,
default: null,
required: false,
},
@@ -31,6 +36,7 @@ export const previewType = {
type ListType = 'text' | 'picture' | 'picture-card';
export const basicProps = {
disabled: { type: Boolean, default: false },
listType: {
type: String as PropType<ListType>,
default: 'picture-card',
@@ -107,9 +113,13 @@ export const uploadContainerProps = {
export const previewProps = {
value: {
type: Array as PropType<string[]>,
type: Array as PropType<BaseFileItem[] | any[]>,
default: () => [],
},
maxNumber: {
type: Number as PropType<number>,
default: 1,
},
...previewType,
};

View File

@@ -20,6 +20,11 @@ export interface FileItem {
uuid: string;
}
export interface BaseFileItem {
uid: string | number;
url: string;
name?: string;
}
export interface PreviewFileItem {
url: string;
name: string;

View File

@@ -28,7 +28,7 @@ const loadingDirective: Directive = {
}
},
unmounted(el) {
el?.instance?.close();
el?.instance?.destory();
},
};

View File

@@ -10,7 +10,7 @@
<router-link v-else to="" @click="handleClick(routeItem)">
{{ t((routeItem.meta.title || routeItem.name) as string) }}
</router-link>
<template v-if="routeItem.children" #overlay>
<template v-if="routeItem.children && !routeItem.meta?.hideChildrenInMenu" #overlay>
<Menu>
<template v-for="childItem in routeItem.children" :key="childItem.name">
<MenuItem>
@@ -91,7 +91,7 @@
const breadcrumbList = filterItem(matched);
if (currentRoute.value.meta?.currentActiveMenu) {
if (currentRoute.value.meta?.currentActiveMenu && !currentRoute.value.meta?.hideBreadcrumb) {
breadcrumbList.push({
...currentRoute.value,
name: currentRoute.value.meta?.title || currentRoute.value.name,
@@ -131,7 +131,6 @@
}
function handleClick(route) {
console.log(route);
const { children, redirect, meta } = route;
if (children?.length && !redirect) {

View File

@@ -37,7 +37,7 @@
},
"editor": {
"editor": "Editor",
"jsonEditor": "Json editor",
"codeEditor": "Code editor",
"markdown": "Markdown editor",
"tinymce": "Rich text",
"tinymceBasic": "Basic",
@@ -53,6 +53,7 @@
"feat": {
"feat": "Page Function",
"icon": "Icon",
"screenShot": "Screen Shot",
"tabs": "Tabs",
"tabDetail": "Tab Detail",
"sessionTimeout": "Session Timeout",
@@ -176,4 +177,4 @@
"resizeParentHeightTable": "resizeParentHeightTable",
"vxeTable": "VxeTable"
}
}
}

View File

@@ -37,7 +37,7 @@
},
"editor": {
"editor": "编辑器",
"jsonEditor": "Json编辑器",
"codeEditor": "代码编辑器",
"markdown": "markdown编辑器",
"tinymce": "富文本",
"tinymceBasic": "基础使用",
@@ -53,6 +53,7 @@
"feat": {
"feat": "功能",
"icon": "图标",
"screenShot": "截图",
"sessionTimeout": "登录过期",
"tabs": "标签页操作",
"tabDetail": "标签详情页",
@@ -175,4 +176,4 @@
"resizeParentHeightTable": "继承父元素高度",
"vxeTable": "VxeTable"
}
}
}

View File

@@ -345,12 +345,30 @@ const comp: AppRouteModule = {
},
children: [
{
path: 'json',
component: () => import('@/views/demo/editor/json/index.vue'),
name: 'JsonEditorDemo',
path: 'code',
component: () => import('@/views/demo/editor/code/index.vue'),
name: 'codeEditorDemo',
meta: {
title: t('routes.demo.editor.jsonEditor'),
title: t('routes.demo.editor.codeEditor'),
},
children: [
{
path: 'code',
name: 'codeBasicDemo',
component: () => import('@/views/demo/editor/code/index.vue'),
meta: {
title: t('routes.demo.editor.tinymceBasic'),
},
},
{
path: 'editor',
name: 'codeEditorBasicDemo',
component: () => import('@/views/demo/editor/code/Editor.vue'),
meta: {
title: t('routes.demo.editor.tinymceForm'),
},
},
],
},
{
path: 'markdown',

View File

@@ -23,6 +23,14 @@ const feat: AppRouteModule = {
title: t('routes.demo.feat.icon'),
},
},
{
path: 'screenshot',
name: 'Screenshot',
component: () => import('@/views/demo/feat/screenshot/index.vue'),
meta: {
title: t('routes.demo.feat.screenShot'),
},
},
{
path: 'ws',
name: 'WebSocket',

View File

@@ -3,7 +3,7 @@ import type { AppRouteModule } from '@/router/types';
import { LAYOUT } from '@/router/constant';
import { t } from '@/hooks/web/useI18n';
const charts: AppRouteModule = {
const flow: AppRouteModule = {
path: '/flow',
name: 'FlowDemo',
component: LAYOUT,
@@ -25,4 +25,4 @@ const charts: AppRouteModule = {
],
};
export default charts;
export default flow;

View File

@@ -3,7 +3,7 @@ import type { AppRouteModule } from '@/router/types';
import { getParentLayout, LAYOUT } from '@/router/constant';
import { t } from '@/hooks/web/useI18n';
const permission: AppRouteModule = {
const level: AppRouteModule = {
path: '/level',
name: 'Level',
component: LAYOUT,
@@ -65,4 +65,4 @@ const permission: AppRouteModule = {
],
};
export default permission;
export default level;

View File

@@ -134,7 +134,7 @@ export const useMultipleTabStore = defineStore({
// Existing pages, do not add tabs repeatedly
const tabHasExits = this.tabList.some((tab, index) => {
updateIndex = index;
return (tab.fullPath || tab.path) === (fullPath || path);
return decodeURIComponent(tab.fullPath || tab.path) === decodeURIComponent(fullPath || path);
});
// If the tab already exists, perform the update operation

View File

@@ -0,0 +1,23 @@
<template>
<Alert message="基础示例" />
<BasicUpload
:maxSize="20"
:maxNumber="10"
@change="handleChange"
:api="uploadApi"
class="my-5"
:accept="['image/*']"
/>
</template>
<script setup lang="ts">
import { Alert } from 'ant-design-vue';
import { BasicUpload } from '@/components/Upload';
import { useMessage } from '@/hooks/web/useMessage';
import { uploadApi } from '@/api/sys/upload';
const { createMessage } = useMessage();
function handleChange(list: string[]) {
createMessage.success(`已上传文件${JSON.stringify(list)}`);
}
</script>

View File

@@ -0,0 +1,56 @@
<template>
<Alert message="嵌入表单,加入表单校验" />
<BasicForm @register="registerValiate" class="my-5" />
</template>
<script setup lang="ts">
import { Alert } from 'ant-design-vue';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { uploadApi } from '@/api/sys/upload';
const { createMessage } = useMessage();
const schemasValiate: FormSchema[] = [
{
field: 'field1',
component: 'Upload',
label: '字段1',
rules: [{ required: true, message: '请选择上传文件' }],
componentProps: {
api: uploadApi,
},
},
{
field: 'field2',
component: 'ImageUpload',
label: '字段2(ImageUpload)',
colProps: {
span: 8,
},
componentProps: {
api: uploadApi,
},
},
];
const [registerValiate, { getFieldsValue: getFieldsValueValiate, validate }] = useForm({
labelWidth: 160,
schemas: schemasValiate,
actionColOptions: {
span: 18,
},
submitFunc: () => {
return new Promise((resolve) => {
validate()
.then(() => {
resolve();
console.log(getFieldsValueValiate());
createMessage.success(`请到控制台查看结果`);
})
.catch(() => {
createMessage.error(`请输入必填项`);
});
});
},
});
</script>

View File

@@ -0,0 +1,73 @@
<template>
<Alert message="嵌入表单,加入resultFiled自定义返回值" />
<BasicForm @register="registerCustom" class="my-5" />
</template>
<script setup lang="ts">
import { Alert } from 'ant-design-vue';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { uploadApi } from '@/api/sys/upload';
const { createMessage } = useMessage();
const schemasCustom: FormSchema[] = [
{
field: 'field3',
component: 'Upload',
label: '字段3',
componentProps: {
resultField: 'data3.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data3: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
{
field: 'field4',
component: 'ImageUpload',
label: '字段4(ImageUpload)',
colProps: {
span: 8,
},
componentProps: {
resultField: 'data4.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data4: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
];
const [registerCustom, { getFieldsValue: getFieldsValueCustom }] = useForm({
labelWidth: 160,
schemas: schemasCustom,
actionColOptions: {
span: 18,
},
submitFunc: () => {
return new Promise((resolve) => {
console.log(getFieldsValueCustom());
resolve();
createMessage.success(`请到控制台查看结果`);
});
},
});
</script>

View File

@@ -0,0 +1,187 @@
<template>
<Alert message="嵌入表单,自定义预览内容" />
<BasicForm @register="registerPreview" class="my-5" />
</template>
<script setup lang="ts">
import { createVNode } from 'vue';
import { Alert, Button } from 'ant-design-vue';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
import { useMessage } from '@/hooks/web/useMessage';
import { uploadApi } from '@/api/sys/upload';
const { createMessage } = useMessage();
const schemasPreview: FormSchema[] = [
{
field: 'field5',
component: 'Upload',
label: '字段5',
componentProps: {
previewColumns: [
{
title: 'url5',
dataIndex: 'url5',
},
{
title: 'type5',
dataIndex: 'type5',
},
{
title: 'name5',
dataIndex: 'name5',
},
{
title: 'operation',
dataIndex: '',
customRender: ({ record }) => {
return createVNode(
Button,
{
onclick: () => {
console.log(record);
createMessage.success(`请到控制台查看该行输出结果`);
},
},
() => '点我输出该行信息',
);
},
},
],
beforePreviewData: (arg) => {
let data = arg
.filter((item) => !!item)
.map((item) => {
if (typeof item !== 'string') {
console.error('return value should be string');
return;
}
return {
url5: item,
type5: item.split('.').pop() || '',
name5: item.split('/').pop() || '',
};
});
return data;
},
resultField: 'data5.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data5: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
{
field: 'field6',
component: 'Upload',
label: '字段6',
componentProps: {
maxNumber: 2,
previewColumns: ({ handleRemove, handleAdd }) => {
return [
{
title: 'url6',
dataIndex: 'url6',
},
{
title: 'type6',
dataIndex: 'type6',
},
{
title: '操作1',
dataIndex: 'operation',
customRender: ({ record }) => {
return createVNode('div', {}, [
createVNode(
Button,
{
type: 'primary',
style: 'margin:4px',
onclick: () => {
handleAdd({
record: {
id6: new Date().getTime(),
url6: 'https://vebn.oss-cn-beijing.aliyuncs.com/vben/logo.png',
},
uidKey: 'id6',
valueKey: 'url6',
});
},
},
() => '点我新增',
),
createVNode(
Button,
{
danger: true,
onclick: () => {
handleRemove({
record: { url6: record.url6 },
uidKey: 'url6',
valueKey: 'url6',
});
},
},
() => '点我删除',
),
]);
},
},
];
},
beforePreviewData: (arg) => {
let data = arg
.filter((item) => !!item)
.map((item) => {
if (typeof item !== 'object') {
console.error('return value should be object');
return;
}
return {
uid: item?.uid,
url6: item?.url,
type6: item?.url?.split('.').pop() || '',
name6: item?.url?.split('/').pop() || '',
};
});
return data;
},
resultField: 'data6.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data6: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
];
const [registerPreview, { getFieldsValue: getFieldsValuePreview }] = useForm({
labelWidth: 160,
schemas: schemasPreview,
actionColOptions: {
span: 18,
},
submitFunc: () => {
return new Promise((resolve) => {
console.log(getFieldsValuePreview());
resolve();
createMessage.success(`请到控制台查看结果`);
});
},
});
</script>

View File

@@ -1,226 +1,15 @@
<template>
<PageWrapper title="上传组件示例">
<Alert message="基础示例" />
<BasicUpload
:maxSize="20"
:maxNumber="10"
@change="handleChange"
:api="uploadApi"
class="my-5"
:accept="['image/*']"
/>
<Alert message="嵌入表单,加入表单校验" />
<BasicForm @register="registerValiate" class="my-5" />
<Alert message="嵌入表单,加入resultFiled自定义返回值" />
<BasicForm @register="registerCustom" class="my-5" />
<Alert message="嵌入表单,自定义预览内容" />
<BasicForm @register="registerPreview" class="my-5" />
<Upload1 />
<Upload2 />
<Upload3 />
<Upload4 />
</PageWrapper>
</template>
<script lang="ts" setup>
import { BasicUpload } from '@/components/Upload';
import { useMessage } from '@/hooks/web/useMessage';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
import Upload1 from './Upload1.vue';
import Upload2 from './Upload2.vue';
import Upload3 from './Upload3.vue';
import Upload4 from './Upload4.vue';
import { PageWrapper } from '@/components/Page';
import { Alert, Button } from 'ant-design-vue';
import { uploadApi } from '@/api/sys/upload';
import { createVNode } from 'vue';
const schemasValiate: FormSchema[] = [
{
field: 'field1',
component: 'Upload',
label: '字段1',
rules: [{ required: true, message: '请选择上传文件' }],
componentProps: {
api: uploadApi,
},
},
{
field: 'field2',
component: 'ImageUpload',
label: '字段2(ImageUpload)',
colProps: {
span: 8,
},
componentProps: {
api: uploadApi,
},
},
];
const schemasCustom: FormSchema[] = [
{
field: 'field3',
component: 'Upload',
label: '字段3',
componentProps: {
resultField: 'data3.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data3: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
{
field: 'field4',
component: 'ImageUpload',
label: '字段4(ImageUpload)',
colProps: {
span: 8,
},
componentProps: {
resultField: 'data4.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data4: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
];
const schemasPreview: FormSchema[] = [
{
field: 'field5',
component: 'Upload',
label: '字段5',
componentProps: {
previewColumns: [
{
title: 'url5',
dataIndex: 'url5',
},
{
title: 'type5',
dataIndex: 'type5',
},
{
title: 'name5',
dataIndex: 'name5',
},
{
title: 'operation',
dataIndex: '',
customRender: ({ record }) => {
return createVNode(
Button,
{
onclick: () => {
console.log(record);
createMessage.success(`请到控制台查看该行输出结果`);
},
},
'点我',
);
},
},
],
beforePreviewData: (arg) => {
let data = arg
.filter((item) => !!item)
.map((item) => {
if (typeof item !== 'string') {
console.error('return value should be string');
return;
}
return {
url5: item,
type5: item.split('.').pop() || '',
name5: item.split('/').pop() || '',
};
});
return data;
},
resultField: 'data5.url',
api: (file, progress) => {
return new Promise((resolve) => {
uploadApi(file, progress).then((uploadApiResponse) => {
resolve({
code: 200,
data5: {
url: uploadApiResponse.data.url,
},
});
});
});
},
},
},
];
const { createMessage } = useMessage();
function handleChange(list: string[]) {
createMessage.success(`已上传文件${JSON.stringify(list)}`);
}
const [registerValiate, { getFieldsValue: getFieldsValueValiate, validate }] = useForm({
labelWidth: 160,
schemas: schemasValiate,
actionColOptions: {
span: 18,
},
submitFunc: () => {
return new Promise((resolve) => {
validate()
.then(() => {
resolve();
console.log(getFieldsValueValiate());
createMessage.success(`请到控制台查看结果`);
})
.catch(() => {
createMessage.error(`请输入必填项`);
});
});
},
});
// resultFields 字段示例
const [registerCustom, { getFieldsValue: getFieldsValueCustom }] = useForm({
labelWidth: 160,
schemas: schemasCustom,
actionColOptions: {
span: 18,
},
submitFunc: () => {
return new Promise((resolve) => {
console.log(getFieldsValueCustom());
resolve();
createMessage.success(`请到控制台查看结果`);
});
},
});
// registerPreview
const [registerPreview, { getFieldsValue: getFieldsValuePreview }] = useForm({
labelWidth: 160,
schemas: schemasPreview,
actionColOptions: {
span: 18,
},
submitFunc: () => {
return new Promise((resolve) => {
console.log(getFieldsValuePreview());
resolve();
createMessage.success(`请到控制台查看结果`);
});
},
});
</script>

View File

@@ -0,0 +1,85 @@
<template>
<PageWrapper title="代码编辑器组件嵌入Form示例">
<CollapseContainer title="代码编辑器组件">
<BasicForm
:labelWidth="100"
:schemas="schemas"
:actionColOptions="{ span: 24 }"
:baseColProps="{ span: 24 }"
@submit="handleSubmit"
/>
</CollapseContainer>
</PageWrapper>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { BasicForm, FormSchema } from '@/components/Form';
import { CollapseContainer } from '@/components/Container';
import { useMessage } from '@/hooks/web/useMessage';
import { PageWrapper } from '@/components/Page';
import { CodeEditor, MODE } from '@/components/CodeEditor';
const schemas: FormSchema[] = [
{
field: 'title',
component: 'Input',
label: 'title',
defaultValue: '标题',
rules: [{ required: true }],
},
{
field: 'JSON',
component: 'Input',
label: 'JSON',
defaultValue: `{
"name":"BeJson",
"url":"http://www.xxx.com",
"page":88,
"isNonProfit":true,"
address:{
"street":"科技园路.",
"city":"江苏苏州",
"country":"中国"
},
}`,
rules: [{ required: true, trigger: 'blur' }],
render: ({ model, field }) => {
return h(CodeEditor, {
value: model[field],
mode: MODE.JSON,
onChange: (value: string) => {
model[field] = value;
},
config: {
tabSize: 10,
},
});
},
},
{
field: 'PYTHON',
component: 'Input',
label: 'PYTHON',
defaultValue: `def functionname( parameters ):
"函数_文档字符串"
function_suite
return [expression]`,
rules: [{ required: true, trigger: 'blur' }],
render: ({ model, field }) => {
return h(CodeEditor, {
value: model[field],
mode: MODE.PYTHON,
onChange: (value: string) => {
model[field] = value;
},
});
},
},
];
const { createMessage } = useMessage();
function handleSubmit(values: any) {
console.log('click search,values:', values);
createMessage.success('click search,values:' + JSON.stringify(values));
}
</script>

View File

@@ -66,11 +66,11 @@
value.value = jsonData;
return;
}
if (mode === MODE.HTML) {
if (mode === MODE.HTMLMIXED) {
value.value = htmlData;
return;
}
if (mode === MODE.JS) {
if (mode === MODE.JAVASCRIPT) {
value.value = jsData;
return;
}

View File

@@ -0,0 +1,49 @@
<template>
<PageWrapper title="截图示例">
<Row :gutter="24">
<Col :span="3">
<Card title="截图">
<a-button type="primary" @click="screenShot">点击截图</a-button>
<div class="mt-8" v-show="open">
<a-button type="primary" @click="Dele">点击删除</a-button>
</div>
</Card>
</Col>
<Col :span="21">
<Card title="截图内容" v-show="open">
<div ref="picture"></div>
</Card>
</Col>
</Row>
</PageWrapper>
</template>
<script lang="ts" setup>
import { PageWrapper } from '@/components/Page';
import html2canvas from 'html2canvas';
import { ref } from 'vue';
import { Card, Col, Row } from 'ant-design-vue';
const picture = ref();
const open = ref(false);
function screenShot() {
if (open.value) {
return;
}
html2canvas(document.body, {
backgroundColor: '#ffffff',
allowTaint: true, //开启跨域
useCORS: true,
scrollY: 0,
scrollX: 0,
}).then(function (canvas) {
canvas.style.width = '100%';
canvas.style.height = '100%';
picture.value.appendChild(canvas);
open.value = true;
});
}
function Dele() {
picture.value.innerHTML = '';
open.value = false;
}
</script>

View File

@@ -45,7 +45,6 @@
},
{
field: '0',
// component: 'Input',
label: ' ',
slot: 'add',
},

View File

@@ -80,7 +80,6 @@
},
{
field: 'field3',
// component: 'Input',
label: '自定义Slot',
slot: 'f3',
colProps: {
@@ -233,6 +232,7 @@
createMessage.success('click search,values:' + JSON.stringify(values));
}
</script>
<style lang="less" scoped>
:deep(.local_form) .local_typeValue {
width: calc(100% - 120px);

View File

@@ -18,7 +18,8 @@
import { useMessage } from '@/hooks/web/useMessage';
import { PageWrapper } from '@/components/Page';
import { isAccountExist } from '@/api/demo/system';
import dayjs from "dayjs"
const schemas: FormSchema[] = [
{
field: 'field1',
@@ -119,14 +120,11 @@
rules: [
{
required: true,
// @ts-ignore
validator: async (rule, value) => {
validator: async (_, value) => {
if (!value) {
/* eslint-disable-next-line */
return Promise.reject('值不能为空');
}
if (value === '1') {
/* eslint-disable-next-line */
return Promise.reject('值不能为1');
}
return Promise.resolve();
@@ -238,7 +236,7 @@
field5: ['1'],
field7: '1',
field33: '2020-12-12',
field3: '2020-12-12',
field3: dayjs('2020-12-12',"YYYY-MM-DD"),
});
}

View File

@@ -160,7 +160,6 @@
colProps: {
span: 8,
},
// componentProps:{},
// can func
componentProps: ({ schema, formModel }) => {
console.log('form:', schema);
@@ -460,7 +459,6 @@
},
{
field: 'field31',
// component: 'Input',
label: '下拉本地搜索',
helpMessage: ['ApiSelect组件', '远程数据源本地搜索', '只发起一次请求获取所有选项'],
required: true,
@@ -475,7 +473,6 @@
},
{
field: 'field32',
// component: 'Input',
label: '下拉远程搜索',
helpMessage: ['ApiSelect组件', '将关键词发送到接口进行远程搜索'],
required: true,
@@ -690,7 +687,6 @@
},
{
field: 'selectA',
// component: 'Select',
label: '互斥SelectA',
slot: 'selectA',
defaultValue: [],
@@ -700,7 +696,6 @@
},
{
field: 'selectB',
// component: 'Select',
label: '互斥SelectB',
slot: 'selectB',
defaultValue: [],

View File

@@ -1,41 +1,38 @@
<template>
<PageWrapper title="引导页" content="用于给用户的指引操作">
<a-button type="primary" @click="handleStart">开始</a-button>
<a-button type="primary" @click="handleOpen(true)">开始</a-button>
<Tour v-model:current="current" :open="open" :steps="steps" @close="handleOpen(false)" />
</PageWrapper>
</template>
<script lang="ts" setup>
import { PageWrapper } from '@/components/Page';
import { useDesign } from '@/hooks/web/useDesign';
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';
import { ref } from 'vue';
import { Tour, TourProps } from 'ant-design-vue';
const open = ref<boolean>(false);
const { prefixVar } = useDesign('');
function handleStart() {
driver({
showProgress: true,
steps: [
{
popover: {
title: 'Welcome',
description: 'Hello World! 👋',
},
},
{
element: `.${prefixVar}-layout-header-trigger`,
popover: {
title: 'Collapse Button',
description: 'This is the menu collapse button.',
},
},
{
element: `.${prefixVar}-layout-header-action`,
popover: {
title: 'User Action',
description: 'This is the user function area.',
},
},
],
}).drive();
}
const current = ref(0);
const steps: TourProps['steps'] = [
{
title: 'Welcome',
description: 'Hello World! 👋',
},
{
title: 'Collapse Button',
description: 'This is the menu collapse button.',
target: () => document.querySelector(`.${prefixVar}-layout-header-trigger`) as HTMLElement,
},
{
title: 'User Action',
description: 'This is the user function area.',
target: () => document.querySelector(`.${prefixVar}-layout-header-action`) as HTMLElement,
},
];
const handleOpen = (val: boolean): void => {
current.value = 0;
open.value = val;
};
</script>

View File

@@ -135,7 +135,6 @@
width: 250,
title: 'Action',
dataIndex: 'action',
// slots: { customRender: 'action' },
},
showSelectionBar: true, // 显示多选状态栏
});

View File

@@ -38,14 +38,12 @@
title: 'ID',
dataIndex: 'id',
helpMessage: <div>这个是tsx渲染出来的helpMessage</div>,
// slots: { customRender: 'id' },
},
{
title: '头像',
dataIndex: 'avatar',
width: 100,
helpMessage: h('div', '这是vue h函数渲染出来的helpMessage'),
// slots: { customRender: 'avatar' },
},
{
title: '分类',
@@ -53,7 +51,6 @@
width: 80,
align: 'center',
defaultHidden: true,
// slots: { customRender: 'category' },
},
{
title: '姓名',
@@ -65,13 +62,11 @@
dataIndex: 'imgArr',
helpMessage: ['这是简单模式的图片列表', '只会显示一张在表格中', '但点击可预览多张图片'],
width: 140,
// slots: { customRender: 'img' },
},
{
title: '照片列表2',
dataIndex: 'imgs',
width: 160,
// slots: { customRender: 'imgs' },
},
{
title: '地址',
@@ -80,7 +75,6 @@
{
title: '编号',
dataIndex: 'no',
// slots: { customRender: 'no' },
},
{
title: '开始时间',

View File

@@ -238,7 +238,6 @@
width: 160,
title: 'Action',
dataIndex: 'action',
// slots: { customRender: 'action' },
},
});

View File

@@ -54,7 +54,6 @@
title: 'Action',
dataIndex: 'action',
fixed: 'right',
// slots: { customRender: 'action' },
},
});
function handleDelete(record: Recordable) {

View File

@@ -142,13 +142,11 @@ export function getCustomHeaderColumns(): BasicColumn[] {
// title: '姓名',
dataIndex: 'name',
width: 120,
// slots: { title: 'customTitle' },
},
{
// title: '地址',
dataIndex: 'address',
width: 120,
// slots: { title: 'customAddress' },
sorter: true,
},
@@ -252,7 +250,6 @@ export function getFormConfig(): Partial<FormProps> {
{
field: `field11`,
label: `Slot示例`,
// component: 'Select',
slot: 'custom',
colProps: {
xl: 12,

View File

@@ -87,7 +87,7 @@
}
const actionList: TreeActionItem[] = [
{
// show:()=>boolean;
// show:() => boolean;
render: (node) => {
return h(PlusOutlined, {
class: 'ml-2',

View File

@@ -1,5 +1,4 @@
import { defineApplicationConfig } from '@vben/vite-config';
import Inspector from 'vite-plugin-vue-inspector';
export default defineApplicationConfig({
overrides: {
@@ -37,10 +36,5 @@ export default defineApplicationConfig({
clientFiles: ['./index.html', './src/{views,components}/*'],
},
},
plugins: [
Inspector({
openInEditorHost: 'http://localhost:5173',
}),
],
},
});