Compare commits

..

122 Commits

Author SHA1 Message Date
a8b9db109e fix(all): 新增icon-v2图标合集, 修改material算法配置组件 2024-07-23 11:22:37 +08:00
江志雄
d643a4f495 Merge branch 'feat/iconfont-v2' into 'develop'
feat: 添加新icon

See merge request web-project/zhst-lambo!70
2024-07-22 17:55:42 +08:00
lifan
ecf46d7ffc feat: 添加新icon 2024-07-22 16:03:08 +08:00
江志雄
bdd3ed09e0 Merge branch 'feat/iconfont-v2' into 'develop'
feat: 引入新icon+icon居中处理

See merge request web-project/zhst-lambo!69
2024-07-22 15:48:01 +08:00
lifan
8eb94b1ed8 feat: 引入新icon+icon居中处理 2024-07-22 09:51:31 +08:00
b198e9b1fe feat(material,biz,meta): 修改算法配置物料,树组件业务传参,修复大图组件od变化不重新渲染 2024-07-11 17:43:15 +08:00
7bde75ea15 feat(material,biz,meta): 修改算法配置物料,树组件业务传参,修复大图组件od变化不重新渲染 2024-07-11 17:42:02 +08:00
765f485556 feat(biz): 修改穿梭框的样式和注释 2024-07-05 13:59:38 +08:00
320e17ea93 feat(biz): 修改穿梭框的样式和注释 2024-07-04 16:05:49 +08:00
490cc9ae4a Merge branch 'feat/updateTreeTransferAdapterToMingMou' into develop 2024-07-04 16:04:19 +08:00
9e6a87881d feat(biz): 修改穿梭框的样式和注释 2024-07-04 16:03:37 +08:00
江志雄
c1c0b865dc Merge branch 'feat/iconfont-v2' into 'develop'
fix: 更改不能换色的图标

See merge request web-project/zhst-lambo!68
2024-07-01 18:58:58 +08:00
lifan
e8e36098e4 fix: 更改不能换色的图标 2024-06-28 14:52:50 +08:00
e5f69c915c feat(material,meta): 修改算法物料,修改视频组件 2024-06-28 14:32:00 +08:00
42d8f26af5 Merge branch 'develop' into feat/update-alg-material 2024-06-28 14:15:14 +08:00
c5fd96fbf0 feat(icon): 新增 icon-v2 图标库 2024-06-28 11:59:01 +08:00
b5a74de4d9 feat(zhst/material): 升级智能柜的算法配置面板 2024-06-28 11:56:15 +08:00
江志雄
21e43c2d35 Merge branch 'feat/iconfont-v2' into 'develop'
添加新icon与复制兼容、优化ts提示、icon-v2更改版本

See merge request web-project/zhst-lambo!67
2024-06-27 10:16:53 +08:00
lifan
53ab45c536 fix: 删除依赖 2024-06-26 18:47:32 +08:00
lifan
17767ac3c0 fix: 检索优化 2024-06-26 11:05:26 +08:00
lifan
f47c8533a9 fix: 添加新icon与复制兼容 2024-06-26 10:56:33 +08:00
lifan
70ffe63766 fix: 优化ts提示 2024-06-25 20:02:04 +08:00
lifan
a30e8c646f feat: icon-v2 2024-06-25 19:23:15 +08:00
lifan
46f35cb918 fix: icon-v2更改版本 2024-06-22 15:08:23 +08:00
lifan
afd7ae13c9 feat: icon-v2 2024-06-22 15:07:00 +08:00
7f64ffe221 fix(tsconfig): 修改father build 2024-06-13 10:33:44 +08:00
657af1c40d fix(tsconfig): 修改father build 2024-06-12 18:09:06 +08:00
c9174fd9b7 fix(tsconfig): 修改father build 2024-06-12 18:07:27 +08:00
28822b45d8 fix(tsconfig): 修改ts提示 2024-06-12 13:47:28 +08:00
012aaacb66 fix(tsconfig): 修改ts提示 2024-06-12 12:04:23 +08:00
ec3938a2cf fix(tsconfig): 修改ts提示 2024-06-12 11:35:34 +08:00
e44fa64760 fix(tsconfig): 修改ts提示 2024-06-12 11:01:48 +08:00
91ba49d94c fix(tsconfig): 修改ts提示 2024-06-12 10:37:47 +08:00
bfc2dd7930 fix(tsconfig): 修改ts提示 2024-06-12 10:14:40 +08:00
2a7ea08e02 fix(tsconfig): 修改ts提示 2024-06-12 10:11:25 +08:00
90b18d2624 fix(es,lib): 删除所有es,lib文件夹 2024-06-12 10:02:22 +08:00
江志雄
ba96f96f58 Merge branch 'develop' into 'master'
feat(zhst/biz,all): 新增zhst/biz的大图组件v2,修改其它包打包样式文件为css

See merge request web-project/zhst-lambo!63
2024-06-11 20:32:18 +08:00
5e370b0af3 feat(zhst/biz,all): 新增zhst/biz的大图组件v2,修改其它包打包样式文件为css 2024-06-11 20:31:43 +08:00
ce6a719bc2 feat(zhst/biz): 添加大图组件v2 2024-06-11 20:25:25 +08:00
江志雄
a501656907 Merge branch 'develop' into 'master'
feat(zhst/biz): 修复一些ts报错

See merge request web-project/zhst-lambo!62
2024-06-06 13:38:02 +08:00
ed4918c6a2 feat(zhst/biz): 修复一些ts报错 2024-06-06 13:37:45 +08:00
江志雄
121ebb5007 Merge branch 'develop' into 'master'
feat(zhst/biz): 修复一些ts报错

See merge request web-project/zhst-lambo!61
2024-06-06 12:00:30 +08:00
27958367ff feat(zhst/biz): 修复一些ts报错 2024-06-06 12:00:12 +08:00
江志雄
79a2b74a16 Merge branch 'develop' into 'master'
feat(zhst/biz): 添加treePanel的一些例子,修复一些ts报错

See merge request web-project/zhst-lambo!60
2024-06-06 11:40:43 +08:00
12e5046f53 feat(zhst/biz): 添加treePanel的一些例子,修复一些ts报错 2024-06-06 11:36:12 +08:00
782a0f021d Merge branch 'master' into develop 2024-06-06 11:34:35 +08:00
03433e3108 feat(zhst/biz): 新增树面板2.0 2024-06-06 11:31:37 +08:00
江志雄
08ea724555 Merge branch 'develop' into 'master'
feat(zhst/biz): 新增树面板2.0

See merge request web-project/zhst-lambo!59
2024-06-05 18:16:12 +08:00
dc98cd8ad4 feat(zhst/biz): 新增树面板2.0 2024-06-05 18:15:50 +08:00
d7a29f51b7 Merge branch 'feat/selectTreev2' into develop 2024-06-05 18:14:30 +08:00
5f15108949 feat(zhst/biz): 新增树面板2.0 2024-06-05 18:14:15 +08:00
江志雄
b75aaaf22e Merge branch 'develop' into 'master'
feat(zhst/meta): 修改ts

See merge request web-project/zhst-lambo!58
2024-06-05 16:03:55 +08:00
89ca980f9e feat(zhst/meta): 修改ts 2024-06-05 16:03:37 +08:00
江志雄
5c5115471d Merge branch 'develop' into 'master'
feat(zhst/meta): 修改ts

See merge request web-project/zhst-lambo!57
2024-06-05 15:47:30 +08:00
066428c8b6 feat(zhst/meta): 修改ts 2024-06-05 15:47:14 +08:00
江志雄
b0a2dc1bd2 Merge branch 'develop' into 'master'
feat(zhst/meta): 修改ts

See merge request web-project/zhst-lambo!56
2024-06-05 15:21:09 +08:00
1ff779c1de feat(zhst/meta): 修改ts 2024-06-05 15:15:55 +08:00
江志雄
5a2c358fe8 Merge branch 'develop' into 'master'
feat(zhst/meta): 修改ts

See merge request web-project/zhst-lambo!55
2024-06-05 14:56:53 +08:00
074766b7fa feat(zhst/meta): 修改ts 2024-06-05 14:56:38 +08:00
江志雄
bb5db6d967 Merge branch 'develop' into 'master'
feat(zhst/meta): 全量迁移antd5.17.4

See merge request web-project/zhst-lambo!54
2024-06-05 14:06:26 +08:00
fcf7f08fe4 feat(zhst/meta): 全量迁移antd5.17.4 2024-06-05 14:06:04 +08:00
3af900f998 Merge branch 'develop' into feat/selectTreev2 2024-06-05 11:32:21 +08:00
江志雄
2798253fb9 Merge branch 'develop' into 'master'
Develop

See merge request web-project/zhst-lambo!53
2024-06-05 11:29:37 +08:00
e56a19e4fa feat(zhst/meta): 全量迁移antd5.17.4 2024-06-05 11:28:53 +08:00
357a4c677a feat(zhst/meta): 全量迁移antd5.17.4 2024-06-05 11:17:48 +08:00
a129d39168 feat(zhst/biz): treePanel添加 2024-06-04 16:01:30 +08:00
江志雄
e5b176c148 Merge branch 'feat/ts-upgrade-20240603' into 'master'
fix(ts): 添加ts配置

See merge request web-project/zhst-lambo!52
2024-06-03 16:39:24 +08:00
15536f32c8 fix(ts): 添加ts配置 2024-06-03 16:36:59 +08:00
江志雄
05064d9473 Merge branch 'feat/ts-upgrade-20240603' into 'master'
fix(ts): 添加ts配置

See merge request web-project/zhst-lambo!51
2024-06-03 16:22:04 +08:00
4fb101b7e3 fix(ts): 添加ts配置 2024-06-03 16:21:16 +08:00
江志雄
17fa6f6990 Merge branch 'feat/ts-upgrade-20240603' into 'master'
fix(ts): 添加ts配置

See merge request web-project/zhst-lambo!50
2024-06-03 16:02:00 +08:00
98439dc5e8 fix(ts): 添加ts配置 2024-06-03 15:59:20 +08:00
江志雄
006dc0ceeb Merge branch 'feat/ts-upgrade-20240603' into 'master'
fix(ts): 添加ts配置

See merge request web-project/zhst-lambo!49
2024-06-03 15:48:20 +08:00
c4489fb0a8 fix(ts): 添加ts配置 2024-06-03 15:47:57 +08:00
江志雄
a2afc51ad8 Merge branch 'feat/ts-upgrade-20240603' into 'master'
fix(ts): 添加ts配置

See merge request web-project/zhst-lambo!48
2024-06-03 15:17:37 +08:00
3d8959891e fix(ts): 添加ts配置 2024-06-03 14:42:23 +08:00
江志雄
5e483436b0 Merge branch 'feat/ts-upgrade-20240603' into 'master'
fix(ts): 添加ts配置

See merge request web-project/zhst-lambo!47
2024-06-03 14:23:41 +08:00
f5ff6e760f fix(ts): 添加ts配置 2024-06-03 14:21:08 +08:00
江志雄
079df27f4b Update package.json 2024-06-03 11:31:22 +08:00
江志雄
fe789b5479 Update tsconfig.json 2024-06-03 11:31:04 +08:00
江志雄
08a14b3ae0 Update tsconfig.json 2024-06-03 11:26:47 +08:00
江志雄
8acf8242ea Update tsconfig.json 2024-06-03 11:22:12 +08:00
江志雄
92bcaace2b Update tsconfig.json 2024-06-03 11:19:03 +08:00
江志雄
b104d383e8 Update build.sh 2024-06-03 11:04:55 +08:00
江志雄
5bab7caa8c Merge branch 'develop' into 'master'
fix(tsconfig): 修改配置

See merge request web-project/zhst-lambo!46
2024-06-03 10:06:55 +08:00
085b790121 fix(tsconfig): 修改配置 2024-06-03 10:06:36 +08:00
江志雄
28ba34894d Merge branch 'develop' into 'master'
fix(tsconfig): 修改配置

See merge request web-project/zhst-lambo!45
2024-06-03 09:58:26 +08:00
da8a0be612 fix(tsconfig): 修改配置 2024-06-03 09:57:44 +08:00
江志雄
aa1930e1b7 Merge branch 'develop' into 'master'
fix(tsconfig): 修改配置

See merge request web-project/zhst-lambo!44
2024-06-03 09:37:36 +08:00
638ccf5d8d fix(tsconfig): 修改配置 2024-06-03 09:37:05 +08:00
江志雄
e1d7ab2621 Merge branch 'develop' into 'master'
fix(tsconfig): 修改配置

See merge request web-project/zhst-lambo!43
2024-05-31 18:10:33 +08:00
5a1e41a7a3 fix(tsconfig): 修改配置 2024-05-31 18:09:54 +08:00
江志雄
b64abd7164 Merge branch 'develop' into 'master'
fix(tsconfig): 修改配置

See merge request web-project/zhst-lambo!42
2024-05-31 17:26:01 +08:00
dev
d0607af936 fix(tsconfig): 修改配置 2024-05-31 17:25:31 +08:00
江志雄
efb5acaf0a Merge branch 'develop' into 'master'
feat(all): update version

See merge request web-project/zhst-lambo!41
2024-05-31 17:15:05 +08:00
dev
8d45ba6481 feat(all): update version 2024-05-31 17:14:31 +08:00
江志雄
2ada801a32 Merge branch 'develop' into 'master'
穿梭框完成,新增meta组件

See merge request web-project/zhst-lambo!40
2024-05-31 17:09:44 +08:00
江志雄
c3573d9efb Merge branch 'feat/transfer-20240530' into 'develop'
feat(zhst/biz,zhst/meta): 穿梭框完成,新增meta组件

See merge request web-project/zhst-lambo!39
2024-05-31 16:23:07 +08:00
46e6d0b4d0 feat(zhst/biz,zhst/meta): 穿梭框完成,新增meta组件 2024-05-31 16:22:09 +08:00
戴炜
8e6b1fc8f2 Update build.sh 2024-05-30 14:17:33 +08:00
54a680c0ff feat(tsconfig): 修改参数 2024-05-30 11:41:50 +08:00
江志雄
6a75d0412a Merge branch 'hotfix/fixRequestTs-20240530' into 'master'
修改ts报错

See merge request web-project/zhst-lambo!38
2024-05-30 10:41:17 +08:00
0718d8c4d1 修改ts报错 2024-05-30 10:40:46 +08:00
江志雄
ed22f8bcf9 Merge branch 'develop' into 'master'
feat(biz,map): 添加文档

See merge request web-project/zhst-lambo!37
2024-05-30 10:39:07 +08:00
6f4797694f feat(biz,map): 添加文档 2024-05-30 10:37:55 +08:00
江志雄
375d5983f4 Merge branch 'feat/update-mapDoc' into 'develop'
feat(zhst/map,zhst/biz): map完善文档,biz修改boxSelectTree的文档格式

See merge request web-project/zhst-lambo!36
2024-05-30 10:36:03 +08:00
c65756bbb2 feat(zhst/map,zhst/biz): map完善文档,biz修改boxSelectTree的文档格式 2024-05-30 10:34:04 +08:00
3666fc4ed1 feat(zhst/map): 添加例子 2024-05-30 09:13:15 +08:00
江志雄
05f99f7359 Merge branch 'develop' into 'master'
feat(zhst/biz,zhst/material): 修改版本统一

See merge request web-project/zhst-lambo!35
2024-05-29 14:56:55 +08:00
3bd50deeb3 Merge branch 'develop' of http://10.0.0.88/web-project/zhst-lambo into develop 2024-05-29 14:55:03 +08:00
江志雄
60ef9e88ee Merge branch 'develop' into 'master'
feat(zhst/meta,zhst/map): zhst/map:拓展地图api,加入tools组件、cluster组价、marker组件

See merge request web-project/zhst-lambo!34
2024-05-29 14:53:21 +08:00
b930efa40d feat(zhst/biz,zhst/material): 修改版本统一 2024-05-29 14:52:01 +08:00
江志雄
0cfdaa4851 Merge branch 'feat/create-map-20240521' into 'develop'
feat(zhst/meta,zhst/map): zhst/map:拓展地图api,加入tools组件、cluster组价、marker组件、draw组件、popup组件;zhst/mata:添加floatButton组件

See merge request web-project/zhst-lambo!33
2024-05-29 14:50:16 +08:00
f9c5dccb73 fix(biz,material): 修改冲突 2024-05-29 14:49:53 +08:00
f778ec1b72 feat(zhst/meta,zhst/map): zhst/map:拓展地图api,加入tools组件、cluster组价、marker组件
、draw组件、popup组件;zhst/mata:添加floatButton组件
2024-05-29 14:43:41 +08:00
dd6ff1d2be feat(@zhst/map): 添加popup功能 2024-05-24 18:03:43 +08:00
江志雄
e8ddd60daa Merge branch 'develop' into 'master'
feat(@zhst/biz): 树组件支持tag面板、优化filter传参、优化option传参、废弃之前的定制化方案

See merge request web-project/zhst-lambo!32
2024-05-23 15:16:14 +08:00
da3c1714c5 feat(@zhst/biz): 树组件支持tag面板、优化filter传参、优化option传参、废弃之前的定制化方案 2024-05-23 15:14:17 +08:00
江志雄
e90dcce641 Merge branch 'develop' into 'master'
feat(biz无限滚动组件): 添加屏幕自适应撑开

See merge request web-project/zhst-lambo!31
2024-05-21 10:45:10 +08:00
d02a2b2015 feat(biz): 提交打包信息 2024-05-21 10:44:25 +08:00
江志雄
d95fe37e43 Merge branch 'feat/upgrade-infinityList' into 'develop'
Feat/upgrade infinity list

See merge request web-project/zhst-lambo!30
2024-05-21 10:40:24 +08:00
570f183382 feat(biz无限滚动组件): 添加屏幕自适应撑开 2024-05-21 10:37:47 +08:00
2801 changed files with 651288 additions and 265413 deletions

View File

@ -16,7 +16,7 @@
#root .dumi-default-sidebar {
width: 188px;
overflow-y: hidden;
overflow-y: scroll;
/* stylelint-disable-next-line rule-empty-line-before */
&:hover {
overflow-y: auto;

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<defs>
<path id="path" d="M50 15A15 35 0 0 1 50 85A15 35 0 0 1 50 15" fill="none"></path>
<path id="patha" d="M0 0A15 35 0 0 1 0 70A15 35 0 0 1 0 0" fill="none"></path>
</defs><g transform="rotate(0 50 50)">
<use xlink:href="#path" stroke="#dfdfdf" stroke-width="3"></use>
</g><g transform="rotate(60 50 50)">
<use xlink:href="#path" stroke="#dfdfdf" stroke-width="3"></use>
</g><g transform="rotate(120 50 50)">
<use xlink:href="#path" stroke="#dfdfdf" stroke-width="3"></use>
</g><g transform="rotate(0 50 50)">
<circle cx="50" cy="15" r="9" fill="#e15b64">
<animateMotion dur="1s" repeatCount="indefinite" begin="0s">
<mpath xlink:href="#patha"></mpath>
</animateMotion>
</circle>
</g><g transform="rotate(60 50 50)">
<circle cx="50" cy="15" r="9" fill="#f8b26a">
<animateMotion dur="1s" repeatCount="indefinite" begin="-0.16666666666666666s">
<mpath xlink:href="#patha"></mpath>
</animateMotion>
</circle>
</g><g transform="rotate(120 50 50)">
<circle cx="50" cy="15" r="9" fill="#abbd81">
<animateMotion dur="1s" repeatCount="indefinite" begin="-0.3333333333333333s">
<mpath xlink:href="#patha"></mpath>
</animateMotion>
</circle>
</g></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,11 +0,0 @@
import React from 'react';
import loading from './loading.svg'
export default () => {
return (
<div style={{ textAlign: 'center' }}>
<image src={loading} />
<p>...</p>
</div>
);
};

View File

@ -19,6 +19,7 @@ export default defineConfig({
'@zhst/slave': path.join(__dirname, 'packages/slave/src'),
'@zhst/material': path.join(__dirname, 'packages/material/src'),
'@zhst/icon': path.join(__dirname, 'packages/icon/src'),
'@zhst/icon-v2': path.join(__dirname, 'packages/icon-v2/src'),
'@zhst/map': path.join(__dirname, 'packages/map/src'),
},
resolve: {
@ -34,6 +35,7 @@ export default defineConfig({
{ type: 'slave', dir: 'packages/slave/src' },
{ type: 'material', dir: 'packages/material/src' },
{ type: 'icon', dir: 'packages/icon/src' },
{ type: 'icon-v2', dir: 'packages/icon-v2/src' },
{ type: 'map', dir: 'packages/map/src' },
],
},

View File

@ -1,5 +1,15 @@
import { defineConfig } from 'father';
import { defineConfig } from 'father-plugin-less';
export default defineConfig({
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
esm: {
output: 'es',
ignores: ['**/demo/*', 'src/**/demo/*'],
transformer: 'babel',
},
cjs: {
output: 'lib',
ignores: ['**/demo/*', 'src/**/demo/*'],
transformer: 'babel',
},
});

4
.gitignore vendored
View File

@ -9,6 +9,10 @@ vueuse
/temp
packages/**/es
packages/**/lib
**/es
**/lib
**/**/es
**/**/lib
/es
/lib
pnpm-lock.yaml

View File

@ -10,6 +10,9 @@
"flvjs",
"indicatorsize",
"lambo",
"mapbox",
"maxzoom",
"minzoom",
"remuxer",
"stylelint",
"transmuxer",

View File

@ -1,7 +1,14 @@
pnpm --version
pnpm -v
node -v
pnpm install --force
pnpm run pkg:build
if [ $? -ne 0 ]; then
exit 1
fi
pnpm run pub
# 打tag失败

3
global.d.ts vendored
View File

@ -1 +1,4 @@
declare module '*.less';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';

View File

@ -61,6 +61,7 @@
"dumi": "^2.2.13",
"eslint": "^8.23.0",
"father": "^4.1.0",
"father-plugin-less": "^0.0.2",
"husky": "^8.0.1",
"lerna": "^8.0.0",
"lint-staged": "^13.0.3",
@ -82,5 +83,12 @@
},
"authors": [
"dev<710328466@qq.com>"
]
],
"dependencies": {
"@ant-design/happy-work-theme": "^1.0.0",
"@zhst/meta": "workspace:^",
"rc-rate": "~2.12.0",
"react-fast-marquee": "^1.6.4",
"react-infinite-scroll-component": "^6.1.0"
}
}

