Compare commits
168 Commits
@zhst/meta
...
master
Author | SHA1 | Date | |
---|---|---|---|
a8b9db109e | |||
|
d643a4f495 | ||
|
ecf46d7ffc | ||
|
bdd3ed09e0 | ||
|
8eb94b1ed8 | ||
b198e9b1fe | |||
7bde75ea15 | |||
765f485556 | |||
320e17ea93 | |||
490cc9ae4a | |||
9e6a87881d | |||
|
c1c0b865dc | ||
|
e8e36098e4 | ||
e5f69c915c | |||
42d8f26af5 | |||
c5fd96fbf0 | |||
b5a74de4d9 | |||
|
21e43c2d35 | ||
|
53ab45c536 | ||
|
17767ac3c0 | ||
|
f47c8533a9 | ||
|
70ffe63766 | ||
|
a30e8c646f | ||
|
46f35cb918 | ||
|
afd7ae13c9 | ||
7f64ffe221 | |||
657af1c40d | |||
c9174fd9b7 | |||
28822b45d8 | |||
012aaacb66 | |||
ec3938a2cf | |||
e44fa64760 | |||
91ba49d94c | |||
bfc2dd7930 | |||
2a7ea08e02 | |||
90b18d2624 | |||
|
ba96f96f58 | ||
5e370b0af3 | |||
ce6a719bc2 | |||
|
a501656907 | ||
ed4918c6a2 | |||
|
121ebb5007 | ||
27958367ff | |||
|
79a2b74a16 | ||
12e5046f53 | |||
782a0f021d | |||
03433e3108 | |||
|
08ea724555 | ||
dc98cd8ad4 | |||
d7a29f51b7 | |||
5f15108949 | |||
|
b75aaaf22e | ||
89ca980f9e | |||
|
5c5115471d | ||
066428c8b6 | |||
|
b0a2dc1bd2 | ||
1ff779c1de | |||
|
5a2c358fe8 | ||
074766b7fa | |||
|
bb5db6d967 | ||
fcf7f08fe4 | |||
3af900f998 | |||
|
2798253fb9 | ||
e56a19e4fa | |||
357a4c677a | |||
a129d39168 | |||
|
e5b176c148 | ||
15536f32c8 | |||
|
05064d9473 | ||
4fb101b7e3 | |||
|
17fa6f6990 | ||
98439dc5e8 | |||
|
006dc0ceeb | ||
c4489fb0a8 | |||
|
a2afc51ad8 | ||
3d8959891e | |||
|
5e483436b0 | ||
f5ff6e760f | |||
|
079df27f4b | ||
|
fe789b5479 | ||
|
08a14b3ae0 | ||
|
8acf8242ea | ||
|
92bcaace2b | ||
|
b104d383e8 | ||
|
5bab7caa8c | ||
085b790121 | |||
|
28ba34894d | ||
da8a0be612 | |||
|
aa1930e1b7 | ||
638ccf5d8d | |||
|
e1d7ab2621 | ||
5a1e41a7a3 | |||
|
b64abd7164 | ||
d0607af936 | |||
|
efb5acaf0a | ||
8d45ba6481 | |||
|
2ada801a32 | ||
|
c3573d9efb | ||
46e6d0b4d0 | |||
|
8e6b1fc8f2 | ||
54a680c0ff | |||
|
6a75d0412a | ||
0718d8c4d1 | |||
|
ed22f8bcf9 | ||
6f4797694f | |||
|
375d5983f4 | ||
c65756bbb2 | |||
3666fc4ed1 | |||
|
05f99f7359 | ||
3bd50deeb3 | |||
|
60ef9e88ee | ||
b930efa40d | |||
|
0cfdaa4851 | ||
f9c5dccb73 | |||
f778ec1b72 | |||
dd6ff1d2be | |||
23706fabf9 | |||
|
e8ddd60daa | ||
da3c1714c5 | |||
2e753a7259 | |||
|
e90dcce641 | ||
d02a2b2015 | |||
e3e3c05ae0 | |||
|
d95fe37e43 | ||
570f183382 | |||
|
1f6c7ccb18 | ||
b2dbace653 | |||
|
3b9d13d9b9 | ||
8215fa5e4f | |||
|
f09b77e689 | ||
a65d6e062d | |||
fe321d1c3b | |||
b3a18efa8f | |||
8dc81e1743 | |||
|
6b7dc33bce | ||
5fe34f8b87 | |||
f766136844 | |||
2310cac125 | |||
|
eb72f16e3a | ||
|
1f6d3cd5d7 | ||
37c34eb785 | |||
|
511471e5c5 | ||
d80cab2407 | |||
59f0924c66 | |||
0f0644495a | |||
457fa88dcc | |||
3083ef7865 | |||
5f806dadba | |||
|
72db5f1273 | ||
|
64d38943f7 | ||
066cd46614 | |||
b4d001475a | |||
defd87dfc7 | |||
|
6eb14f8eef | ||
f9fd6389c4 | |||
|
9f49dfe9b4 | ||
|
93bc21a29a | ||
|
faf0a5682e | ||
5f2873c152 | |||
3f92df816c | |||
|
0144f5cc33 | ||
|
a4ff47ea1a | ||
|
68e7ac2642 | ||
|
8486837339 | ||
|
9ed531d9da | ||
|
ac9e098c0c | ||
|
15a46122e8 | ||
1e1ac401e5 |
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#root .dumi-default-sidebar {
|
#root .dumi-default-sidebar {
|
||||||
width: 188px;
|
width: 188px;
|
||||||
overflow-y: hidden;
|
overflow-y: scroll;
|
||||||
/* stylelint-disable-next-line rule-empty-line-before */
|
/* stylelint-disable-next-line rule-empty-line-before */
|
||||||
&:hover {
|
&:hover {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -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 |
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -19,6 +19,7 @@ export default defineConfig({
|
|||||||
'@zhst/slave': path.join(__dirname, 'packages/slave/src'),
|
'@zhst/slave': path.join(__dirname, 'packages/slave/src'),
|
||||||
'@zhst/material': path.join(__dirname, 'packages/material/src'),
|
'@zhst/material': path.join(__dirname, 'packages/material/src'),
|
||||||
'@zhst/icon': path.join(__dirname, 'packages/icon/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'),
|
'@zhst/map': path.join(__dirname, 'packages/map/src'),
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -34,6 +35,7 @@ export default defineConfig({
|
|||||||
{ type: 'slave', dir: 'packages/slave/src' },
|
{ type: 'slave', dir: 'packages/slave/src' },
|
||||||
{ type: 'material', dir: 'packages/material/src' },
|
{ type: 'material', dir: 'packages/material/src' },
|
||||||
{ type: 'icon', dir: 'packages/icon/src' },
|
{ type: 'icon', dir: 'packages/icon/src' },
|
||||||
|
{ type: 'icon-v2', dir: 'packages/icon-v2/src' },
|
||||||
{ type: 'map', dir: 'packages/map/src' },
|
{ type: 'map', dir: 'packages/map/src' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
12
.fatherrc.ts
12
.fatherrc.ts
@ -1,5 +1,15 @@
|
|||||||
import { defineConfig } from 'father';
|
import { defineConfig } from 'father-plugin-less';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
// 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
4
.gitignore
vendored
@ -9,6 +9,10 @@ vueuse
|
|||||||
/temp
|
/temp
|
||||||
packages/**/es
|
packages/**/es
|
||||||
packages/**/lib
|
packages/**/lib
|
||||||
|
**/es
|
||||||
|
**/lib
|
||||||
|
**/**/es
|
||||||
|
**/**/lib
|
||||||
/es
|
/es
|
||||||
/lib
|
/lib
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
@ -10,9 +10,9 @@ cache:
|
|||||||
- node_modules/
|
- node_modules/
|
||||||
|
|
||||||
build:
|
build:
|
||||||
image: registry.zhst.com/video-analysis/pnpm4
|
image: registry.zhst.com/video-analysis/pnpm4-lambo
|
||||||
tags:
|
tags:
|
||||||
- linux
|
- linux_docker_web_204
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- chmod +x ./deploy/build.sh && ./deploy/build.sh
|
- chmod +x ./deploy/build.sh && ./deploy/build.sh
|
||||||
@ -20,6 +20,8 @@ build:
|
|||||||
name: '${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${CI_JOB_NAME}_${CI_PIPELINE_ID}'
|
name: '${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${CI_JOB_NAME}_${CI_PIPELINE_ID}'
|
||||||
paths:
|
paths:
|
||||||
- ./app/public/*
|
- ./app/public/*
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
package:
|
package:
|
||||||
tags:
|
tags:
|
||||||
@ -29,4 +31,6 @@ package:
|
|||||||
- chmod +x ./deploy/package.sh && ./deploy/package.sh
|
- chmod +x ./deploy/package.sh && ./deploy/package.sh
|
||||||
dependencies:
|
dependencies:
|
||||||
- build
|
- build
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
4
.npmrc
4
.npmrc
@ -1,4 +1,6 @@
|
|||||||
|
//10.0.0.77:4874/:always-auth=true
|
||||||
|
//10.0.0.77:4874/:_authToken=VbcnvB0eKGAqsT5ZNbfKiw==
|
||||||
registry="https://registry.npmmirror.com"
|
registry="https://registry.npmmirror.com"
|
||||||
@zhst:registry="http://10.0.0.77:4874"
|
@zhst:registry="http://10.0.0.77:4874/"
|
||||||
strict-peer-dependencies=false
|
strict-peer-dependencies=false
|
||||||
ignore-workspace-root-check=true
|
ignore-workspace-root-check=true
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -10,6 +10,9 @@
|
|||||||
"flvjs",
|
"flvjs",
|
||||||
"indicatorsize",
|
"indicatorsize",
|
||||||
"lambo",
|
"lambo",
|
||||||
|
"mapbox",
|
||||||
|
"maxzoom",
|
||||||
|
"minzoom",
|
||||||
"remuxer",
|
"remuxer",
|
||||||
"stylelint",
|
"stylelint",
|
||||||
"transmuxer",
|
"transmuxer",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM alpine:latest
|
FROM registry.zhst.com/video-analysis/alpine:latest
|
||||||
|
|
||||||
ADD ./app/public/ /app/public/
|
ADD ./app/public/ /app/public/
|
||||||
|
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
|
pnpm --version
|
||||||
|
pnpm -v
|
||||||
|
node -v
|
||||||
pnpm install --force
|
pnpm install --force
|
||||||
|
|
||||||
|
pnpm run pkg:build
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pnpm run pub
|
||||||
|
|
||||||
|
# 打tag失败
|
||||||
|
|
||||||
pnpm run build:master
|
pnpm run build:master
|
||||||
|
|
||||||
mkdir -p ./app/public/
|
mkdir -p ./app/public/
|
||||||
|
@ -1,40 +1,81 @@
|
|||||||
---
|
---
|
||||||
hero:
|
hero:
|
||||||
title: lambo
|
title: lambo
|
||||||
description: 致力于提升前端开发效率与规范
|
description: 致力于提升前端开发效率与规范(开发前请先阅读开发流程)
|
||||||
actions:
|
actions:
|
||||||
- text: 快速上手
|
- text: 快速上手
|
||||||
link: /bizs
|
link: /bizs
|
||||||
features:
|
# features:
|
||||||
- title: biz
|
# - title: biz
|
||||||
emoji: 🍑
|
# emoji: 🍑
|
||||||
description: 业务库
|
# description: 业务库
|
||||||
- title: hooks
|
# - title: hooks
|
||||||
emoji: 💎
|
# emoji: 💎
|
||||||
description: hooks
|
# description: hooks
|
||||||
- title: func
|
# - title: func
|
||||||
emoji: 🌈
|
# emoji: 🌈
|
||||||
description: 常用函数库
|
# description: 常用函数库
|
||||||
- title: meta
|
# - title: meta
|
||||||
emoji: ☀️
|
# emoji: ☀️
|
||||||
description: 原子组件库
|
# description: 原子组件库
|
||||||
- title: constants
|
# - title: constants
|
||||||
emoji: 🈶️
|
# emoji: 🈶️
|
||||||
description: 静态定义库
|
# description: 静态定义库
|
||||||
- title: request
|
# - title: request
|
||||||
emoji: 🥣
|
# emoji: 🥣
|
||||||
description: 网络请求库
|
# description: 网络请求库
|
||||||
- title: types
|
# - title: types
|
||||||
emoji: 🈸
|
# emoji: 🈸
|
||||||
description: typescript 声明库
|
# description: typescript 声明库
|
||||||
- title: material
|
# - title: material
|
||||||
emoji: 🥱
|
# emoji: 🥱
|
||||||
description: 物料库
|
# description: 物料库
|
||||||
- title: cli
|
# - title: cli
|
||||||
emoji: 🐔
|
# emoji: 🐔
|
||||||
description: 脚手架
|
# description: 脚手架
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 开发流程
|
||||||
|
|
||||||
|
### 1. 确定需求
|
||||||
|
|
||||||
|
从 gitlab 上的 [issue](http://10.0.0.88/web-project/zhst-lambo/boards) 模块找到对应的需求。将 Assignee 负责人指派为自己(如果多人协同开发可以将需求拆分为多个需求,分别指派),然后将 Labels 标签改为 doing 状态。(截止日期选填)
|
||||||
|
|
||||||
|
> issuse 命名规则:@zhst/{包名} - {模块名},然后在详情页描述对应需求。
|
||||||
|
|
||||||
|
### 2. 创建 git 分支
|
||||||
|
|
||||||
|
按照 git flow 规范从 [master](http://10.0.0.88/web-project/zhst-lambo) 上创建分支, 分支的命名规则参考:
|
||||||
|
|
||||||
|
1. feat/XXX: 需求新增
|
||||||
|
2. hotfix/XXX: bug 修复
|
||||||
|
|
||||||
|
### 3. 开始开发
|
||||||
|
|
||||||
|
进入项目文件夹,在 packages 下找到对应的 npm 包, 然后在 src 目录下按已有的格式进行开发,如果是功能变更就找到对应的页面进行修改
|
||||||
|
|
||||||
|
### 4. 提交代码,并提交 mr 到 develop 分支
|
||||||
|
|
||||||
|
完成开发后,给代码提交 commit,格式参考 ${行为}(${影响范围}): ${变更内容}, 例如:
|
||||||
|
|
||||||
|
> feat(package.json): 修改版本号
|
||||||
|
> fix(app.ts): 修改环境变量
|
||||||
|
|
||||||
|
对应的变更会在最终的 npm 包版本号体现:a.b.c - a 对应重构(一般用不上) - b 对应 feat(功能新增) - c 对应 hotfix(一般是 bug 修复)
|
||||||
|
|
||||||
|
push 完代码之后,在 gitlab 上提交一个 mr 到 develop 分支,指定给对应的人员审核(@江志雄),合并成功之后,将 [issue](http://10.0.0.88/web-project/zhst-lambo/boards) 对应的 Labels 状态改为 waittingPublish。
|
||||||
|
|
||||||
|
### 5. 发布成功
|
||||||
|
|
||||||
|
发布成功之后,会有两个行为:
|
||||||
|
|
||||||
|
1. 在钉钉群通知发布成功。
|
||||||
|
2. 生成线上预览[说明文档](http://10.0.0.204:30080)
|
||||||
|
|
||||||
|
一旦触发了钉钉通知,则需要去到 [issue](http://10.0.0.88/web-project/zhst-lambo/boards) 板块将对应的需求 **close** 掉。
|
||||||
|
|
||||||
|
这就是 npm 包整个开发链路。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
<Tree>
|
<Tree>
|
||||||
|
3
global.d.ts
vendored
3
global.d.ts
vendored
@ -1 +1,4 @@
|
|||||||
declare module '*.less';
|
declare module '*.less';
|
||||||
|
declare module '*.png';
|
||||||
|
declare module '*.jpg';
|
||||||
|
declare module '*.jpeg';
|
||||||
|
10
package.json
10
package.json
@ -61,6 +61,7 @@
|
|||||||
"dumi": "^2.2.13",
|
"dumi": "^2.2.13",
|
||||||
"eslint": "^8.23.0",
|
"eslint": "^8.23.0",
|
||||||
"father": "^4.1.0",
|
"father": "^4.1.0",
|
||||||
|
"father-plugin-less": "^0.0.2",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.1",
|
||||||
"lerna": "^8.0.0",
|
"lerna": "^8.0.0",
|
||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
@ -82,5 +83,12 @@
|
|||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
"dev<710328466@qq.com>"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { defineConfig } from 'father';
|
import { defineConfig } from 'father-plugin-less';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||||
esm: {
|
esm: {
|
||||||
output: 'es',
|
output: 'es',
|
||||||
ignores: ['**/demo/*', 'src/**/demo/*']
|
ignores: ['**/demo/*', 'src/**/demo/*'],
|
||||||
|
transformer: 'babel',
|
||||||
},
|
},
|
||||||
cjs: {
|
cjs: {
|
||||||
output: 'lib',
|
output: 'lib',
|
||||||
ignores: ['**/demo/*', 'src/**/demo/*']
|
ignores: ['**/demo/*', 'src/**/demo/*'],
|
||||||
|
transformer: 'babel',
|
||||||
},
|
},
|
||||||
|
lessInBabel: {
|
||||||
|
modifyVars: {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: ['father-plugin-less'],
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,270 @@
|
|||||||
# @zhst/biz
|
# @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
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/func@0.16.0
|
||||||
|
- @zhst/hooks@0.13.1
|
||||||
|
- @zhst/meta@0.21.2
|
||||||
|
|
||||||
|
## 0.22.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: 修改 cropperHelper 的无效引用
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/meta@0.21.1
|
||||||
|
|
||||||
|
## 0.22.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- fix: zhst/meta、zhst/biz、zhst/material-修改图片标注组件
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/meta@0.21.0
|
||||||
|
|
||||||
|
## 0.21.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: 修改 zhst/meta
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/meta@0.20.3
|
||||||
|
|
||||||
|
## 0.21.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- zhst/biz、zhst/meta、zhst/material: 修改 od、修改 boxSelectTree
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/meta@0.20.2
|
||||||
|
|
||||||
|
## 0.21.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- zhst/biz: fix: 边框颜色主题色,实时监控窗口有图片和选中,预警记录文字溢出
|
||||||
|
|
||||||
|
## 0.21.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/meta@0.20.1
|
||||||
|
|
||||||
|
## 0.21.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/meta@0.20.0
|
||||||
|
|
||||||
|
## 0.21.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- feat: 修复之前发版错乱问题
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/hooks@0.13.0
|
||||||
|
- @zhst/func@0.15.0
|
||||||
|
- @zhst/icon@0.5.0
|
||||||
|
- @zhst/meta@0.19.0
|
||||||
|
|
||||||
|
## 0.20.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/func@0.14.1
|
||||||
|
- @zhst/hooks@0.12.1
|
||||||
|
- @zhst/meta@0.18.1
|
||||||
|
|
||||||
|
## 0.20.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- feat: 重新发版
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fix: zhst/biz 优化实时监控布局
|
||||||
|
|
||||||
|
## 0.19.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- zhst/biz: 实时监控预警记录添加滚动条
|
||||||
|
|
||||||
|
## 0.19.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- fix: 修复适配问题
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @zhst/hooks@0.11.0
|
||||||
|
- @zhst/func@0.13.0
|
||||||
|
- @zhst/icon@0.3.0
|
||||||
|
- @zhst/meta@0.17.0
|
||||||
|
|
||||||
## 0.18.8
|
## 0.18.8
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -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;
|
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@zhst/biz",
|
"name": "@zhst/biz",
|
||||||
"version": "0.18.8",
|
"version": "0.34.0",
|
||||||
"description": "业务库",
|
"description": "业务库",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"business",
|
"business",
|
||||||
@ -35,6 +35,7 @@
|
|||||||
"registry": "http://10.0.0.77:4874"
|
"registry": "http://10.0.0.77:4874"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@swc/core": "^1.3.9",
|
||||||
"@zhst/types": "workspace:^"
|
"@zhst/types": "workspace:^"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
304
packages/biz/src/BigImage/BigImage.tsx
Normal file
304
packages/biz/src/BigImage/BigImage.tsx
Normal 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
|
@ -0,0 +1,3 @@
|
|||||||
|
.zhst-big-image-combime-image {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
82
packages/biz/src/BigImage/components/CombineImage/index.tsx
Normal file
82
packages/biz/src/BigImage/components/CombineImage/index.tsx
Normal 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
|
44
packages/biz/src/BigImage/components/navigation/index.less
Normal file
44
packages/biz/src/BigImage/components/navigation/index.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
40
packages/biz/src/BigImage/components/navigation/index.tsx
Normal file
40
packages/biz/src/BigImage/components/navigation/index.tsx
Normal 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 |
71
packages/biz/src/BigImage/demo/index.tsx
Normal file
71
packages/biz/src/BigImage/demo/index.tsx
Normal 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
|
128
packages/biz/src/BigImage/demo/oldData.tsx
Normal file
128
packages/biz/src/BigImage/demo/oldData.tsx
Normal 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
|
89
packages/biz/src/BigImage/demo/withEdit.tsx
Normal file
89
packages/biz/src/BigImage/demo/withEdit.tsx
Normal 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
|
78
packages/biz/src/BigImage/demo/withRelatedImage.tsx
Normal file
78
packages/biz/src/BigImage/demo/withRelatedImage.tsx
Normal 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
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 514 B |
132
packages/biz/src/BigImage/index.less
Normal file
132
packages/biz/src/BigImage/index.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
96
packages/biz/src/BigImage/index.md
Normal file
96
packages/biz/src/BigImage/index.md
Normal 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. 人脸碰撞模型
|
||||||
|
- 支持用户自定义传入数据
|
||||||
|
|
||||||
|
本来想通过插件的形式按需加载
|
5
packages/biz/src/BigImage/index.tsx
Normal file
5
packages/biz/src/BigImage/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import BigImage from "./BigImage";
|
||||||
|
|
||||||
|
export type { BigImageProps, BigImageRef } from './interface'
|
||||||
|
|
||||||
|
export default BigImage
|
84
packages/biz/src/BigImage/interface.ts
Normal file
84
packages/biz/src/BigImage/interface.ts
Normal 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
|
||||||
|
}
|
293
packages/biz/src/BigImage/mock.ts
Normal file
293
packages/biz/src/BigImage/mock.ts
Normal 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: '否',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
195
packages/biz/src/BigImage/util/bigImageModalAdapter.tsx
Normal file
195
packages/biz/src/BigImage/util/bigImageModalAdapter.tsx
Normal 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
|
36
packages/biz/src/BigImage/util/interface.ts
Normal file
36
packages/biz/src/BigImage/util/interface.ts
Normal 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
|
||||||
|
}[]
|
||||||
|
}
|
@ -223,11 +223,6 @@ const BigImageModal: React.FC<BigImageModalProps, BigModalRef> = forwardRef((pro
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: 页面初始化
|
|
||||||
useEffect(() => {
|
|
||||||
}, [dataSource]);
|
|
||||||
|
|
||||||
// 暴露 ref 实例
|
// 暴露 ref 实例
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
ref,
|
ref,
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
* Created by jiangzhixiong on 2024/04/28
|
* 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 { ConfigProvider, EMPTY_BASE64 } from '@zhst/meta'
|
||||||
import { Flex, Image } from 'antd';
|
import { Image } from 'antd';
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
const { ConfigContext } = ConfigProvider
|
const { ConfigContext } = ConfigProvider
|
||||||
|
|
||||||
export interface Idata {
|
export interface IData {
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
url?: string;
|
url?: string;
|
||||||
sort?: number;
|
sort?: number;
|
||||||
@ -17,24 +17,25 @@ export interface Idata {
|
|||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchCardProps extends Idata {
|
export interface CommonCardProps extends IData {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
data?: Idata
|
data?: IData
|
||||||
width?: string;
|
width?: string;
|
||||||
height?: string;
|
height?: string;
|
||||||
onCreateTxt?: string;
|
onCreateTxt?: string;
|
||||||
onCreate?: (data: any) => void;
|
onCreate?: () => void;
|
||||||
onAddTxt?: string;
|
onAddTxt?: string;
|
||||||
onAdd?: (data: any) => void;
|
onAdd?: (data: any) => void;
|
||||||
onRemoveTxt?: string;
|
onRemoveTxt?: string;
|
||||||
onRemove?: (data: any) => void;
|
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 {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
prefixCls: customizePrefixCls,
|
||||||
url,
|
url,
|
||||||
@ -43,26 +44,29 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
|
|||||||
subtitle,
|
subtitle,
|
||||||
sort,
|
sort,
|
||||||
data,
|
data,
|
||||||
onCreate,
|
actions = [],
|
||||||
onCreateTxt = '创建检索',
|
|
||||||
onAddTxt = '添加目标',
|
|
||||||
onRemoveTxt = '移除轨迹',
|
|
||||||
onAdd,
|
|
||||||
onRemove,
|
|
||||||
customOptionRender,
|
|
||||||
width = '184px',
|
width = '184px',
|
||||||
height = '100%'
|
height = '100%',
|
||||||
|
onItemClick
|
||||||
} = props
|
} = props
|
||||||
const { getPrefixCls } = useContext(ConfigContext)
|
const { getPrefixCls } = useContext(ConfigContext)
|
||||||
const componentName = getPrefixCls('biz-search-card', customizePrefixCls);
|
const componentName = getPrefixCls('biz-search-card', customizePrefixCls);
|
||||||
|
|
||||||
const stopBumble = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, fn?: ((data: Idata) => void), data?: Idata) => {
|
const optionListRender = (_actions: ReactNode[]) => {
|
||||||
e.stopPropagation()
|
return _actions.map((action, i) => (
|
||||||
fn?.(data!)
|
// 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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -72,27 +76,20 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
|
|||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
}}
|
}}
|
||||||
|
onClick={e => handleItemClick(e, data)}
|
||||||
>
|
>
|
||||||
<div className={`${componentName}-main`}>
|
<div className={`${componentName}-main`}>
|
||||||
<i className={`${componentName}-main-num`}>{id || sort}</i>
|
<i className={`${componentName}-main-num`}>{sort || id}</i>
|
||||||
<Image
|
<Image
|
||||||
|
className={`${componentName}-main-img`}
|
||||||
src={url || data?.url}
|
src={url || data?.url}
|
||||||
preview={false}
|
|
||||||
width={'100%'}
|
|
||||||
height={'240px'}
|
height={'240px'}
|
||||||
|
preview={false}
|
||||||
fallback={EMPTY_BASE64}
|
fallback={EMPTY_BASE64}
|
||||||
/>
|
/>
|
||||||
<Flex align='center' justify='space-between' className={`${componentName}-main-opt`}>
|
<ul className={`${componentName}-main-opt`}>
|
||||||
{customOptionRender || (
|
{optionListRender(actions)}
|
||||||
<>
|
</ul>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`${componentName}-footer`}>
|
<div className={`${componentName}-footer`}>
|
||||||
<p className={`${componentName}-footer-tit`}>{title || data?.title}</p>
|
<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
|
@ -1,18 +1,23 @@
|
|||||||
.zhst-biz-search-card {
|
.zhst-biz-search-card {
|
||||||
|
display: inline-block;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
transition: .1s ease-in all;
|
transition: .1s ease-in all;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
transition: .5s ease all;
|
||||||
border: 2px solid #09f;
|
border: 2px solid #09f;
|
||||||
|
|
||||||
.zhst-biz-search-card-main-opt {
|
.zhst-biz-search-card-main-opt {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-main {
|
&-main {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&-num {
|
&-num {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -30,8 +35,19 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transition: .5s ease-in all;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-opt {
|
&-opt {
|
||||||
display: none;
|
display: none;
|
||||||
|
margin: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 6px 3px;
|
padding: 6px 3px;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -43,11 +59,28 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: .2s ease-in all;
|
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 {
|
a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.9;
|
opacity: 0.88;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import CommonCard from './CommonCard'
|
||||||
|
|
||||||
|
export type { CommonCardProps, CommonCardRefProps } from './CommonCard'
|
||||||
|
|
||||||
|
export default CommonCard
|
42
packages/biz/src/CustomCard/demo/commonCard.tsx
Normal file
42
packages/biz/src/CustomCard/demo/commonCard.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
31
packages/biz/src/CustomCard/index.md
Normal file
31
packages/biz/src/CustomCard/index.md
Normal 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 | - | - |
|
6
packages/biz/src/CustomCard/index.tsx
Normal file
6
packages/biz/src/CustomCard/index.tsx
Normal 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'
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useRef } from 'react';
|
||||||
import WindowToggle, { ISize } from './components/WindowToggle';
|
import WindowToggle, { ISize } from './components/WindowToggle';
|
||||||
import WarningRecordList from './components/WarningRecordList';
|
import WarningRecordList from './components/WarningRecordList';
|
||||||
import { ConfigProvider } from '@zhst/meta';
|
import { ConfigProvider } from '@zhst/meta';
|
||||||
@ -34,6 +34,8 @@ interface RealTimeMonitorProps {
|
|||||||
largeImageTitle?: string;
|
largeImageTitle?: string;
|
||||||
size: ISize;
|
size: ISize;
|
||||||
setSize: React.Dispatch<React.SetStateAction<ISize>>
|
setSize: React.Dispatch<React.SetStateAction<ISize>>
|
||||||
|
maxRecordCount?: number;
|
||||||
|
warningImgStyle?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RealTimeMonitor: React.FC<RealTimeMonitorProps> = (props) => {
|
export const RealTimeMonitor: React.FC<RealTimeMonitorProps> = (props) => {
|
||||||
@ -53,19 +55,23 @@ export const RealTimeMonitor: React.FC<RealTimeMonitorProps> = (props) => {
|
|||||||
selectedRecordId,
|
selectedRecordId,
|
||||||
isRecordListLoading,
|
isRecordListLoading,
|
||||||
size,
|
size,
|
||||||
setSize
|
setSize,
|
||||||
|
maxRecordCount,
|
||||||
|
warningImgStyle,
|
||||||
} = props
|
} = props
|
||||||
|
const toggleRef = useRef<HTMLDivElement>()
|
||||||
const componentName = getPrefixCls('biz-real-time-monitor', customizePrefixCls);
|
const componentName = getPrefixCls('biz-real-time-monitor', customizePrefixCls);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={componentName} style={{ display: 'flex' }} >
|
<div className={componentName} style={{ display: 'flex' }} >
|
||||||
<WindowToggle
|
<WindowToggle
|
||||||
|
toggleRef={toggleRef}
|
||||||
selectedWindowKey={selectedWindowKey}
|
selectedWindowKey={selectedWindowKey}
|
||||||
dataSource={videoDataSource}
|
dataSource={videoDataSource}
|
||||||
handleWindowClick={handleWindowClick}
|
handleWindowClick={handleWindowClick}
|
||||||
handleCloseButtonClick={handleCloseButtonClick}
|
handleCloseButtonClick={handleCloseButtonClick}
|
||||||
size = {size}
|
size={size}
|
||||||
setSize = {setSize}
|
setSize={setSize}
|
||||||
/>
|
/>
|
||||||
<WarningRecordList
|
<WarningRecordList
|
||||||
dataSource={warningDataSource}
|
dataSource={warningDataSource}
|
||||||
@ -75,6 +81,9 @@ export const RealTimeMonitor: React.FC<RealTimeMonitorProps> = (props) => {
|
|||||||
viewLargerImageModalRef={viewLargerImageModalRef}
|
viewLargerImageModalRef={viewLargerImageModalRef}
|
||||||
isRecordListLoading={isRecordListLoading}
|
isRecordListLoading={isRecordListLoading}
|
||||||
recordListTitle="监控预警记录"
|
recordListTitle="监控预警记录"
|
||||||
|
maxHeight={toggleRef.current?.offsetHeight}
|
||||||
|
maxRecordCount={maxRecordCount}
|
||||||
|
imgStyle={warningImgStyle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,8 @@ import { IRecord } from '../../../WarningRecordCard';
|
|||||||
import { ViewLargerImageModalRef } from '../../../ViewLargerImageModal';
|
import { ViewLargerImageModalRef } from '../../../ViewLargerImageModal';
|
||||||
import WarningRecordCard from '../../../WarningRecordCard';
|
import WarningRecordCard from '../../../WarningRecordCard';
|
||||||
import ViewLargerImageModal from '../../../ViewLargerImageModal';
|
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 { LoadingOutlined } from '@ant-design/icons';
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ interface WarningRecordListProps {
|
|||||||
cardStyle?: React.CSSProperties;
|
cardStyle?: React.CSSProperties;
|
||||||
imgStyle?: React.CSSProperties;
|
imgStyle?: React.CSSProperties;
|
||||||
largeImageTitle?: string;
|
largeImageTitle?: string;
|
||||||
|
maxHeight?: number;
|
||||||
|
maxRecordCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WarningRecordList: React.FC<WarningRecordListProps> = (props) => {
|
const WarningRecordList: React.FC<WarningRecordListProps> = (props) => {
|
||||||
@ -43,11 +46,12 @@ const WarningRecordList: React.FC<WarningRecordListProps> = (props) => {
|
|||||||
style,
|
style,
|
||||||
cardStyle,
|
cardStyle,
|
||||||
imgStyle,
|
imgStyle,
|
||||||
largeImageTitle
|
largeImageTitle,
|
||||||
|
maxHeight,
|
||||||
|
maxRecordCount = 10
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='zhst-biz-warning-record-list' style={style}>
|
<div className='zhst-biz-warning-record-list' style={{ maxHeight: `${pxToRem(`${maxHeight}`)}`, ...style }} >
|
||||||
<div className='header'>{recordListTitle}</div>
|
<div className='header'>{recordListTitle}</div>
|
||||||
<div className='body'>
|
<div className='body'>
|
||||||
{
|
{
|
||||||
@ -58,14 +62,14 @@ const WarningRecordList: React.FC<WarningRecordListProps> = (props) => {
|
|||||||
: (dataSource?.length) > 0 ?
|
: (dataSource?.length) > 0 ?
|
||||||
<Space direction='vertical' size={10} >
|
<Space direction='vertical' size={10} >
|
||||||
{dataSource?.map((record, index) => {
|
{dataSource?.map((record, index) => {
|
||||||
if (index > 2) return
|
if (index > maxRecordCount - 1) return
|
||||||
return (<WarningRecordCard
|
return (<WarningRecordCard
|
||||||
key={record?.id}
|
key={record?.id}
|
||||||
record={record}
|
record={record}
|
||||||
onRecordClick={(record) => { onRecordClick?.(record) }}
|
onRecordClick={(record) => { onRecordClick?.(record) }}
|
||||||
selectedRecordId={selectedRecordId}
|
selectedRecordId={selectedRecordId}
|
||||||
cardStyle={{ width: 300, height: 264, ...cardStyle }}
|
cardStyle={{ ...cardStyle }}
|
||||||
imgStyle={{ width: 280, height: 169, ...imgStyle }}
|
imgStyle={{ ...imgStyle }}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
@ -2,19 +2,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-left: solid 1px #00000026;
|
border-left: solid 1px #00000026;
|
||||||
width: 320px;
|
min-width: 320px;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
background-color: #EFF2F4;
|
background-color: #EFF2F4;
|
||||||
padding: 10px 20px;
|
padding: 0 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
line-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow: hidden;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import VideoPlayerCard from '../../../VideoPlayerCard';
|
import VideoPlayerCard from '../../../VideoPlayerCard';
|
||||||
import { VideoPlayerCardProps } from '../../../VideoPlayerCard';
|
import { VideoPlayerCardProps } from '../../../VideoPlayerCard';
|
||||||
import { Segmented } from 'antd';
|
import { Row, Col, Segmented, theme } from '@zhst/meta';
|
||||||
import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
|
import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
|
||||||
import { theme } from 'antd/lib';
|
import { pxToRem } from '@zhst/func'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
export type ISize = 'large' | 'small'
|
export type ISize = 'large' | 'small'
|
||||||
@ -15,22 +15,21 @@ interface WindowToggleProps {
|
|||||||
selectedWindowKey?: string;
|
selectedWindowKey?: string;
|
||||||
size: ISize;
|
size: ISize;
|
||||||
setSize: React.Dispatch<React.SetStateAction<ISize>>
|
setSize: React.Dispatch<React.SetStateAction<ISize>>
|
||||||
|
toggleRef: React.MutableRefObject<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WindowToggle: React.FC<WindowToggleProps> = (props) => {
|
export const WindowToggle: React.FC<WindowToggleProps> = (props) => {
|
||||||
|
|
||||||
const { dataSource = [], handleWindowClick, handleCloseButtonClick, selectedWindowKey , size = "large",setSize} = props
|
const { dataSource = [], handleWindowClick, handleCloseButtonClick, selectedWindowKey, size = "large", setSize, toggleRef } = props
|
||||||
const { useToken } = theme
|
const { useToken } = theme
|
||||||
const { token } = useToken()
|
const { token } = useToken()
|
||||||
|
|
||||||
const getLabelStyle = (isSelected: boolean) => ({
|
const getLabelStyle = (isSelected: boolean) => ({
|
||||||
padding: "0 11px", background: "#fff",
|
padding: `0 ${pxToRem("11px")}`, background: "#fff",
|
||||||
...(isSelected ? { background: token.colorPrimary, color: '#fff' } : {}),
|
...(isSelected ? { background: token.colorPrimary, color: '#fff' } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='zhst-biz-window-toggle'>
|
<div className='zhst-biz-window-toggle' ref={toggleRef}>
|
||||||
{/* 切换按钮 */}
|
{/* 切换按钮 */}
|
||||||
<div className='header'>
|
<div className='header'>
|
||||||
<Segmented
|
<Segmented
|
||||||
@ -51,11 +50,33 @@ export const WindowToggle: React.FC<WindowToggleProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='body'>
|
<div className='body'>
|
||||||
{
|
|
||||||
dataSource?.map((item, index) => {
|
|
||||||
|
|
||||||
if (size === "large" && index > 0) return
|
<Row gutter={[0, 20]} style={{ width: "100%" }} > {/* 设置栅格间距 */}
|
||||||
|
{
|
||||||
|
size === "large" ?
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
dataSource?.map((item, index) => { // 仅显示前四个元素,即两行两列
|
||||||
|
if (index > 0) return null
|
||||||
return (
|
return (
|
||||||
|
<Col xs={24} sm={24} md={24} lg={24} xl={24} key={item.windowKey}>
|
||||||
|
<VideoPlayerCard
|
||||||
|
key={""}
|
||||||
|
selectedWindowKey={selectedWindowKey}
|
||||||
|
size={size}
|
||||||
|
handleWindowClick={handleWindowClick}
|
||||||
|
handleCloseButtonClick={handleCloseButtonClick}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
|
</Col>)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
: <>
|
||||||
|
{dataSource?.map((item) => {
|
||||||
|
return (
|
||||||
|
<Col xs={24} sm={12} md={12} lg={12} xl={12} className='sm-card' key={item.windowKey}>
|
||||||
<VideoPlayerCard
|
<VideoPlayerCard
|
||||||
key={item.windowKey}
|
key={item.windowKey}
|
||||||
selectedWindowKey={selectedWindowKey}
|
selectedWindowKey={selectedWindowKey}
|
||||||
@ -63,9 +84,12 @@ export const WindowToggle: React.FC<WindowToggleProps> = (props) => {
|
|||||||
handleWindowClick={handleWindowClick}
|
handleWindowClick={handleWindowClick}
|
||||||
handleCloseButtonClick={handleCloseButtonClick}
|
handleCloseButtonClick={handleCloseButtonClick}
|
||||||
{...item}
|
{...item}
|
||||||
/>)
|
/>
|
||||||
})
|
</Col>
|
||||||
|
);
|
||||||
|
})}</>
|
||||||
}
|
}
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -29,17 +29,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #E5EAEC;
|
background-color: #E5EAEC;
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
>div {
|
.sm-card:nth-child(odd) {
|
||||||
margin: 10px;
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-card:nth-child(even) {
|
||||||
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ import React, { useState } from 'react';
|
|||||||
import { IRecord, RealTimeMonitor, VideoPlayerCardProps, useViewLargerImageModal } from '@zhst/biz';
|
import { IRecord, RealTimeMonitor, VideoPlayerCardProps, useViewLargerImageModal } from '@zhst/biz';
|
||||||
import { videoData, warningData } from './mock';
|
import { videoData, warningData } from './mock';
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
|
import { pxToRem } from '@zhst/func';
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ export default () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 对后端返回数据进行处理 组装一套符合属性的 数据
|
// 对后端返回数据进行处理 组装一套符合属性的 数据
|
||||||
// videoSrc : videoData.videoSrc
|
// videoSrc : videoData.videoSrc
|
||||||
const newVideoData: VideoPlayerCardProps = { imageKey: videoData.imageKey, title: videoData.title, odRect: videoData.odRect, videoSrc: videoData.videoSrc }
|
const newVideoData: VideoPlayerCardProps = { imageKey: videoData.imageKey, title: videoData.title, odRect: videoData.odRect }
|
||||||
setVideoDataSource((pre) => {
|
setVideoDataSource((pre) => {
|
||||||
const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => {
|
const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => {
|
||||||
// 传给 选中的视频窗口
|
// 传给 选中的视频窗口
|
||||||
@ -153,6 +154,7 @@ export default () => {
|
|||||||
recordListTitle="监控预警记录"
|
recordListTitle="监控预警记录"
|
||||||
size={size}
|
size={size}
|
||||||
setSize={setSize}
|
setSize={setSize}
|
||||||
|
warningImgStyle={{ width: `${pxToRem("280px")}`, height: `${pxToRem("169px")}` }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button onClick={() => { mockData() }}>模拟请求</button>
|
<button onClick={() => { mockData() }}>模拟请求</button>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Card, Space, CardProps, Spin, Button } from 'antd';
|
import { Card, Space, CardProps, Spin, Button, theme } from 'antd';
|
||||||
import { theme } from 'antd/lib';
|
|
||||||
import { ConfigProvider, VideoPlayer, CropperImage, type VideoViewRef, } from '@zhst/meta';
|
import { ConfigProvider, VideoPlayer, CropperImage, type VideoViewRef, } from '@zhst/meta';
|
||||||
import React, { useState, useEffect, ReactNode, useRef, useContext } from 'react';
|
import React, { useState, useEffect, ReactNode, useRef, useContext } from 'react';
|
||||||
import { CloseOutlined, LoadingOutlined } from '@ant-design/icons';
|
import { CloseOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||||
@ -31,7 +30,6 @@ export interface VideoPlayerCardProps {
|
|||||||
|
|
||||||
export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
||||||
|
|
||||||
|
|
||||||
const { ConfigContext } = ConfigProvider;
|
const { ConfigContext } = ConfigProvider;
|
||||||
const { getPrefixCls } = useContext(ConfigContext);
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
const { prefixCls: customizePrefixCls, showType, imageKey, videoSrc, cardProps, isWindowLoading, errorReasonText, size, title, handleCloseButtonClick, handleWindowClick, windowKey, selectedWindowKey = '', odRect = [] } = props;
|
const { prefixCls: customizePrefixCls, showType, imageKey, videoSrc, cardProps, isWindowLoading, errorReasonText, size, title, handleCloseButtonClick, handleWindowClick, windowKey, selectedWindowKey = '', odRect = [] } = props;
|
||||||
@ -48,11 +46,8 @@ export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
|||||||
border: `2px solid ${token.colorPrimary}`, boxShadow: " 0px 2px 9px 0px rgba(0,0,0,0.16)"
|
border: `2px solid ${token.colorPrimary}`, boxShadow: " 0px 2px 9px 0px rgba(0,0,0,0.16)"
|
||||||
}
|
}
|
||||||
const cardStyle: React.CSSProperties = {
|
const cardStyle: React.CSSProperties = {
|
||||||
...(size === 'large' ? { height: 931 } : { height: 456, cursor: 'pointer' }),
|
|
||||||
...(size === 'small' && selectedWindowKey === windowKey ? selectedBorderStyle : {})
|
...(size === 'small' && selectedWindowKey === windowKey ? selectedBorderStyle : {})
|
||||||
};
|
};
|
||||||
const videoPlayerCardStyle = size === 'small' ? { width: "calc(50% - 20px)" } : { flex: 1 }
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isWindowLoading && (videoSrc || imageKey)) {
|
if (!isWindowLoading && (videoSrc || imageKey)) {
|
||||||
let contentElement: JSX.Element | null = null;
|
let contentElement: JSX.Element | null = null;
|
||||||
@ -64,15 +59,14 @@ export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
|||||||
|
|
||||||
} else if (imageKey) {
|
} else if (imageKey) {
|
||||||
contentElement = (
|
contentElement = (
|
||||||
|
<div style={{ width: "100%", height: "100%" }}>
|
||||||
<div style={{ width: "100%", height: "100%", display: 'block' }}>
|
|
||||||
<CropperImage
|
<CropperImage
|
||||||
// editAble={true}
|
// editAble={true}
|
||||||
|
selectAble={false}
|
||||||
odList={odRectDefault}
|
odList={odRectDefault}
|
||||||
url={imageKey}
|
url={imageKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setCardContent(contentElement);
|
setCardContent(contentElement);
|
||||||
@ -80,10 +74,10 @@ export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
|||||||
setCardContent(null)
|
setCardContent(null)
|
||||||
}
|
}
|
||||||
}, [showType, imageKey, videoSrc, isWindowLoading]);
|
}, [showType, imageKey, videoSrc, isWindowLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={componentName} onClick={() => { handleWindowClick?.(windowKey) }} style={videoPlayerCardStyle}>
|
<div className={componentName} onClick={() => { handleWindowClick?.(windowKey) }} >
|
||||||
<Card
|
<Card
|
||||||
|
className={`${size === 'large' ? `${componentName}-large` : `${componentName}-small`}`}
|
||||||
title={
|
title={
|
||||||
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||||
<div>{title}</div>
|
<div>{title}</div>
|
||||||
@ -94,7 +88,7 @@ export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Space>}
|
</Space>}
|
||||||
style={{ display: "flex", flexDirection: "column", borderRadius: 4, overflow: "hidden", ...cardStyle }}
|
style={{ display: "flex", flexDirection: "column", borderRadius: 4, overflow: "hidden", ...cardStyle }}
|
||||||
bodyStyle={{ flex: 1 }}
|
styles={{ body: { flex: 1 } }}
|
||||||
{...cardProps}
|
{...cardProps}
|
||||||
>
|
>
|
||||||
{cardContent ? (
|
{cardContent ? (
|
||||||
@ -102,7 +96,7 @@ export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
|
|||||||
{cardContent}
|
{cardContent}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ backgroundColor: '#000', height: '100%', display: 'flex', padding: '20px', boxSizing: 'border-box' }}>
|
<div className={`${componentName}-error`} >
|
||||||
{
|
{
|
||||||
isWindowLoading ?
|
isWindowLoading ?
|
||||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
@ -1,4 +1,21 @@
|
|||||||
.zhst-biz-video-player-card {
|
.zhst-biz-video-player-card {
|
||||||
|
&-large {
|
||||||
|
height: 931px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-small {
|
||||||
|
height: 456px;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
background-color: #000;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card-head {
|
.ant-card-head {
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useImperativeHandle, useRef, useState, forwardRef, useContext } from 'react';
|
import React, { useImperativeHandle, useRef, useState, forwardRef, useContext } from 'react';
|
||||||
import { Modal, ModalProps, Space, SpaceProps } from 'antd';
|
import { Modal, ModalProps, Space, SpaceProps, theme } from '@zhst/meta';
|
||||||
import theme from 'antd/lib/theme';
|
|
||||||
import { DownloadOutlined } from '@ant-design/icons';
|
import { DownloadOutlined } from '@ant-design/icons';
|
||||||
import { ConfigProvider, CropperImage } from '@zhst/meta';
|
import { ConfigProvider, CropperImage } from '@zhst/meta';
|
||||||
|
|
||||||
@ -44,8 +43,6 @@ export const ViewLargerImageModal = forwardRef<ViewLargerImageModalRef, ViewLarg
|
|||||||
const { getPrefixCls } = useContext(ConfigContext);
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
const { prefixCls: customizePrefixCls, modalProps, downloadImg, imgStyle, title = '预警大图', downloadText = '下载大图', spaceProps } = props
|
const { prefixCls: customizePrefixCls, modalProps, downloadImg, imgStyle, title = '预警大图', downloadText = '下载大图', spaceProps } = props
|
||||||
const componentName = getPrefixCls('biz-warning-larger-image', customizePrefixCls);
|
const componentName = getPrefixCls('biz-warning-larger-image', customizePrefixCls);
|
||||||
|
|
||||||
|
|
||||||
const { useToken } = theme
|
const { useToken } = theme
|
||||||
const { token } = useToken()
|
const { token } = useToken()
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
@ -85,8 +82,7 @@ export const ViewLargerImageModal = forwardRef<ViewLargerImageModalRef, ViewLarg
|
|||||||
{...modalProps}
|
{...modalProps}
|
||||||
>
|
>
|
||||||
<Space size={0} {...spaceProps}>
|
<Space size={0} {...spaceProps}>
|
||||||
<div className={`${componentName}-left-img`}>
|
<div className={`${componentName}-left-img`} style={{ ...imgStyle }}>
|
||||||
<div style={{ width: 789, height: 444, ...imgStyle }}>
|
|
||||||
<CropperImage
|
<CropperImage
|
||||||
// editAble={false}
|
// editAble={false}
|
||||||
// selectAble={false}
|
// selectAble={false}
|
||||||
@ -94,7 +90,6 @@ export const ViewLargerImageModal = forwardRef<ViewLargerImageModalRef, ViewLarg
|
|||||||
url={imageKey}
|
url={imageKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className='right-context'>
|
<div className='right-context'>
|
||||||
{warningData?.map(({ label, value }) => (
|
{warningData?.map(({ label, value }) => (
|
||||||
<div key={label} >
|
<div key={label} >
|
||||||
@ -102,7 +97,7 @@ export const ViewLargerImageModal = forwardRef<ViewLargerImageModalRef, ViewLarg
|
|||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{imageKey && downloadImg && <div className='img-download' style={{ color: token.colorPrimary }} onClick={() => downloadImg?.(imageKey)} ><DownloadOutlined /><span style={{ paddingLeft: 3 }}>{downloadText}</span></div>}
|
{imageKey && downloadImg && <div className='img-download' style={{ color: token.colorPrimary }} onClick={() => downloadImg?.(imageKey)} ><DownloadOutlined /><span className='img-download-text'>{downloadText}</span></div>}
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
.zhst-biz-warning-larger-image {
|
.zhst-biz-warning-larger-image {
|
||||||
font-family: MicrosoftYaHei;
|
font-family: MicrosoftYaHei;
|
||||||
|
|
||||||
|
&-left-img {
|
||||||
|
width: 789px;
|
||||||
|
height: 444px;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-modal-content {
|
.ant-modal-content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 492px;
|
height: 492px;
|
||||||
@ -52,6 +57,10 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.img-download-text {
|
||||||
|
padding-left: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { Card, Space, Divider, CardProps } from 'antd';
|
import { Card, Space, Divider, CardProps, theme } from '@zhst/meta';
|
||||||
import { theme } from 'antd/lib';
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { ConfigProvider, CropperImage } from '@zhst/meta';
|
import { ConfigProvider, CropperImage } from '@zhst/meta';
|
||||||
|
|
||||||
import './index.less'
|
import './index.less'
|
||||||
export interface IRecord {
|
export interface IRecord {
|
||||||
|
|
||||||
@ -100,12 +98,14 @@ export const WarningRecordCard: React.FC<WarningRecordCardProps> = (props) => {
|
|||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onRecordClick?.(record);
|
onRecordClick?.(record);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={componentName} key={id} onClick={handleClick} style={style}>
|
<div className={componentName} key={id} onClick={handleClick} style={style}>
|
||||||
<Card
|
<Card
|
||||||
cover={
|
className={`${componentName}-card`}
|
||||||
<div style={{ width: 336, height: 203, ...imgStyle }}>
|
style={{ ...selectedCardStyle, ...cardStyle }}
|
||||||
|
{...cardProps}
|
||||||
|
>
|
||||||
|
<div className={`${componentName}-card-img`} style={{ ...imgStyle }}>
|
||||||
<CropperImage
|
<CropperImage
|
||||||
// 无法触发 图片点击时间需要 加 selectAble
|
// 无法触发 图片点击时间需要 加 selectAble
|
||||||
selectAble={false}
|
selectAble={false}
|
||||||
@ -113,10 +113,7 @@ export const WarningRecordCard: React.FC<WarningRecordCardProps> = (props) => {
|
|||||||
url={imageKey}
|
url={imageKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div style={{ display: 'flex' }}>
|
||||||
style={{ width: 356, height: 302, padding: 10, borderRadius: 4, ...selectedCardStyle, ...cardStyle }}
|
|
||||||
{...cardProps}
|
|
||||||
>
|
|
||||||
<div className={`${componentName}-left-context`}>
|
<div className={`${componentName}-left-context`}>
|
||||||
<div className={`${componentName}-left-context-warning-type`}>{warningType}</div>
|
<div className={`${componentName}-left-context-warning-type`}>{warningType}</div>
|
||||||
<Space size={0} split={<Divider type="vertical" />}>
|
<Space size={0} split={<Divider type="vertical" />}>
|
||||||
@ -129,6 +126,9 @@ export const WarningRecordCard: React.FC<WarningRecordCardProps> = (props) => {
|
|||||||
<div className={`${componentName}-left-context-warning-time`}>{warningTimeShow}</div>
|
<div className={`${componentName}-left-context-warning-time`}>{warningTimeShow}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${componentName}-cabietInfo`} >{cabietText}</div>
|
<div className={`${componentName}-cabietInfo`} >{cabietText}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ import { Space } from 'antd';
|
|||||||
// 例如 后端返回这样的数据结构
|
// 例如 后端返回这样的数据结构
|
||||||
const backEndData = [
|
const backEndData = [
|
||||||
{
|
{
|
||||||
imageKey : 'https://i.yourimageshare.com/lRHiD2UnAT.png',
|
imageKey: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
|
||||||
id: '1561561',
|
id: '1561561',
|
||||||
warningType: '火焰识别',
|
warningType: '火焰识别',
|
||||||
boxId: '2',
|
boxId: '2',
|
||||||
@ -24,7 +24,7 @@ const backEndData = [
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
imageKey : 'https://i.yourimageshare.com/lRHiD2UnAT.png',
|
imageKey: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
|
||||||
id: '156156155',
|
id: '156156155',
|
||||||
warningType: '火焰识别',
|
warningType: '火焰识别',
|
||||||
boxId: '1',
|
boxId: '1',
|
||||||
@ -46,7 +46,7 @@ const backEndData = [
|
|||||||
// 前端处理数据结构
|
// 前端处理数据结构
|
||||||
const dataSource = backEndData.map(o => {
|
const dataSource = backEndData.map(o => {
|
||||||
return {
|
return {
|
||||||
imageKey : o.imageKey ,
|
imageKey: o.imageKey,
|
||||||
id: o.id,
|
id: o.id,
|
||||||
warningType: o.warningType,
|
warningType: o.warningType,
|
||||||
boxId: o.boxId,
|
boxId: o.boxId,
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
.zhst-biz-warning-record-card {
|
.zhst-biz-warning-record-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
font-family: MicrosoftYaHei;
|
|
||||||
line-height: 19px;
|
|
||||||
display: flex;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-bordered {
|
.ant-card-bordered {
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-cover-img img {
|
&-card {
|
||||||
width: 336px;
|
border-radius: 4px;
|
||||||
|
max-width: 380px;
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
width: 356px ;
|
||||||
height: 203px;
|
height: 203px;
|
||||||
border-radius: 0,
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 10px;
|
||||||
|
font-family: MicrosoftYaHei;
|
||||||
|
line-height: 19px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
&-left-context {
|
&-left-context {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
@ -1,75 +1,57 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC, ReactNode, useContext } from 'react';
|
||||||
import { Tabs, TabsProps } from 'antd'
|
import { ConfigProvider, Tabs, TabsProps } from '@zhst/meta';
|
||||||
import BoxPanel from './components/boxPanel';
|
import BoxPanel from './components/boxPanel';
|
||||||
import type { BoxPanelProps } from './components/boxPanel';
|
import type { BoxPanelProps } from './components/boxPanel';
|
||||||
|
import './index.less'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export interface BoxSelectTreeProps extends BoxPanelProps {
|
export interface BoxSelectTreeProps extends BoxPanelProps {
|
||||||
onTabChange?: (e: any) => void
|
onTabChange?: (e: any) => void
|
||||||
tabsProps?: TabsProps
|
tabsProps?: TabsProps
|
||||||
|
prefixCls?: string;
|
||||||
|
footer?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { ConfigContext } = ConfigProvider
|
||||||
|
|
||||||
const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
|
const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
data,
|
prefixCls: customizePrefixCls,
|
||||||
boxDataSource = [],
|
|
||||||
onTabChange,
|
|
||||||
onSearch,
|
|
||||||
onItemCheck,
|
|
||||||
onItemSelect,
|
|
||||||
onBoxBatchDelete,
|
|
||||||
onBoxDelete,
|
|
||||||
onCreateSubmit,
|
|
||||||
onClockClick,
|
|
||||||
onImport,
|
|
||||||
onCreate,
|
|
||||||
tabsProps,
|
tabsProps,
|
||||||
searchInputProps,
|
onTabChange,
|
||||||
treeProps,
|
footer,
|
||||||
customImport,
|
...rest
|
||||||
showOptions,
|
|
||||||
extraBtns,
|
|
||||||
} = props
|
} = props
|
||||||
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
|
const componentName = getPrefixCls('biz-box-select-tree', customizePrefixCls);
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
label: <div style={{ textAlign:'center', width: '160px' }} >盒子</div>,
|
label: <div className={classNames(componentName + '-tab')} style={{ textAlign:'center' }} >盒子</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '2',
|
key: '2',
|
||||||
label: <div style={{ textAlign:'center', width: '160px' }} >盒子组</div>,
|
label: <div className={classNames(componentName + '-tab')} style={{ textAlign:'center' }} >盒子组</div>,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='box-select-tree'>
|
<div className={componentName}>
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey="1"
|
defaultActiveKey="1"
|
||||||
centered
|
centered
|
||||||
items={items}
|
items={items}
|
||||||
onChange={onTabChange}
|
onChange={onTabChange}
|
||||||
tabBarGutter={0}
|
tabBarGutter={0}
|
||||||
|
// @ts-ignore
|
||||||
indicator={{ size: (origin) => origin, align: 'center' }}
|
indicator={{ size: (origin) => origin, align: 'center' }}
|
||||||
{...tabsProps}
|
{...tabsProps}
|
||||||
/>
|
/>
|
||||||
<BoxPanel
|
<BoxPanel
|
||||||
searchInputProps={searchInputProps}
|
{...rest}
|
||||||
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}
|
|
||||||
/>
|
/>
|
||||||
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
.zhst-biz-box-select-tree-panel {
|
||||||
|
&-search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 12px;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-btns {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-btns {
|
||||||
|
padding: 6px 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-common {
|
||||||
|
flex: 1;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-import {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-divider {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +1,165 @@
|
|||||||
import React, { FC, useState, useRef } from 'react';
|
import React, { FC, useState, useContext, ReactNode } from 'react';
|
||||||
import{ Button, Divider, Input, Space, TreeDataNode } from 'antd'
|
import { ModalFormProps } from '@ant-design/pro-components'
|
||||||
import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } 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 { ClockCircleOutlined, CloseCircleOutlined, DiffOutlined, FolderAddOutlined, ImportOutlined, SwitcherOutlined } from '@ant-design/icons'
|
||||||
import type { TreeProps, InputProps } from 'antd';
|
import classNames from 'classnames';
|
||||||
import type { BoxTreeProps } from '../../../tree';
|
import './index.less'
|
||||||
import TreeTransferModal from '../../../treeTransferModal'
|
|
||||||
import BoxTree from '../../../tree';
|
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 {
|
export interface BoxPanelProps {
|
||||||
searchInputProps?: InputProps
|
searchInputProps?: InputProps
|
||||||
showOptions?: boolean
|
showOptions?: boolean
|
||||||
treeProps?: Partial<BoxTreeProps>
|
treeProps?: Partial<BoxTreeProps>
|
||||||
data: TreeDataNode[]
|
data: TreeDataNode[]
|
||||||
boxDataSource: TreeDataNode[]
|
|
||||||
handleImport?: () => void
|
|
||||||
onSearch?: (e: any) => void
|
onSearch?: (e: any) => void
|
||||||
onItemCheck?: TreeProps['onCheck']
|
onItemCheck?: TreeProps['onCheck']
|
||||||
onItemSelect?: TreeProps['onSelect']
|
onItemSelect?: TreeProps['onSelect']
|
||||||
|
/**
|
||||||
|
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||||||
|
*/
|
||||||
onBoxBatchDelete?: (data?: any) => void
|
onBoxBatchDelete?: (data?: any) => void
|
||||||
onBoxDelete?: (data?: any) => void
|
/**
|
||||||
|
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||||||
|
*/
|
||||||
onCreateSubmit?: ModalFormProps['onFinish']
|
onCreateSubmit?: ModalFormProps['onFinish']
|
||||||
onClockClick?: () => void
|
/**
|
||||||
|
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||||||
|
*/
|
||||||
|
onClockClick?: () => void //
|
||||||
|
/**
|
||||||
|
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||||||
|
*/
|
||||||
onImport?: () => void
|
onImport?: () => void
|
||||||
|
/**
|
||||||
|
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||||||
|
*/
|
||||||
onBatch?: () => void
|
onBatch?: () => void
|
||||||
|
/**
|
||||||
|
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||||||
|
*/
|
||||||
onCreate?: () => void
|
onCreate?: () => void
|
||||||
customImport?: any
|
customImport?: ReactNode | string // 自定义搜索栏边上的过滤图标
|
||||||
extraBtns?: any
|
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 BoxPanel: FC<BoxPanelProps> = (props) => {
|
||||||
|
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
|
||||||
const {
|
const {
|
||||||
searchInputProps,
|
searchInputProps,
|
||||||
showOptions = true,
|
showOptions = true,
|
||||||
extraBtns,
|
extraBtns,
|
||||||
|
noFilter,
|
||||||
data = [],
|
data = [],
|
||||||
onSearch,
|
onSearch,
|
||||||
treeProps,
|
treeProps,
|
||||||
onItemCheck,
|
onItemCheck,
|
||||||
onItemSelect,
|
onItemSelect,
|
||||||
onCreateSubmit,
|
|
||||||
onBoxBatchDelete,
|
onBoxBatchDelete,
|
||||||
onBoxDelete,
|
|
||||||
onClockClick,
|
onClockClick,
|
||||||
onImport,
|
onImport,
|
||||||
onBatch,
|
onBatch,
|
||||||
onCreate,
|
onCreate,
|
||||||
boxDataSource,
|
showSearchBar = true,
|
||||||
customImport
|
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: customFilter
|
||||||
} = props
|
} = props
|
||||||
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
|
|
||||||
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
const [boxChoiceOpen, setBoxChoiceOpen] = useState(false)
|
const componentName = getPrefixCls('biz-box-select-tree-panel', customizePrefixCls);
|
||||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
|
||||||
const createFormRef = useRef<
|
|
||||||
ProFormInstance<{
|
|
||||||
name: string;
|
|
||||||
boxList?: any[];
|
|
||||||
}>
|
|
||||||
>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改选择状态
|
* 修改选择状态
|
||||||
@ -67,153 +169,163 @@ const BoxPanel: FC<BoxPanelProps> = (props) => {
|
|||||||
setIsTreeCheckable(pre => !pre)
|
setIsTreeCheckable(pre => !pre)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
|
/**
|
||||||
let _targetItems: TreeDataNode[] = []
|
* 初始化拓展 filter
|
||||||
setCheckedKeys(keys)
|
* @param _list
|
||||||
info.checkedNodes.forEach(o => {
|
* @returns
|
||||||
o.isLeaf && _targetItems.push(o)
|
*/
|
||||||
})
|
const initFilter = (_list?: BoxPanelProps['filterList']) => {
|
||||||
setTargetItems(_targetItems)
|
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>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 初始化拓展 filter
|
||||||
* @param key
|
* @param _list
|
||||||
* @param param1
|
* @returns
|
||||||
*/
|
*/
|
||||||
const onItemDelete = (key: any, { keys }: any) => {
|
const initOptions = (_list?: BoxPanelProps['optionList']) => {
|
||||||
setCheckedKeys(pre => {
|
return _list?.map((item, idx) => (
|
||||||
const newKeys = pre.filter(_key => !keys.includes(_key))
|
<>
|
||||||
return newKeys
|
<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 (
|
return (
|
||||||
<div style={{ padding: '0 16px' }}>
|
<div className={componentName}>
|
||||||
{/* 盒子选择弹框 */}
|
{/* 搜索栏 */}
|
||||||
<TreeTransferModal
|
{showSearchBar && (
|
||||||
open={boxChoiceOpen}
|
<div className={classNames(componentName + '-search')}>
|
||||||
onCancel={() => setBoxChoiceOpen(false)}
|
<Input
|
||||||
onRadioChange={(e) => console.log('radio', e.target.value)} // 顶部 radio 事件
|
className={classNames(componentName + '-search-input')}
|
||||||
dataSource={boxDataSource} // 数据源
|
size='middle'
|
||||||
targetItems={targetItems} // 右侧选中项
|
onChange={(e) => onSearch?.(e)}
|
||||||
checkedKeys={checkedKeys} // 左侧选中
|
placeholder='请输入盒子名称'
|
||||||
onReset={onBoxChoiceReset} // 重置按钮事件
|
{...searchInputProps}
|
||||||
onOk={onBoxChoiceOk} // 确定按钮事件
|
|
||||||
onTreeCheck={onTreeCheck} // 树check选中事件
|
|
||||||
onItemDelete={onItemDelete} // 右侧点击删除事件
|
|
||||||
/>
|
/>
|
||||||
<Space size={12} direction='vertical' style={{ width: '100%' }}>
|
{customFilter || (!noFilter && (
|
||||||
<Space size={4} style={{ width: '100%', justifyContent: 'space-between' }} >
|
<div
|
||||||
<Input size='middle' onChange={(e) => onSearch?.(e)} placeholder='请输入盒子名称' {...searchInputProps} />
|
className={classNames(componentName + '-search-btns')}
|
||||||
{customImport || (
|
>
|
||||||
<>
|
{/* @ts-ignore */}
|
||||||
<Button type="text" onClick={() => onBatch?.() || handleCheckable()} icon={isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />} />
|
{initFilter(filterList)}
|
||||||
<Button type="text" onClick={() => onClockClick?.()} icon={<ClockCircleOutlined />} />
|
</div>
|
||||||
</>
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Space>
|
{/* 默认操作按钮 */}
|
||||||
{/* 是否显示操作按钮 */}
|
|
||||||
{showOptions && (
|
{showOptions && (
|
||||||
<>
|
<>
|
||||||
<Space align='center'>
|
<div className={classNames(componentName + '-btns')}>
|
||||||
<Button type='text' style={{ padding: '4px 8px' }} onClick={() => onImport?.()} icon={<ImportOutlined />} >导入盒子</Button>
|
{initOptions(optionList)}
|
||||||
<Divider type="vertical" style={{ margin: '8px 0' }} />
|
</div>
|
||||||
{onCreate ?
|
|
||||||
(
|
|
||||||
<Button onClick={onCreate} type='text' style={{ padding: '4px 8px' }} icon={<FolderAddOutlined />} >新建组</Button>
|
|
||||||
) : (
|
|
||||||
<ModalForm<{
|
|
||||||
name: string
|
|
||||||
boxList?: any[]
|
|
||||||
}>
|
|
||||||
width={'600px'}
|
|
||||||
open={onCreate ? false : undefined}
|
|
||||||
formRef={createFormRef}
|
|
||||||
title="新建组"
|
|
||||||
modalProps={{ destroyOnClose: true }}
|
|
||||||
layout='horizontal'
|
|
||||||
labelCol={{ span: 6 }}
|
|
||||||
wrapperCol={{ span: 18 }}
|
|
||||||
trigger={<Button type='text' style={{ padding: '4px 8px' }} 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 type="vertical" style={{ margin: '8px 0' }} />
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
<Button danger type='text' style={{ padding: '4px 8px' }} icon={<CloseCircleOutlined />} disabled={treeProps?.checkedKeys?.length <= 0} onClick={onBoxBatchDelete} >删除</Button>
|
|
||||||
</Space>
|
|
||||||
<Divider style={{ margin: 0 }} />
|
<Divider style={{ margin: 0 }} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{extraBtns}
|
{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
|
<BoxTree
|
||||||
treeCheckable={isTreeCheckable}
|
className={classNames(componentName + '-tree')}
|
||||||
data={data}
|
checkable={isTreeCheckable}
|
||||||
onItemSelect={onItemSelect}
|
treeData={data}
|
||||||
onItemCheck={onItemCheck}
|
blockNode
|
||||||
onItemDelete={onBoxDelete}
|
onSelect={onItemSelect}
|
||||||
|
onCheck={onItemCheck}
|
||||||
{...treeProps}
|
{...treeProps}
|
||||||
/>
|
/>
|
||||||
</Space>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { BoxSelectTree } from '@zhst/biz';
|
import { BoxSelectTree } from '@zhst/biz';
|
||||||
import { treeData, boxDataSource } from './mock'
|
import { treeData, boxDataSource } from './mock'
|
||||||
import { Select, TreeProps, Modal, Checkbox } from 'antd';
|
import { Select, TreeProps, Modal, Checkbox, Button } from '@zhst/meta';
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
@ -17,7 +17,6 @@ const demo = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onBoxBatchDelete = () => {
|
const onBoxBatchDelete = () => {
|
||||||
console.log('盒子批量删除', checkedKeys)
|
|
||||||
modal.warning({
|
modal.warning({
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
@ -56,6 +55,7 @@ const demo = () => {
|
|||||||
addonBefore: (
|
addonBefore: (
|
||||||
<Select
|
<Select
|
||||||
value={searchType}
|
value={searchType}
|
||||||
|
dropdownMatchSelectWidth={false}
|
||||||
onChange={_type => {
|
onChange={_type => {
|
||||||
setSearchType(_type)
|
setSearchType(_type)
|
||||||
setSearchVal('')
|
setSearchVal('')
|
||||||
@ -80,6 +80,7 @@ const demo = () => {
|
|||||||
onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data),
|
onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data),
|
||||||
checkedKeys,
|
checkedKeys,
|
||||||
}}
|
}}
|
||||||
|
footer={<Button block >加载更多</Button>}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
68
packages/biz/src/boxSelectTree/demo/customFilter.tsx
Normal file
68
packages/biz/src/boxSelectTree/demo/customFilter.tsx
Normal 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;
|
33
packages/biz/src/boxSelectTree/demo/customOptions.tsx
Normal file
33
packages/biz/src/boxSelectTree/demo/customOptions.tsx
Normal 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;
|
@ -2,10 +2,12 @@ import React, { useState } from 'react';
|
|||||||
import { BoxSelectTree } from '@zhst/biz';
|
import { BoxSelectTree } from '@zhst/biz';
|
||||||
import { treeData, boxDataSource } from './mock'
|
import { treeData, boxDataSource } from './mock'
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
|
import { ClockCircleOutlined, DiffOutlined, SwitcherOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const demo = () => {
|
const demo = () => {
|
||||||
const [activeKey, setActiveKey] = useState('1')
|
const [activeKey, setActiveKey] = useState('1')
|
||||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||||
|
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
|
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
|
||||||
@ -13,10 +15,23 @@ const demo = () => {
|
|||||||
data={activeKey === '1' ? treeData : boxDataSource}
|
data={activeKey === '1' ? treeData : boxDataSource}
|
||||||
boxDataSource={boxDataSource}
|
boxDataSource={boxDataSource}
|
||||||
showOptions={false}
|
showOptions={false}
|
||||||
extraBtns={<Button type="dashed" style={{ color: 'green' }}>自定义按钮</Button>}
|
extraBtns={<div><Button type="dashed" style={{ color: 'red' }}>自定义区域</Button> Hello Lambo</div>}
|
||||||
tabsProps={{
|
tabsProps={{
|
||||||
activeKey,
|
activeKey,
|
||||||
}}
|
}}
|
||||||
|
filterList={[
|
||||||
|
{
|
||||||
|
label: '多选',
|
||||||
|
key: 'multi',
|
||||||
|
icon: isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />,
|
||||||
|
onClick: () => setIsTreeCheckable(pre => !pre)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '时钟',
|
||||||
|
key: 'clock',
|
||||||
|
icon: <ClockCircleOutlined />,
|
||||||
|
}
|
||||||
|
]}
|
||||||
treeProps={{
|
treeProps={{
|
||||||
checkedKeys
|
checkedKeys
|
||||||
}}
|
}}
|
||||||
|
27
packages/biz/src/boxSelectTree/demo/noFilter.tsx
Normal file
27
packages/biz/src/boxSelectTree/demo/noFilter.tsx
Normal 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;
|
@ -30,7 +30,7 @@ const demo = () => {
|
|||||||
tabsProps={{
|
tabsProps={{
|
||||||
activeKey,
|
activeKey,
|
||||||
}}
|
}}
|
||||||
customImport={<Button type="text" icon={<FilterOutlined />} />}
|
customImport={<div style={{ color: 'red' }}>自定义区域</div>}
|
||||||
searchInputProps={{
|
searchInputProps={{
|
||||||
addonBefore: (
|
addonBefore: (
|
||||||
<Select
|
<Select
|
||||||
@ -43,7 +43,6 @@ const demo = () => {
|
|||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
{ value: '1', label: '盒子' },
|
{ value: '1', label: '盒子' },
|
||||||
{ value: '2', label: '盒子组' }
|
|
||||||
].map(item => (
|
].map(item => (
|
||||||
<Option value={item.value}>{item.label}</Option>
|
<Option value={item.value}>{item.label}</Option>
|
||||||
))}
|
))}
|
||||||
|
84
packages/biz/src/boxSelectTree/demo/withTagPanel.tsx
Normal file
84
packages/biz/src/boxSelectTree/demo/withTagPanel.tsx
Normal 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;
|
5
packages/biz/src/boxSelectTree/index.less
Normal file
5
packages/biz/src/boxSelectTree/index.less
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.zhst-biz-box-select-tree {
|
||||||
|
&-tab {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,10 @@ group:
|
|||||||
<code src="./demo/basic.tsx">基本用法</code>
|
<code src="./demo/basic.tsx">基本用法</code>
|
||||||
<code src="./demo/extraBtns.tsx">自定义其它按钮</code>
|
<code src="./demo/extraBtns.tsx">自定义其它按钮</code>
|
||||||
<code src="./demo/noOptions.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>
|
<code src="./demo/async.tsx">异步加载数据</code>
|
||||||
|
|
||||||
## API
|
## API
|
||||||
@ -22,17 +26,34 @@ group:
|
|||||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| data | 数据源 | Array[] | [] | - |
|
| 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 | - | - |
|
| onSearch | 搜索监听 | function: (e) => void | - | - |
|
||||||
| onItemSelect | 树当前选中(单选) | function: (e) => void | - | - |
|
| onItemSelect | 树当前选中(单选) | function: (e) => void | - | - |
|
||||||
| onItemCheck | 树选择(支持多选) | function: (e) => void | - | - |
|
| onItemCheck | 树选择(支持多选) | function: (e) => void | - | - |
|
||||||
| tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - |
|
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
|
||||||
| searchInputProps | 搜索框的Props | antd的Input组件 | - | - |
|
| onBatch | 多选 | function: (e) => void | - | 0.23.0 以后弃用 |
|
||||||
| onTabChange | tab切换监听 | function: (e) => void | - | - |
|
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
|
||||||
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | - |
|
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | 0.23.0 以后弃用 |
|
||||||
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | - |
|
| onImport | 监听导入盒子点击事件 | function: () => void | - | 0.23.0 以后弃用 |
|
||||||
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | - |
|
| onClockClick | 监听时钟点击事件 | function: () => void | - | 0.23.0 以后弃用 |
|
||||||
| onImport | 监听导入盒子点击事件 | function: () => void | - | - |
|
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | 0.23.0 以后弃用 |
|
||||||
| onClockClick | 监听时钟点击事件 | function: () => void | - | - |
|
|
||||||
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | - |
|
|
||||||
| showOptions | 展示其它功能按钮 | boolean | true | - |
|
|
||||||
|
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
export { default as BigImageModal } from './BigImageModal'
|
export { default as BigImageModal } from './BigImageModal'
|
||||||
export type { BigImageModalProps } 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 { default as BoxSelectTree } from './boxSelectTree'
|
||||||
export type { BoxSelectTreeProps } from './boxSelectTree'
|
export type { BoxSelectTreeProps } from './boxSelectTree'
|
||||||
export { default as Tree } from './tree'
|
export { default as Tree } from './tree'
|
||||||
export type { BoxTreeProps, TreeData } 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 { default as TreeTransfer } from './treeTransfer'
|
||||||
export type { TreeTransferProps } from './treeTransfer'
|
export type { TreeTransferProps } from './treeTransfer'
|
||||||
export { default as TreeTransferModal } from './treeTransferModal'
|
export { default as TreeTransferModal } from './treeTransferModal'
|
||||||
export type { TreeTransferModalProps } from './treeTransferModal'
|
export type { TreeTransferModalProps } from './treeTransferModal'
|
||||||
export { default as WarningRecordCard } from './WarningRecordCard'
|
export { default as WarningRecordCard } from './WarningRecordCard'
|
||||||
|
export { CommonCard } from './CustomCard'
|
||||||
|
export type {
|
||||||
|
CommonCardProps,
|
||||||
|
CommonCardRefProps
|
||||||
|
} from './CustomCard'
|
||||||
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard'
|
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard'
|
||||||
export { default as OdModal } from './odModal'
|
export { default as OdModal } from './odModal'
|
||||||
export type { ODModalProps } from './odModal'
|
export type { ODModalProps } from './odModal'
|
||||||
|
@ -2,24 +2,29 @@
|
|||||||
* Created by jiangzhixiong
|
* Created by jiangzhixiong
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { forwardRef, ReactNode, useContext, useImperativeHandle, useRef } from 'react'
|
import React, { forwardRef, ReactNode, useContext, useEffect, useImperativeHandle, useRef } from 'react'
|
||||||
import { ConfigProvider } from '@zhst/meta';
|
import { ConfigProvider, Spin, SpinProps } from '@zhst/meta';
|
||||||
import { Divider, Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
import { SearchCard, SearchCardProps } from './components';
|
import { useSize } from '@zhst/hooks';
|
||||||
import './index.less'
|
import './index.less'
|
||||||
import { Idata } from './components/SearchCard';
|
|
||||||
|
|
||||||
const { ConfigContext } = ConfigProvider
|
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'
|
type?: 'custom' | 'auto'
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
height?: number;
|
height?: number;
|
||||||
itemRender?: (data?: any) => React.ReactNode
|
itemRender?: (data?: IData, index?: number) => React.ReactNode
|
||||||
loading?: boolean; //
|
loading?: boolean; //
|
||||||
data: Idata[];
|
data: IData[];
|
||||||
targetId?: string; // 滚动列表 ID
|
targetId?: string; // 滚动列表 ID
|
||||||
loadMore?: (data?: any) => any;
|
loadMore?: (data?: any) => any;
|
||||||
params?: {
|
params?: {
|
||||||
@ -28,63 +33,66 @@ export interface InfiniteListProps {
|
|||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
endMessage?: ReactNode
|
endMessage?: ReactNode
|
||||||
loadingMessage?: ReactNode
|
loadingMessage?: ReactNode
|
||||||
onItemClick?: (data: any) => void;
|
loadingProps?: SpinProps
|
||||||
searchCardProps?: SearchCardProps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfiniteListRefProps {
|
export interface InfiniteListRefProps {
|
||||||
|
scrollViewSize?: { width: number; height: number }
|
||||||
|
listSize?: { width: number; height: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props, ref) => {
|
const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
prefixCls: customizePrefixCls,
|
||||||
height,
|
height = 600,
|
||||||
|
loading,
|
||||||
type = 'auto',
|
type = 'auto',
|
||||||
loadingMessage = <p style={{ textAlign: 'center' }}>加载中...</p>,
|
loadingMessage = <p style={{ textAlign: 'center' }}>加载中...</p>,
|
||||||
targetId = 'scrollableDiv',
|
targetId = 'scrollableDiv',
|
||||||
itemRender,
|
itemRender = (data) => <div>{data?.title}</div>,
|
||||||
hasMore,
|
hasMore,
|
||||||
onItemClick,
|
|
||||||
loadMore,
|
loadMore,
|
||||||
data = [],
|
data = [],
|
||||||
endMessage = <Divider plain>没有更多数据了...🤐</Divider>,
|
endMessage = <div style={{ textAlign: 'center' }} >没有更多数据了...🤐</div>,
|
||||||
searchCardProps
|
style,
|
||||||
|
loadingProps,
|
||||||
|
className
|
||||||
} = props
|
} = props
|
||||||
const { getPrefixCls } = useContext(ConfigContext);
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
const componentName = getPrefixCls('biz-infinite-list', customizePrefixCls);
|
const componentName = getPrefixCls('biz-infinite-list', customizePrefixCls);
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
|
scrollViewSize,
|
||||||
|
listSize
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Spin spinning={loading} {...loadingProps}>
|
||||||
<div
|
<div
|
||||||
id={targetId}
|
id={targetId}
|
||||||
className={classNames(componentName)}
|
className={classNames(componentName, className)}
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
style={{
|
style={{
|
||||||
height,
|
height,
|
||||||
overflow: 'auto',
|
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
|
<InfiniteScroll
|
||||||
|
// @ts-ignore
|
||||||
|
ref={scrollRef}
|
||||||
dataLength={data.length}
|
dataLength={data.length}
|
||||||
next={type === 'auto' ? loadMore! : () => {}}
|
next={type === 'auto' ? loadMore! : () => {}}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
@ -92,37 +100,12 @@ const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props,
|
|||||||
endMessage={endMessage}
|
endMessage={endMessage}
|
||||||
scrollableTarget={targetId}
|
scrollableTarget={targetId}
|
||||||
>
|
>
|
||||||
<Flex wrap='wrap' gap="small" className={classNames(componentName + 'items')}>
|
|
||||||
{data?.map((item, idx) => (
|
{data?.map((item, idx) => (
|
||||||
itemRender?.(item) || (
|
itemRender?.({ ...item, index: idx})
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className={classNames(componentName + 'items-item')}
|
|
||||||
onClick={() => {
|
|
||||||
onItemClick?.(item)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SearchCard
|
|
||||||
id={idx + 1}
|
|
||||||
data={item}
|
|
||||||
width="184px"
|
|
||||||
{...searchCardProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
))}
|
))}
|
||||||
</Flex>
|
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
{/* <div style={{ marginTop: 8 }}>
|
|
||||||
{!noMore && (
|
|
||||||
<Button onClick={loadMore} disabled={loadingMore}>
|
|
||||||
{loadingMore ? '加载中...' : '点击加载更多'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{noMore && <span>没有更多数据了</span>}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
</Spin>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by jiangzhixiong on 2024/04/28
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { default as SearchCard } from './SearchCard'
|
|
||||||
export type { SearchCardProps, SearchCardRefProps } from './SearchCard'
|
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { InfiniteList } from '@zhst/biz'
|
import { InfiniteList, CommonCard } from '@zhst/biz'
|
||||||
|
import { uniqueId } from '@zhst/func'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [data, setData] = useState([])
|
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')
|
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((body) => {
|
.then((body) => {
|
||||||
let res = body.results.map(o => {
|
let res = body.results.map((o, index) => {
|
||||||
return {
|
return {
|
||||||
|
id: uniqueId(),
|
||||||
|
sort: index + 1,
|
||||||
title: o.name.first,
|
title: o.name.first,
|
||||||
subtitle: o.name.last,
|
subtitle: o.name.last,
|
||||||
url: o.picture.large
|
url: o.picture.large
|
||||||
@ -36,14 +39,23 @@ export default () => {
|
|||||||
<InfiniteList
|
<InfiniteList
|
||||||
loading={loading}
|
loading={loading}
|
||||||
loadMore={loadMoreData}
|
loadMore={loadMoreData}
|
||||||
height={300}
|
height={1200}
|
||||||
hasMore={data.length < 100}
|
hasMore={data.length < 60}
|
||||||
data={data}
|
data={data}
|
||||||
onItemClick={_data => console.log('item点击:', _data)}
|
itemRender={(item) => {
|
||||||
searchCardProps={{
|
return (
|
||||||
onAdd: (_data) => console.log('新增', _data),
|
<CommonCard
|
||||||
onCreate: (_data) => console.log('创建', _data),
|
key={item.id}
|
||||||
onRemove: (_data) => console.log('删除', _data),
|
sort={item.sort}
|
||||||
|
data={item}
|
||||||
|
width="184px"
|
||||||
|
actions={[
|
||||||
|
<a>创建检索</a>,
|
||||||
|
<a>创建布控</a>,
|
||||||
|
<a>删除点位</a>
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -3,11 +3,11 @@ import { InfiniteList } from '@zhst/biz'
|
|||||||
import { Button, Input, Space } from 'antd'
|
import { Button, Input, Space } from 'antd'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState<any>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [params, setParams] = useState({})
|
const [params, setParams] = useState({})
|
||||||
|
|
||||||
const loadMoreData = (params?: { name: string; age?: number; sex: string; tel: number }) => {
|
const loadMoreData = (params?: any) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ export default () => {
|
|||||||
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
|
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((body) => {
|
.then((body) => {
|
||||||
let res = body.results.map(o => {
|
let res = body.results.map((o: { name: { first: any; last: any }; picture: { large: any } }) => {
|
||||||
return {
|
return {
|
||||||
title: o.name.first,
|
title: o.name.first,
|
||||||
subtitle: o.name.last,
|
subtitle: o.name.last,
|
||||||
@ -35,7 +35,7 @@ export default () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction='vertical'>
|
<Space direction='vertical' size={10} style={{ padding: '12px', border: '1px solid #ccc' }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Input placeholder='名称' onChange={(e) => setParams(pre => ({ ...pre, name: e.target.value }))} style={{ width: '120px' }} />
|
<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' }} />
|
<Input placeholder='年龄' onChange={(e) => setParams(pre => ({ ...pre, age: e.target.value }))} style={{ width: '120px' }} />
|
||||||
@ -49,9 +49,7 @@ export default () => {
|
|||||||
height={300}
|
height={300}
|
||||||
hasMore={data.length < 100}
|
hasMore={data.length < 100}
|
||||||
data={data}
|
data={data}
|
||||||
type="custom"
|
|
||||||
loadingMessage={<Button onClick={() => loadMoreData(params)}>加载更多</Button>}
|
loadingMessage={<Button onClick={() => loadMoreData(params)}>加载更多</Button>}
|
||||||
onItemClick={data => console.log('item点击:', data)}
|
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
|
@ -18,21 +18,20 @@ group:
|
|||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| data | 数据源 | Idata[] | [] | - |
|
| data | 数据源 | IData[] | [] | - |
|
||||||
|
| height | 无限滚动列表可视区高度 | number | 600 | - |
|
||||||
| loading | 数据源 | Array[] | [] | - |
|
| loading | 数据源 | Array[] | [] | - |
|
||||||
| data | 数据源 | Array[] | [] | - |
|
| dataLength | 数据数量 | number | [] | - |
|
||||||
| data | 数据源 | Array[] | [] | - |
|
| next | 下一页方法 | function | () => {} | - |
|
||||||
| data | 数据源 | Array[] | [] | - |
|
| hasMore | 是否还有更多 | boolean | false | - |
|
||||||
| data | 数据源 | Array[] | [] | - |
|
| loadingProps | 参考 antd-spin | spinProps | [] | - |
|
||||||
| data | 数据源 | Array[] | [] | - |
|
| itemRender | 自定义渲染项 | (IData) => ReactNode | - | - |
|
||||||
|
|
||||||
## Idata
|
## 设计思路
|
||||||
|
|
||||||
```js
|
无限滚动,同时支持:
|
||||||
interface Idata {
|
|
||||||
id?: string | number;
|
1. 自动、主动加载更多
|
||||||
url?: string; // 链接
|
2. 一屏没加载完,继续加载,直到填满屏幕:
|
||||||
title?: string; // 标题
|
- 需要第二次加载的内容是否为空,为空则停止加载
|
||||||
subtitle?: string; // 副标题
|
- 通过整体的page-height 和 浏览器可视区域
|
||||||
}
|
|
||||||
```
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React, { forwardRef, useContext, useImperativeHandle, useRef } from 'react';
|
import React, { forwardRef, useContext, useImperativeHandle, useRef } from 'react';
|
||||||
import { Button, Modal, ModalProps, Select, SelectProps, Space, theme } from 'antd';
|
import { ConfigProvider, CropperImage, Scanner, CropperImageProps, CropperImageRefProps, Button, Modal, ModalProps, Select, SelectProps, Space, theme } from '@zhst/meta'
|
||||||
import { ConfigProvider, CropperImage, Scanner, CropperImageProps, CropperImageRefProps } from '@zhst/meta'
|
|
||||||
import { IconFont } from '@zhst/icon'
|
import { IconFont } from '@zhst/icon'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
export interface ODModalProps extends ModalProps {
|
export interface ODModalProps extends ModalProps {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
@ -95,7 +96,7 @@ const ODModal = forwardRef<ODModalRefProps, ODModalProps>((props, ref) => {
|
|||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onOk={onOk}
|
onOk={onOk}
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
className={componentName}
|
rootClassName={componentName}
|
||||||
>
|
>
|
||||||
<Space size={12} direction='vertical' align='center' style={{ textAlign: 'center' }}>
|
<Space size={12} direction='vertical' align='center' style={{ textAlign: 'center' }}>
|
||||||
<Scanner style={{ width: odWidth, height: odHeight }} visible={scanning}>
|
<Scanner style={{ width: odWidth, height: odHeight }} visible={scanning}>
|
||||||
@ -118,7 +119,7 @@ const ODModal = forwardRef<ODModalRefProps, ODModalProps>((props, ref) => {
|
|||||||
<div>
|
<div>
|
||||||
选中类型:
|
选中类型:
|
||||||
<Select
|
<Select
|
||||||
style={{ width: '90px' }}
|
className={classNames(componentName + '-type')}
|
||||||
defaultValue={selectDefaultValue}
|
defaultValue={selectDefaultValue}
|
||||||
value={selectedType}
|
value={selectedType}
|
||||||
options={selectOptions}
|
options={selectOptions}
|
||||||
@ -129,7 +130,7 @@ const ODModal = forwardRef<ODModalRefProps, ODModalProps>((props, ref) => {
|
|||||||
<Button disabled={handSelectDisable} size='small' type="link" onClick={onHandSelect}><IconFont icon="icon-shoudongkuangxuan" /> 手动框选</Button>
|
<Button disabled={handSelectDisable} size='small' type="link" onClick={onHandSelect}><IconFont icon="icon-shoudongkuangxuan" /> 手动框选</Button>
|
||||||
<Button disabled={autoSelectDisable} size='small' type="link" onClick={onAutoSelect}><IconFont icon="icon-zidong" /> 自动框选</Button>
|
<Button disabled={autoSelectDisable} size='small' type="link" onClick={onAutoSelect}><IconFont icon="icon-zidong" /> 自动框选</Button>
|
||||||
<Button disabled={resetDisable} size='small' type="link" onClick={onReset}><IconFont icon="icon-zhongzhi3" /> 重置</Button>
|
<Button disabled={resetDisable} size='small' type="link" onClick={onReset}><IconFont icon="icon-zhongzhi3" /> 重置</Button>
|
||||||
{showRotateButton && <IconFont onIconClick={onRotate} styles={{ marginLeft: '6px' }} color={token.colorPrimary} icon="icon-xuanzhuan1" />}
|
{showRotateButton && <IconFont className={classNames(componentName + '-rotate')} onIconClick={onRotate} color={token.colorPrimary} icon="icon-xuanzhuan1" />}
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
9
packages/biz/src/odModal/index.less
Normal file
9
packages/biz/src/odModal/index.less
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.zhst-od-modal {
|
||||||
|
&-type {
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-rotate {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
|
// !! 已经弃用,改为使用meta/tree
|
||||||
import React, { FC, useState } from 'react';
|
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 { CloseOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons'
|
||||||
import { ModalForm, ProFormText } from '@ant-design/pro-components';
|
import classNames from 'classnames';
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
const componentName = 'zhst-biz-tree'
|
const componentName = 'zhst-biz-tree'
|
||||||
@ -30,7 +31,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
|
|||||||
showItemOption = true,
|
showItemOption = true,
|
||||||
treeCheckable = false,
|
treeCheckable = false,
|
||||||
onItemRename,
|
onItemRename,
|
||||||
onItemRenameFinish,
|
className: customClassName,
|
||||||
customOptions
|
customOptions
|
||||||
} = props
|
} = props
|
||||||
const { token } = useToken()
|
const { token } = useToken()
|
||||||
@ -45,6 +46,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tree
|
<Tree
|
||||||
|
className={classNames(componentName, customClassName)}
|
||||||
checkable={treeCheckable}
|
checkable={treeCheckable}
|
||||||
blockNode
|
blockNode
|
||||||
onSelect={(selectedKeys, info) => {
|
onSelect={(selectedKeys, info) => {
|
||||||
@ -70,38 +72,11 @@ const boxTree: FC<BoxTreeProps> = (props) => {
|
|||||||
<Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
|
<Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
|
||||||
{customOptions || (
|
{customOptions || (
|
||||||
<>
|
<>
|
||||||
<ModalForm
|
<EditOutlined onClick={(e) => {
|
||||||
title="重命名"
|
|
||||||
width={600}
|
|
||||||
modalProps={{ destroyOnClose: true }}
|
|
||||||
layout='horizontal'
|
|
||||||
labelCol={{ span: 6 }}
|
|
||||||
wrapperCol={{ span: 18 }}
|
|
||||||
trigger={<EditOutlined onClick={(e) => {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onItemRename?.(_nodeData)
|
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) => {
|
<SettingOutlined onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
.zhst-biz-tree-item-render {
|
.zhst-biz-tree {
|
||||||
|
&-item-render {
|
||||||
&_right {
|
&_right {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -6,4 +7,5 @@
|
|||||||
&:hover &_right {
|
&:hover &_right {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
category: Components
|
category: Components
|
||||||
title: Tree 树
|
title: Tree 树 [废弃]
|
||||||
toc: content
|
toc: content
|
||||||
demo:
|
demo:
|
||||||
cols: 2
|
cols: 2
|
||||||
@ -10,6 +10,11 @@ group:
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
:::warning{title=0.25.1之后版本已废弃}
|
||||||
|
组件迁移到 @zhst/meta
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
## 代码演示
|
## 代码演示
|
||||||
|
|
||||||
<code src="./demo/basic.tsx">基本用法</code>
|
<code src="./demo/basic.tsx">基本用法</code>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { TreeDataNode } from 'antd';
|
// !! 该组件已被废弃
|
||||||
|
import { DataNode } from '@zhst/meta';
|
||||||
import BoxTree from './boxTree';
|
import BoxTree from './boxTree';
|
||||||
|
|
||||||
export interface TreeData extends TreeDataNode {
|
export interface TreeData extends DataNode {
|
||||||
children?: TreeDataNode['children'] & {
|
children?: DataNode['children'] & {
|
||||||
isCamera?: boolean
|
isCamera?: boolean
|
||||||
/**
|
/**
|
||||||
* 0-失败 1-成功 2-进行中 3-未知
|
* 0-失败 1-成功 2-进行中 3-未知
|
||||||
|
269
packages/biz/src/treePanel/TreePanel.tsx
Normal file
269
packages/biz/src/treePanel/TreePanel.tsx
Normal 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
|
90
packages/biz/src/treePanel/demo/basic.tsx
Normal file
90
packages/biz/src/treePanel/demo/basic.tsx
Normal 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;
|
54
packages/biz/src/treePanel/demo/mock.tsx
Normal file
54
packages/biz/src/treePanel/demo/mock.tsx
Normal 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 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
24
packages/biz/src/treePanel/demo/normal.tsx
Normal file
24
packages/biz/src/treePanel/demo/normal.tsx
Normal 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;
|
135
packages/biz/src/treePanel/demo/withTags.tsx
Normal file
135
packages/biz/src/treePanel/demo/withTags.tsx
Normal 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;
|
131
packages/biz/src/treePanel/index.less
Normal file
131
packages/biz/src/treePanel/index.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
82
packages/biz/src/treePanel/index.md
Normal file
82
packages/biz/src/treePanel/index.md
Normal 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. 按钮列表
|
7
packages/biz/src/treePanel/index.tsx
Normal file
7
packages/biz/src/treePanel/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Created by jiangzhixiong on 2024/06/04
|
||||||
|
*/
|
||||||
|
import TreePanel from './TreePanel'
|
||||||
|
export type { TreePanelProps } from './TreePanel'
|
||||||
|
|
||||||
|
export default TreePanel
|
@ -1,95 +1,245 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { ReactNode, useContext } from 'react';
|
||||||
import { Button, Card, ConfigProvider, theme, Flex, Input, InputProps, TransferProps, TreeDataNode, TreeProps, Tree } from 'antd';
|
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 './index.less'
|
||||||
import { DeleteOutlined, DoubleRightOutlined, SearchOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const componentName = 'zhst-biz-treeTransfer'
|
const { ConfigContext } = ConfigProvider
|
||||||
|
|
||||||
export interface TreeTransferProps {
|
export interface TreeTransferProps {
|
||||||
|
customizePrefixCls?: string
|
||||||
|
/**
|
||||||
|
* @description 自定义标题
|
||||||
|
* @array []
|
||||||
|
*/
|
||||||
|
titles?: string[] | ReactNode[]
|
||||||
|
/**
|
||||||
|
* @description 边框
|
||||||
|
*/
|
||||||
|
bordered?: boolean
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* 树的数据结构 ,参考antd-treeData
|
||||||
|
*/
|
||||||
dataSource: TreeDataNode[]
|
dataSource: TreeDataNode[]
|
||||||
|
/**
|
||||||
|
* @description 树的透传参数
|
||||||
|
*/
|
||||||
treeProps?: TreeProps
|
treeProps?: TreeProps
|
||||||
|
/**
|
||||||
|
* 是否显示左侧树面板的搜索
|
||||||
|
*/
|
||||||
|
showLeftSearch?: boolean
|
||||||
|
/**
|
||||||
|
* @description 搜索的透传
|
||||||
|
*/
|
||||||
searchInputProps?: InputProps
|
searchInputProps?: InputProps
|
||||||
|
/**
|
||||||
|
* @description 选中的对象
|
||||||
|
*/
|
||||||
targetItems: TreeDataNode[];
|
targetItems: TreeDataNode[];
|
||||||
|
/**
|
||||||
|
* @description 选中对象的id
|
||||||
|
*/
|
||||||
checkedKeys: string[];
|
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']
|
onTreeSelect?: TreeProps['onSelect']
|
||||||
onTreeCheck?: TreeProps['onCheck']
|
onTreeCheck?: TreeProps['onCheck']
|
||||||
onItemDelete?: (key: string, info?: TreeDataNode) => void
|
onItemDelete?: (key: string, info?: TreeDataNode) => void
|
||||||
onChange?: TransferProps['onChange'];
|
|
||||||
onOk?: (data: any) => void;
|
onOk?: (data: any) => void;
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
|
onSearch?: InputProps['onChange']
|
||||||
}
|
}
|
||||||
|
|
||||||
const { useToken } = theme
|
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,
|
dataSource,
|
||||||
treeProps,
|
treeProps,
|
||||||
|
titles = ['可选择的范围', '已选择的范围'],
|
||||||
|
bordered,
|
||||||
searchInputProps,
|
searchInputProps,
|
||||||
|
showLeftSearch = true,
|
||||||
|
leftPanelScrollY = 300,
|
||||||
|
leftPanelWidth = 500,
|
||||||
|
rightPanelScrollY = 422,
|
||||||
|
rightPanelWidth = 300,
|
||||||
targetItems = [],
|
targetItems = [],
|
||||||
checkedKeys = [],
|
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,
|
onTreeCheck,
|
||||||
onTreeSelect,
|
onTreeSelect,
|
||||||
onItemDelete,
|
onItemDelete,
|
||||||
|
onSearch,
|
||||||
onOk,
|
onOk,
|
||||||
onReset
|
onReset
|
||||||
}) => {
|
} = props
|
||||||
|
|
||||||
const { token } = useToken()
|
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 (
|
return (
|
||||||
<Flex gap={20} className={componentName} align='center' justify='center'>
|
<div className={componentName}>
|
||||||
<div className={`${componentName}-left`}>
|
<div className={`${componentName}-left`}
|
||||||
<Card
|
style={{
|
||||||
className={`${componentName}-left_card`}
|
width: typeof leftPanelWidth === 'number' ? leftPanelWidth + 'px' : leftPanelWidth,
|
||||||
title={<div style={{ textAlign: 'center' }} >可选择的范围</div>}
|
border: bordered ? `1px solid ${token.colorBorder}` : 'unset',
|
||||||
bodyStyle={{ padding: 12 }}
|
|
||||||
>
|
|
||||||
<Input prefix={<SearchOutlined />} onChange={e => setKeyWords(e.target.value)} placeholder='请输入设备名称' {...searchInputProps} />
|
|
||||||
<ConfigProvider
|
|
||||||
theme={{
|
|
||||||
components: {
|
|
||||||
Tree: {
|
|
||||||
colorBgContainer: '#FCFCFC'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tree
|
<div
|
||||||
style={{ marginTop: '6px' }}
|
className={`${componentName}-left_card`}
|
||||||
height={420}
|
style={{ backgroundColor: token.colorBgContainer }}
|
||||||
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>}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
|
|
||||||
>
|
>
|
||||||
|
<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
|
<div
|
||||||
className={`${componentName}-right_card__items`}
|
className={`${componentName}-right_card__items`}
|
||||||
style={{ maxHeight: '422px' }}
|
style={{ height: typeof rightPanelScrollY === 'number' ? rightPanelScrollY + 'px' : rightPanelScrollY}}
|
||||||
>
|
>
|
||||||
{targetItems.map(item => (
|
{targetItems.map(item => (
|
||||||
<div
|
<div
|
||||||
@ -106,8 +256,7 @@ const TreeTransfer: React.FC<TreeTransferProps> = ({
|
|||||||
>
|
>
|
||||||
{item.title as any}
|
{item.title as any}
|
||||||
<div style={{ float: 'right' }}>
|
<div style={{ float: 'right' }}>
|
||||||
<DeleteOutlined onClick={() => {
|
<IconFont icon="icon-shanchu" onIconClick={() => {
|
||||||
// const { root, keys } = getAllRootKeyById(item.key as string, dataSource)
|
|
||||||
onItemDelete?.(item.key as string, item)
|
onItemDelete?.(item.key as string, item)
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
@ -115,19 +264,25 @@ const TreeTransfer: React.FC<TreeTransferProps> = ({
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{showRightPanelFooter && (
|
||||||
<Flex
|
<Flex
|
||||||
className={`${componentName}-right_card__btns`}
|
className={`${componentName}-right_card__btns`}
|
||||||
>
|
>
|
||||||
|
{rightPanelFooterRender?.({ onOk, onReset }) || (
|
||||||
|
<>
|
||||||
<Button style={{ marginRight: 8, width: '50%' }} disabled={targetItems.length <= 0} onClick={onReset}>重置</Button>
|
<Button style={{ marginRight: 8, width: '50%' }} disabled={targetItems.length <= 0} onClick={onReset}>重置</Button>
|
||||||
<Button
|
<Button
|
||||||
style={{ width: '50%' }}
|
style={{ width: '50%' }}
|
||||||
type='primary'
|
type='primary'
|
||||||
onClick={() => onOk?.(targetItems)}
|
onClick={() => onOk?.(targetItems)}
|
||||||
>确定</Button>
|
>确定</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +42,18 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeTransfer
|
<TreeTransfer
|
||||||
|
leftPanelWidth={800}
|
||||||
|
rightPanelWidth={500}
|
||||||
|
showLeftPanelFooter
|
||||||
dataSource={boxDataSource}
|
dataSource={boxDataSource}
|
||||||
targetItems={targetItems}
|
targetItems={targetItems}
|
||||||
checkedKeys={checkedKeys}
|
checkedKeys={checkedKeys}
|
||||||
onTreeCheck={onTreeCheck}
|
onTreeCheck={onTreeCheck}
|
||||||
|
leftPanelFooterRender={(
|
||||||
|
<div style={{ padding: '6px', color: 'red' }}>
|
||||||
|
自定义脚部dom
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
onItemDelete={onItemDelete}
|
onItemDelete={onItemDelete}
|
||||||
onOk={onOk}
|
onOk={onOk}
|
||||||
onReset={onReset}
|
onReset={onReset}
|
||||||
|
71
packages/biz/src/treeTransfer/demo/mingmou.tsx
Normal file
71
packages/biz/src/treeTransfer/demo/mingmou.tsx
Normal 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;
|
57
packages/biz/src/treeTransfer/demo/noSearch.tsx
Normal file
57
packages/biz/src/treeTransfer/demo/noSearch.tsx
Normal 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;
|
66
packages/biz/src/treeTransfer/demo/withFilter.tsx
Normal file
66
packages/biz/src/treeTransfer/demo/withFilter.tsx
Normal 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;
|
103
packages/biz/src/treeTransfer/demo/withMap.tsx
Normal file
103
packages/biz/src/treeTransfer/demo/withMap.tsx
Normal 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;
|
@ -1,23 +1,90 @@
|
|||||||
.zhst-biz-treeTransfer {
|
.zhst-biz-treeTransfer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
&-left {
|
&-left {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
&_card {
|
&_card {
|
||||||
width: 500px;
|
display: flex;
|
||||||
min-height: 522px;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
min-height: 544px;
|
||||||
background-color: #FCFCFC;
|
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: 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 {
|
&-right {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
&_card {
|
&_card {
|
||||||
width: 300px;
|
position: relative;
|
||||||
min-height: 544px;
|
min-height: 544px;
|
||||||
background-color: #FCFCFC;
|
background-color: #FCFCFC;
|
||||||
|
border: 2px solid #f7f7f7;
|
||||||
|
|
||||||
|
&_title {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 2px solid #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
&__items {
|
&__items {
|
||||||
padding: 8px 4px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 105px);
|
font-size: 14px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
@ -39,7 +106,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 2px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user