View File

@ -1,13 +1,20 @@
import { defineConfig } from 'father';
import { defineConfig } from 'father-plugin-less';
export default defineConfig({
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
esm: {
output: 'es',
ignores: ['**/demo/*', 'src/**/demo/*']
ignores: ['**/demo/*', 'src/**/demo/*'],
transformer: 'babel',
},
cjs: {
output: 'lib',
ignores: ['**/demo/*', 'src/**/demo/*']
ignores: ['**/demo/*', 'src/**/demo/*'],
transformer: 'babel',
},
lessInBabel: {
modifyVars: {
},
},
plugins: ['father-plugin-less'],
});

View File

@ -1,5 +1,153 @@
# @zhst/biz
## 0.34.0
### Minor Changes
- feat(material,biz,meta): 修改算法配置物料,树组件业务传参,修复大图组件 od 变化不重新渲染
### Patch Changes
- Updated dependencies
- @zhst/meta@0.30.0
## 0.33.2
### Patch Changes
- 修改穿梭框不能修改宽度
## 0.33.1
### Patch Changes
- 修改穿梭框不能修改宽度 bug
## 0.33.0
### Minor Changes
- feat(biz): 修改穿梭框的样式和注释
### Patch Changes
- Updated dependencies
- @zhst/meta@0.29.0
## 0.32.2
### Patch Changes
- Updated dependencies
- @zhst/meta@0.28.0
## 0.32.1
### Patch Changes
- Updated dependencies
- @zhst/icon@0.8.0
- @zhst/meta@0.27.1
## 0.32.0
### Minor Changes
- zhst/biz 废弃 tree 组件
## 0.31.0
### Minor Changes
- 修改 less to css 配置
### Patch Changes
- Updated dependencies
- @zhst/func@0.17.0
- @zhst/hooks@0.15.0
- @zhst/icon@0.7.0
- @zhst/meta@0.27.0
## 0.30.0
### Minor Changes
- 新增 zhst/biz 的大图组件 v2,修改其它包打包样式文件为 css
### Patch Changes
- Updated dependencies
- @zhst/hooks@0.14.0
- @zhst/icon@0.6.0
- @zhst/meta@0.26.0
## 0.29.0
### Minor Changes
- zhst/biz: 添加 treePanel 的一些例子,修复一些 ts 报错
## 0.28.0
### Minor Changes
- zhst/biz: 新增树面板
### Patch Changes
- Updated dependencies
- @zhst/meta@0.25.0
## 0.27.0
### Minor Changes
- zhst/meta 全量迁移 antd-5.17.4
### Patch Changes
- Updated dependencies
- @zhst/meta@0.24.0
## 0.26.0
### Minor Changes
- 穿梭框完成,新增 meta 组件
### Patch Changes
- Updated dependencies
- @zhst/meta@0.23.0
- @zhst/hooks@0.13.2
- @zhst/func@0.16.1
- @zhst/icon@0.5.1
## 0.25.1
### Patch Changes
- feat(zhst/map,zhst/biz): map 完善文档,biz 修改 boxSelectTree 的文档格式
## 0.25.0
### Minor Changes
- zhst/map:拓展地图 api,加入 tools 组件、cluster 组价、marker 组件、draw 组件、popup 组件zhst/mata:添加 floatButton 组件
## 0.24.0
### Minor Changes
- 树组件支持 tag 面板、优化 filter 传参、优化 option 传参、废弃之前的定制化方案
## 0.23.0
### Minor Changes
- feat(biz 无限滚动组件): 添加无限滚动组件,屏幕自适应撑开
## 0.22.2
### Patch Changes

View File

@ -1,256 +0,0 @@
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
// @ts-nocheck
import React, { forwardRef, useImperativeHandle, useRef, useState, useEffect } from 'react';
import { ConfigProvider, Descriptions, Modal, Tabs, CropperImage, AttachImage, VideoPlayer, BtnGroup, RelatedImage } from '@zhst/meta';
import classNames from 'classnames';
import { get, isEmpty, pick } from '@zhst/func';
import Navigation from "./components/navigation";
import CombineImage from "./components/CombineImage";
import "./index.less";
var DescriptionsItem = Descriptions.Item;
export var componentPrefix = 'zhst-image';
// 对比图模式、场景图模式、视频模式
var initialStyle = {
fontSize: '12px'
};
var BigImageModal = /*#__PURE__*/forwardRef(function (props, ref) {
var _props$title = props.title,
title = _props$title === void 0 ? '-' : _props$title,
width = props.width,
open = props.open,
children = props.children,
onCancel = props.onCancel,
activeTab = props.activeTab,
_props$attributeList = props.attributeList,
attributeList = _props$attributeList === void 0 ? [] : _props$attributeList,
_props$isRelated = props.isRelated,
isRelated = _props$isRelated === void 0 ? false : _props$isRelated,
_props$tabs = props.tabs,
tabs = _props$tabs === void 0 ? {} : _props$tabs,
_props$footer = props.footer,
footer = _props$footer === void 0 ? null : _props$footer,
_props$dataSource = props.dataSource,
dataSource = _props$dataSource === void 0 ? {} : _props$dataSource,
onTabChange = props.onTabChange,
compareImageProps = props.compareImageProps,
showNavigation = props.showNavigation,
modalProps = props.modalProps,
cropperImageProps = props.cropperImageProps,
prevButtonProps = props.prevButtonProps,
onPrevButtonClick = props.onPrevButtonClick,
onNextButtonClick = props.onNextButtonClick,
nextButtonProps = props.nextButtonProps,
tabsProps = props.tabsProps,
btnGroupProps = props.btnGroupProps,
descriptionsProps = props.descriptionsProps,
relatedImageProps = props.relatedImageProps,
theme = props.theme;
var combineImageRef = useRef(null);
var videoPlayerRef = useRef(null);
var modalRef = useRef(null);
// ========================== 头切换 =========================
var _useState = useState(activeTab || get(tabsProps, 'data[0].key')),
_useState2 = _slicedToArray(_useState, 2),
tab = _useState2[0],
setTab = _useState2[1];
var _useState3 = useState('related'),
_useState4 = _slicedToArray(_useState3, 2),
activeKey = _useState4[0],
setActiveKey = _useState4[1];
// ========================= 预览切换下标 =========================
var _useState5 = useState('auto'),
_useState6 = _slicedToArray(_useState5, 2),
cropType = _useState6[0],
setCropType = _useState6[1];
var cropBtnDataSource = [{
key: 'close',
icon: 'icon-danchuangguanbi',
title: '退出'
}, {
key: 'auto',
icon: 'icon-zidong',
title: '智能框选'
}, {
key: 'custom',
icon: 'icon-shoudong',
title: '手动框选'
}];
var handleCropBtnClick = function handleCropBtnClick(v) {
setCropType(v);
};
var defaultTabsProps = !isEmpty(tabs) ? tabs : {
items: [{
label: '对比图模式',
key: 'COMPATER',
children: /*#__PURE__*/React.createElement(CombineImage, _extends({
ref: combineImageRef,
data: pick(dataSource, 'compaterImage', 'imgSummary', 'imageKey', 'score')
}, compareImageProps))
}, {
label: '场景图模式',
key: 'NORMAL',
children: /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
justifyContent: 'center',
width: '100%'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: '85%',
height: '500px'
}
}, /*#__PURE__*/React.createElement(CropperImage, _extends({
type: "rect",
odList: get(dataSource, 'odRect', [])
}, cropperImageProps), !(cropperImageProps !== null && cropperImageProps !== void 0 && cropperImageProps.editAble) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AttachImage, {
data: [{
label: '测试',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
}]
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
right: '24px',
bottom: '24px',
fontSize: '18px',
color: 'red',
cursor: 'default'
}
}, "\u4EBA\u8138\u8D28\u91CF\u5206\uFF1A".concat(Number(cropperImageProps === null || cropperImageProps === void 0 ? void 0 : cropperImageProps.score).toFixed(2)))), (cropperImageProps === null || cropperImageProps === void 0 ? void 0 : cropperImageProps.showEditTools) && /*#__PURE__*/React.createElement(BtnGroup, _extends({
circle: true,
style: {
position: 'absolute',
top: 0,
right: 0
},
dataSource: cropBtnDataSource,
onClick: handleCropBtnClick,
selectKey: cropType
}, btnGroupProps)))))
}]
};
// TODO: 页面初始化
useEffect(function () {}, [dataSource]);
// 暴露 ref 实例
useImperativeHandle(ref, function () {
return {
ref: ref,
tab: tab,
setTab: setTab,
modalRef: modalRef,
activeKey: activeKey,
setActiveKey: setActiveKey,
videoPlayerRef: videoPlayerRef,
combineImageRef: combineImageRef
};
});
return /*#__PURE__*/React.createElement(Modal, _extends({
destroyOnClose: true,
width: width,
open: open,
ref: modalRef,
footer: footer,
className: componentPrefix,
title: title,
onCancel: onCancel
}, modalProps), /*#__PURE__*/React.createElement("div", {
style: {
marginTop: '16px'
}
}, /*#__PURE__*/React.createElement(ConfigProvider, {
theme: _objectSpread({
token: {
colorTextSecondary: 'rgba(0,0,0,0.45)'
},
components: {
Descriptions: {
titleMarginBottom: '20px',
viewBg: '#f6f6f6',
titleColor: 'rgba(0,0,0,0.45)',
colorTextLabel: 'rgba(0,0,0,0.45)',
contentColor: 'rgba(0,0,0,0.88)'
}
}
}, theme)
}, attributeList.map(function (descriptions) {
var _descriptions$childre;
return /*#__PURE__*/React.createElement(Descriptions, _extends({
key: descriptions.title,
title: /*#__PURE__*/React.createElement("p", {
style: {
margin: '12px 0 0',
fontSize: initialStyle.fontSize
}
}, descriptions.title),
column: 8,
style: {
padding: '0 64px'
}
}, descriptionsProps), descriptions === null || descriptions === void 0 || (_descriptions$childre = descriptions.children) === null || _descriptions$childre === void 0 ? void 0 : _descriptions$childre.map(function (item) {
return /*#__PURE__*/React.createElement(DescriptionsItem, {
key: item.key,
label: item.label,
span: 1,
contentStyle: {
fontSize: initialStyle.fontSize
},
labelStyle: {
fontSize: initialStyle.fontSize
}
}, item.children);
}));
})), /*#__PURE__*/React.createElement("div", {
className: classNames("".concat(componentPrefix, "-view-container"))
}, /*#__PURE__*/React.createElement(Tabs, _extends({
activeKey: tab,
centered: true,
destroyInactiveTabPane: true,
onChange: function onChange(v) {
setTab(function (pre) {
onTabChange === null || onTabChange === void 0 || onTabChange(v, pre);
return v;
});
},
tabBarStyle: {
fontSize: '18px',
fontWeight: 'bold'
}
}, defaultTabsProps, tabsProps)), tab === 'VIDEO' && /*#__PURE__*/React.createElement(VideoPlayer, {
ref: videoPlayerRef,
url: dataSource === null || dataSource === void 0 ? void 0 : dataSource.flvUrl
}), /*#__PURE__*/React.createElement(Navigation, _extends({
className: classNames("".concat(componentPrefix, "-view-container__nav"), (prevButtonProps === null || prevButtonProps === void 0 ? void 0 : prevButtonProps.disabled) && "".concat(componentPrefix, "-view-container__nav--disabled"), "".concat(componentPrefix, "-view-container__nav--left")),
prev: true,
show: showNavigation,
onClick: onPrevButtonClick
}, prevButtonProps)), /*#__PURE__*/React.createElement(Navigation, _extends({
className: classNames("".concat(componentPrefix, "-view-container__nav"), (nextButtonProps === null || nextButtonProps === void 0 ? void 0 : nextButtonProps.disabled) && "".concat(componentPrefix, "-view-container__nav--disabled"), "".concat(componentPrefix, "-view-container__nav--right")),
next: true,
show: showNavigation,
onClick: onNextButtonClick
}, nextButtonProps)), isRelated && /*#__PURE__*/React.createElement("div", {
style: {
margin: '24px 0'
}
}, /*#__PURE__*/React.createElement(RelatedImage, relatedImageProps)))), children);
});
export default BigImageModal;

View File

@ -1,6 +1,6 @@
{
"name": "@zhst/biz",
"version": "0.22.2",
"version": "0.34.0",
"description": "业务库",
"keywords": [
"business",
@ -35,6 +35,7 @@
"registry": "http://10.0.0.77:4874"
},
"devDependencies": {
"@swc/core": "^1.3.9",
"@zhst/types": "workspace:^"
},
"dependencies": {

View File

@ -0,0 +1,304 @@
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
import {
ConfigProvider,
Descriptions,
CropperImage,
AttachImage,
VideoPlayer,
RelatedImage,
Radio,
Tooltip,
Button,
Tabs
} from '@zhst/meta';
import classNames from 'classnames'
import { IconFont } from '@zhst/icon'
import { BigImageProps, BigImageRef } from './interface'
import CombineImage from './components/CombineImage'
import './index.less'
const DescriptionsItem = Descriptions.Item
export const componentPrefix = 'zhst-big-image'
const initialStyle ={
fontSize: '12px'
}
const BigImage = forwardRef<BigImageRef, BigImageProps>((props, ref) => {
const {
// ------------ 通用配置 -------------------
type,
viewHeight,
width = '100%',
children,
// ------------ 顶部按钮 -----------------
topButtonRender,
// ------------ 描述 -----------------
descriptionList = [],
showDescription,
// ------------- tab 导航 ----------------
customTabBarExtraContent,
tabProps,
onTabChange,
// ------------- 场景图 -------------
cropperImageProps = {
cropButtonList: []
},
// ----------------- 对比图 ---------------------
combineImageProps,
// ------------ 视频模式 -----------------
videoProps,
// ------------ view 操作按钮 -----------------
showModeChange,
modeButtonGroupProps,
viewTopModeButtonList = [
{
key: 'image',
label: '图片'
},
{
key: 'video',
label: '视频'
}
],
// ------------------ 翻页 ----------------------
showNavigation,
prevButtonProps,
onPrevButtonClick,
onNextButtonClick,
nextButtonProps,
// ------------------ 人脸碰撞模型 -----------------
relatedImageProps,
isRelated = false,
theme,
} = props
const combineImageRef = useRef<any>(null)
const videoPlayerRef = useRef<any>(null)
const cropperImageRef = useRef<any>(null)
cropperImageProps.cropButtonList = cropperImageProps?.cropButtonList || [
{
key: 'auto',
icon: <IconFont icon='icon-zidong' />,
label: '智能框选',
},
{
key: 'custom',
icon: <IconFont icon='icon-shoudong' />,
label: '手动框选',
}
];
// ------------------- 初始化模式 ----------------------
const initMode = (type: BigImageProps['type'] = 'normal') => {
switch (type) {
case 'compater':
return (
<CombineImage
// @ts-ignore
ref={combineImageRef}
height={viewHeight}
{...combineImageProps}
/>
)
case 'normal':
return (
<div style={{ height: viewHeight, width: '100%' }}>
<CropperImage
ref={cropperImageRef}
type='rect'
// @ts-ignore
height={viewHeight}
{...cropperImageProps}
>
{/* // ------------ 显示条件:当不为编辑状态,并且有图片值 ----------------- */}
{!cropperImageProps?.editAble && cropperImageProps?.attachImageData && (
<>
<AttachImage
data={cropperImageProps?.attachImageData}
{...cropperImageProps?.attachImageProps}
/>
<div
style={{
position: 'absolute',
right: '4px',
bottom: '4px',
fontSize: '18px',
color: 'red',
cursor: 'default'
}}
>{`人脸质量分:${(Number(cropperImageProps?.score) as number).toFixed(2)}`}</div>
</>
)}
{/* // ------------ 场景图绘制工具栏 ----------------- */}
{cropperImageProps?.showEditTools && (
<div className={`${componentPrefix}-view-cropper-btn`}>
<Radio.Group
className={`${componentPrefix}-view-cropper-btn-group`}
size="small"
buttonStyle='solid'
// @ts-ignore
onChange={cropperImageProps?.onCropperTypeChange}
{...cropperImageProps.btnGroupProps}
>
{cropperImageProps?.cropButtonList?.map(btn => (
<Tooltip key={btn.key} title={btn.label}>
{/* @ts-ignore */}
<Radio.Button value={btn.key}{...btn.props}><div className={`${componentPrefix}-view-cropper-btn-group-radio`}>{btn.icon} {btn.label}</div></Radio.Button>
</Tooltip>
))}
</Radio.Group>
<Button onClick={cropperImageProps?.onExitEdit} className={`${componentPrefix}-view-cropper-btn_close`} type="primary" size="small" icon={<IconFont icon="icon-danchuangguanbi" />} />
</div>
)}
</CropperImage>
</div>
)
case 'video':
return (
// @ts-ignore
<VideoPlayer ref={videoPlayerRef} height={viewHeight} {...videoProps} />
)
}
}
// 暴露 ref 实例
useImperativeHandle(ref, () => ({
// @ts-ignore
cropperImageRef: cropperImageRef?.current,
videoPlayerRef: videoPlayerRef?.current,
combineImageRef: combineImageRef?.current,
}));
return (
<div
className={classNames(`${componentPrefix}-view`)}
style={{ width: `${parseInt(width as string)}px` }}
>
<div className={classNames(`${componentPrefix}-view-btns`)}>
{topButtonRender}
</div>
{showDescription && (
<div
className={classNames(`${componentPrefix}-view-desc`)}
>
<ConfigProvider
theme={{
components: {
Descriptions: {
viewBg: '#f6f6f6',
colorBgLayout:'#ccc',
titleColor: 'rgba(0,0,0,0.45)',
colorTextLabel: 'rgba(0,0,0,0.45)',
contentColor: 'rgba(0,0,0,0.88)',
},
},
...theme
}}
>
{descriptionList.map(descriptions => (
<Descriptions
key={descriptions.title}
title={descriptions.showTitle && descriptions.title}
column={8}
{...descriptions.props}
>
{descriptions?.children?.map(item => (
<DescriptionsItem
key={item.key}
label={item.label}
span={1}
contentStyle={{ fontSize: initialStyle.fontSize }}
labelStyle={{ fontSize: initialStyle.fontSize }}
>{item.children}</DescriptionsItem>
))}
</Descriptions>
))}
</ConfigProvider>
</div>
)}
<Tabs
tabBarExtraContent={customTabBarExtraContent}
onChange={onTabChange}
items={[
{
label: `场景图`,
key: '1',
},
{
label: `对比图`,
key: '2',
}
]}
{...tabProps}
/>
<div
className={classNames(`${componentPrefix}-view-container`)}
>
{/* ---------------- view 视图左上角导航按钮 ------------------ */}
{showModeChange && (
<div
className={classNames(`${componentPrefix}-view-container-modeNav`)}
>
<Radio.Group
buttonStyle='solid'
size='small'
{...modeButtonGroupProps}
>
{viewTopModeButtonList?.map(btn => (
<Tooltip key={btn.key} title={btn.tooltipTxt}>
{/* @ts-ignore */}
<Radio.Button value={btn.key} {...btn.props}>{btn.icon} {btn.label}</Radio.Button>
</Tooltip>
))}
</Radio.Group>
</div>
)}
{/* --------------------------------- 视频播放模式 --------------------------------- */}
{initMode(type)}
{/* 切换按钮组件 */}
{/* ----------------------------------- 上一张按钮 ---------------------------------- */}
{showNavigation && (
<Button
className={classNames(
`${componentPrefix}-view-container-nav`,
`${componentPrefix}-view-container-nav_left`
)}
icon={<IconFont icon="icon-qiehuanzuo" />}
type="primary"
// style={{ backgroundColor: 'rgba(255,255,255,0.8)' }}
shape='circle'
onClick={onPrevButtonClick}
{...prevButtonProps}
/>
)}
{/* ----------------------------------- 下一张按钮 ---------------------------------- */}
{showNavigation && (
<Button
className={classNames(
`${componentPrefix}-view-container-nav`,
`${componentPrefix}-view-container-nav_right`
)}
icon={<IconFont icon="icon-qiehuanyou" />}
type="primary"
// style={{ backgroundColor: 'rgba(255,255,255,0.8)' }}
shape='circle'
onClick={onNextButtonClick}
{...nextButtonProps}
/>
)}
</div>
{/* ----------------------------------- 人脸碰撞组件 ---------------------------------- */}
{isRelated && (
// @ts-ignore
<RelatedImage
{...relatedImageProps}
/>
)}
{children}
</div>
)
})
export default BigImage

View File

@ -0,0 +1,3 @@
.zhst-big-image-combime-image {
background-color: #f7f7f7;
}

View File

@ -0,0 +1,82 @@
import React, { FC, useRef, forwardRef, useImperativeHandle } from 'react';
import { CompareImage, Flex, Progress, ProgressProps, CompareImageProps } from '@zhst/meta'
import './index.less'
export interface ComBineImageProps {
height?: string | number
data: {
imgSummary: string;
compaterImage: string;
imageKey: string;
score: number;
}
prevDisable?: boolean;
nextDisable?: boolean;
onPre?: () => void;
onNext?: () => void;
openRoll?: boolean
targetImageProps?: CompareImageProps
sourceImageProps?: CompareImageProps
}
const conicColors: ProgressProps['strokeColor'] = {
'0%': '#42E2AC',
'50%': '#DFAF2E',
'100%': '#F95C55',
};
const componentName = 'zhst-big-image-combime-image'
const ComBineImage: FC<ComBineImageProps> = forwardRef((props, ref) => {
const {
height,
data,
prevDisable,
nextDisable,
onNext,
onPre,
openRoll
} = props
const { imgSummary, compaterImage, score } = data
const targetImageRef = useRef(null)
const compareImageRef = useRef(null)
useImperativeHandle(ref, () => ({
compareImageRef,
targetImageRef
}));
return (
<Flex className={componentName} justify='space-between' align='center' >
<CompareImage
ref={targetImageRef}
height={height as string}
width="400px"
preDisable={prevDisable}
nextDisable={nextDisable}
onNext={onNext}
onPre={onPre}
showScore={false}
openRoll={openRoll}
url={imgSummary}
label="目标图"
{...props.targetImageProps}
/>
<Progress type="circle" size={72} strokeWidth={6} percent={Number(score || 0)} strokeColor={conicColors} />
<CompareImage
ref={compareImageRef}
width="400px"
height={height as string}
url={compaterImage}
openRoll={false}
score={score}
label="预警图"
labelColor='#FA5852'
{...props.sourceImageProps}
/>
</Flex>
)
})
export default ComBineImage

View File

@ -0,0 +1,44 @@
.zhst-image__nav {
position: absolute;
display: flex;
width: 48px;
height: 100%;
flex-shrink: 0;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 0;
&>button {
& span {
display: flex;
}
}
:global {
i:hover {
color: #fff !important;
}
}
&--disable {
:global {
i {
color: #f0f0f0;
cursor: not-allowed !important;
}
}
}
&--hide {
display: none;
}
&-prev {
left: 12px;
}
&-next {
right: 12px;
}
}

View File

@ -0,0 +1,40 @@
// !! 已废弃
import * as React from 'react';
import classnames from 'classnames';
import { Button } from '@zhst/meta';
import { IconFont } from '@zhst/icon'
import './index.less';
const componentName = `zhst-image__nav`;
const Navigation: React.FC<{
show?: boolean;
onClick?: React.MouseEventHandler<HTMLElement>;
prev?: boolean;
next?: boolean;
disabled?: boolean;
className?: string;
color?: string;
hoverColor?: string;
}> = (props) => {
const { show, prev, next, disabled, onClick, className, color } = props;
return (
<div
className={classnames(
`${componentName}`,
prev && `${componentName}-prev`,
next && `${componentName}-next`,
disabled && `${componentName}--disable`,
!show && `${componentName}--hide`,
className
)}
>
<Button type="text" disabled={disabled} shape='circle' onClick={onClick}>
<IconFont size={28} color={color} icon={prev ? 'icon-qiehuanzuo' : 'icon-qiehuanyou'} />
</Button>
</div>
);
};
export default Navigation;

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -0,0 +1,71 @@
import React, { useEffect, useRef, useState } from 'react';
import { BigImage } from '@zhst/biz'
import { Space, Switch } from '@zhst/meta'
import { pick, get } from '@zhst/func'
import { BIG_IMAGE_DATA, attributeList } from '../mock'
import Img from './imgs/photo-1503185912284-5271ff81b9a8.webp'
const BigModal = (props: any) => {
const {
} = props
const [dataSource, setDataSource] = useState(BIG_IMAGE_DATA)
const [showDesc, setShowDesc] = useState(true)
const [type, setType] = useState('normal')
const modalRef = useRef(null)
return (
<div style={{ padding: '32px', border: '1px solid #09f' }}>
<Space>
<span><Switch value={showDesc} onChange={e => setShowDesc(pre => !pre)} /></span>
</Space>
<BigImage
type={type}
ref={modalRef}
width={896}
viewHeight={'440px'}
showModeChange
onTabChange={(newVal) => setType(newVal === '1'? 'normal': 'compater')}
// ------------ 属性列表 -----------------
showDescription={showDesc}
descriptionList={attributeList}
// ------------ 图片/视频切换 -----------------
viewTopModeButtonList={[
{
key: 'image',
label: '图片'
},
{
key: 'video',
label: '视频'
}
]}
modeButtonGroupProps={{
defaultValue: 'image',
onChange: e => console.log('模式切换', e)
}}
// ------------ 导航功能 -----------------
showNavigation
onPrevButtonClick={val => console.log('pre', val)}
onNextButtonClick={val => console.log('next', val)}
// ------------ 场景图功能 -----------------
cropperImageProps={{
url: Img,
score: 50,
odList: get(dataSource, 'odRect', []),
attachImageData: get(dataSource, 'attachImg', []),
}}
// ------------ 对比图模式 -----------------
combineImageProps={{
data: pick(dataSource, 'compaterImage', 'imgSummary', 'imageKey', 'score'),
}}
// ------------ 视频模块 -----------------
videoProps={{
url: get(dataSource, 'flvUrl', '')
}}
>
</BigImage>
</div>
)
}
export default BigModal

View File

@ -0,0 +1,128 @@
// @ts-nocheck
import React, { useState } from 'react';
import { BigImageModal } from '@zhst/biz'
import { Button, DescriptionsProps } from '@zhst/meta'
import { IMAGE_DATA, BIG_IMAGE_DATA } from '../mock'
import bigImageModalAdapter from '../util/bigImageModalAdapter'
import { get } from '@zhst/func';
const descriptionList: DescriptionsProps['items'] = [
{
title: '人员属性',
children: [
{
key: '1',
label: '性别',
children: '男',
},
{
key: '2',
label: '年龄',
children: '成年',
},
{
key: '3',
label: '帽子',
children: '无',
},
{
key: '4',
label: '上身颜色',
children: '灰',
},
{
key: '5',
label: '下身颜色',
children: '蓝色',
},
{
key: '6',
label: '附着物',
children: '无',
},
{
key: '7',
label: '骑行',
children: '否',
},
]
}
];
// 适配器,适配老接口
const NewImageModal = bigImageModalAdapter(BigImageModal, {
oldMode: true
})
const BigModal = (props) => {
const {
onClose,
isArchiveDetail = false,
specialTitle = '对比图2',
transformPropFunc,
screenshotButtonRender,
showLowFaceTip = false
} = props
const [visible, setVisible] = useState(false)
const [selectIndex, setSelectIndex] = useState(0)
const [dataSource, setDataSource] = useState(IMAGE_DATA.dataSource)
const [dataSources, setDataSources] = useState(IMAGE_DATA.dataSource)
const [selectItem, setSelectItem] = useState({})
return (
<div>
<Button onClick={() => setVisible(true)} ></Button>
<NewImageModal
title="查看大图"
dataSource={dataSource}
imageData={dataSource}
width={1098}
onClose={() => onClose}
descriptionConfig={{ data: descriptionList }}
visible={visible}
isArchiveDetail={isArchiveDetail}
ToolProps={{
// renderLeft: leftOperateBar({ disableBtn, onActionClick: onBigImageActionClick }),
// renderRight: rightOperateBar({
// disableBtn,
// onActionClick: onBigImageActionClick,
// isArchiveDetail,
// }),
// renderVideoBtn: !disableBtn.includes(OPT['PLAY_VIDEO']),
// disableVideo: disableVideo,
}}
selectIndex={selectIndex}
onSelectIndexChange={(index: number) => {
index > 0 && index < dataSources.length && setSelectIndex(index);
}}
// tabsFilter={tabsFilter}
specialTitle={specialTitle}
transformPropFunc={async (item: any) => {
let bigImageInfo = !!transformPropFunc && (await transformPropFunc(item));
setSelectItem({ ...bigImageInfo });
return { ...bigImageInfo };
}}
screenshotButtonRender={screenshotButtonRender}
//@ts-ignore
transformVideoPropFunc={async (item) => {
const { maxDuration: duration = 20 } = item || {};
const time = get(item, 'timestamp');
const cameraId = get(item, 'cameraId');
const { url: flvUrl } = {
url: 'url',
downloadUrl: 'url',
};
return {
flvUrl,
maxDuration: duration,
};
}}
nullDialogProp={{
emptyText: showLowFaceTip ? '目标图人脸质量较低,暂无聚类数据' : '暂无数据',
}}
/>
</div>
)
}
export default BigModal

View File

@ -0,0 +1,89 @@
import React, { useEffect, useRef, useState } from 'react';
import { BigImage } from '@zhst/biz'
import { Space, Switch, Button } from '@zhst/meta'
import { pick, get } from '@zhst/func'
import { BIG_IMAGE_DATA, attributeList } from '../mock'
import Img from './imgs/photo-1503185912284-5271ff81b9a8.webp'
const testOd = [
{
"id": "456",
"x": 0.58543766,
"y": 0.3203356,
"w": 0.052037954,
"h": 0.2664015
}
]
const BigModal = (props: any) => {
const {
} = props
const [dataSource, setDataSource] = useState(BIG_IMAGE_DATA)
const [showDesc, setShowDesc] = useState(true)
const [editAble, setEditAble] = useState(false)
const [odList, setOdList] = useState(get(dataSource, 'odRect', []))
const [type, setType] = useState('normal')
const bigImageRef = useRef(null)
return (
<div style={{ padding: '32px', border: '1px solid #09f' }}>
<Space>
<span><Switch value={showDesc} onChange={e => setShowDesc(pre => !pre)} /></span>
<span><Switch value={editAble} onChange={e => setEditAble(pre => !pre)} /></span>
<Button onClick={() => setOdList(testOd)}>Od</Button>
</Space>
<BigImage
type={type}
ref={bigImageRef}
width={896}
viewHeight={'440px'}
showModeChange
onTabChange={(newVal) => setType(newVal === '1'? 'normal': 'compater')}
// ------------ 属性列表 -----------------
showDescription={showDesc}
descriptionList={attributeList}
// ------------ 图片/视频切换 -----------------
viewTopModeButtonList={[
{
key: 'image',
label: '图片'
},
{
key: 'video',
label: '视频'
}
]}
modeButtonGroupProps={{
defaultValue: 'image',
onChange: e => console.log('模式切换', e)
}}
// ------------ 导航功能 -----------------
showNavigation
onPrevButtonClick={val => console.log('pre', val)}
onNextButtonClick={val => console.log('next', val)}
// ------------ 场景图功能 -----------------
cropperImageProps={{
editAble,
url: Img,
score: 50,
odList: odList,
showEditTools: editAble,
attachImageData: get(dataSource, 'attachImg', []),
onCropperTypeChange: v => console.log('框选模式', v),
onExitEdit: () => setEditAble(pre => !pre)
}}
// ------------ 对比图模式 -----------------
combineImageProps={{
data: pick(dataSource, 'compaterImage', 'imgSummary', 'imageKey', 'score'),
}}
// ------------ 视频模块 -----------------
videoProps={{
url: get(dataSource, 'flvUrl', '')
}}
>
</BigImage>
</div>
)
}
export default BigModal

View File

@ -0,0 +1,78 @@
import React, { useEffect, useRef, useState } from 'react';
import { BigImage } from '@zhst/biz'
import { Button, Space, Switch } from '@zhst/meta'
import { pick, get } from '@zhst/func'
import { BIG_IMAGE_DATA, attributeList, RELATED_IMAGES } from '../mock'
const BigModal = (props: any) => {
const {
} = props
const [visible, setVisible] = useState(true)
const [dataSource, setDataSource] = useState(BIG_IMAGE_DATA)
const [selectedItemKey, setSelectedItemKey] = useState()
const [showFaceModel, setShowFaceModel] = useState(true)
const [type, setType] = useState('normal')
const modalRef = useRef(null)
return (
<div style={{ padding: '32px', border: '1px solid #09f' }}>
<Space>
<span><Switch value={showFaceModel} onChange={e => setShowFaceModel(pre => !pre)} /></span>
</Space>
<BigImage
type={type}
open={visible}
ref={modalRef}
viewHeight={'440px'}
width={896}
onCancel={() => setVisible(false)}
showModeChange
onTabChange={(newVal, oldVal) => setType(newVal === '1'? 'normal': 'compater')}
// ------------ 图片/视频切换 -----------------
viewTopModeButtonList={[
{
key: 'image',
label: '图片'
},
{
key: 'video',
label: '视频'
}
]}
modeButtonGroupProps={{
defaultValue: 'image',
onChange: e => console.log('模式切换', e)
}}
// ------------ 场景图功能 -----------------
cropperImageProps={{
url: "https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp",
score: 50,
odList: get(dataSource, 'odRect', []),
attachImageData: get(dataSource, 'attachImg', []),
}}
// ------------ 对比图模式 -----------------
combineImageProps={{
data: pick(dataSource, 'compaterImage', 'imgSummary', 'imageKey', 'score'),
}}
// ------------ 视频模块 -----------------
videoProps={{
url: get(dataSource, 'flvUrl', '')
}}
// ------------ 人脸碰撞模型 -----------------
isRelated={showFaceModel}
relatedImageProps={{
activeTab: 'related',
selectedItemKey,
data: RELATED_IMAGES,
onCancel: () => console.log('取消关联'),
onConnect: () => console.log('关联'),
onTabChange: (newVal, oldVal) => console.log('tab切换', newVal, oldVal),
onItemSelected: (item) => setSelectedItemKey(item.key)
}}
>
</BigImage>
</div>
)
}
export default BigModal

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

View File

@ -0,0 +1,132 @@
.zhst-big-image {
.zhst-dialog-content {
box-shadow: 0 4px 12px rgb(0 0 0 / 20%);
}
.zhst-tabs .zhst-tabs-nav-wrap {
background-color: #f6f6f6;
}
&-view {
&-btns {
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
&-desc {
margin-bottom: 16px;
padding: 16px 16px 0 16px;
background-color: #f7f7f7;
.zhst-descriptions-header {
margin-bottom: 8px;
}
.zhst-descriptions-title {
font-size: 14px;
}
}
// ------------ tab -----------------
.zhst-tabs-nav {
margin-bottom: 8px;
.zhst-tabs-tab {
&:not(:first-child) {
margin-left: 24px;
}
padding: 0;
padding-bottom: 4px;
}
&::before {
display: none;
}
}
&-container {
position: relative;
min-height: 440px;
width: 100%;
margin-bottom: 16px;
font-size: 0;
&-modeNav {
padding: 2px;
position: absolute;
top: 4px;
left: 4px;
z-index: 10;
border-radius: 4px;
}
&__nav {
position: absolute;
z-index: 99;
top: 50%;
width: 40px !important;
height: 40px !important;
background: #d9d9d9;
border-radius: 100%;
cursor: pointer;
transform: translateY(-50%);
&>button {
display: flex;
align-items: center;
color: #fff !important;
}
&--disabled {
opacity: 0.3;
&>button {
color: #fff !important;
}
}
}
&__nav:hover {
background: #09f;
color: #fff !important;
}
// ------------ 导航 -----------------
&-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(255, 255, 255, 0.8);
&:hover {
opacity: 0.8;
background-color: rgba(255, 255, 255, 0.8)!important;
}
&_left {
left: 8px;
}
&_right {
right: 8px;
}
}
}
// ------------ 场景图 -----------------
&-cropper-btn {
position: absolute;
top: 4px;
right: 4px;
&_close {
margin-left: 4px;
vertical-align: top;
}
&-group {
.zhst-radio-button-wrapper {
display: inline-flex;
font-size: 12px;
color: #191919;
background-color: rgba(255, 255, 255, 0.8)
}
&-radio {
display: inline-flex;
align-items: center;
.anticon {
margin-right: 4px;
}
}
}
}
}
}

View File

@ -0,0 +1,96 @@
---
group: 进阶组件
category: Components
subtitle: 大图组件
toc: content
title: BigImage 大图组件
---
## 大图弹框
<code src="./demo/index.tsx">基本</code>
<code src="./demo/withRelatedImage.tsx">人脸碰撞模型</code>
<code src="./demo/withEdit.tsx">编辑模式</code>
### API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| type | 当前模式 | 'compater'、'normal'、'video' | - | |
| viewHeight | 视图高度 | string、number | - | |
| width | 宽度 | string、number | - | |
| showDescription | 描述列表显示\隐藏 | boolean | false | |
| descriptionsProps | 描述列表透传 | | antd - DescriptionsProps | |
| descriptionList | 描述列表数据(见下文) | | IDescriptionList | |
| topButtonRender | 顶部按钮 | | ReactNode、string | |
| customTabBarExtraContent | 自定义tab尾部导航插槽 | | ReactNode、 string | |
| onTabChange | tab事件监听 | | antd - TabsProps['onChange'] | |
| tabProps | tab透传 | | antd - TabsProps | |
| showModeChange | 是否显示模式切换按钮 | | boolean | |
| viewTopModeButtonList | 模式切换按钮列表 | | TypeRadio[] | |
| modeButtonGroupProps | 模式切换按钮组透传 | RadioGroupProps | - | |
| isRelated | 人脸碰撞模型显示\隐藏 | boolean | | |
| relatedImageProps | 人脸碰撞模型透传 | zhst/meta - RelatedImageProps | | |
| cropperImageProps | 场景图模式透传 | ICropperImageProps | | |
| combineImageProps | 对比图模式透传 | ComBineImageProps | | |
| videoProps | 视频模式透传 | videoProps | | |
| showNavigation | 是否展示导航 | boolean | | |
| prevButtonProps | 前翻箭头透传 | prevButtonProps | | |
| onPrevButtonClick | 前翻箭头点击事件 | () => void; | | |
| nextButtonProps | 下翻箭头透传 | () => void; | | |
| onNextButtonClick | 下翻箭头点击事件 | () => void; | | |
| children | | ReactNode | | |
### IDescriptionList
```ts
interface IDescriptionList {
title: string;
showTitle?: boolean;
props?: DescriptionsProps
children: DescriptionsProps['items']
}[]
```
###
```ts
interface ICropperImageProps extends CropperImageProps {
showEditTools?: boolean // 是否展示编辑按钮
editAble?: boolean // 是否开启编辑模式
score?: string | number // 分数
btnGroupProps?: BtnGroupProps; // crop 场景图模式下的按钮拓展
cropButtonList?: TypeRadio[] // 编辑按钮列表
onCropperTypeChange?: (type: RadioProps['onChange']) => void
onExitEdit?: ButtonProps['onClick'] // 退出编辑模式
attachImageData?: AttachImageProps['data'] // 左下角预览图
attachImageProps?: AttachImageProps // 左下角预览图透传
}
```
## 设计方案
结合当下的业务场景,目前大图组件有三种模式
1. 场景图模式
-
2. 对比图模式
3. 视频模式
场景图和视频模式,支持用户编辑圈选
三种模式状态下,都会有外层模块的嵌套,目前有以下几种:
1. 描述模块
2. 顶部拓展
- 目前仅支持自定义
- 默认下边间距 16px
3. tabs 切换
- 默认下间距16px
- 支持自定义文案
- 支持自定义数量
- 支持后方插槽
4. 人脸碰撞模型
- 支持用户自定义传入数据
本来想通过插件的形式按需加载

View File

@ -0,0 +1,5 @@
import BigImage from "./BigImage";
export type { BigImageProps, BigImageRef } from './interface'
export default BigImage

View File

@ -0,0 +1,84 @@
import { ReactNode } from 'react'
import {
ButtonProps,
RadioProps,
RadioGroupProps,
VideoViewProps,
DescriptionsProps,
TabsProps,
RelatedImageProps,
BtnGroupProps,
CropperImageProps,
VideoViewRef,
AttachImageProps
} from '@zhst/meta'
import { ComBineImageProps } from './components/CombineImage'
export type TypeRadio = {
label?: string;
key: string;
tooltipTxt?: string;
onClick?: ButtonProps['onClick']
icon?: ReactNode | string;
props?: ButtonProps
}
type TypePlugin = 'compater' | 'normal' | 'video' | 'faceMode'
export interface BigImageProps {
type?: TypePlugin // 当前 tab
viewHeight?: string | number;
width?: string | number
typePlugins?: TypePlugin[] // TODO: 开启插件类型
// ------------ 描述列表 -----------------
showDescription?: boolean;
descriptionsProps?: DescriptionsProps
descriptionList?: {
title: string;
showTitle?: boolean;
props?: DescriptionsProps
children: DescriptionsProps['items']
}[]
// ------------------ 顶部按钮列表
topButtonRender?: ReactNode | string
// ---------------- Tabs 导航 ------------------
customTabBarExtraContent?: string | ReactNode
onTabChange?: TabsProps['onChange']
tabProps?: TabsProps
// ------------ 图片 视频切换导航 -----------------
showModeChange?: boolean
viewTopModeButtonList?: TypeRadio[]
modeButtonGroupProps?: RadioGroupProps
// ----------------- 人脸碰转 -----------------
isRelated?: boolean // 人脸碰撞功能打开
relatedImageProps?: RelatedImageProps
// -------------------------- crop 场景图模式 -----------------------
cropperImageProps?: CropperImageProps & {
showEditTools?: boolean
editAble?: boolean
score?: string | number
btnGroupProps?: BtnGroupProps; // crop 场景图模式下的按钮拓展
cropButtonList?: TypeRadio[]
onCropperTypeChange?: (type: RadioProps['onChange']) => void
onExitEdit?: ButtonProps['onClick']
attachImageData?: AttachImageProps['data']
attachImageProps?: AttachImageProps
}
// -------------------- 对比图模式 -------------------------
combineImageProps?: ComBineImageProps
// ------------ 导航 -----------------
showNavigation?: boolean // 是否展示导航箭头
prevButtonProps?: any;
onPrevButtonClick?: () => void;
nextButtonProps?: any
onNextButtonClick?: () => void;
theme?: any
children?: React.ReactNode
// ------------ 视频模式 -----------------
videoProps?: VideoViewProps
}
export interface BigImageRef {
videoPlayerRef: VideoViewRef
combineImageRef: any
}

View File

@ -0,0 +1,293 @@
// @ts-nocheck
import imageUrl from './demo/imgs/photo-1503185912284-5271ff81b9a8.webp'
export const IMAGE_DATA = {
"enAbleDeleteFeature": true,
"tabsFilter": [
"COMPATER",
"NORMAL"
],
"selectIndex": 4,
"disableBtn": [
0,
1,
4,
20
],
"dataSource": {
"objectId": "1742110565582518272",
"condition": {
"featureInfo": null,
"featureData": "AAAAAAAAAAAAAAAAAAAAAAA==",
"imageData": "",
"alg": "VERSION_REID_HEAD_ATTR",
"rect": {
"x": 0.271875,
"y": 0.32222223,
"w": 0.2859375,
"h": 0.67777777
},
"objectImageUrl": "singer-20240102/1/129529/1742047651878156288.jpg",
"srcImageUrl": "singer-20240102/1/129529/1742047652511496192.jpg"
},
"score": 0.7163062,
"timestamp": 1704186491979,
"deviceId": "129533",
"id": "129533",
"name": "4楼门口过道人脸",
"dirid": "0",
"status": "1",
"longitude": 120.125,
"latitude": 30.280500411987305,
"caseId": "0",
"caseGroup": "",
"isDeleted": "DEVICEMANAGER_BOOL_DEFAULT",
"objectIndex": {
"objectId": "1742110565582518272",
"solutionId": "1",
"deviceId": "129533",
"fragmentId": "0"
},
"objectType": "OBJECT_TYPE_PEDESTRAIN",
"isObjectTrack": true,
"pathId": "1742110532019697664",
"frameInfo": {
"frameId": "0",
"frameTimestamp": "1704186491979",
"width": 0,
"height": 0,
"originWidth": 1920,
"originHeight": 1080,
"offsetTime": "24714687",
"skipNumber": "0"
},
"level": 1,
"bboxInFrame": {
"x": 0.603125,
"y": 0.3314815,
"w": 0.0578125,
"h": 0.2712963
},
"bboxExtInFrame": {
"x": 0.546875,
"y": 0.2638889,
"w": 0.17135416,
"h": 0.40648147
},
"objectImageKey": "",
"objectExtImageKey": "http://10.0.0.7:30003/file/singer-20240102/1/129533/1742110565582518272.jpg",
"frameImageKey": "http://10.0.0.7:30003/file/singer-20240102/1/129533/1742110565603489792.jpg",
"confidence": 0.817271,
"sourceObjectId": "1742110565603489792",
"storeTimestamp": "0",
"gbNumber": "",
"qualityScore": 0,
"subObjectCount": 1,
"subObjectType": [
"OBJECT_TYPE_FACE"
],
"subObjectIds": [
"1742110532015503360"
],
"solutionId": "1",
"fragmentId": "0",
"contrastKey": "singer-20240102/1/129533/1742110565582518272.jpg",
"compaterImages": [
"http://10.0.0.7:30003/file/singer-20240102/1/129529/1742047651878156288.jpg"
],
"imgSummary": "singer-20240102/1/129533/1742110565582518272.jpg",
"imageKey": "http://10.0.0.7:30003/file/singer-20240102/1/129533/1742110565582518272.jpg",
"srcImageUrl": "http://10.0.0.7:30003/file/singer-20240102/1/129533/1742110565603489792.jpg",
"algorithmVersion": "VERSION_REID_HEAD_ATTR",
"cameraId": "129533",
"cameraName": "4楼门口过道人脸"
},
"isArchiveDetail": false,
"ToolProps": {
"renderVideoBtn": true,
"disableVideo": false
},
"specialTitle": ""
}
export const RELATED_IMAGES = [
{ key: '123', url: imageUrl },
{ key: '22', url: imageUrl },
{ key: '22122333', url: imageUrl },
{ key: '2212243', url: imageUrl },
{ key: '224523433', url: imageUrl },
{ key: '224235453', url: imageUrl },
{ key: '245423', url: imageUrl },
{ key: '224233543', url: imageUrl },
{ key: '22452343', url: imageUrl },
{ key: '22323243', url: imageUrl },
{ key: '2236456', url: imageUrl },
{ key: '224563', url: imageUrl },
{ key: '24565423', url: imageUrl },
{ key: '245645623', url: imageUrl },
{ key: '2456435623', url: imageUrl },
{ key: '2323', url: imageUrl }
]
export const BIG_IMAGE_DATA = {
imageKey: imageUrl,
imgSummary: imageUrl,
flvUrl: 'ws://10.0.0.120:9033/flv/HaikangNvr/45.flv?ip=10.0.2.103&stime=1712539148&etime=1712539168',
compaterImage: imageUrl,
odRect: [
{
id: '123',
"x":0.5445312,
"y":0.19166666,
"w":0.08671875,
"h":0.40138888
},
{
"id": "123",
"x": 0.5519352,
"y": 0.2965385,
"w": 0.05185461,
"h": 0.24698898,
},
{
"id": "456",
"x": 0.58543766,
"y": 0.3203356,
"w": 0.052037954,
"h": 0.2664015
}
],
attachImg: [
{
"url": imageUrl,
"label": "形体"
},{
"url": imageUrl,
"label": "人脸"
}
],
score: 60, // 人脸质量分
showScore: true, // 人脸质量分
cameraPosition: 'string', // 摄像头位置
time: '2022-01-01', // 摄像头拍摄时间
objects: [
{
"objectIndex": {
"objectId": "1746832189053474816",
"solutionId": "0",
"deviceId": "0",
"fragmentId": "0"
},
"objectType": "OBJECT_TYPE_PEDESTRAIN",
"sourceObjectId": "0",
"level": 0,
"confidence": 0.881164,
"frameInfo": {
"frameId": "0",
"frameTimestamp": "1705312223057",
"width": 0,
"height": 0,
"originWidth": 0,
"originHeight": 0,
"offsetTime": "0",
"skipNumber": "0"
},
"infoOnSource": {
"bboxInFrame": {
"bboxRatio": {
"x": 0.61418945,
"y": 0.34309354,
"w": 0.067661405,
"h": 0.34659258
},
},
"countInSource": 0,
"indexInSource": 0
},
"qualityScore": 0,
}
]
}
export const attributeList = [
{
title: '人员属性',
children: [
{
key: '1',
label: '性别',
children: '男',
},
{
key: '2',
label: '年龄',
children: '成年',
},
{
key: '3',
label: '帽子',
children: '无',
},
{
key: '4',
label: '上身颜色',
children: '灰',
},
{
key: '5',
label: '下身颜色',
children: '蓝色',
},
{
key: '6',
label: '附着物',
children: '无',
},
{
key: '7',
label: '骑行',
children: '否',
},
]
},
{
title: '社区规范',
children: [
{
key: '1',
label: '性别',
children: '男',
},
{
key: '2',
label: '年龄',
children: '成年',
},
{
key: '3',
label: '帽子',
children: '无',
},
{
key: '4',
label: '上身颜色',
children: '灰',
},
{
key: '5',
label: '下身颜色',
children: '蓝色',
},
{
key: '6',
label: '附着物',
children: '无',
},
{
key: '7',
label: '骑行',
children: '否',
},
]
}
];

View File

@ -0,0 +1,195 @@
/**
*
*/
import React, { } from 'react';
import { AlgorithmVersionStr, HumanProperty, ObjectType, Rect, IScreenshotButtonProp } from '@zhst/types'
import { VideoViewProps, ImgViewProps, VideoViewRef, ImgViewRef } from '@zhst/meta'
export type TAB_TYPE = 'COMPATER' | 'NORMAL' | 'TRACK';
export type MODEL_TYPE = 'VIDEO' | 'IMAGE';
export interface CarouselProps {
hasPre?: boolean;
hasNext?: boolean;
selectIndex: number;
setSelectIndex: React.Dispatch<React.SetStateAction<number>>;
dataSource: Array<{
key: string;
url: string;
}>;
}
export type ISelectItem = Partial<Omit<ImgViewProps, 'screenshotButtonRender'>> &
Partial<Omit<VideoViewProps, 'screenshotButtonRender'>>;
/**
* description
*/
export interface HeaderProps {
value: TAB_TYPE;
onChange: (type: TAB_TYPE) => void;
tabsFilter: TAB_TYPE[];
}
export interface ParamProps {
tab: string;
selectItem: ISelectItem;
imgViewRef: React.MutableRefObject<ImgViewRef>;
VideoViewRef: React.MutableRefObject<VideoViewRef>;
model: MODEL_TYPE;
setModel: React.Dispatch<React.SetStateAction<MODEL_TYPE>>;
/* 可观测值 */
scale$: number;
showCrop$: boolean;
}
/**
*
*/
export interface ToolProps {
renderRight?: (props: ParamProps) => React.ReactNode;
renderLeft?: (props: ParamProps) => React.ReactNode;
renderVideoBtn?: boolean;
param: ParamProps;
disableVideo: boolean;
}
export interface BigImageData {
//imageKey 小图
extendRectList: (Rect & { algorithmVersion: AlgorithmVersionStr; imageKey: string })[];
rectList: (Rect & { algorithmVersion: AlgorithmVersionStr; imageKey: string })[];
attachImg: { url: string; label: '形体' | '人脸' }[];
odRect: Rect;
compaterImages: string[] // 目标图列表
constractKey: string; // 当前比较中的目标图
frameImageKey: string; // 场景图
imageKey?: string; // 大图
imgSummary: string; // 摘要图
objectExtImageKey: string; //比对到的目标图扩展图 === imgSummary
attributeList: { label: string; list: any[] }[];
archiveImages?: any;
spaceName: string;
objectIndex?: {
deviceId: string;
fragmentId: string;
objectId: string;
solutionId: string;
}
objectType: ObjectType;
objectId: string; //这张摘要本身的Id
bodyObjectId?: string;
faceObjectId?: string; //这张摘要下的人脸Id(如果有)
sourceObjectId?: string; //这张摘要上游的形体Id(如果有)
cameraId: string;
cameraName: string;
selectIndex: number;
humanProperty: HumanProperty;
qualityScore?: number; //人脸质量分
score: number; // 相似度
timestamp: string;
bodyImageUrl: string;
faceImageUrl: string;
algorithmVersion: AlgorithmVersionStr;
bodySpaceName: string;
faceSpaceName: string;
position: {
lat: number
lng: number
}
solutionId?: string;
[index: string]: any;
}
interface IOldImageData {
visible?: boolean; // 显示隐藏弹框
defaultModel?: MODEL_TYPE; // 视频模式 | 图片模式
onClose?: () => void; // 关闭弹框
isLoading?: boolean; // 是否加载中
hasPre?: boolean; // 向前翻页
hasNext?: boolean; // 向后翻页
selectIndex?: number; // 选中的数据dataSource为数组情况下
onSelectIndexChange?: (i: number) => void; // 修改当前下标
dataSource: BigImageData[]; // 数据1
dataSources: BigImageData[]; // 数据2
relatedData?: BigImageData[]; // 数据3
transformPropFunc: (item: any) => ISelectItem; // 格式化数据方法
transformVideoPropFunc: (
item: ISelectItem
) => Promise<Omit<VideoViewProps, 'screenshotButtonRender'>>; // 视频模式格式化数据方法
screenshotButtonRender?: (screenshotButtonProp: IScreenshotButtonProp) => React.ReactElement;
showTool?: boolean; // 是否显示底部菜单
showCarousel?: boolean; //
imgViewProp?: Partial<ImgViewProps>; // 图片模式的配置
videoViewProp?: Partial<VideoViewProps>; // 视频模式的配置
ToolProps?: Partial<ToolProps>; // 底部菜单的配置
nullDialogProp?: {
emptyText?: string;
}; // 暂无数据的配置
showHeader?: boolean; // 是否显示 description
tabsFilter?: TAB_TYPE[]; // tabs 过滤
useVirtual?: boolean; // 是否显示虚拟
loadNext?: (i: number) => Promise<void>; // 下一个
loadPre?: (i: number) => Promise<void>; // 前一个
children: React.ReactNode; // 子元素
title?: string; // 标题
specialTitle?: string; // 对比图模式下标题
isRelated?: boolean;
carouselProp?: Partial<CarouselProps>;
}
export interface ImageModalDataProps {
targetData: BigImageData[]
compactData: BigImageData[]
}
export interface ModalAdapterConfigProps {
oldMode?: boolean; // 是否是老模式
}
/**
*
* @param _data
* @returns newData
*/
const translateOldImageData = (_data: IOldImageData) => {
return {
..._data,
open: _data.visible,
onCancel: _data.onClose
}
}
/**
*
* @param Cmp
* @param config
* @returns
*/
const adapter = (Cmp: any, config: ModalAdapterConfigProps): any => {
const { oldMode = false } = config
return (props: IOldImageData) => {
const newProps = oldMode ? translateOldImageData(props) : props
console.log('adapter----适配数据', props, newProps)
// 该属性已经废弃
delete newProps.visible
return (
<Cmp
{...newProps}
/>
)
}
}
export default adapter

View File

@ -0,0 +1,36 @@
export interface IBigImageModalData {
imageKey?: string // 目标图
imgSummary?: string // 大图
flvUrl?: string // 视频链接
compaterImages?: string[] // 对比图
odRect?: { // od 框数据
"x": number
"y": number
"w": number
"h": number
[key: string]: string | number; // 拓展参数
}[],
attachImg?: { // 小图,只有在场景图模式生效(人脸、形体)
"url": string
"label": string
[key: string]: string
}[],
score?: number | string // 人脸质量分
showScore?: boolean // 人脸质量分
cameraPosition?: string // 摄像头位置
time?: string // 摄像头拍摄时间
objects: { // 拓展参数、可以自由支配
objectIndex: {
[key: string]: any
},
objectType: string
sourceObjectId: string
level: number
confidence: number
infoOnSource: {
[key: string]: any
},
qualityScore: number
[key: string]: any
}[]
}

View File

@ -2,14 +2,14 @@
* Created by jiangzhixiong on 2024/04/28
*/
import React, { forwardRef, useContext, useImperativeHandle } from 'react'
import React, { forwardRef, ReactNode, useContext, useImperativeHandle } from 'react'
import { ConfigProvider, EMPTY_BASE64 } from '@zhst/meta'
import { Flex, Image } from 'antd';
import { Image } from 'antd';
import './index.less'
const { ConfigContext } = ConfigProvider
export interface Idata {
export interface IData {
id?: string | number;
url?: string;
sort?: number;
@ -17,24 +17,25 @@ export interface Idata {
subtitle?: string;
}
export interface SearchCardProps extends Idata {
export interface CommonCardProps extends IData {
prefixCls?: string;
data?: Idata
data?: IData
width?: string;
height?: string;
onCreateTxt?: string;
onCreate?: (data: any) => void;
onCreate?: () => void;
onAddTxt?: string;
onAdd?: (data: any) => void;
onRemoveTxt?: string;
onRemove?: (data: any) => void;
customOptionRender?: React.ReactNode
actions?: ReactNode[]
onItemClick?: (data: any, e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}
export interface SearchCardRefProps {
export interface CommonCardRefProps {
}
const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref) => {
const CommonCard = forwardRef<CommonCardRefProps, CommonCardProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
url,
@ -43,26 +44,29 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
subtitle,
sort,
data,
onCreate,
onCreateTxt = '创建检索',
onAddTxt = '添加目标',
onRemoveTxt = '移除轨迹',
onAdd,
onRemove,
customOptionRender,
actions = [],
width = '184px',
height = '100%'
height = '100%',
onItemClick
} = props
const { getPrefixCls } = useContext(ConfigContext)
const componentName = getPrefixCls('biz-search-card', customizePrefixCls);
const stopBumble = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, fn?: ((data: Idata) => void), data?: Idata) => {
e.stopPropagation()
fn?.(data!)
const optionListRender = (_actions: ReactNode[]) => {
return _actions.map((action, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={`${componentName}-main-opt-action-${i}`}>
{action}
{i !== _actions.length - 1 && <em className={`${componentName}-main-opt-action-split`} />}
</li>
))
}
const handleItemClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, _data: IData | undefined) => {
onItemClick?.(data, e)
}
useImperativeHandle(ref, () => ({
}))
return (
@ -72,9 +76,10 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
width,
height
}}
onClick={e => handleItemClick(e, data)}
>
<div className={`${componentName}-main`}>
<i className={`${componentName}-main-num`}>{id || sort}</i>
<i className={`${componentName}-main-num`}>{sort || id}</i>
<Image
className={`${componentName}-main-img`}
src={url || data?.url}
@ -82,17 +87,9 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
preview={false}
fallback={EMPTY_BASE64}
/>
<Flex align='center' justify='space-between' className={`${componentName}-main-opt`}>
{customOptionRender || (
<>
<a onClick={(e) => stopBumble(e, onCreate, data)}>{onCreateTxt}</a>
|
<a onClick={(e) => stopBumble(e, onAdd, data)}>{onAddTxt}</a>
|
<a onClick={(e) => stopBumble(e, onRemove, data)}>{onRemoveTxt}</a>
</>
)}
</Flex>
<ul className={`${componentName}-main-opt`}>
{optionListRender(actions)}
</ul>
</div>
<div className={`${componentName}-footer`}>
<p className={`${componentName}-footer-tit`}>{title || data?.title}</p>
@ -102,4 +99,4 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
)
})
export default SearchCard
export default CommonCard

View File

@ -1,18 +1,23 @@
.zhst-biz-search-card {
display: inline-block;
border: 2px solid transparent;
transition: .1s ease-in all;
cursor: pointer;
overflow: hidden;
&:hover {
transition: .5s ease all;
border: 2px solid #09f;
.zhst-biz-search-card-main-opt {
display: flex;
align-items: center;
}
}
&-main {
position: relative;
overflow: hidden;
&-num {
position: absolute;
@ -32,11 +37,17 @@
&-img {
width: 100%;
height: 240px;
overflow: hidden;
&:hover {
transition: .5s ease-in all;
transform: scale(1.05);
}
}
&-opt {
display: none;
margin: 0;
position: absolute;
padding: 6px 3px;
left: 0;
@ -48,11 +59,28 @@
box-sizing: border-box;
transition: .2s ease-in all;
li {
position: relative;
list-style: none;
flex: 1;
text-align: center;
}
&-action-split {
position: absolute;
top: 50%;
right: 0;
width: 1px;
height: 80%;
transform: translateY(-50%);
background-color: #fff;
}
a {
color: #fff;
&:hover {
opacity: 0.9;
opacity: 0.88;
}
}
}

View File

@ -0,0 +1,5 @@
import CommonCard from './CommonCard'
export type { CommonCardProps, CommonCardRefProps } from './CommonCard'
export default CommonCard

View File

@ -0,0 +1,42 @@
import React, { useEffect, useState } from 'react'
import { CommonCard } from '@zhst/biz'
import { uniqueId } from '@zhst/func'
export default () => {
const [data, setData] = useState([])
useEffect(() => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json())
.then((body) => {
let res = body.results.map((o, index) => {
return {
id: uniqueId(),
sort: index + 1,
title: o.name.first,
subtitle: o.name.last,
url: o.picture.large
}
})
setData(res);
})
}, [])
return (
<div>
{data?.map(item => (
<CommonCard
key={item.id}
sort={item.sort}
data={item}
width="184px"
actions={[
<a></a>,
<a></a>,
<a></a>
]}
/>
))}
</div>
)
}

View File

@ -0,0 +1,31 @@
---
category: Components
title: CustomCard 定制化卡片
toc: content
group:
title: 数据展示
---
定制化卡片
## 代码演示
<code src="./demo/commonCard.tsx">基本卡片</code>
## API
### CommonCardProps
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| data | 数据源 | IData[] | [] | - |
| prefixCls | 数据源 | string | [] | - |
| width | 宽度 | string | [] | - |
| height | 高度 | string | [] | - |
| onCreateTxt | 创建方法文字 | string | [] | - |
| onCreate | 创建方法 | () => void | [] | - |
| onAddTxt | 数据源 | string | [] | - |
| onAdd | 数据源 | () => void | [] | - |
| onRemoveTxt | 数据源 | string | [] | - |
| onRemove | 数据源 | () => void | [] | - |
| customOptionRender | 数据源 | React.ReactNode | - | - |

View File

@ -0,0 +1,6 @@
/**
* Created by jiangzhixiong on 2024/04/28
*/
export { default as CommonCard } from './components/commonCard'
export type { CommonCardProps, CommonCardRefProps } from './components/commonCard'

View File

@ -1,12 +0,0 @@
import React from 'react'
import { Button } from '@zhst/meta'
import { useThrottleFn } from '@zhst/hooks'
export default () => {
const { run } = useThrottleFn(() => console.log('123'))
return (
<Button onClick={() => run()} ></Button>
)
}

View File

@ -3,7 +3,7 @@ import { IRecord } from '../../../WarningRecordCard';
import { ViewLargerImageModalRef } from '../../../ViewLargerImageModal';
import WarningRecordCard from '../../../WarningRecordCard';
import ViewLargerImageModal from '../../../ViewLargerImageModal';
import { Empty, Space, Spin } from 'antd';
import { Empty, Space, Spin } from '@zhst/meta';
import { pxToRem } from '@zhst/func'
import { LoadingOutlined } from '@ant-design/icons';
import "./index.less"

View File

@ -1,7 +1,7 @@
import React from 'react';
import VideoPlayerCard from '../../../VideoPlayerCard';
import { VideoPlayerCardProps } from '../../../VideoPlayerCard';
import { Row, Col, Segmented, theme } from 'antd';
import { Row, Col, Segmented, theme } from '@zhst/meta';
import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
import { pxToRem } from '@zhst/func'
import './index.less'

View File

@ -1,5 +1,5 @@
import React, { useImperativeHandle, useRef, useState, forwardRef, useContext } from 'react';
import { Modal, ModalProps, Space, SpaceProps, theme } from 'antd';
import { Modal, ModalProps, Space, SpaceProps, theme } from '@zhst/meta';
import { DownloadOutlined } from '@ant-design/icons';
import { ConfigProvider, CropperImage } from '@zhst/meta';

View File

@ -1,4 +1,4 @@
import { Card, Space, Divider, CardProps, theme } from 'antd';
import { Card, Space, Divider, CardProps, theme } from '@zhst/meta';
import React, { useContext } from 'react';
import dayjs from 'dayjs';
import { ConfigProvider, CropperImage } from '@zhst/meta';

View File

@ -1,39 +1,26 @@
import React, { FC, useContext } from 'react';
import { Tabs, TabsProps } from 'antd'
import { ConfigProvider } from '@zhst/meta';
import React, { FC, ReactNode, useContext } from 'react';
import { ConfigProvider, Tabs, TabsProps } from '@zhst/meta';
import BoxPanel from './components/boxPanel';
import type { BoxPanelProps } from './components/boxPanel';
import './index.less'
import classNames from 'classnames';
export interface BoxSelectTreeProps extends BoxPanelProps {
onTabChange?: (e: any) => void
tabsProps?: TabsProps
prefixCls?: string;
footer?: ReactNode
}
const { ConfigContext } = ConfigProvider
const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
const {
data,
boxDataSource = [],
onTabChange,
onSearch,
onItemCheck,
onItemSelect,
onBoxBatchDelete,
onBoxDelete,
onCreateSubmit,
onClockClick,
onImport,
onCreate,
prefixCls: customizePrefixCls,
tabsProps,
searchInputProps,
treeProps,
customImport,
showOptions,
extraBtns,
prefixCls: customizePrefixCls
onTabChange,
footer,
...rest
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-box-select-tree', customizePrefixCls);
@ -57,27 +44,14 @@ const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
items={items}
onChange={onTabChange}
tabBarGutter={0}
// @ts-ignore
indicator={{ size: (origin) => origin, align: 'center' }}
{...tabsProps}
/>
<BoxPanel
searchInputProps={searchInputProps}
boxDataSource={boxDataSource}
treeProps={treeProps}
data={data}
onCreate={onCreate}
onCreateSubmit={onCreateSubmit}
onBoxBatchDelete={onBoxBatchDelete}
onBoxDelete={onBoxDelete}
onSearch={onSearch}
onItemCheck={onItemCheck}
onItemSelect={onItemSelect}
showOptions={showOptions}
customImport={customImport}
extraBtns={extraBtns}
onClockClick={onClockClick}
onImport={onImport}
{...rest}
/>
{footer}
</div>
);
};

View File

@ -1,9 +1,11 @@
.zhst-biz-box-select-tree-panel {
&-search {
display: flex;
align-items: center;
padding: 0 12px;
&-input {
flex: 1;
margin-right: 4px;
}
@ -18,6 +20,7 @@
justify-content: space-between;
&-common {
flex: 1;
padding: 4px 8px;
}
@ -30,6 +33,60 @@
}
}
&-tags {
position: relative;
height: 100%;
margin-top: 6px;
padding: 12px;
font-size: 0;
border-top: 1px solid #09f;
border-bottom: 1px solid #09f;
&-tag {
margin-right: 6px;
font-weight: bold;
font-size: 14px;
transition:.3s ease all;
&_common{
margin-bottom: 6px;
display: inline-block;
margin-right: 6px;
padding: 6px;
font-size: 12px;
cursor: pointer;
background-color: #f6f6f6;
transition: .3s ease all;
border-radius: 3px;
box-sizing: content-box;
&:hover {
color: #09f;
background-color: rgba(0, 0, 0, 6%);
}
}
&_checked {
color: #09f;
background-color: #edf8ff;
}
&_fz12 {
font-size: 12px;
}
&_option {
display: inline-block;
}
&_absolute {
position: absolute;
top: 12px;
right: 0;
}
}
}
&-tree {
padding: 6px 0;
}

View File

@ -1,73 +1,165 @@
import React, { FC, useState, useRef, useContext } from 'react';
import{ Button, Divider, Input, Space, TreeDataNode } from 'antd'
import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } from '@ant-design/pro-components'
import { ConfigProvider } from '@zhst/meta';
import React, { FC, useState, useContext, ReactNode } from 'react';
import { ModalFormProps } from '@ant-design/pro-components'
import {
ButtonProps,
ConfigProvider,
Input,
Dropdown,
Tooltip,
Button,
Divider,
DataNode as TreeDataNode,
Tree as BoxTree,
TreeProps as BoxTreeProps,
TreeProps,
InputProps,
DropDownProps
} from '@zhst/meta';
import { IconFont } from '@zhst/icon';
import { ClockCircleOutlined, CloseCircleOutlined, DiffOutlined, FolderAddOutlined, ImportOutlined, SwitcherOutlined } from '@ant-design/icons'
import type { TreeProps, InputProps } from 'antd';
import type { BoxTreeProps } from '../../../tree';
import TreeTransferModal from '../../../treeTransferModal'
import BoxTree from '../../../tree';
import './index.less'
import classNames from 'classnames';
import './index.less'
interface IOption {
label: string
key: string
icon?: string | ReactNode
type?: ButtonProps['type'] | 'dropdown'
disabled?: boolean;
showTooltip?: boolean;
props?: ButtonProps
onClick?: () => void
dropdownConfig?: DropDownProps
}
interface ITag {
label: string
value: string
icon?: ReactNode
parentNode?: string
children?: ITag[]
}
export interface BoxPanelProps {
searchInputProps?: InputProps
showOptions?: boolean
treeProps?: Partial<BoxTreeProps>
data: TreeDataNode[]
boxDataSource: TreeDataNode[]
handleImport?: () => void
onSearch?: (e: any) => void
onItemCheck?: TreeProps['onCheck']
onItemSelect?: TreeProps['onSelect']
/**
* @deprecated 0.23.0
*/
onBoxBatchDelete?: (data?: any) => void
onBoxDelete?: (data?: any) => void
/**
* @deprecated 0.23.0
*/
onCreateSubmit?: ModalFormProps['onFinish']
onClockClick?: () => void
/**
* @deprecated 0.23.0
*/
onClockClick?: () => void //
/**
* @deprecated 0.23.0
*/
onImport?: () => void
/**
* @deprecated 0.23.0
*/
onBatch?: () => void
/**
* @deprecated 0.23.0
*/
onCreate?: () => void
customImport?: any
extraBtns?: any
prefixCls?: string;
customImport?: ReactNode | string // 自定义搜索栏边上的过滤图标
extraBtns?: ReactNode | string // 搜索栏下面的插槽
prefixCls?: string
showSearchBar?: boolean // 是否显示搜索栏
noFilter?: boolean // 是否显示搜索拓展 icon
filterList?: IOption[]
optionList?: IOption[]
showTagPanel?: boolean // 标签插槽
tagList?: ITag[] // 标签列表
tagExpandAll?: boolean // 展开所有
onTagCheck?: (value: string, tag: ITag) => void;
checkedTags?: string[]
onResetTags?: () => void
onTagExpand?: (e: any) => void
tagFootRender?: ReactNode
}
const { ConfigContext } = ConfigProvider
const BoxPanel: FC<BoxPanelProps> = (props) => {
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
const {
searchInputProps,
showOptions = true,
extraBtns,
noFilter,
data = [],
onSearch,
treeProps,
onItemCheck,
onItemSelect,
onCreateSubmit,
onBoxBatchDelete,
onBoxDelete,
onClockClick,
onImport,
onBatch,
onCreate,
boxDataSource,
showSearchBar = true,
optionList = [
{
label: '导入盒子',
key: 'import',
icon: <ImportOutlined />,
onClick: () => onImport?.()
},
{
label: '新建组',
key: 'add',
icon: <FolderAddOutlined />,
onClick: () => onCreate?.()
},
{
label: '删除',
key: 'del',
icon: <CloseCircleOutlined />,
onClick: () => onBoxBatchDelete?.(),
props: {
danger: true
}
}
],
filterList = [
{
label: '多选',
key: 'multi',
icon: isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />,
onClick: () => onBatch?.() || handleCheckable()
},
{
label: '时钟',
key: 'clock',
icon: <ClockCircleOutlined />,
onClick: () => onClockClick?.()
}
],
showTagPanel,
tagList,
tagExpandAll,
onTagExpand,
checkedTags = [],
onTagCheck,
onResetTags,
tagFootRender,
prefixCls: customizePrefixCls,
customImport
customImport: customFilter
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-box-select-tree-panel', customizePrefixCls);
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [boxChoiceOpen, setBoxChoiceOpen] = useState(false)
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const createFormRef = useRef<
ProFormInstance<{
name: string;
boxList?: any[];
}>
>()
/**
*
@ -77,57 +169,95 @@ const BoxPanel: FC<BoxPanelProps> = (props) => {
setIsTreeCheckable(pre => !pre)
}
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
let _targetItems: TreeDataNode[] = []
setCheckedKeys(keys)
info.checkedNodes.forEach(o => {
o.isLeaf && _targetItems.push(o)
})
setTargetItems(_targetItems)
/**
* filter
* @param _list
* @returns
*/
const initFilter = (_list?: BoxPanelProps['filterList']) => {
const WithDropdown = (dom: ReactNode, _config?: DropDownProps) => {
return (
<Dropdown placement="bottomLeft" arrow {..._config}>
{dom}
</Dropdown>
)
}
return _list?.map(item => (
<Tooltip
title={item.label}
open={item.showTooltip}
>
{item.type === 'dropdown' ? (
WithDropdown(
<Button className={classNames(componentName + '-search-btns-btn')} type="text" onClick={item.onClick} icon={item.icon} />,
item.dropdownConfig
)
) : (
<Button className={classNames(componentName + '-search-btns-btn')} type="text" onClick={item.onClick} icon={item.icon} />
)}
</Tooltip>
))
}
/**
*
* @param key
* @param param1
* filter
* @param _list
* @returns
*/
const onItemDelete = (key: any, { keys }: any) => {
setCheckedKeys(pre => {
const newKeys = pre.filter(_key => !keys.includes(_key))
return newKeys
const initOptions = (_list?: BoxPanelProps['optionList']) => {
return _list?.map((item, idx) => (
<>
<Tooltip
title={item.label}
>
{/* @ts-ignore */}
<Button className={classNames(componentName + '-btns-common')} type={item.type || 'text'} onClick={item.onClick} icon={item.icon} {...item?.props}>{item.label}</Button>
</Tooltip>
{idx !== _list.length - 1 && <Divider className={classNames(componentName + '-btns-divider')} type="vertical" />}
</>
))
}
/**
*
* @param _tagList
* @param sort
* @returns
*/
const initTagPanel = (_tagList: BoxPanelProps['tagList'], sort?: boolean) => {
// 正常标签渲染
const commonTag = (_tagProps: ITag) => (
<span
className={classNames(
componentName + '-tags-tag_common',
{[componentName + '-tags-tag_checked']: checkedTags.includes(_tagProps.value)}
)}
key={_tagProps.value}
onClick={() => onTagCheck?.(_tagProps.value, _tagProps)}
>{_tagProps.label}</span>
)
// 包装父级标签
const _withFather = (tag: ITag) => (
<div key={tag.value}>
<span className={classNames(componentName + '-tags-tag')}>{tag.label}</span>
{tag.children?.map?.(_tag => commonTag(_tag))}
</div>
)
return _tagList?.map(tag => {
if (tag.children?.length && sort) {
return _withFather(tag)
} else {
return commonTag(tag)
}
})
setTargetItems(pre => pre.filter(o => o.key !== key))
}
// 盒子点击确定
const onBoxChoiceOk = async (data: any) => {
createFormRef.current?.setFieldValue('boxList', data)
createFormRef.current?.setFieldValue('boxName', 123)
console.log(createFormRef.current?.getFieldValue('boxList'))
setBoxChoiceOpen(false)
}
// 盒子选择重置
const onBoxChoiceReset = () => {
setCheckedKeys([])
setTargetItems([])
}
return (
<div className={componentName}>
{/* 盒子选择弹框 */}
<TreeTransferModal
open={boxChoiceOpen}
onCancel={() => setBoxChoiceOpen(false)}
onRadioChange={(e) => console.log('radio', e.target.value)} // 顶部 radio 事件
dataSource={boxDataSource} // 数据源
targetItems={targetItems} // 右侧选中项
checkedKeys={checkedKeys} // 左侧选中
onReset={onBoxChoiceReset} // 重置按钮事件
onOk={onBoxChoiceOk} // 确定按钮事件
onTreeCheck={onTreeCheck} // 树check选中事件
onItemDelete={onItemDelete} // 右侧点击删除事件
/>
{/* 搜索栏 */}
{showSearchBar && (
<div className={classNames(componentName + '-search')}>
<Input
className={classNames(componentName + '-search-input')}
@ -136,99 +266,64 @@ const BoxPanel: FC<BoxPanelProps> = (props) => {
placeholder='请输入盒子名称'
{...searchInputProps}
/>
{customImport || (
{customFilter || (!noFilter && (
<div
className={classNames(componentName + '-search-btns')}
>
<Button type="text" onClick={() => onBatch?.() || handleCheckable()} icon={isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />} />
<Button type="text" onClick={() => onClockClick?.()} icon={<ClockCircleOutlined />} />
{/* @ts-ignore */}
{initFilter(filterList)}
</div>
))}
</div>
)}
</div>
{/* 是否显示操作按钮 */}
{/* 默认操作按钮 */}
{showOptions && (
<>
<div className={classNames(componentName + '-btns')}>
<Button className={classNames(componentName + '-btns-common')} type='text' onClick={() => onImport?.()} icon={<ImportOutlined />} ></Button>
<Divider className={classNames(componentName + '-btns-divider')} type="vertical" />
{onCreate ?
(
<Button className={classNames(componentName + '-btns-common')} onClick={onCreate} type='text' icon={<FolderAddOutlined />} ></Button>
) : (
<ModalForm<{
name: string
boxList?: any[]
}>
className={classNames(componentName + '-create-modal')}
open={onCreate ? false : undefined}
formRef={createFormRef}
title="新建组"
modalProps={{ destroyOnClose: true }}
layout='horizontal'
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
trigger={<Button type='text' className={classNames(componentName + '-btns-common')} icon={<FolderAddOutlined />} ></Button>}
submitter={{
searchConfig: {
submitText: '确定',
resetText: '取消',
},
}}
onFinish={onCreateSubmit}
>
<ProFormText
rules={[
{
required: true,
max: 20,
},
{
pattern: /^[^\s]*$/g,
message: '禁止输入空格'
}
]}
fieldProps={{ showCount: true }}
width="md"
name="name"
label="盒子组名称"
placeholder="请输入盒子名称"
/>
<ProFormText
width="md"
name="boxList"
label="盒子选择"
fieldProps={{
readOnly: true,
value: `已选择${createFormRef.current?.getFieldValue('boxList')?.length || 0}个盒子`,
suffix: (
<Space>
<a onClick={() => {
createFormRef.current?.setFieldValue('boxList', null)
onBoxChoiceReset()
}} ></a>
<a onClick={() => setBoxChoiceOpen(true)}></a>
</Space>
)
}}
/>
</ModalForm>
)
}
<Divider className={classNames(componentName + '-btns-divider')} type="vertical" />
{/* @ts-ignore */}
<Button className={classNames(componentName + '-btns-common')} danger type='text' icon={<CloseCircleOutlined />} disabled={treeProps?.checkedKeys?.length <= 0} onClick={onBoxBatchDelete} ></Button>
{initOptions(optionList)}
</div>
<Divider style={{ margin: 0 }} />
</>
)}
{extraBtns}
{showTagPanel && (
<div className={classNames(componentName + '-tags')}>
<span
className={classNames(componentName + '-tags-tag')}
style={tagExpandAll ? {
marginBottom: '12px',
display: tagExpandAll ? 'block' : 'inline-block'
} : {}
}
></span>
{initTagPanel(tagList, tagExpandAll)}
<div
className={classNames(
componentName + '-tags-tag_option',
{ [componentName + '-tags-tag_absolute']: tagExpandAll },
)}
>
{tagExpandAll && (
<span
className={classNames(componentName + '-tags-tag_common', componentName + '-tags-tag_fz12')}
onClick={onResetTags}
></span>
)}
<span
className={classNames(componentName + '-tags-tag_common', componentName + '-tags-tag_fz12')}
onClick={onTagExpand}
>{tagExpandAll ? '收起' : '更多'}<IconFont icon={tagExpandAll ? 'icon-shangjiantou' : 'icon-xiajiantou'} /></span>
</div>
{tagFootRender}
</div>
)}
<BoxTree
className={classNames(componentName + '-tree')}
treeCheckable={isTreeCheckable}
data={data}
onItemSelect={onItemSelect}
onItemCheck={onItemCheck}
onItemDelete={onBoxDelete}
checkable={isTreeCheckable}
treeData={data}
blockNode
onSelect={onItemSelect}
onCheck={onItemCheck}
{...treeProps}
/>
</div>

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { Select, TreeProps, Modal, Checkbox } from 'antd';
import { Select, TreeProps, Modal, Checkbox, Button } from '@zhst/meta';
const { Option } = Select
@ -17,7 +17,6 @@ const demo = () => {
}
const onBoxBatchDelete = () => {
console.log('盒子批量删除', checkedKeys)
modal.warning({
content: (
<div>
@ -81,6 +80,7 @@ const demo = () => {
onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data),
checkedKeys,
}}
footer={<Button block ></Button>}
/>
</div>
);

View File

@ -0,0 +1,68 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { FilterOutlined } from '@ant-design/icons';
import { Badge } from 'antd';
const demo = () => {
const [activeKey] = useState('1')
const [checkedKeys] = useState<string[]>([]);
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
filterList={[
{
label: '过滤',
key: 'multi',
icon: <FilterOutlined />,
type: 'dropdown',
showTooltip: false,
dropdownConfig: {
menu: {
// 自定义返回项
_internalRenderMenuItem: (originNode, menuItemProps, stateProps) => {
return (
<div>
{originNode}
</div>
)
},
selectable: true,
items: [
{
label: <p style={{ margin: '0', textAlign: 'center' }} ></p>,
key: 'all',
onClick: () => console.log('多选1')
},
{
icon: <Badge status="success" />,
label: '多选1',
key: 'multi1',
onClick: () => console.log('多选1')
},
{
label: '多选2',
icon: <Badge status='error' />,
key: 'multi2',
},
],
}
}
},
]}
/>
</div>
);
};
export default demo;

View File

@ -0,0 +1,33 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { CloseCircleOutlined } from '@ant-design/icons';
const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
optionList={[
{
label: '删除',
key: 'del',
icon: <CloseCircleOutlined />,
}
]}
/>
</div>
);
};
export default demo;

View File

@ -2,10 +2,12 @@ import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { Button } from 'antd';
import { ClockCircleOutlined, DiffOutlined, SwitcherOutlined } from '@ant-design/icons';
const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
@ -13,10 +15,23 @@ const demo = () => {
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
extraBtns={<Button type="dashed" style={{ color: 'green' }}></Button>}
extraBtns={<div><Button type="dashed" style={{ color: 'red' }}></Button> Hello Lambo</div>}
tabsProps={{
activeKey,
}}
filterList={[
{
label: '多选',
key: 'multi',
icon: isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />,
onClick: () => setIsTreeCheckable(pre => !pre)
},
{
label: '时钟',
key: 'clock',
icon: <ClockCircleOutlined />,
}
]}
treeProps={{
checkedKeys
}}

View File

@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
noFilter
/>
</div>
);
};
export default demo;

View File

@ -30,7 +30,7 @@ const demo = () => {
tabsProps={{
activeKey,
}}
customImport={<Button type="text" icon={<FilterOutlined />} />}
customImport={<div style={{ color: 'red' }}></div>}
searchInputProps={{
addonBefore: (
<Select
@ -43,7 +43,6 @@ const demo = () => {
>
{[
{ value: '1', label: '盒子' },
{ value: '2', label: '盒子组' }
].map(item => (
<Option value={item.value}>{item.label}</Option>
))}

View File

@ -0,0 +1,84 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { Button, Switch } from 'antd';
const demo = () => {
const [activeKey] = useState('1')
const [checkedKeys] = useState<string[]>([]);
const [checkedTags, setCheckedTags] = useState<string[]>([]);
const [tagExpandAll, setTagExpandAll] = useState(false);
const [showTagPanel, setShowTagPanel] = useState(true);
return (
<div>
<div style={{ marginBottom: '12px' }}>
<Switch value={showTagPanel} onChange={status => setShowTagPanel(status)} />
</div>
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
showTagPanel={showTagPanel}
tagExpandAll={tagExpandAll}
onTagExpand={() => {
setTagExpandAll(pre => !pre)
setCheckedTags([])
}}
onTagCheck={(value) => setCheckedTags(pre => {
if (pre.includes(value)) {
return pre.filter(item => item !== value)
} else {
return [...pre, value]
}
})}
onResetTags={() => setCheckedTags([])}
checkedTags={checkedTags}
tagFootRender={<Button danger>dom</Button>}
tagList={[
{
label: '标签组1',
value: '1',
children: [
{
label: '标签1-1',
value: '1-1',
},
{
label: '标签1-2',
value: '1-2',
}
]
},
{
label: '标签组2',
value: '2',
children: [
{
label: '标签2-1',
value: '2-1',
},
{
label: '标签2-2',
value: '2-2',
}
]
},
]
}
/>
</div>
</div>
);
};
export default demo;

View File

@ -15,6 +15,10 @@ group:
<code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/extraBtns.tsx">自定义其它按钮</code>
<code src="./demo/noOptions.tsx">不显示其它按钮</code>
<code src="./demo/customOptions.tsx">自定义配置按钮</code>
<code src="./demo/noFilter.tsx">不显示过滤按钮</code>
<code src="./demo/customFilter.tsx">自定义过滤按钮</code>
<code src="./demo/withTagPanel.tsx">标签看板</code>
<code src="./demo/async.tsx">异步加载数据</code>
## API
@ -22,17 +26,34 @@ group:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| data | 数据源 | Array[] | [] | - |
| tabsProps | Tabs组件的Props | 参考antd的Tabs组件 | - | - |
| searchInputProps | 搜索框的 Props | 参考antd的Input组件 | - | - |
| showOptions | 展示其它功能按钮 | boolean | true | - |
| filterList | 搜索框边上的插槽列表 | boolean | true | - |
| customImport | 自定义搜索栏边上的过滤图标 | ReactNode、string | - | - |
| extraBtns | 搜索栏下面的插槽 | ReactNode、string | - | - |
| prefixCls | class前缀 | string | - | - |
| showSearchBar | 是否显示搜索框 | boolean | true | - |
| noFilter | 是否显示搜索拓展 icon | boolean | false | - |
| filterList | 拓展列表 | IOption[] | [] | - |
| optionList | 操作按钮列表 | IOption[] | [] | - |
| showTagPanel | 是否显示标签看板 | boolean | false | - |
| tagList | 标签列表 | ITag[] | [] | - |
| tagExpandAll | 标签是否展开 | boolean | false | - |
| checkedTags | 选中的标签值 | string[] | [] | - |
| footer | 盒子树底部渲染(需要内容撑开) | ReactNode、string | - | - |
| tagFootRender | 标签看板底部自定义 | ReactNode、string | - | - |
| onResetTags | 标签重置 | () => void | [] | - |
| onTagExpand | 标签展开 | (e: any) => void | [] | - |
| onTabChange | tab切换监听 | function: (e) => void | - | - |
| onTagCheck | 标签选中事件 | (value: string, tag: ITag) => void | false | - |
| onSearch | 搜索监听 | function: (e) => void | - | - |
| onItemSelect | 树当前选中(单选) | function: (e) => void | - | - |
| onItemCheck | 树选择(支持多选) | function: (e) => void | - | - |
| tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - |
| searchInputProps | 搜索框的Props | antd的Input组件 | - | - |
| onTabChange | tab切换监听 | function: (e) => void | - | - |
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | - |
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | - |
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | - |
| onImport | 监听导入盒子点击事件 | function: () => void | - | - |
| onClockClick | 监听时钟点击事件 | function: () => void | - | - |
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | - |
| showOptions | 展示其它功能按钮 | boolean | true | - |
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onBatch | 多选 | function: (e) => void | - | 0.23.0 以后弃用 |
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onImport | 监听导入盒子点击事件 | function: () => void | - | 0.23.0 以后弃用 |
| onClockClick | 监听时钟点击事件 | function: () => void | - | 0.23.0 以后弃用 |
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | 0.23.0 以后弃用 |

View File

@ -1,14 +1,23 @@
export { default as BigImageModal } from './BigImageModal'
export type { BigImageModalProps } from './BigImageModal'
export { default as BigImage } from './BigImage'
export type { BigImageProps, BigImageRef } from './BigImage'
export { default as BoxSelectTree } from './boxSelectTree'
export type { BoxSelectTreeProps } from './boxSelectTree'
export { default as Tree } from './tree'
export type { BoxTreeProps, TreeData } from './tree'
export { default as TreePanel } from './treePanel'
export type { TreePanelProps } from './treePanel'
export { default as TreeTransfer } from './treeTransfer'
export type { TreeTransferProps } from './treeTransfer'
export { default as TreeTransferModal } from './treeTransferModal'
export type { TreeTransferModalProps } from './treeTransferModal'
export { default as WarningRecordCard } from './WarningRecordCard'
export { CommonCard } from './CustomCard'
export type {
CommonCardProps,
CommonCardRefProps
} from './CustomCard'
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard'
export { default as OdModal } from './odModal'
export type { ODModalProps } from './odModal'

View File

@ -2,24 +2,29 @@
* Created by jiangzhixiong
*/
import React, { forwardRef, ReactNode, useContext, useImperativeHandle, useRef } from 'react'
import { ConfigProvider } from '@zhst/meta';
import { Divider, Flex } from 'antd';
import React, { forwardRef, ReactNode, useContext, useEffect, useImperativeHandle, useRef } from 'react'
import { ConfigProvider, Spin, SpinProps } from '@zhst/meta';
import classNames from 'classnames';
import InfiniteScroll from 'react-infinite-scroll-component';
import { SearchCard, SearchCardProps } from './components';
import { useSize } from '@zhst/hooks';
import './index.less'
import { Idata } from './components/SearchCard';
const { ConfigContext } = ConfigProvider
export interface InfiniteListProps {
export interface IData {
key: string;
title: string;
index?: number;
[k: string]: any;
}
export interface InfiniteListProps extends React.HtmlHTMLAttributes<HTMLDivElement> {
type?: 'custom' | 'auto'
prefixCls?: string;
height?: number;
itemRender?: (data?: any) => React.ReactNode
itemRender?: (data?: IData, index?: number) => React.ReactNode
loading?: boolean; //
data: Idata[];
data: IData[];
targetId?: string; // 滚动列表 ID
loadMore?: (data?: any) => any;
params?: {
@ -28,63 +33,66 @@ export interface InfiniteListProps {
hasMore: boolean;
endMessage?: ReactNode
loadingMessage?: ReactNode
onItemClick?: (data: any) => void;
searchCardProps?: SearchCardProps
loadingProps?: SpinProps
}
export interface InfiniteListRefProps {
scrollViewSize?: { width: number; height: number }
listSize?: { width: number; height: number }
}
const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
height,
height = 600,
loading,
type = 'auto',
loadingMessage = <p style={{ textAlign: 'center' }}>...</p>,
targetId = 'scrollableDiv',
itemRender,
itemRender = (data) => <div>{data?.title}</div>,
hasMore,
onItemClick,
loadMore,
data = [],
endMessage = <Divider plain>...🤐</Divider>,
searchCardProps
endMessage = <div style={{ textAlign: 'center' }} >...🤐</div>,
style,
loadingProps,
className
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-infinite-list', customizePrefixCls);
const listRef = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const scrollViewSize = useSize(listRef.current) || { width: 0, height: 0 }; // 无限滚动视窗大小
// @ts-ignore
const listSize = useSize(scrollRef.current?._infScroll) || { width: 0, height: 0 } // 无限滚动列表大小
useEffect(() => {
// 当数据不够一屏时继续加载
if (listSize.height < scrollViewSize.height) {
loadMore?.()
}
}, [listSize.height])
useImperativeHandle(ref, () => ({
scrollViewSize,
listSize
}))
return (
<Spin spinning={loading} {...loadingProps}>
<div
id={targetId}
className={classNames(componentName)}
className={classNames(componentName, className)}
ref={listRef}
style={{
height,
overflow: 'auto',
padding: 12
...style
}}
>
{/* {loading ? (
<p>...</p>
) : (
<Flex wrap='wrap' gap="small" className={classNames(componentName + 'items')}>
{data?.list?.map((item) => (
itemRender?.(item) || (
<div className={classNames(componentName + 'items-item')}>
<SearchCard data={item} />
</div>
)
))}
</Flex>
)} */}
<InfiniteScroll
// @ts-ignore
ref={scrollRef}
dataLength={data.length}
next={type === 'auto' ? loadMore! : () => {}}
hasMore={hasMore}
@ -92,37 +100,12 @@ const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props,
endMessage={endMessage}
scrollableTarget={targetId}
>
<Flex wrap='wrap' gap="small" className={classNames(componentName + 'items')}>
{data?.map((item, idx) => (
itemRender?.(item) || (
<div
key={idx}
className={classNames(componentName + 'items-item')}
onClick={() => {
onItemClick?.(item)
}}
>
<SearchCard
id={idx + 1}
data={item}
width="184px"
{...searchCardProps}
/>
</div>
)
itemRender?.({ ...item, index: idx})
))}
</Flex>
</InfiniteScroll>
{/* <div style={{ marginTop: 8 }}>
{!noMore && (
<Button onClick={loadMore} disabled={loadingMore}>
{loadingMore ? '加载中...' : '点击加载更多'}
</Button>
)}
{noMore && <span></span>}
</div> */}
</div>
</Spin>
)
})

View File

@ -1,6 +0,0 @@
/**
* Created by jiangzhixiong on 2024/04/28
*/
export { default as SearchCard } from './SearchCard'
export type { SearchCardProps, SearchCardRefProps } from './SearchCard'

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
import { InfiniteList } from '@zhst/biz'
import { InfiniteList, CommonCard } from '@zhst/biz'
import { uniqueId } from '@zhst/func'
export default () => {
const [data, setData] = useState([])
@ -13,8 +14,10 @@ export default () => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json())
.then((body) => {
let res = body.results.map(o => {
let res = body.results.map((o, index) => {
return {
id: uniqueId(),
sort: index + 1,
title: o.name.first,
subtitle: o.name.last,
url: o.picture.large
@ -36,14 +39,23 @@ export default () => {
<InfiniteList
loading={loading}
loadMore={loadMoreData}
height={300}
hasMore={data.length < 100}
height={1200}
hasMore={data.length < 60}
data={data}
onItemClick={_data => console.log('item点击', _data)}
searchCardProps={{
onAdd: (_data) => console.log('新增', _data),
onCreate: (_data) => console.log('创建', _data),
onRemove: (_data) => console.log('删除', _data),
itemRender={(item) => {
return (
<CommonCard
key={item.id}
sort={item.sort}
data={item}
width="184px"
actions={[
<a></a>,
<a></a>,
<a></a>
]}
/>
)
}}
/>
)

View File

@ -3,11 +3,11 @@ import { InfiniteList } from '@zhst/biz'
import { Button, Input, Space } from 'antd'
export default () => {
const [data, setData] = useState([])
const [data, setData] = useState<any>([])
const [loading, setLoading] = useState(false)
const [params, setParams] = useState({})
const loadMoreData = (params?: { name: string; age?: number; sex: string; tel: number }) => {
const loadMoreData = (params?: any) => {
if (loading) {
return;
}
@ -15,7 +15,7 @@ export default () => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json())
.then((body) => {
let res = body.results.map(o => {
let res = body.results.map((o: { name: { first: any; last: any }; picture: { large: any } }) => {
return {
title: o.name.first,
subtitle: o.name.last,
@ -35,7 +35,7 @@ export default () => {
}, []);
return (
<Space direction='vertical'>
<Space direction='vertical' size={10} style={{ padding: '12px', border: '1px solid #ccc' }}>
<Space>
<Input placeholder='名称' onChange={(e) => setParams(pre => ({ ...pre, name: e.target.value }))} style={{ width: '120px' }} />
<Input placeholder='年龄' onChange={(e) => setParams(pre => ({ ...pre, age: e.target.value }))} style={{ width: '120px' }} />
@ -49,9 +49,7 @@ export default () => {
height={300}
hasMore={data.length < 100}
data={data}
type="custom"
loadingMessage={<Button onClick={() => loadMoreData(params)}></Button>}
onItemClick={data => console.log('item点击', data)}
/>
</Space>
)

View File

@ -18,21 +18,20 @@ group:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| data | 数据源 | Idata[] | [] | - |
| data | 数据源 | IData[] | [] | - |
| height | 无限滚动列表可视区高度 | number | 600 | - |
| loading | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| dataLength | 数据数量 | number | [] | - |
| next | 下一页方法 | function | () => {} | - |
| hasMore | 是否还有更多 | boolean | false | - |
| loadingProps | 参考 antd-spin | spinProps | [] | - |
| itemRender | 自定义渲染项 | (IData) => ReactNode | - | - |
## Idata
## 设计思路
```js
interface Idata {
id?: string | number;
url?: string; // 链接
title?: string; // 标题
subtitle?: string; // 副标题
}
```
无限滚动,同时支持:
1. 自动、主动加载更多
2. 一屏没加载完,继续加载,直到填满屏幕:
- 需要第二次加载的内容是否为空,为空则停止加载
- 通过整体的page-height 和 浏览器可视区域

View File

@ -1,6 +1,5 @@
import React, { forwardRef, useContext, useImperativeHandle, useRef } from 'react';
import { Button, Modal, ModalProps, Select, SelectProps, Space, theme } from 'antd';
import { ConfigProvider, CropperImage, Scanner, CropperImageProps, CropperImageRefProps } from '@zhst/meta'
import { ConfigProvider, CropperImage, Scanner, CropperImageProps, CropperImageRefProps, Button, Modal, ModalProps, Select, SelectProps, Space, theme } from '@zhst/meta'
import { IconFont } from '@zhst/icon'
import classNames from 'classnames';
import './index.less'

View File

@ -1,7 +1,8 @@
// !! 已经弃用改为使用meta/tree
import React, { FC, useState } from 'react';
import { Tree, Badge, TreeDataNode, Space, TreeProps, theme } from 'antd';
import { Tree, Badge, DataNode as TreeDataNode, Space, TreeProps, theme } from '@zhst/meta'
import { CloseOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons'
import { ModalForm, ProFormText } from '@ant-design/pro-components';
import classNames from 'classnames';
import './index.less'
const componentName = 'zhst-biz-tree'
@ -30,7 +31,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
showItemOption = true,
treeCheckable = false,
onItemRename,
onItemRenameFinish,
className: customClassName,
customOptions
} = props
const { token } = useToken()
@ -45,6 +46,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
return (
<Tree
className={classNames(componentName, customClassName)}
checkable={treeCheckable}
blockNode
onSelect={(selectedKeys, info) => {
@ -70,38 +72,11 @@ const boxTree: FC<BoxTreeProps> = (props) => {
<Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
{customOptions || (
<>
<ModalForm
title="重命名"
width={600}
modalProps={{ destroyOnClose: true }}
layout='horizontal'
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
trigger={<EditOutlined onClick={(e) => {
<EditOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onItemRename?.(_nodeData)
}} />}
submitter={{
searchConfig: {
submitText: '确定',
resetText: '取消',
},
}}
onFinish={async (value) => onItemRenameFinish?.(value, _nodeData)}
>
<ProFormText
rules={[
{
required: true,
},
]}
width="md"
name="name"
label="盒子名称"
placeholder="请输入盒子名称"
/>
</ModalForm>
}} />
<SettingOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();

View File

@ -1,4 +1,5 @@
.zhst-biz-tree-item-render {
.zhst-biz-tree {
&-item-render {
&_right {
display: none;
}
@ -7,3 +8,4 @@
display: inline-flex;
}
}
}

View File

@ -1,6 +1,6 @@
---
category: Components
title: Tree 树
title: Tree 树 [废弃]
toc: content
demo:
cols: 2
@ -10,6 +10,11 @@ group:
---
:::warning{title=0.25.1之后版本已废弃}
组件迁移到 @zhst/meta
:::
## 代码演示
<code src="./demo/basic.tsx">基本用法</code>

View File

@ -1,8 +1,9 @@
import { TreeDataNode } from 'antd';
// !! 该组件已被废弃
import { DataNode } from '@zhst/meta';
import BoxTree from './boxTree';
export interface TreeData extends TreeDataNode {
children?: TreeDataNode['children'] & {
export interface TreeData extends DataNode {
children?: DataNode['children'] & {
isCamera?: boolean
/**
* 0- 1- 2- 3-

View File

@ -0,0 +1,269 @@
import React, { FC, useContext, ReactNode } from 'react';
import {
ConfigProvider,
Input,
Dropdown,
Tooltip,
Button,
DataNode as TreeDataNode,
Tree as BoxTree,
TreeProps as BoxTreeProps,
TreeProps,
InputProps,
DropDownProps,
SelectProps,
Select
} from '@zhst/meta';
import { IconFont } from '@zhst/icon';
import classNames from 'classnames';
import './index.less'
import { ButtonProps } from 'antd';
export interface IOption {
type?: ButtonProps['type'] & 'dropdown';
label: string
key: string
icon?: string | ReactNode
disabled?: boolean;
showTooltip?: boolean;
onClick?: () => void
className?: string;
dropdownConfig?: DropDownProps
}
export interface ITag {
label: string
value: string
icon?: ReactNode
parentNode?: string
children?: ITag[]
}
export interface TreePanelProps {
treeType?: 'directory' | 'normal'
searchInputProps?: InputProps
showOptions?: boolean
showSelectBar?: boolean // 显示搜索框
filterSelectProps?: SelectProps
onSelect?: SelectProps['onChange']
treeProps?: Partial<BoxTreeProps>
data: TreeDataNode[]
onSearch?: (e: any) => void
onItemCheck?: TreeProps['onCheck']
onItemSelect?: TreeProps['onSelect']
customImport?: ReactNode | string // 自定义搜索栏边上的过滤图标
extra?: ReactNode | string // 搜索栏下面的插槽
prefixCls?: string
showSearchBar?: boolean // 是否显示搜索栏
noFilter?: boolean // 是否显示搜索拓展 icon
filterList?: IOption[]
optionList?: IOption[]
showTagPanel?: boolean // 标签插槽
tagList?: ITag[] // 标签列表
tagExpandAll?: boolean // 展开所有
onTagCheck?: (value: string, tag: ITag) => void;
checkedTags?: string[]
onResetTags?: () => void
onTagExpand?: (e: any) => void
tagFootRender?: ReactNode
}
const { ConfigContext } = ConfigProvider
const { DirectoryTree } = BoxTree
const TreePanel: FC<TreePanelProps> = (props) => {
const {
treeType = 'directory',
searchInputProps,
showOptions = true,
showSelectBar,
filterSelectProps,
extra,
noFilter,
data = [],
treeProps,
onSelect,
onSearch,
onItemCheck,
onItemSelect,
showSearchBar = true,
optionList = [],
filterList = [],
showTagPanel,
tagList,
tagExpandAll,
onTagExpand,
checkedTags = [],
onTagCheck,
onResetTags,
tagFootRender,
prefixCls: customizePrefixCls,
customImport: customFilter
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-tree-panel', customizePrefixCls);
const CurrentTree = treeType === 'directory' ? DirectoryTree : BoxTree
/**
* filter
* @param _list
* @returns
*/
const initFilter = (_list?: TreePanelProps['filterList']) => {
const WithDropdown = (dom: ReactNode, isShow?: boolean, _config?: DropDownProps) => {
if (!isShow) {
return dom
}
return (
<Dropdown placement="bottomLeft" arrow {..._config}>
{dom}
</Dropdown>
)
}
return _list?.map(item => (
<Tooltip
title={item.label}
open={item.showTooltip}
>
{WithDropdown(
<Button className={classNames(componentName + '-search-btns-btn')} type={item.type} onClick={item.onClick} icon={item.icon} {...item} />,
item.type === 'dropdown',
item.dropdownConfig
)}
</Tooltip>
))
}
/**
* filter
* @param _list
* @returns
*/
const initOptions = (_list?: TreePanelProps['optionList']) => {
return _list?.map((item, idx) => (
<>
{/* @ts-ignore */}
<div key={idx} className={classNames(componentName + '-btns-btn')}>
{item.icon}
<span onClick={item.onClick} className={classNames(componentName + '-btns-btn-label', item.className)}>{item.label}</span>
</div>
{idx % 2 !== 0 && (<br/>)}
</>
))
}
/**
*
* @param _tagList
* @param sort
* @returns
*/
const initTagPanel = (_tagList: TreePanelProps['tagList'], sort?: boolean) => {
// 正常标签渲染
const commonTag = (_tagProps: ITag) => (
<span
className={classNames(
componentName + '-tags-tag_common',
{[componentName + '-tags-tag_checked']: checkedTags.includes(_tagProps.value)}
)}
key={_tagProps.value}
onClick={() => onTagCheck?.(_tagProps.value, _tagProps)}
>{_tagProps.label}</span>
)
// 包装父级标签
const _withFather = (tag: ITag) => (
<div key={tag.value}>
<span className={classNames(componentName + '-tags-tag')}>{tag.label}</span>
{tag.children?.map?.(_tag => commonTag(_tag))}
</div>
)
return _tagList?.map(tag => {
if (tag.children?.length && sort) {
return _withFather(tag)
} else {
return commonTag(tag)
}
})
}
return (
<div className={componentName}>
{/* 搜索栏 */}
{showSearchBar && (
<div className={classNames(componentName + '-search')}>
<Input
className={classNames(componentName + '-search-input')}
onChange={(e) => onSearch?.(e)}
placeholder='请输入盒子名称'
{...searchInputProps}
/>
</div>
)}
{/* 搜索栏 */}
{showSelectBar && (
<div className={classNames(componentName + '-search')}>
<Select
className={classNames(componentName + '-search-input')}
onChange={onSelect}
{...filterSelectProps}
/>
{customFilter || (!noFilter && (
<div
className={classNames(componentName + '-search-btns')}
>
{/* @ts-ignore */}
{initFilter(filterList)}
</div>
))}
</div>
)}
{showTagPanel && (
<div className={classNames(componentName + '-tags')}>
<div className={classNames(componentName + '-tags-title')}>
<div
className={classNames(
componentName + '-tags-tag_option',
)}
>
{tagExpandAll && (
<span
className={classNames(componentName + '-tags-tag_option-btn')}
onClick={onResetTags}
></span>
)}
<span
className={classNames(componentName + '-tags-tag_option-btn')}
onClick={onTagExpand}
>{tagExpandAll ? '收起' : '展开'}<IconFont icon={tagExpandAll ? 'icon-shangjiantou' : 'icon-xiajiantou'} /></span>
</div>
</div>
{initTagPanel(tagList, tagExpandAll)}
{tagFootRender}
</div>
)}
{/* 默认操作按钮 */}
{showOptions && (
<div className={classNames(componentName + '-btns')}>
{initOptions(optionList)}
</div>
)}
{extra}
<CurrentTree
className={classNames(componentName + '-tree')}
treeData={data}
showIcon={false}
blockNode
onSelect={onItemSelect}
onCheck={onItemCheck}
{...treeProps}
/>
</div>
)
}
export default TreePanel

View File

@ -0,0 +1,90 @@
import React, {} from 'react';
import { TreePanel } from '@zhst/biz';
import { Badge, Checkbox } from '@zhst/meta'
import { boxDataSource } from './mock'
import { ImportOutlined, FolderAddOutlined, CloseCircleOutlined, FilterOutlined } from '@ant-design/icons';
const demo = () => {
return (
<div style={{ padding: '12px', width: '240px', border: '1px solid #09f' }}>
<TreePanel
data={boxDataSource}
showSelectBar
filterList={[
{
label: '过滤',
key: 'multi',
icon: <FilterOutlined />,
type: 'dropdown',
showTooltip: false,
dropdownConfig: {
menu: {
// 自定义返回项
_internalRenderMenuItem: (originNode, menuItemProps, stateProps) => {
return (
<div>
{originNode}
</div>
)
},
selectable: true,
items: [
{
label: <p style={{ margin: '0', textAlign: 'center' }} ></p>,
key: 'all',
onClick: () => console.log('多选1')
},
{
icon: <Badge status="success" />,
label: '多选1',
key: 'multi1',
onClick: () => console.log('多选1')
},
{
label: '多选2',
icon: <Badge status='error' />,
key: 'multi2',
},
],
}
}
},
{
label: '过滤',
key: 'ee',
icon: <FilterOutlined />,
showTooltip: false,
},
]}
extra={(
<div style={{ border: '1px solid red' }}>
<span><Checkbox></Checkbox></span>
<a style={{ float: 'right', color: '#09f' }} ></a>
</div>
)}
optionList={[
{
label: '导入盒子',
key: 'import',
icon: <ImportOutlined />,
},
{
label: '新建组',
key: 'add',
icon: <FolderAddOutlined />,
},
{
label: '删除',
key: 'del',
icon: <CloseCircleOutlined />,
props: {
danger: true
}
},
]}
/>
</div>
);
};
export default demo;

View File

@ -0,0 +1,54 @@
import { TreeData } from "@zhst/biz";
export const boxDataSource: TreeData[] = [
{
title: '全部盒子',
key: '0-0',
children: [
{
title: '盒子组1',
key: '0-0-0',
children: [
{
title: '摄像头1',
key: '0-0-0-0',
isCamera: true
},
{
title: '摄像头2',
key: '0-0-0-1',
isCamera: true
},
],
},
{
title: '盒子组2',
key: '0-0-1',
children: [
{
title: '摄像头4',
key: '0-0-1-0',
isCamera: true
}
],
},
],
},
];
export const treeData: TreeData[] = [
{ key: '0-1-0', title: '分组0-1-0', isLeaf: true, checkable: false },
{ key: '0-1-1', title: '分组0-1-1', isLeaf: true, checkable: false },
{ key: '0-1-2', title: '分组0-1-2', isLeaf: true, checkable: false },
{
key: '0-1-3',
title: '分组0-1-3',
isLeaf: false,
children: [
{ key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true, isCamera: true },
{ key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true, isCamera: true },
{ key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true, isCamera: true },
],
},
];

View File

@ -0,0 +1,24 @@
import React, { useState, useRef } from 'react';
import { TreePanel } from '@zhst/biz';
import { boxDataSource } from './mock'
const demo = () => {
return (
<div style={{ padding: '12px', width: '240px', border: '1px solid #09f' }}>
<TreePanel
data={boxDataSource}
treeType='normal'
showSelectBar
filterSelectProps={{
placeholder: '请输入',
options: [
{ label: '测试', key: '123', value: 'test' }
]
}}
/>
</div>
);
};
export default demo;

View File

@ -0,0 +1,135 @@
import React, { useState, useRef } from 'react';
import { TreePanel } from '@zhst/biz';
import { Badge, Checkbox } from '@zhst/meta'
import { treeData, boxDataSource } from './mock'
import { ImportOutlined, FolderAddOutlined, CloseCircleOutlined, FilterOutlined } from '@ant-design/icons';
const demo = () => {
const [checkedTags, setCheckedTags] = useState<string[]>([]);
const [tagExpandAll, setTagExpandAll] = useState(false);
const [showTagPanel, setShowTagPanel] = useState(true);
return (
<div style={{ padding: '12px', width: '240px', border: '1px solid #09f' }}>
<TreePanel
data={boxDataSource}
showTagPanel={showTagPanel}
tagExpandAll={tagExpandAll}
showSelectBar
onTagCheck={(value) => setCheckedTags(pre => {
if (pre.includes(value)) {
return pre.filter(item => item !== value)
} else {
return [...pre, value]
}
})}
onResetTags={() => setCheckedTags([])}
checkedTags={checkedTags}
filterList={[
{
label: '过滤',
key: 'multi',
icon: <FilterOutlined />,
type: 'dropdown',
showTooltip: false,
dropdownConfig: {
menu: {
// 自定义返回项
_internalRenderMenuItem: (originNode, menuItemProps, stateProps) => {
return (
<div>
{originNode}
</div>
)
},
selectable: true,
items: [
{
label: <p style={{ margin: '0', textAlign: 'center' }} ></p>,
key: 'all',
onClick: () => console.log('多选1')
},
{
icon: <Badge status="success" />,
label: '多选1',
key: 'multi1',
onClick: () => console.log('多选1')
},
{
label: '多选2',
icon: <Badge status='error' />,
key: 'multi2',
},
],
}
}
},
]}
extra={(
<div>
<span><Checkbox></Checkbox></span>
<a style={{ float: 'right', color: '#09f' }} ></a>
</div>
)}
optionList={[
{
label: '导入盒子',
key: 'import',
icon: <ImportOutlined />,
},
{
label: '新建组',
key: 'add',
icon: <FolderAddOutlined />,
},
{
label: '删除',
key: 'del',
icon: <CloseCircleOutlined />,
props: {
danger: true
}
},
]}
tagList={[
{
label: '标签组1',
value: '1',
children: [
{
label: '标签1-1',
value: '1-1',
},
{
label: '标签1-2',
value: '1-2',
}
]
},
{
label: '标签组2',
value: '2',
children: [
{
label: '标签2-1',
value: '2-1',
},
{
label: '标签2-2',
value: '2-2',
}
]
},
]
}
onTagExpand={() => {
setTagExpandAll(pre => !pre)
setCheckedTags([])
}}
/>
</div>
);
};
export default demo;

View File

@ -0,0 +1,131 @@
.zhst-biz-tree-panel {
&-search {
margin-bottom: 16px;
display: flex;
align-items: center;
&-input {
flex: 1;
}
&-btns {
flex: none;
&-btn {
margin-left: 8px;
}
}
}
&-btns {
&-btn {
margin-right: 16px;
margin-bottom: 12px;
display: inline-block;
cursor: pointer;
font-size: 14px;
transition: .3s ease all;
&:hover {
opacity: 0.7;
}
&-label {
margin-left: 4px;
}
}
&-common {
flex: 1;
padding: 4px 8px;
}
&-import {
padding: 4px 8px;
}
}
&-tags {
margin-bottom: 12px;
position: relative;
height: 100%;
padding: 12px;
font-size: 0;
border-radius: 4px;
background-color: #f7f7f7;
&-title {
font-size: 12px;
color: #595959;
line-height: 20px;
font-weight: 500;
&::after {
position: absolute;
content: '';
clear: both;
}
}
&-tag {
margin-right: 8px;
font-weight: bold;
color: #595959;
font-size: 14px;
transition:.3s ease all;
&_common {
margin-top: 6px;
display: inline-block;
margin-right: 8px;
padding: 2px 8px;
font-size: 12px;
cursor: pointer;
color: #191919;
background-color: #EBEBEB;
transition: .3s ease all;
border-radius: 3px;
box-sizing: content-box;
&:hover {
color: #fff;
background-color: #09f;
opacity: 0.7;
}
}
&_checked {
color: #fff;
background-color: #09f;
}
&_fz12 {
font-size: 12px;
}
&_option {
float: right;
&-btn {
margin-left: 12px;
color: #09f;
cursor: pointer;
transition: .3s ease all;
&:hover {
opacity: 0.7;
}
}
&::after {
position: absolute;
content: '';
clear: both;
}
}
&_absolute {
position: absolute;
top: 12px;
right: 0;
}
}
}
&-tree {
padding: 8px 0;
}
}

View File

@ -0,0 +1,82 @@
---
category: Components
title: TreePanel 树面板
toc: content
demo:
cols: 2
group:
title: 数据展示
---
## 代码演示
<code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/normal.tsx">普通树</code>
<code src="./demo/withTags.tsx">标签面板</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| treeType | 树的类型 | 'directory' 'normal' | directory | --- |
| searchInputProps | antd-inputProps | --- | --- | --- |
| showOptions | --- | boolean | --- | --- |
| treeProps | --- | antd-treeProps | --- | --- |
| data | --- | TreeDataNode[] | [] | --- |
| onSearch | --- | (e: any) => void | - | --- |
| onItemCheck | --- | TreeProps['onCheck'] | - | --- |
| onItemSelect | --- | TreeProps['onSelect'] | - | --- |
| customImport | 自定义搜索栏边上的过滤图标 | ReactNode 、string | - | --- |
| extra | 数组件上方插槽 | ReactNode 、string | --- | --- |
| prefixCls | class前缀用于覆盖class | string | --- | --- |
| showSelectBar | 显示搜索框 | boolean | false | --- |
| filterSelectProps | 搜索框 | antd-SelectProps | - | --- |
| showSearchBar | 显示搜索框 | boolean | false | --- |
| noFilter | 是否显示搜索拓展 | boolean | false | --- |
| filterList | 过滤插槽列表 | IOption[] | [] | --- |
| optionList | 操作按钮列表 | IOption[] | [] | --- |
| showTagPanel | 显示标签插槽 | boolean | false | --- |
| tagList | 标签列表 | ITag[] | [] | --- |
| onSelect | 搜索选中事件 | SelectProps['onChange'] | - | --- |
| tagExpandAll | 标签展开状态 | boolean | false | --- |
| onTagCheck | 标签点击事件 | (value: string, tag: ITag) => void; | false | --- |
| checkedTags | 标签选中状态 | string[] | [] | --- |
| onResetTags | 重置标签事件 | () => void | - | --- |
| onTagExpand | 标签展开事件 | (e: any) => void | - | --- |
| tagFootRender | 标签展开状态 | ReactNode string | false | --- |
### IOption
```ts
interface IOption {
label: string
key: string
icon?: string | ReactNode
disabled?: boolean;
showTooltip?: boolean;
onClick?: () => void
className?: string;
dropdownConfig?: DropDownProps
}
```
### ITag
```ts
interface ITag {
label: string
value: string
icon?: ReactNode
parentNode?: string
children?: ITag[]
}
```
## 组件设计
该组件包含以下功能:
1. 顶部按钮支持
2. 输入框单行展示
3. 选择框和筛选框同一行
4. 按钮列表

View File

@ -0,0 +1,7 @@
/**
* Created by jiangzhixiong on 2024/06/04
*/
import TreePanel from './TreePanel'
export type { TreePanelProps } from './TreePanel'
export default TreePanel

View File

@ -1,91 +1,245 @@
import React, { useState } from 'react';
import { Button, Card, ConfigProvider, theme, Flex, Input, InputProps, TransferProps, TreeDataNode, TreeProps, Tree } from 'antd';
import React, { ReactNode, useContext } from 'react';
import { Button, Input, ConfigProvider, theme, Flex, InputProps, TabsProps, Tabs, ButtonProps, Tree, TreeProps, DataNode as TreeDataNode } from '@zhst/meta'
import { IconFont } from '@zhst/icon'
import './index.less'
import { DeleteOutlined, DoubleRightOutlined, SearchOutlined } from '@ant-design/icons';
const componentName = 'zhst-biz-treeTransfer'
const { ConfigContext } = ConfigProvider
export interface TreeTransferProps {
customizePrefixCls?: string
/**
* @description
* @array []
*/
titles?: string[] | ReactNode[]
/**
* @description
*/
bordered?: boolean
/**
* @description
* antd-treeData
*/
dataSource: TreeDataNode[]
/**
* @description
*/
treeProps?: TreeProps
/**
*
*/
showLeftSearch?: boolean
/**
* @description
*/
searchInputProps?: InputProps
/**
* @description
*/
targetItems: TreeDataNode[];
/**
* @description id
*/
checkedKeys: string[];
/**
* @description
*/
showFilter?: boolean;
/**
* @description
*/
showLeftPanelFooter?: boolean;
/**
* @description
*/
leftPanelFooterRender?: ReactNode | string;
/**
* @description
*/
showRightPanelFooter?: boolean;
/**
*
* @param events
* @returns
*/
rightPanelFooterRender?: (events: { onOk: TreeTransferProps['onOk'], onReset: TreeTransferProps['onReset'] }) => ReactNode | string;
/**
* @description
*/
leftPanelWidth?: string | number;
/**
* @description
*/
rightPanelWidth?: string | number;
/**
* @description
*/
leftPanelScrollY?: number;
/**
* @description
*/
middleIcon?: ReactNode | string
rightPanelScrollY?: number;
tabsItems?: TabsProps['items']
showLeftTabs?: boolean
activeTabKey?: string
treeBackgroundColor?: string
leftTabsProps?: Partial<TabsProps>
customLeftPanelContent?: (data?: TreeDataNode[]) => ReactNode
filters?: (ButtonProps & {
label?: string;
icon?: ReactNode;
})[]
onLeftTabsChange?: TabsProps['onChange']
onTreeSelect?: TreeProps['onSelect']
onTreeCheck?: TreeProps['onCheck']
onItemDelete?: (key: string, info?: TreeDataNode) => void
onChange?: TransferProps['onChange'];
onOk?: (data: any) => void;
onReset?: () => void;
onSearch?: InputProps['onChange']
}
const { useToken } = theme
const TreeTransfer: React.FC<TreeTransferProps> = ({
const TreeTransfer: React.FC<TreeTransferProps> = (props) => {
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-treeTransfer', props.customizePrefixCls);
const {
dataSource,
treeProps,
titles = ['可选择的范围', '已选择的范围'],
bordered,
searchInputProps,
showLeftSearch = true,
leftPanelScrollY = 300,
leftPanelWidth = 500,
rightPanelScrollY = 422,
rightPanelWidth = 300,
targetItems = [],
checkedKeys = [],
showFilter = false,
showLeftPanelFooter,
showRightPanelFooter = true,
customLeftPanelContent,
leftPanelFooterRender,
rightPanelFooterRender,
showLeftTabs,
leftTabsProps,
middleIcon,
activeTabKey,
tabsItems = [
{
key: 'camera',
label: <div className={`${componentName}-left_card_tabs_tab`} style={{ textAlign:'center' }} ></div>,
},
{
key: 'plan',
label: <div className={`${componentName}-left_card_tabs_tab`} style={{ textAlign:'center' }} ></div>,
},
{
key: 'map',
label: <div className={`${componentName}-left_card_tabs_tab`} style={{ textAlign:'center' }} ></div>,
},
],
filters = [],
onLeftTabsChange,
onTreeCheck,
onTreeSelect,
onItemDelete,
onSearch,
onOk,
onReset
}) => {
} = props
const { token } = useToken()
const [keyWords, setKeyWords ] = useState('')
function findNodesWithKeyword(_keyWords: string, _treeData: TreeDataNode[]) {
// @ts-ignore
function dfs(node: any) {
return node.filter((item: { title: string | string[]; }) => item.title.includes(_keyWords))
}
const data = dfs(_treeData)
return data || [];
}
return (
<Flex gap={20} className={componentName} align='center' justify='center'>
<div className={`${componentName}-left`}>
<Card
className={`${componentName}-left_card`}
title={<div style={{ textAlign: 'center' }} ></div>}
>
<Input prefix={<SearchOutlined />} onChange={e => setKeyWords(e.target.value)} placeholder='请输入设备名称' {...searchInputProps} />
<ConfigProvider
theme={{
components: {
Tree: {
colorBgContainer: '#FCFCFC'
}
}
<div className={componentName}>
<div className={`${componentName}-left`}
style={{
width: typeof leftPanelWidth === 'number' ? leftPanelWidth + 'px' : leftPanelWidth,
border: bordered ? `1px solid ${token.colorBorder}` : 'unset',
}}
>
<Tree
className={`${componentName}-left_card-tree`}
height={420}
blockNode
checkable
checkedKeys={checkedKeys}
treeData={findNodesWithKeyword(keyWords, dataSource)}
onCheck={(keys, info) => onTreeCheck?.(keys, info)}
onSelect={(keys, info) => onTreeSelect?.(keys, info)}
{...treeProps}
/>
</ConfigProvider>
</Card>
</div>
<DoubleRightOutlined/>
<div className={`${componentName}-right`}>
<Card
className={`${componentName}-right_card`}
title={<div style={{ textAlign: 'center' }}></div>}
<div
className={`${componentName}-left_card`}
style={{ backgroundColor: token.colorBgContainer }}
>
<div
className={`${componentName}-left_card_title`}
style={{ backgroundColor: '#f7f7f7' }}
>
{titles?.[0]}
</div>
{showLeftTabs && (
<Tabs
activeKey={activeTabKey}
items={tabsItems}
onChange={onLeftTabsChange}
{...leftTabsProps}
/>
)}
{showLeftSearch && (
<div
className={`${componentName}-left_card_search`}
>
<Input
className={`${componentName}-left_card_search_input`}
prefix={<IconFont icon="icon-sousuo" />}
placeholder='请输入设备名称'
onChange={onSearch}
{...searchInputProps}
/>
{showFilter && (
<div
className={`${componentName}-left_card_search_filters`}
>
{filters?.map(item => (
<Button icon={item.icon} {...item} >{item.label}</Button>
))}
</div>
)}
</div>
)}
{customLeftPanelContent?.(dataSource) || (
<Tree
className={`${componentName}-left_card-tree`}
blockNode
checkable
height={leftPanelScrollY}
checkedKeys={checkedKeys}
treeData={dataSource}
onCheck={(keys: any, info: any) => onTreeCheck?.(keys, info)}
onSelect={(keys: any, info: any) => onTreeSelect?.(keys, info)}
{...treeProps}
/>
)}
{showLeftPanelFooter && (
<div className={`${componentName}-left_card-footer`}>
{leftPanelFooterRender}
</div>
)}
</div>
</div>
<div className={`${componentName}-middle`}>
{middleIcon || <IconFont icon="icon-zhankai" />}
</div>
<div className={`${componentName}-right`}
style={{
width: typeof rightPanelWidth === 'number' ? rightPanelWidth + 'px' : rightPanelWidth,
border: bordered ? `1px solid ${token.colorBorder}` : 'unset',
}}
>
<div
className={`${componentName}-right_card`}
style={{ backgroundColor: token.colorBgContainer }}
>
<div
className={`${componentName}-right_card_title`}
style={{ backgroundColor: '#f7f7f7' }}
>{titles?.[1]}</div>
<div
className={`${componentName}-right_card__items`}
style={{ height: typeof rightPanelScrollY === 'number' ? rightPanelScrollY + 'px' : rightPanelScrollY}}
>
{targetItems.map(item => (
<div
@ -102,8 +256,7 @@ const TreeTransfer: React.FC<TreeTransferProps> = ({
>
{item.title as any}
<div style={{ float: 'right' }}>
<DeleteOutlined onClick={() => {
// const { root, keys } = getAllRootKeyById(item.key as string, dataSource)
<IconFont icon="icon-shanchu" onIconClick={() => {
onItemDelete?.(item.key as string, item)
}} />
</div>
@ -111,19 +264,25 @@ const TreeTransfer: React.FC<TreeTransferProps> = ({
))}
</div>
{showRightPanelFooter && (
<Flex
className={`${componentName}-right_card__btns`}
>
{rightPanelFooterRender?.({ onOk, onReset }) || (
<>
<Button style={{ marginRight: 8, width: '50%' }} disabled={targetItems.length <= 0} onClick={onReset}></Button>
<Button
style={{ width: '50%' }}
type='primary'
onClick={() => onOk?.(targetItems)}
></Button>
</>
)}
</Flex>
</Card>
)}
</div>
</div>
</div>
</Flex>
);
}

View File

@ -42,10 +42,18 @@ const App: React.FC = () => {
return (
<TreeTransfer
leftPanelWidth={800}
rightPanelWidth={500}
showLeftPanelFooter
dataSource={boxDataSource}
targetItems={targetItems}
checkedKeys={checkedKeys}
onTreeCheck={onTreeCheck}
leftPanelFooterRender={(
<div style={{ padding: '6px', color: 'red' }}>
dom
</div>
)}
onItemDelete={onItemDelete}
onOk={onOk}
onReset={onReset}

View File

@ -0,0 +1,71 @@
import React, { useState } from 'react';
import { TreeTransfer } from '@zhst/biz';
import { TreeDataNode } from 'antd';
import { TreeProps } from 'antd/lib';
import { boxDataSource } from './mock'
const App: React.FC = () => {
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
let _targetItems: TreeDataNode[] = []
setCheckedKeys(keys)
info.checkedNodes.forEach(o => {
o.isLeaf && _targetItems.push(o)
})
setTargetItems(_targetItems)
}
/**
*
* @param key
* @param param1
*/
const onItemDelete = (key: any, { keys = [] }: any) => {
setCheckedKeys(pre => {
const newKeys = pre.filter(_key => _key !== key)
console.log('newKeys', newKeys, keys)
return newKeys
})
setTargetItems(pre => pre.filter(o => o.key !== key))
}
const onOk = (data: any) => {
console.log('data', data)
}
const onReset = () => {
setCheckedKeys([])
setTargetItems([])
}
return (
<div style={{ padding: 24 }}>
<TreeTransfer
leftPanelWidth={800}
rightPanelWidth={500}
bordered
titles={[
<div style={{ textAlign: 'left' }}>(123)</div>,
(
<div onClick={onReset} style={{ textAlign: 'left' }}>(123)<a style={{ float: 'right' }}></a></div>
),
]}
showRightPanelFooter={false}
dataSource={boxDataSource}
targetItems={targetItems}
checkedKeys={checkedKeys}
onTreeCheck={onTreeCheck}
onItemDelete={onItemDelete}
onOk={onOk}
onReset={onReset}
searchInputProps={{
onChange: e => console.log('123123')
}}
/>
</div>
)
};
export default App;

View File

@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { TreeTransfer } from '@zhst/biz';
import { TreeDataNode } from 'antd';
import { TreeProps } from 'antd/lib';
import { boxDataSource } from './mock'
const App: React.FC = () => {
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
let _targetItems: TreeDataNode[] = []
setCheckedKeys(keys)
info.checkedNodes.forEach(o => {
o.isLeaf && _targetItems.push(o)
})
setTargetItems(_targetItems)
}
/**
*
* @param key
* @param param1
*/
const onItemDelete = (key: any, { keys = [] }: any) => {
setCheckedKeys(pre => {
const newKeys = pre.filter(_key => _key !== key)
console.log('newKeys', newKeys, keys)
return newKeys
})
setTargetItems(pre => pre.filter(o => o.key !== key))
}
const onOk = (data: any) => {
console.log('data', data)
}
const onReset = () => {
setCheckedKeys([])
setTargetItems([])
}
return (
<TreeTransfer
dataSource={boxDataSource}
targetItems={targetItems}
checkedKeys={checkedKeys}
onTreeCheck={onTreeCheck}
showLeftSearch={false}
onItemDelete={onItemDelete}
onOk={onOk}
onReset={onReset}
/>
)
};
export default App;

View File

@ -0,0 +1,66 @@
import React, { useState } from 'react';
import { TreeTransfer } from '@zhst/biz';
import { TreeDataNode } from 'antd';
import { IconFont } from '@zhst/icon'
import { TreeProps } from 'antd/lib';
import { boxDataSource } from './mock'
const App: React.FC = () => {
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
let _targetItems: TreeDataNode[] = []
setCheckedKeys(keys)
info.checkedNodes.forEach(o => {
o.isLeaf && _targetItems.push(o)
})
setTargetItems(_targetItems)
}
/**
*
* @param key
* @param param1
*/
const onItemDelete = (key: any, { keys = [] }: any) => {
setCheckedKeys(pre => {
const newKeys = pre.filter(_key => _key !== key)
console.log('newKeys', newKeys, keys)
return newKeys
})
setTargetItems(pre => pre.filter(o => o.key !== key))
}
const onOk = (data: any) => {
console.log('data', data)
}
const onReset = () => {
setCheckedKeys([])
setTargetItems([])
}
return (
<TreeTransfer
dataSource={boxDataSource}
targetItems={targetItems}
checkedKeys={checkedKeys}
onTreeCheck={onTreeCheck}
showFilter
filters={[
{
label: '',
icon: <IconFont icon="icon-bangzhu2" />,
danger: true,
onClick: () => {}
}
]}
onItemDelete={onItemDelete}
onOk={onOk}
onReset={onReset}
/>
)
};
export default App;

View File

@ -0,0 +1,103 @@
import React, { useRef, useState } from 'react';
import { TreeTransfer } from '@zhst/biz';
import { TreeDataNode } from 'antd';
import { TreeProps } from 'antd/lib';
import { MapBox } from '@zhst/map'
import { boxDataSource } from './mock'
const App: React.FC = () => {
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const [activeTabKey, setActiveTabKey] = useState<string>('camera');
const mapRef = useRef(null);
const [canDraw, setCanDraw] = useState(false)
const [toolsBarOpen, setToolsBarOpen] = useState(false)
// 地图初始化
const handleMapLoad = (e: mapboxgl.MapboxEvent<undefined>) => {
const map = e.target;
if (!map) return
map.flyTo({
// center: [120,30],
// zoom: map?.getMaxZoom(),
});
};
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
let _targetItems: TreeDataNode[] = []
setCheckedKeys(keys)
info.checkedNodes.forEach(o => {
o.isLeaf && _targetItems.push(o)
})
setTargetItems(_targetItems)
}
/**
*
* @param key
* @param param1
*/
const onItemDelete = (key: any, { keys = [] }: any) => {
setCheckedKeys(pre => {
const newKeys = pre.filter(_key => _key !== key)
console.log('newKeys', newKeys, keys)
return newKeys
})
setTargetItems(pre => pre.filter(o => o.key !== key))
}
const onOk = (data: any) => {
console.log('data', data)
}
const onReset = () => {
setCheckedKeys([])
setTargetItems([])
}
return (
<TreeTransfer
dataSource={boxDataSource}
targetItems={targetItems}
checkedKeys={checkedKeys}
leftPanelWidth={800}
rightPanelWidth={600}
onTreeCheck={onTreeCheck}
showLeftTabs
activeTabKey={activeTabKey}
onLeftTabsChange={val => setActiveTabKey(val)}
customLeftPanelContent={(data) => {
if (activeTabKey !== 'map') return null
return (
<div
style={{ flex: 1 }}
>
<MapBox
onLoad={handleMapLoad}
ref={mapRef}
width='100%'
height="402px"
draw={canDraw}
toolsBarOpen={toolsBarOpen}
onToolClick={e => {
setCanDraw(pre => !pre)
setToolsBarOpen(pre => !pre)
}}
drawerProps={{
onActionable: e => console.log('e', e)
}}
></MapBox>
</div>
)
}}
onItemDelete={onItemDelete}
onOk={onOk}
onReset={onReset}
/>
)
};
export default App;

View File

@ -1,28 +1,90 @@
.zhst-biz-treeTransfer {
display: flex;
align-items: center;
&-left {
border-radius: 8px;
overflow: hidden;
&_card {
width: 500px;
display: flex;
flex-direction: column;
position: relative;
min-height: 544px;
background-color: #FCFCFC;
border: 2px solid #f7f7f7;
&_title {
padding: 12px;
text-align: center;
border-bottom: 2px solid #f7f7f7;
}
.ant-tabs {
.ant-tabs-nav {
margin-bottom: 0;
}
.ant-tabs-nav-list {
width: 100%;
}
.ant-tabs-tab {
margin: 0;
justify-content: center;
text-align: center;
flex: 1;
}
}
&_search {
display: flex;
margin: 16px 20px;
&_input {
flex: 1;
}
&_filters {
margin-left: 8px;
}
}
&-tree {
margin-top: 6px;
margin: 0 20px;
}
&-footer {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
}
}
}
&-middle {
padding: 24px;
height: 100%;
color: #d9d9d9;
transform-origin: center center;
transform: rotate(90deg);
}
&-right {
border-radius: 8px;
overflow: hidden;
&_card {
width: 300px;
position: relative;
min-height: 544px;
background-color: #FCFCFC;
border: 2px solid #f7f7f7;
&_title {
padding: 12px;
text-align: center;
border-bottom: 2px solid #f7f7f7;
}
&__items {
padding: 8px 4px;
width: 100%;
height: calc(100% - 105px);
font-size: 14px;
overflow-y: scroll;
max-height: 422px;
box-sizing: border-box;
&::-webkit-scrollbar {
display: none;
@ -44,7 +106,7 @@
left: 50%;
transform: translateX(-50%);
box-sizing: border-box;
border-top: 1px solid #f0f0f0;
border-top: 2px solid #f0f0f0;
}
}
}

View File

@ -12,10 +12,42 @@ group:
## 代码演示
<code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/noSearch.tsx">没有搜索框</code>
<code src="./demo/withFilter.tsx">加载filter按钮</code>
<code src="./demo/withMap.tsx">和地图组件搭配使用</code>
<code src="./demo/withModal.tsx">和Modal组合使用</code>
<code src="./demo/mingmou.tsx">明眸同款</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| data | 数据源 | Array[] | [] | - |
| titles | 顶部标题 | string[]、ReactNode[] | [] | - |
| dataSource | 数据 | TreeDataNode[] | [] | - |
| treeProps | 左侧树的props | antd-tree | [] | - |
| showLeftSearch | 显示左侧搜索框 | boolean | true | - |
| searchInputProps | 左侧搜索透传 | InputProps | {} | - |
| targetItems | 选中数据 | TreeDataNode[] | [] | - |
| checkedKeys | 选中的key数组 | string[] | [] | - |
| showFilter | 是否显示搜索框边上的dom | boolean | false | - |
| leftPanelWidth | 左边面板高度 | string、number | - | - |
| leftPanelScrollY | 左边面板滚动高度 | number | - | - |
| showLeftPanelFooter | 是否显示左边面板的底部 | boolean | false | - |
| leftPanelFooterRender | 左边面板底部自定义 | | ReactNode、string | - |
| rightPanelWidth | 右边面板高度 | number、string | - | - |
| rightPanelScrollY | 右边面板滚动高度 | number | - | - |
| showRightPanelFooter | 是否显示右边面板的底部 | boolean | false | - |
| rightPanelFooterRender | 右边边面板底部自定义 | ReactNode、string | - | - |
| tabsItems | tab列表 | 参考antd-tabs组件的 tabsProps['items'] | - | - |
| showLeftTabs | 是否显示tabs组件 | boolean | false | - |
| activeTabKey | 当前选中tab | string | - | - |
| treeBackgroundColor | 左侧树颜色 | string | #FCFCFC | - |
| leftTabsProps | 左侧树tabs的props | tabsProps | - | - |
| customLeftPanelContent | 左侧树自定义内容 | (data?: TreeDataNode[]) => ReactNode | - | - |
| filters | 左侧过滤自定义 | ButtonProps & { label: string; icon?: string } | - | - |
| onLeftTabsChange | 左侧tab点击事件 | TabsProps['onChange'] | - | - |
| onTreeSelect | 左侧树点击事件 | TreeProps['onSelect'] | - | - |
| onTreeCheck | 左侧树勾选☑️事件 | TreeProps['onCheck'] | - | - |
| onOk | 提交事件 | (data: any) => void; | - | - |
| onReset | 重置事件 | () => void; | - | - |
| onSearch | 左侧树搜索事件 | InputProps['onChange'] | - | - |

View File

@ -1,17 +1,16 @@
import React, { FC, useState } from 'react';
import { Modal, ModalProps, Radio, RadioGroupProps, Select, SelectProps, TransferProps, TreeDataNode, TreeProps } from 'antd';
import { Modal, ModalProps, Radio, RadioGroupProps, Select, SelectProps, DataNode, TreeProps } from '@zhst/meta';
import TreeTransfer from '../treeTransfer';
import { TreeTransferProps } from '../treeTransfer'
export interface TreeTransferModalProps {
dataSource: TreeDataNode[]
dataSource: DataNode[]
treeProps?: TreeProps
targetItems: TreeDataNode[];
targetItems: DataNode[];
checkedKeys: string[];
onTreeSelect?: TreeProps['onSelect']
onTreeCheck?: TreeProps['onCheck']
onItemDelete?: TreeTransferProps['onItemDelete']
onChange?: TransferProps['onChange'];
onOk?: (data: any) => void;
onReset?: () => void;
open?: boolean

View File

@ -1,5 +1,11 @@
# @zhst/biz
## 0.7.0
### Minor Changes
- 修改 less to css 配置
## 0.6.2
### Patch Changes

View File

@ -1,30 +0,0 @@
export var OBJECT_GRNER_THRESHOLD = 0.8; //目标图判断性别阈值
export var OBJECT_AGE_TYPE_THRESHOLD = 0.5; //目标图判断年龄段阈值
export var MODE_KEY = 'test_mode';
//业务约定
export var SEARCH_IMG_COUNT = 10; //检索图片的最大个数
export var GLOBAL_IS_ITEM_NUMBER_SHOW = false; //是否展示索引
export var publicPath = 'hummingbird';
export var ENTER_CIRCLE = 'MONITORTYPE_ENTER_CIRCLE';
export var OUT_CIRCLE = 'MONITORTYPE_OUT_CIRCLE';
export var TEMP = 'MONITORTYPE_TEMP';
export var GLOBAL_IS_BOX_VMS_SHOW = true; //是否展示盒子vms
export var BODY_SEARCH_THRESHOID = 0.45; //形体检索阈值
export var RECORD_VERSION = '3.0.0'; //保存记录的版本号
export var DeviceTab = {
EMPTY: 0,
REAL_CAMERA: 1,
PREPROCESS_CAMERA: 2,
//摄像头列表
TAG_CAMERA: 3,
//预案列表
HISTORY_VIDEO_GROUP: 4,
//录像回放
VIRTUAL_CAMERA: 5,
//离线视频
REAL_CAMERA_NOFACE: 6,
REAL_CAMERA_ONLYFACE: 7,
REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8 // 只有普通摄像头,没有人脸、没有盒子、直连
};

View File

@ -1 +0,0 @@
export var TYPE = '';

View File

@ -1,3 +0,0 @@
export * from "./camera";
export * from "./base";
export * from "./user";

View File

@ -1 +0,0 @@
export default {};

View File

@ -1,6 +1,6 @@
{
"name": "@zhst/constants",
"version": "0.6.2",
"version": "0.7.0",
"description": "常量库",
"keywords": [
"constants",

View File

@ -1,5 +1,24 @@
# @zhst/utils
## 0.17.0
### Minor Changes
- 修改 less to css 配置
### Patch Changes
- Updated dependencies
- @zhst/request@0.17.0
## 0.16.1
### Patch Changes
- 穿梭框完成,新增 meta 组件
- Updated dependencies
- @zhst/request@0.16.1
## 0.16.0
### Minor Changes

File diff suppressed because one or more lines are too long

View File

@ -1,41 +0,0 @@
/**
*
*/
export declare const DeviceType: {
VMS: string;
DIR: string;
CAMERA: string;
};
export declare const LOCAL_KEY = "local";
export declare const DIRE_CONNECT_KEY = "direconnect";
export declare const BOX_LIST_KEY = "boxlist";
export declare enum VmsplatformOpt {
VMSPLATFORMOPT_ID = 0,
VMSPLATFORMOPT_PLATFORMNAME = 1,
VMSPLATFORMOPT_PLUGINNAME = 2,
VMSPLATFORMOPT_IP = 3,
VMSPLATFORMOPT_PORT = 4,
VMSPLATFORMOPT_USERNAME = 5,
VMSPLATFORMOPT_PASSWORD = 6
}
export declare enum OPT {
OR = 0,
AND = 1,
ORNOT = 2,
ANDNOT = 3
}
export declare enum DevicemanagerCameraType {
DEVICEMANAGER_CAMERA_TYPE_DEFAULT = 0,
DEVICEMANAGER_CAMERA_TYPE_NORMAL = 1,
DEVICEMANAGER_CAMERA_TYPE_1400 = 97,
DEVICEMANAGER_CAMERA_TYPE_DHGRABBER = 98,
DEVICEMANAGER_CAMERA_TYPE_HKGRABBER = 99,
DEVICEMANAGER_CAMERA_TYPE_LOCAL = 100
}
export declare const BOX_DIRECONNECT_PLATFORM_FILTER: {
filtervmsplatformList: {
opt: OPT;
vmsplatformOpt: VmsplatformOpt;
value: string;
}[];
};

View File

@ -1,50 +0,0 @@
/**
* 设备类型枚举
*/
export var DeviceType = {
VMS: 'vms',
DIR: 'dir',
CAMERA: 'camera'
};
export var LOCAL_KEY = 'local'; //离线摄像头key 约定
export var DIRE_CONNECT_KEY = 'direconnect';
export var BOX_LIST_KEY = 'boxlist';
export var VmsplatformOpt = /*#__PURE__*/function (VmsplatformOpt) {
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_ID"] = 0] = "VMSPLATFORMOPT_ID";
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_PLATFORMNAME"] = 1] = "VMSPLATFORMOPT_PLATFORMNAME";
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_PLUGINNAME"] = 2] = "VMSPLATFORMOPT_PLUGINNAME";
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_IP"] = 3] = "VMSPLATFORMOPT_IP";
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_PORT"] = 4] = "VMSPLATFORMOPT_PORT";
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_USERNAME"] = 5] = "VMSPLATFORMOPT_USERNAME";
VmsplatformOpt[VmsplatformOpt["VMSPLATFORMOPT_PASSWORD"] = 6] = "VMSPLATFORMOPT_PASSWORD";
return VmsplatformOpt;
}({});
export var OPT = /*#__PURE__*/function (OPT) {
OPT[OPT["OR"] = 0] = "OR";
OPT[OPT["AND"] = 1] = "AND";
OPT[OPT["ORNOT"] = 2] = "ORNOT";
OPT[OPT["ANDNOT"] = 3] = "ANDNOT";
return OPT;
}({});
export var DevicemanagerCameraType = /*#__PURE__*/function (DevicemanagerCameraType) {
DevicemanagerCameraType[DevicemanagerCameraType["DEVICEMANAGER_CAMERA_TYPE_DEFAULT"] = 0] = "DEVICEMANAGER_CAMERA_TYPE_DEFAULT";
DevicemanagerCameraType[DevicemanagerCameraType["DEVICEMANAGER_CAMERA_TYPE_NORMAL"] = 1] = "DEVICEMANAGER_CAMERA_TYPE_NORMAL";
DevicemanagerCameraType[DevicemanagerCameraType["DEVICEMANAGER_CAMERA_TYPE_1400"] = 97] = "DEVICEMANAGER_CAMERA_TYPE_1400";
DevicemanagerCameraType[DevicemanagerCameraType["DEVICEMANAGER_CAMERA_TYPE_DHGRABBER"] = 98] = "DEVICEMANAGER_CAMERA_TYPE_DHGRABBER";
DevicemanagerCameraType[DevicemanagerCameraType["DEVICEMANAGER_CAMERA_TYPE_HKGRABBER"] = 99] = "DEVICEMANAGER_CAMERA_TYPE_HKGRABBER";
DevicemanagerCameraType[DevicemanagerCameraType["DEVICEMANAGER_CAMERA_TYPE_LOCAL"] = 100] = "DEVICEMANAGER_CAMERA_TYPE_LOCAL";
return DevicemanagerCameraType;
}({});
// 盒子 直连 平台
export var BOX_DIRECONNECT_PLATFORM_FILTER = {
filtervmsplatformList: [{
opt: OPT.OR,
vmsplatformOpt: VmsplatformOpt.VMSPLATFORMOPT_PLATFORMNAME,
value: 'direconnect'
}, {
opt: OPT.OR,
vmsplatformOpt: VmsplatformOpt.VMSPLATFORMOPT_PLATFORMNAME,
value: 'boxlist'
}]
};

View File

@ -1,40 +0,0 @@
import { DevicemanagerCameraType } from './constants';
export declare const isFaceCamera: (type: DevicemanagerCameraType) => boolean;
/**
*
* @param value itemcamera/vms/dirs/ deviceID
* @param isId
*/
export declare function getDeviceType(value: {
[x: string]: any;
id: any;
} | string): any;
/**
* id/vmsid/dirid是三张表 key
* @param id id
* @param type
*/
export declare function deviceIDToDeviceKey(id: any, type: string, vmsId?: any): string;
/**
* id/vmsid/dirid是三张表 key
* @param item camera/vms/dirs
*/
export declare function deviceToDeviceKey(item: {
[x: string]: any;
id: any;
}): string;
/**
* key id dirid是string/vms&camera number
* @param deviceKey id
*/
export declare function deviceKeyToDeviceId(deviceKey: {
split: (arg0: string) => [any, any];
}): any;
export declare const getVmsIdByDeviceId: (key: string) => string;
/**
* id或设备key在树里面找摄像头
* @param ids cameraId
* @param deviceTree
* @param type "id" | "key"
*/
export declare const findCamerasByInDeviceTree: (ids: never[] | undefined, deviceTree: any, type?: string) => any[];

View File

@ -1,142 +0,0 @@
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import { get, has, isString } from 'lodash-es';
import { loop } from "../utils";
import { DevicemanagerCameraType, DeviceType } from "./constants";
export var isFaceCamera = function isFaceCamera(type) {
return [DevicemanagerCameraType.DEVICEMANAGER_CAMERA_TYPE_1400, DevicemanagerCameraType.DEVICEMANAGER_CAMERA_TYPE_HKGRABBER, DevicemanagerCameraType.DEVICEMANAGER_CAMERA_TYPE_DHGRABBER].includes(type);
};
/**
*
* @param value 传入的数据 可以是 itemcamera/vms/dirs/ deviceID
* @param isId
*/
export function getDeviceType(value) {
var type;
var isDeviceKey = isString(value);
if (isDeviceKey) {
type = value.split('_')[0];
} else {
if (has(value, 'longitude')) {
type = DeviceType['CAMERA'];
}
if (has(value, 'ip')) {
type = DeviceType['VMS'];
}
if (!type) {
type = DeviceType['DIR'];
}
}
return type;
}
/**
* 后端设备id/vmsid/dirid是三张表 合并在一起不保证唯一 前端生成唯一key
* @param id 设备id
* @param type 设备类型
*/
export function deviceIDToDeviceKey(id, type, vmsId) {
if (type == DeviceType['DIR']) {
return "".concat(type, "_").concat(id, "_").concat(vmsId);
} else {
return "".concat(type, "_").concat(id);
}
}
/**
* 后端设备id/vmsid/dirid是三张表 合并在一起不保证唯一 前端生成唯一key
* @param item camera/vms/dirs
*/
export function deviceToDeviceKey(item) {
var deviceKey = '';
var type = getDeviceType(item);
if (!type) {
console.error('device type is null!');
}
switch (type) {
case DeviceType['DIR']:
{
var dirId = item['dirid'] || item['dirId'];
if (!dirId && dirId !== 0) {
console.error('dirId type is null!');
}
var vmsId = get(item, 'extendInfo.vmsPlatformId');
if (!vmsId && vmsId !== 0) {
console.error('vmsId type is null!');
}
deviceKey = "".concat(type, "_").concat(dirId, "_").concat(vmsId);
}
break;
case DeviceType['VMS']:
deviceKey = "".concat(type, "_").concat(item['id']);
break;
case DeviceType['CAMERA']:
{
var _vmsId = get(item, 'extendInfo.vmsPlatformId');
if (!_vmsId && _vmsId !== 0) {
console.error('vmsId type is null!');
}
deviceKey = "".concat(type, "_").concat(item.id);
}
break;
}
return deviceKey;
}
/**
* 设备树key 后端设备原始id dirid是string/vms&camera 是number 和后端保持一致
* @param deviceKey 设备树的id
*/
export function deviceKeyToDeviceId(deviceKey) {
var _deviceKey$split = deviceKey.split('_'),
_deviceKey$split2 = _slicedToArray(_deviceKey$split, 2),
type = _deviceKey$split2[0],
id = _deviceKey$split2[1];
return type === DeviceType['DIR'] ? id : Number(id);
}
export var getVmsIdByDeviceId = function getVmsIdByDeviceId(key) {
var type = getDeviceType(key);
var vmsId = '';
switch (type) {
case DeviceType['CAMERA']:
case DeviceType['DIR']:
vmsId = key.split('_')[2];
break;
case DeviceType['VMS']:
vmsId = key.split('_')[1];
break;
}
if (!vmsId) {
console.error('vmsid is null!');
}
return vmsId;
};
/**
* 通过设备id或设备key在树里面找摄像头
* @param ids cameraId
* @param deviceTree
* @param type "id" | "key"
*/
export var findCamerasByInDeviceTree = function findCamerasByInDeviceTree() {
var ids = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var deviceTree = arguments.length > 1 ? arguments[1] : undefined;
var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'id';
var cameraInfoList = [];
var _ids = ids.map(function (v) {
return String(v);
}); //都转string在做判断 保证格式一致
loop(deviceTree, function (item) {
var isCamera = getDeviceType(get(item, 'key', '')) === DeviceType['CAMERA'];
var isMatch = type === 'key' ? _ids.includes(get(item, 'key')) : _ids.includes("".concat(get(item, 'origin.id')));
if (isCamera && isMatch) {
cameraInfoList.push(item);
}
});
return cameraInfoList;
};

View File

@ -1,80 +0,0 @@
export type Rect = {
x: number;
y: number;
w: number;
h: number;
};
/**
*
* @param url
* @returns dom
*/
export declare const urlToImg: (url: string) => Promise<HTMLImageElement>;
export declare const base64DecodeImageKey: (base64ImgKey: string) => string;
/**
* url获取图片的base64字符串
* @param src
* @param outputFormat
* @returns base64 @string
*/
export declare const getBase64ByUrl: (src: string | URL, outputFormat?: string) => Promise<unknown>;
/**
* base64
* @param file @file
* @returns @string
*/
export declare const fileToBase64: (file: any) => Promise<string>;
/**
*
* @param image @file
* @param width @number
* @param height @number
* @returns @string base64
*/
export declare const getBase64Image: (image: any, width?: any, height?: any) => string;
/**
* base64
* @param src
* @returns @string
*/
export declare const getBase64ByImage: (src: string) => Promise<unknown>;
/**
* url转base64
* @param {String} url - url地址
*/
export declare const urlToBase64V2: (url: string) => Promise<unknown>;
/**
* base64转Blob
* @param {String} base64 - base64
*/
export declare function base64toBlob(base64: string): Blob | undefined;
/**
*
* 1. url -> base64 -> blob
* 2. blob加入jsZip文件夹内file-saver保存
* @param {Array<{url:string,name:string}>} imgDataList
* @param {string} zipName
*/
export declare const downloadPackageImages: (imgDataList: string | any[], zipName: string) => Promise<void>;
export declare function getFileSize(size: number): string;
export declare const dataURLToBlob: (dataurl: string) => Blob;
/**
* key http
* @param originImgkey base64 http链接
* @param host
* @returns {string}
*/
export declare const generateImg: (imgKey: string, host?: string) => string;
/**
*
* @param imageKey v1_开头的字符串
* @returns
*/
export declare const getImageKey: (imageKey: string, preFix?: string) => string;
/**
*
* @param img url链接
* @param odRect
* @returns file
*/
export declare const getFileByRect: (img: string, odRect: Rect) => Promise<File>;

File diff suppressed because one or more lines are too long

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