Merge branch 'master' into 'hostfix/fix_command'

# Conflicts:
#   packages/biz/src/boxSelectTree/components/boxPanel/index.tsx
This commit is contained in:
江志雄 2024-03-19 09:40:40 +08:00
commit 204b050233
108 changed files with 3176 additions and 2753 deletions

View File

@ -1,8 +1,6 @@
import { defineConfig } from 'dumi'; import { defineConfig } from 'dumi';
import path from 'path'; import path from 'path';
console.log(path.join(__dirname, 'packages/hooks/src'));
export default defineConfig({ export default defineConfig({
logo: '/logo.jpg', logo: '/logo.jpg',
favicons: ['/logo.jpg'], favicons: ['/logo.jpg'],
@ -11,6 +9,15 @@ export default defineConfig({
socialLinks: { socialLinks: {
gitlab: 'http://10.0.0.88/web-project/zhst-Lambo', gitlab: 'http://10.0.0.88/web-project/zhst-Lambo',
}, },
nav: {
mode: 'append',
value: [
{
title: '智能柜物料库→',
link: 'http://10.0.0.222:30098',
},
],
},
}, },
alias: { alias: {
'@zhst/hooks': path.join(__dirname, 'packages/hooks/src'), '@zhst/hooks': path.join(__dirname, 'packages/hooks/src'),
@ -35,14 +42,4 @@ export default defineConfig({
srcDir: ['packages', 'src'], srcDir: ['packages', 'src'],
peerDeps: true, peerDeps: true,
}, },
extraBabelPlugins: [
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
},
],
],
}); });

View File

@ -2,14 +2,4 @@ import { defineConfig } from 'father';
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
extraBabelPlugins: [
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
},
],
],
}); });

10
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"configurations": [
{
"type": "chrome",
"name": "http://localhost:8000/metas/big-image-preview",
"request": "launch",
"url": "http://localhost:8000/metas/big-image-preview"
}
]
}

View File

@ -59,7 +59,6 @@
"@types/react": "^18.0.0", "@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"@umijs/lint": "^4.0.0", "@umijs/lint": "^4.0.0",
"babel-plugin-import": "^1.13.8",
"dumi": "^2.2.13", "dumi": "^2.2.13",
"eslint": "^8.23.0", "eslint": "^8.23.0",
"father": "^4.1.0", "father": "^4.1.0",

View File

@ -1,5 +1,115 @@
# @zhst/biz # @zhst/biz
## 0.10.1
### Patch Changes
- Updated dependencies
- @zhst/meta@0.9.1
## 0.10.0
### Minor Changes
- 修改 metad 大图组建
### Patch Changes
- Updated dependencies
- @zhst/meta@0.9.0
## 0.9.8
### Patch Changes
- @zhst/func@0.7.5
- @zhst/hooks@0.8.4
- @zhst/meta@0.8.5
## 0.9.7
### Patch Changes
- 新增 RealTimeMonitor,VideoPlayerCard,ViewLargerImageModal,WarningRecordCard 组件,优化盒子树创建弹框规则校验
- Updated dependencies
- @zhst/meta@0.8.5
## 0.9.6
### Patch Changes
- 新增 tree 组件的监听事件,优化 meta 全选的回调事件监听
- Updated dependencies
- @zhst/hooks@0.8.3
- @zhst/func@0.7.4
- @zhst/meta@0.8.4
## 0.9.5
### Patch Changes
- 删除预览图组件,新增摄像头状态判断,修复 ts 生成失败问题
## 0.9.4
### Patch Changes
- 添加 biz 模块修改树形盒子组件监听事件
## 0.9.3
### Patch Changes
- 新增监听点击事件自定义
## 0.9.2
### Patch Changes
- 新增 BoxSelectTree 监听时钟点击事件,导入盒子事件
## 0.9.1
### Patch Changes
- 修改 boxSelectTree 类型提示
- Updated dependencies
- @zhst/hooks@0.8.2
- @zhst/func@0.7.3
- @zhst/meta@0.8.3
## 0.9.0
### Minor Changes
- 优化 boxSelectTree 组件,添加可以自定义配置按钮功能
### Patch Changes
- Updated dependencies
- @zhst/hooks@0.8.1
- @zhst/func@0.7.2
- @zhst/meta@0.8.2
## 0.8.0
### Minor Changes
- @zhst/biz 优化数组件
### Patch Changes
- Updated dependencies
- @zhst/hooks@0.8.0
- @zhst/func@0.7.1
- @zhst/meta@0.8.1
## 0.7.0
### Minor Changes
- 新增预警记录卡片组件
## 0.6.0 ## 0.6.0
### Minor Changes ### Minor Changes

View File

@ -1,5 +1,17 @@
export { default as BigImageModal } from './BigImageModal'; export { default as BigImageModal } from './BigImageModal';
export type { BigImageModalProps } from './BigImageModal';
export { default as BoxSelectTree } from './boxSelectTree'; export { default as BoxSelectTree } 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 { default as TreeTransfer } from './treeTransfer'; export { default as TreeTransfer } 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 { default as WarningRecordCard } from './WarningRecordCard';
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard';
export type { ViewLargerImageModalRef, ViewLargerImageModalProps } from './ViewLargerImageModal';
export { default as ViewLargerImageModal, useViewLargerImageModal } from './ViewLargerImageModal';
export type { VideoPlayerCardProps } from './VideoPlayerCard';
export { default as VideoPlayerCard } from './VideoPlayerCard';
export { default as RealTimeMonitor } from './RealTimeMonitor';

View File

@ -2,4 +2,8 @@ export { default as BigImageModal } from "./BigImageModal";
export { default as BoxSelectTree } from "./boxSelectTree"; export { default as BoxSelectTree } from "./boxSelectTree";
export { default as Tree } from "./tree"; export { default as Tree } from "./tree";
export { default as TreeTransfer } from "./treeTransfer"; export { default as TreeTransfer } from "./treeTransfer";
export { default as TreeTransferModal } from "./treeTransferModal"; export { default as TreeTransferModal } from "./treeTransferModal";
export { default as WarningRecordCard } from "./WarningRecordCard";
export { default as ViewLargerImageModal, useViewLargerImageModal } from "./ViewLargerImageModal";
export { default as VideoPlayerCard } from "./VideoPlayerCard";
export { default as RealTimeMonitor } from "./RealTimeMonitor";

View File

@ -1,95 +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); }
import { useMemo } from 'react';
import { throttle as loadshThrottle, noop } from '@zhst/func';
import { useDeepEffect, useLatest } from '@zhst/hooks';
import ws from "./ws";
var getSelf = function getSelf(v) {
return v;
};
export var SocketApi = {
CameraTaskStatue: 'singer.DeviceService/SubScribeCameraTaskStatus',
DeviceStatus: 'singer.TaskManagerService/SubscribeTaskStatus',
SubscribeSolutionDeploy: 'singer.SolutionManagerService/SubscribeSolutionDeploy',
SubscribeTasksSummary: 'singer.TaskManagerService/SubscribeTasksSummary',
MonitorSubscribeResult: 'singer.MonitorService/MonitorSubscribeResult',
MonitorSubscribeStatus: 'singer.MonitorService/MonitorSubscribeStatus',
SubscribeArchiveGroupUpload: 'singer.ArchiveGroupService/CreateArchiveByImport',
SubscribeJointTask: 'singer.JointTaskService/SubscribeJointTask',
SubscribeGroupFragment: 'singer.VideoService/SubscribeVideoFragmentStatus',
// 监听视频分片状态变化,包括新建、删除、变化
SubscribeGroup: 'singer.VideoService/SubscribeGroup',
// 监听视频分组状态变化,列表变化也通知
SubscribeStreamEvent: 'singer.MediaManagerService/SubscribeStreamEvent' // 监听视频分组状态变化,列表变化也通知
};
export default (function (topic) {
var iterator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
var opt = arguments.length > 2 ? arguments[2] : undefined;
var _ref = opt || {},
_ref$req = _ref.req,
req = _ref$req === void 0 ? {} : _ref$req,
_ref$throttle = _ref.throttle,
throttle = _ref$throttle === void 0 ? 0 : _ref$throttle,
_ref$beforeLoopTmp = _ref.beforeLoopTmp,
beforeLoopTmp = _ref$beforeLoopTmp === void 0 ? getSelf : _ref$beforeLoopTmp,
_ref$shouldBreak = _ref.shouldBreak,
shouldBreak = _ref$shouldBreak === void 0 ? false : _ref$shouldBreak,
forceRefresh = _ref.forceRefresh,
close = _ref.close;
// 带上token
var reqstring = useMemo(function () {
var newReq = _objectSpread(_objectSpread({}, req), {}, {
extraHeaders: {
authorization: localStorage.getItem('USER-TOKEN')
}
});
return JSON.stringify(newReq);
}, [req]);
var latestIterator = useLatest(iterator);
useDeepEffect(function () {
if (close) {
return;
}
//控制socket 请求发送
if (shouldBreak) {
return;
}
//去抖动
var tmpData = [];
var throttleUpdate = loadshThrottle(function () {
if (tmpData.length == 0) {
return;
}
var _tmpData = beforeLoopTmp(tmpData);
latestIterator.current(_tmpData); //加了throttle 数据就变成数组
tmpData = [];
}, throttle);
var unSubscribe = ws.subscribe(SocketApi[topic], reqstring, function (socketData) {
try {
if (!throttle) {
latestIterator.current(socketData);
} else {
tmpData.push(socketData);
throttleUpdate();
}
} catch (error) {
console.error('useSocke:', error);
}
});
return function () {
try {
unSubscribe();
throttleUpdate.cancel();
} catch (error) {
console.error(error);
throw error;
}
};
}, [topic, reqstring, shouldBreak, forceRefresh, close]);
});

View File

@ -1,2 +0,0 @@
declare const startChannel: (topic: any, req: any, callback: any) => () => void;
export default startChannel;

View File

@ -1,24 +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); }
//@ts-nocheck
import channel from "./ws";
var startChannel = function startChannel(topic, req, callback) {
// 带上token
var reqstring = JSON.stringify(_objectSpread(_objectSpread({}, req), {}, {
extraHeaders: {
authorization: localStorage.getItem('USER-TOKEN')
}
}));
var unSubscribe = channel.subscribe(topic, reqstring, function (socketData) {
var shouldStop = callback(socketData);
if (shouldStop) {
unSubscribe === null || unSubscribe === void 0 || unSubscribe();
}
});
return unSubscribe;
};
export default startChannel;

View File

@ -1,26 +0,0 @@
declare class Channel {
/**
* io
*/
ioIns: any;
/**
*
*/
listeners: never[];
/**
* /
*/
subscribeListenerId: never[];
unSubscribeListenerId: never[];
init: () => void;
retry: (listener: {
[x: string]: any;
lastRetryInterval: number | undefined;
intervalId: NodeJS.Timeout;
} | undefined) => void;
doEmit: (topic: any, req: any, listenerId: any) => void;
subscribe(topic: any, req: any, handle: any): () => void;
unSubscribe(topic: any, req: any, handleId: any, listenerId: any): void;
}
declare const channelIns: Channel;
export default channelIns;

View File

@ -1,244 +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 _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
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); }
// @ts-nocheck
import { v4 as uuidv4 } from 'uuid';
import { has } from '@zhst/func';
import io from 'socket.io-client';
import { SOCKET_HOST } from '@common/constants';
var EMITSTATE = {
NOT_CONNECT: 0,
WAITING: 1,
CONNECT: 2
};
var initRetryTime = 0;
var intervalTime = 5 * 1000; //下次重试增加时间
var maxIntervalTime = 1 * 60 * 60 * 1000; //最大重试时间1小时
var Channel = /*#__PURE__*/function () {
function Channel() {
var _this = this;
_classCallCheck(this, Channel);
/**
* io 实例化对象
*/
_defineProperty(this, "ioIns", void 0);
/**
* 已存在的订阅列表
*/
_defineProperty(this, "listeners", [
// {
// topic: "",
// req: "",
// suInfo: {},
// hasEmit: false,//是否已经订阅
// lastRetryInterval: 0,
// handles: {
// }
// }
]);
/**
* 调试信息 记录订阅/反订阅次数
*/
_defineProperty(this, "subscribeListenerId", []);
_defineProperty(this, "unSubscribeListenerId", []);
_defineProperty(this, "init", function () {
var ioIns = _this.ioIns = io(SOCKET_HOST, {
reconnection: true,
transports: ['websocket'],
forceNew: true
});
ioIns.on('connect', function () {
for (var _len = arguments.length, arg = new Array(_len), _key = 0; _key < _len; _key++) {
arg[_key] = arguments[_key];
}
console.debug('connect', arg);
_this.ioIns = ioIns;
_this.listeners.forEach(function (v) {
_this.doEmit(v['topic'], v['req'], v['id']);
});
});
ioIns.on('event', function () {
for (var _len2 = arguments.length, arg = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
arg[_key2] = arguments[_key2];
}
console.debug('event', arg);
});
ioIns.on('disconnect', function () {
for (var _len3 = arguments.length, arg = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
arg[_key3] = arguments[_key3];
}
console.debug('disconnect', arg);
_this.subscribeListenerId = [];
_this.unSubscribeListenerId = [];
});
ioIns.on('reconnect', function () {
for (var _len4 = arguments.length, arg = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
arg[_key4] = arguments[_key4];
}
console.debug('reconnect', arg);
_this.listeners.forEach(function (v) {
v['hasEmit'] = EMITSTATE.NOT_CONNECT;
_this.doEmit(v['topic'], v['req'], v['id']);
});
});
});
_defineProperty(this, "retry", function (listener) {
//重试逻辑
var intervalId = setTimeout(function () {
var hasExit = _this.listeners.find(function (v) {
return v['topic'] === (listener === null || listener === void 0 ? void 0 : listener['topic']) && v['req'] === listener['req'];
});
if (!hasExit) return;
listener['hasEmit'] = EMITSTATE.NOT_CONNECT;
_this.doEmit(listener['topic'], listener['req'], listener['id']);
}, listener.lastRetryInterval);
listener.intervalId = intervalId;
listener.lastRetryInterval = intervalTime + listener.lastRetryInterval > maxIntervalTime ? maxIntervalTime : intervalTime + listener.lastRetryInterval;
});
_defineProperty(this, "doEmit", function (topic, req, listenerId) {
var _this$ioIns, _this$ioIns$emit;
if (!_this.ioIns) {
_this.init();
}
//订阅过就不订阅了
var hasEmit = _this.listeners.find(function (v) {
return v['topic'] === topic && v['req'] === req && v['hasEmit'] !== EMITSTATE.NOT_CONNECT;
});
if (hasEmit) {
return;
}
var listener = _this.listeners.find(function (v) {
return v['topic'] === topic && v['req'] === req;
});
listener['hasEmit'] = EMITSTATE.WAITING;
(_this$ioIns = _this.ioIns) === null || _this$ioIns === void 0 || (_this$ioIns$emit = _this$ioIns.emit) === null || _this$ioIns$emit === void 0 || _this$ioIns$emit.call(_this$ioIns, topic, req, function (data) {
var _this$ioIns2, _this$ioIns2$on;
console.info('emit', topic, req, data);
var suInfo = JSON.parse(data);
if (has(suInfo, 'Error.code')) {
if (suInfo.Error.code === 500) {
//后端出错
_this.retry(listener);
}
return;
}
// console.debug('SUBSCRIBE', listenerId, topic, req, suInfo);
_this.subscribeListenerId.push(listenerId);
// debugger
//重新找一遍topic
var currentTopicIndex = _this.listeners.findIndex(function (v) {
return v['topic'] === topic && v['req'] === req && v['id'] === listenerId;
});
if (currentTopicIndex == -1) {
// 不存在说明listener取消了 直接反订阅
_this.ioIns.emit('UnSubscribe', JSON.stringify(suInfo), function (data) {
_this.unSubscribeListenerId.push(listenerId);
console.debug('UNSUBSCRIBE', listenerId, topic, req, data);
});
return;
}
if (!suInfo['SubscribeID']) {
_this.listeners.splice(currentTopicIndex, 0);
} else {
_this.listeners[currentTopicIndex]['suInfo'] = suInfo;
_this.listeners[currentTopicIndex]['hasEmit'] = EMITSTATE.CONNECT;
}
(_this$ioIns2 = _this.ioIns) === null || _this$ioIns2 === void 0 || (_this$ioIns2$on = _this$ioIns2.on) === null || _this$ioIns2$on === void 0 || _this$ioIns2$on.call(_this$ioIns2, suInfo['SubscribeID'], function (data) {
console.info('on', suInfo['SubscribeID'], data);
try {
var socketData = JSON.parse(data);
if (has(socketData, 'Error.code')) {
if (socketData.Error.code === 500) {
//后端出错
_this.retry(listener);
}
return;
}
var _ref = _this.listeners.find(function (v) {
return v['topic'] === topic && v['req'] === req;
}) || {},
_ref$handles = _ref.handles,
handles = _ref$handles === void 0 ? {} : _ref$handles;
Object.keys(handles).forEach(function (key) {
try {
//后面观察 为什么delete后在foreach
var func = handles[key];
if (!func) return;
func(socketData);
} catch (error) {
console.error(error);
}
});
} catch (error) {
console.debug('error', error);
}
});
});
});
}
_createClass(Channel, [{
key: "subscribe",
value: function subscribe(topic, req, handle) {
var handleId = uuidv4();
var listenerId = uuidv4();
var listener = this.listeners.find(function (v) {
return v['topic'] === topic && v['req'] === req;
});
if (listener) {
listener['handles'][handleId] = handle;
} else {
this.listeners.push({
topic: topic,
req: req,
suInfo: {},
id: listenerId,
hasEmit: EMITSTATE.NOT_CONNECT,
lastRetryInterval: initRetryTime,
handles: _defineProperty({}, "".concat(handleId), handle)
});
//未注册过 则去订阅
this.doEmit(topic, req, listenerId);
}
return this.unSubscribe.bind(this, topic, req, handleId, listenerId);
}
}, {
key: "unSubscribe",
value: function unSubscribe(topic, req, handleId, listenerId) {
var _this2 = this;
var listener = this.listeners.find(function (v) {
return v['topic'] === topic && v['req'] === req && v['id'] === listenerId;
});
var _ref2 = listener || {},
_ref2$handles = _ref2.handles,
handles = _ref2$handles === void 0 ? {} : _ref2$handles,
suInfo = _ref2.suInfo;
if (handles[handleId]) {
delete handles[handleId];
//如果没有其他订阅就删除
if (Object.keys(handles).length === 0) {
if (this.intervalId) {
clearTimeout(this.intervalId);
}
if (listener['hasEmit'] === EMITSTATE['CONNECT']) {
this.ioIns.emit('UnSubscribe', JSON.stringify(suInfo), function (data) {
_this2.unSubscribeListenerId.push(listenerId);
console.debug('UNSUBSCRIBE', listener['id'], topic, req, data);
});
}
this.listeners = this.listeners.filter(function (v) {
return !(v['topic'] === topic && v['req'] === req);
});
}
}
}
}]);
return Channel;
}(); //单例
var channelIns = new Channel();
//暴露实例 调试用
window.__channel__ = channelIns;
export default channelIns;

View File

@ -1,23 +0,0 @@
export declare const OBJECT_GRNER_THRESHOLD = 0.8;
export declare const OBJECT_AGE_TYPE_THRESHOLD = 0.5;
export declare const MODE_KEY = "test_mode";
export declare const SEARCH_IMG_COUNT = 10;
export declare const GLOBAL_IS_ITEM_NUMBER_SHOW = false;
export declare const publicPath = "hummingbird";
export declare const ENTER_CIRCLE = "MONITORTYPE_ENTER_CIRCLE";
export declare const OUT_CIRCLE = "MONITORTYPE_OUT_CIRCLE";
export declare const TEMP = "MONITORTYPE_TEMP";
export declare const GLOBAL_IS_BOX_VMS_SHOW = true;
export declare const BODY_SEARCH_THRESHOID = 0.45;
export declare const RECORD_VERSION = "3.0.0";
export declare const DeviceTab: {
EMPTY: number;
REAL_CAMERA: number;
PREPROCESS_CAMERA: number;
TAG_CAMERA: number;
HISTORY_VIDEO_GROUP: number;
VIRTUAL_CAMERA: number;
REAL_CAMERA_NOFACE: number;
REAL_CAMERA_ONLYFACE: number;
REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: number;
};

View File

@ -27,4 +27,17 @@ export var DeviceTab = {
REAL_CAMERA_NOFACE: 6, REAL_CAMERA_NOFACE: 6,
REAL_CAMERA_ONLYFACE: 7, REAL_CAMERA_ONLYFACE: 7,
REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8 // 只有普通摄像头,没有人脸、没有盒子、直连 REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8 // 只有普通摄像头,没有人脸、没有盒子、直连
}; };
// 盒子 Tab 切换
export var BOX_TYPE_LIST = [{
value: '1',
label: '盒子'
}, {
value: '2',
label: '盒子组'
}];
export var ALL_LIST = [{
value: '',
label: '全部'
}];

View File

@ -1,5 +1,17 @@
export { default as BigImageModal } from './BigImageModal'; export { default as BigImageModal } from './BigImageModal';
export type { BigImageModalProps } from './BigImageModal';
export { default as BoxSelectTree } from './boxSelectTree'; export { default as BoxSelectTree } 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 { default as TreeTransfer } from './treeTransfer'; export { default as TreeTransfer } 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 { default as WarningRecordCard } from './WarningRecordCard';
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard';
export type { ViewLargerImageModalRef, ViewLargerImageModalProps } from './ViewLargerImageModal';
export { default as ViewLargerImageModal, useViewLargerImageModal } from './ViewLargerImageModal';
export type { VideoPlayerCardProps } from './VideoPlayerCard';
export { default as VideoPlayerCard } from './VideoPlayerCard';
export { default as RealTimeMonitor } from './RealTimeMonitor';

View File

@ -31,9 +31,14 @@ var src_exports = {};
__export(src_exports, { __export(src_exports, {
BigImageModal: () => import_BigImageModal.default, BigImageModal: () => import_BigImageModal.default,
BoxSelectTree: () => import_boxSelectTree.default, BoxSelectTree: () => import_boxSelectTree.default,
RealTimeMonitor: () => import_RealTimeMonitor.default,
Tree: () => import_tree.default, Tree: () => import_tree.default,
TreeTransfer: () => import_treeTransfer.default, TreeTransfer: () => import_treeTransfer.default,
TreeTransferModal: () => import_treeTransferModal.default TreeTransferModal: () => import_treeTransferModal.default,
VideoPlayerCard: () => import_VideoPlayerCard.default,
ViewLargerImageModal: () => import_ViewLargerImageModal.default,
WarningRecordCard: () => import_WarningRecordCard.default,
useViewLargerImageModal: () => import_ViewLargerImageModal.useViewLargerImageModal
}); });
module.exports = __toCommonJS(src_exports); module.exports = __toCommonJS(src_exports);
var import_BigImageModal = __toESM(require("./BigImageModal")); var import_BigImageModal = __toESM(require("./BigImageModal"));
@ -41,11 +46,20 @@ var import_boxSelectTree = __toESM(require("./boxSelectTree"));
var import_tree = __toESM(require("./tree")); var import_tree = __toESM(require("./tree"));
var import_treeTransfer = __toESM(require("./treeTransfer")); var import_treeTransfer = __toESM(require("./treeTransfer"));
var import_treeTransferModal = __toESM(require("./treeTransferModal")); var import_treeTransferModal = __toESM(require("./treeTransferModal"));
var import_WarningRecordCard = __toESM(require("./WarningRecordCard"));
var import_ViewLargerImageModal = __toESM(require("./ViewLargerImageModal"));
var import_VideoPlayerCard = __toESM(require("./VideoPlayerCard"));
var import_RealTimeMonitor = __toESM(require("./RealTimeMonitor"));
// Annotate the CommonJS export names for ESM import in node: // Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = { 0 && (module.exports = {
BigImageModal, BigImageModal,
BoxSelectTree, BoxSelectTree,
RealTimeMonitor,
Tree, Tree,
TreeTransfer, TreeTransfer,
TreeTransferModal TreeTransferModal,
VideoPlayerCard,
ViewLargerImageModal,
WarningRecordCard,
useViewLargerImageModal
}); });

View File

@ -1,118 +0,0 @@
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/useSocket/index.ts
var useSocket_exports = {};
__export(useSocket_exports, {
SocketApi: () => SocketApi,
default: () => useSocket_default
});
module.exports = __toCommonJS(useSocket_exports);
var import_react = require("react");
var import_func = require("@zhst/func");
var import_hooks = require("@zhst/hooks");
var import_ws = __toESM(require("./ws"));
var getSelf = (v) => v;
var SocketApi = {
CameraTaskStatue: "singer.DeviceService/SubScribeCameraTaskStatus",
DeviceStatus: "singer.TaskManagerService/SubscribeTaskStatus",
SubscribeSolutionDeploy: "singer.SolutionManagerService/SubscribeSolutionDeploy",
SubscribeTasksSummary: "singer.TaskManagerService/SubscribeTasksSummary",
MonitorSubscribeResult: "singer.MonitorService/MonitorSubscribeResult",
MonitorSubscribeStatus: "singer.MonitorService/MonitorSubscribeStatus",
SubscribeArchiveGroupUpload: "singer.ArchiveGroupService/CreateArchiveByImport",
SubscribeJointTask: "singer.JointTaskService/SubscribeJointTask",
SubscribeGroupFragment: "singer.VideoService/SubscribeVideoFragmentStatus",
// 监听视频分片状态变化,包括新建、删除、变化
SubscribeGroup: "singer.VideoService/SubscribeGroup",
// 监听视频分组状态变化,列表变化也通知
SubscribeStreamEvent: "singer.MediaManagerService/SubscribeStreamEvent"
// 监听视频分组状态变化,列表变化也通知
};
var useSocket_default = (topic, iterator = import_func.noop, opt) => {
const {
req = {},
throttle = 0,
beforeLoopTmp = getSelf,
shouldBreak = false,
forceRefresh,
close
} = opt || {};
const reqstring = (0, import_react.useMemo)(() => {
const newReq = {
...req,
extraHeaders: {
authorization: localStorage.getItem("USER-TOKEN")
}
};
return JSON.stringify(newReq);
}, [req]);
const latestIterator = (0, import_hooks.useLatest)(iterator);
(0, import_hooks.useDeepEffect)(() => {
if (close) {
return;
}
if (shouldBreak) {
return;
}
let tmpData = [];
const throttleUpdate = (0, import_func.throttle)(() => {
if (tmpData.length == 0) {
return;
}
const _tmpData = beforeLoopTmp(tmpData);
latestIterator.current(_tmpData);
tmpData = [];
}, throttle);
const unSubscribe = import_ws.default.subscribe(SocketApi[topic], reqstring, (socketData) => {
try {
if (!throttle) {
latestIterator.current(socketData);
} else {
tmpData.push(socketData);
throttleUpdate();
}
} catch (error) {
console.error("useSocke:", error);
}
});
return () => {
try {
unSubscribe();
throttleUpdate.cancel();
} catch (error) {
console.error(error);
throw error;
}
};
}, [topic, reqstring, shouldBreak, forceRefresh, close]);
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SocketApi
});

View File

@ -1,2 +0,0 @@
declare const startChannel: (topic: any, req: any, callback: any) => () => void;
export default startChannel;

View File

@ -1,51 +0,0 @@
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/useSocket/onceChannel.tsx
var onceChannel_exports = {};
__export(onceChannel_exports, {
default: () => onceChannel_default
});
module.exports = __toCommonJS(onceChannel_exports);
var import_ws = __toESM(require("./ws"));
var startChannel = (topic, req, callback) => {
let reqstring = JSON.stringify({
...req,
extraHeaders: {
authorization: localStorage.getItem("USER-TOKEN")
}
});
let unSubscribe = import_ws.default.subscribe(topic, reqstring, (socketData) => {
let shouldStop = callback(socketData);
if (shouldStop) {
unSubscribe == null ? void 0 : unSubscribe();
}
});
return unSubscribe;
};
var onceChannel_default = startChannel;

View File

@ -1,26 +0,0 @@
declare class Channel {
/**
* io
*/
ioIns: any;
/**
*
*/
listeners: never[];
/**
* /
*/
subscribeListenerId: never[];
unSubscribeListenerId: never[];
init: () => void;
retry: (listener: {
[x: string]: any;
lastRetryInterval: number | undefined;
intervalId: NodeJS.Timeout;
} | undefined) => void;
doEmit: (topic: any, req: any, listenerId: any) => void;
subscribe(topic: any, req: any, handle: any): () => void;
unSubscribe(topic: any, req: any, handleId: any, listenerId: any): void;
}
declare const channelIns: Channel;
export default channelIns;

View File

@ -1,224 +0,0 @@
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/useSocket/ws.ts
var ws_exports = {};
__export(ws_exports, {
default: () => ws_default
});
module.exports = __toCommonJS(ws_exports);
var import_uuid = require("uuid");
var import_func = require("@zhst/func");
var import_socket = __toESM(require("socket.io-client"));
var import_constants = require("@common/constants");
var EMITSTATE = {
NOT_CONNECT: 0,
WAITING: 1,
CONNECT: 2
};
var initRetryTime = 0;
var intervalTime = 5 * 1e3;
var maxIntervalTime = 1 * 60 * 60 * 1e3;
var Channel = class {
constructor() {
/**
* 已存在的订阅列表
*/
this.listeners = [
// {
// topic: "",
// req: "",
// suInfo: {},
// hasEmit: false,//是否已经订阅
// lastRetryInterval: 0,
// handles: {
// }
// }
];
/**
* 调试信息 记录订阅/反订阅次数
*/
this.subscribeListenerId = [];
this.unSubscribeListenerId = [];
this.init = () => {
const ioIns = this.ioIns = (0, import_socket.default)(import_constants.SOCKET_HOST, {
reconnection: true,
transports: ["websocket"],
forceNew: true
});
ioIns.on("connect", (...arg) => {
console.debug("connect", arg);
this.ioIns = ioIns;
this.listeners.forEach((v) => {
this.doEmit(v["topic"], v["req"], v["id"]);
});
});
ioIns.on("event", (...arg) => {
console.debug("event", arg);
});
ioIns.on("disconnect", (...arg) => {
console.debug("disconnect", arg);
this.subscribeListenerId = [];
this.unSubscribeListenerId = [];
});
ioIns.on("reconnect", (...arg) => {
console.debug("reconnect", arg);
this.listeners.forEach((v) => {
v["hasEmit"] = EMITSTATE.NOT_CONNECT;
this.doEmit(v["topic"], v["req"], v["id"]);
});
});
};
this.retry = (listener) => {
const intervalId = setTimeout(() => {
const hasExit = this.listeners.find(
(v) => v["topic"] === (listener == null ? void 0 : listener["topic"]) && v["req"] === listener["req"]
);
if (!hasExit)
return;
listener["hasEmit"] = EMITSTATE.NOT_CONNECT;
this.doEmit(listener["topic"], listener["req"], listener["id"]);
}, listener.lastRetryInterval);
listener.intervalId = intervalId;
listener.lastRetryInterval = intervalTime + listener.lastRetryInterval > maxIntervalTime ? maxIntervalTime : intervalTime + listener.lastRetryInterval;
};
this.doEmit = (topic, req, listenerId) => {
var _a, _b;
if (!this.ioIns) {
this.init();
}
const hasEmit = this.listeners.find(
(v) => v["topic"] === topic && v["req"] === req && v["hasEmit"] !== EMITSTATE.NOT_CONNECT
);
if (hasEmit) {
return;
}
const listener = this.listeners.find((v) => v["topic"] === topic && v["req"] === req);
listener["hasEmit"] = EMITSTATE.WAITING;
(_b = (_a = this.ioIns) == null ? void 0 : _a.emit) == null ? void 0 : _b.call(_a, topic, req, (data) => {
var _a2, _b2;
console.info("emit", topic, req, data);
const suInfo = JSON.parse(data);
if ((0, import_func.has)(suInfo, "Error.code")) {
if (suInfo.Error.code === 500) {
this.retry(listener);
}
return;
}
this.subscribeListenerId.push(listenerId);
const currentTopicIndex = this.listeners.findIndex(
(v) => v["topic"] === topic && v["req"] === req && v["id"] === listenerId
);
if (currentTopicIndex == -1) {
this.ioIns.emit("UnSubscribe", JSON.stringify(suInfo), (data2) => {
this.unSubscribeListenerId.push(listenerId);
console.debug("UNSUBSCRIBE", listenerId, topic, req, data2);
});
return;
}
if (!suInfo["SubscribeID"]) {
this.listeners.splice(currentTopicIndex, 0);
} else {
this.listeners[currentTopicIndex]["suInfo"] = suInfo;
this.listeners[currentTopicIndex]["hasEmit"] = EMITSTATE.CONNECT;
}
(_b2 = (_a2 = this.ioIns) == null ? void 0 : _a2.on) == null ? void 0 : _b2.call(_a2, suInfo["SubscribeID"], (data2) => {
console.info("on", suInfo["SubscribeID"], data2);
try {
const socketData = JSON.parse(data2);
if ((0, import_func.has)(socketData, "Error.code")) {
if (socketData.Error.code === 500) {
this.retry(listener);
}
return;
}
const { handles = {} } = this.listeners.find((v) => v["topic"] === topic && v["req"] === req) || {};
Object.keys(handles).forEach((key) => {
try {
const func = handles[key];
if (!func)
return;
func(socketData);
} catch (error) {
console.error(error);
}
});
} catch (error) {
console.debug("error", error);
}
});
});
};
}
subscribe(topic, req, handle) {
const handleId = (0, import_uuid.v4)();
const listenerId = (0, import_uuid.v4)();
const listener = this.listeners.find((v) => v["topic"] === topic && v["req"] === req);
if (listener) {
listener["handles"][handleId] = handle;
} else {
this.listeners.push({
topic,
req,
suInfo: {},
id: listenerId,
hasEmit: EMITSTATE.NOT_CONNECT,
lastRetryInterval: initRetryTime,
handles: {
[`${handleId}`]: handle
}
});
this.doEmit(topic, req, listenerId);
}
return this.unSubscribe.bind(this, topic, req, handleId, listenerId);
}
unSubscribe(topic, req, handleId, listenerId) {
const listener = this.listeners.find(
(v) => v["topic"] === topic && v["req"] === req && v["id"] === listenerId
);
const { handles = {}, suInfo } = listener || {};
if (handles[handleId]) {
delete handles[handleId];
if (Object.keys(handles).length === 0) {
if (this.intervalId) {
clearTimeout(this.intervalId);
}
if (listener["hasEmit"] === EMITSTATE["CONNECT"]) {
this.ioIns.emit("UnSubscribe", JSON.stringify(suInfo), (data) => {
this.unSubscribeListenerId.push(listenerId);
console.debug("UNSUBSCRIBE", listener["id"], topic, req, data);
});
}
this.listeners = this.listeners.filter((v) => !(v["topic"] === topic && v["req"] === req));
}
}
}
};
var channelIns = new Channel();
window.__channel__ = channelIns;
var ws_default = channelIns;

View File

@ -1,23 +0,0 @@
export declare const OBJECT_GRNER_THRESHOLD = 0.8;
export declare const OBJECT_AGE_TYPE_THRESHOLD = 0.5;
export declare const MODE_KEY = "test_mode";
export declare const SEARCH_IMG_COUNT = 10;
export declare const GLOBAL_IS_ITEM_NUMBER_SHOW = false;
export declare const publicPath = "hummingbird";
export declare const ENTER_CIRCLE = "MONITORTYPE_ENTER_CIRCLE";
export declare const OUT_CIRCLE = "MONITORTYPE_OUT_CIRCLE";
export declare const TEMP = "MONITORTYPE_TEMP";
export declare const GLOBAL_IS_BOX_VMS_SHOW = true;
export declare const BODY_SEARCH_THRESHOID = 0.45;
export declare const RECORD_VERSION = "3.0.0";
export declare const DeviceTab: {
EMPTY: number;
REAL_CAMERA: number;
PREPROCESS_CAMERA: number;
TAG_CAMERA: number;
HISTORY_VIDEO_GROUP: number;
VIRTUAL_CAMERA: number;
REAL_CAMERA_NOFACE: number;
REAL_CAMERA_ONLYFACE: number;
REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: number;
};

View File

@ -19,7 +19,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
// src/utils/constants.ts // src/utils/constants.ts
var constants_exports = {}; var constants_exports = {};
__export(constants_exports, { __export(constants_exports, {
ALL_LIST: () => ALL_LIST,
BODY_SEARCH_THRESHOID: () => BODY_SEARCH_THRESHOID, BODY_SEARCH_THRESHOID: () => BODY_SEARCH_THRESHOID,
BOX_TYPE_LIST: () => BOX_TYPE_LIST,
DeviceTab: () => DeviceTab, DeviceTab: () => DeviceTab,
ENTER_CIRCLE: () => ENTER_CIRCLE, ENTER_CIRCLE: () => ENTER_CIRCLE,
GLOBAL_IS_BOX_VMS_SHOW: () => GLOBAL_IS_BOX_VMS_SHOW, GLOBAL_IS_BOX_VMS_SHOW: () => GLOBAL_IS_BOX_VMS_SHOW,
@ -62,9 +64,18 @@ var DeviceTab = {
REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8 REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8
// 只有普通摄像头,没有人脸、没有盒子、直连 // 只有普通摄像头,没有人脸、没有盒子、直连
}; };
var BOX_TYPE_LIST = [
{ value: "1", label: "盒子" },
{ value: "2", label: "盒子组" }
];
var ALL_LIST = [
{ value: "", label: "全部" }
];
// Annotate the CommonJS export names for ESM import in node: // Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = { 0 && (module.exports = {
ALL_LIST,
BODY_SEARCH_THRESHOID, BODY_SEARCH_THRESHOID,
BOX_TYPE_LIST,
DeviceTab, DeviceTab,
ENTER_CIRCLE, ENTER_CIRCLE,
GLOBAL_IS_BOX_VMS_SHOW, GLOBAL_IS_BOX_VMS_SHOW,

View File

@ -1,6 +1,6 @@
{ {
"name": "@zhst/biz", "name": "@zhst/biz",
"version": "0.6.0", "version": "0.10.1",
"description": "业务库", "description": "业务库",
"keywords": [ "keywords": [
"business", "business",
@ -35,7 +35,7 @@
"registry": "http://10.0.0.77:4874" "registry": "http://10.0.0.77:4874"
}, },
"devDependencies": { "devDependencies": {
"@types/zhst": "workspace:^" "@zhst/types": "workspace:^"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.2.6", "@ant-design/icons": "^5.2.6",
@ -45,6 +45,7 @@
"@zhst/meta": "workspace:^", "@zhst/meta": "workspace:^",
"antd": "^5.12.5", "antd": "^5.12.5",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"rc-util": "^5.38.1" "rc-util": "^5.38.1",
"dayjs": "^1.11.10"
} }
} }

View File

@ -0,0 +1,69 @@
import React from 'react';
import { IRecord, VideoPlayerCardProps, ViewLargerImageModalRef } from '@zhst/biz';
import WindowToggle from './components/WindowToggle';
import WarningRecordList from './components/WarningRecordList';
interface RealTimeMonitorProps {
videoDataSource?: VideoPlayerCardProps[];
handleWindowClick?: (key?: string) => void;
handleCloseButtonClick?: (key?: string) => void;
selectedWindowKey?: string;
warningDataSource?: IRecord[];
viewLargerImageModalRef?: React.RefObject<ViewLargerImageModalRef>;
/*
*/
handleDownloadImg?: (imgSrc?: string) => void;
/*
*/
onRecordClick?: (record?: IRecord) => void;
/*
id
*/
selectedRecordId?: string;
isRecordListLoading?: boolean;
recordListTitle?: string;
style?: React.CSSProperties;
cardStyle?: React.CSSProperties;
imgStyle?: React.CSSProperties;
largeImageTitle?: string;
}
export const RealTimeMonitor: React.FC<RealTimeMonitorProps> = (props) => {
const { videoDataSource,
handleWindowClick,
handleCloseButtonClick,
selectedWindowKey,
warningDataSource,
viewLargerImageModalRef,
handleDownloadImg,
onRecordClick,
selectedRecordId,
isRecordListLoading,
} = props
return (
<div className='zhst-biz-real-time-monitor' style={{ display: 'flex' }} >
<WindowToggle
selectedWindowKey={selectedWindowKey}
dataSource={videoDataSource}
handleWindowClick={handleWindowClick}
handleCloseButtonClick={handleCloseButtonClick}
/>
<WarningRecordList
dataSource={warningDataSource}
handleDownloadImg={handleDownloadImg}
onRecordClick={onRecordClick}
selectedRecordId={selectedRecordId}
viewLargerImageModalRef={viewLargerImageModalRef}
isRecordListLoading={isRecordListLoading}
recordListTitle="监控预警记录"
/>
</div>
);
};
export default RealTimeMonitor;

View File

@ -0,0 +1,82 @@
import React from 'react';
import { ViewLargerImageModal, WarningRecordCard, IRecord, ViewLargerImageModalRef } from '@zhst/biz';
import { Empty, Space, Spin } from 'antd';
import "./index.less"
import { LoadingOutlined } from '@ant-design/icons';
interface WarningRecordListProps {
dataSource?: IRecord[];
viewLargerImageModalRef?: React.RefObject<ViewLargerImageModalRef>;
/*
*/
handleDownloadImg?: (imgSrc?: string) => void;
/*
*/
onRecordClick?: (record?: IRecord) => void;
/*
id
*/
selectedRecordId?: string;
isRecordListLoading?: boolean;
recordListTitle?: string;
style?: React.CSSProperties;
cardStyle?: React.CSSProperties;
imgStyle?: React.CSSProperties;
largeImageTitle?: string;
}
const WarningRecordList: React.FC<WarningRecordListProps> = (props) => {
const {
dataSource = [],
viewLargerImageModalRef,
selectedRecordId,
handleDownloadImg,
onRecordClick,
isRecordListLoading,
recordListTitle,
style,
cardStyle,
imgStyle,
largeImageTitle
} = props
return (
<div className='zhst-biz-warning-record-list' style={style}>
<div className='header'>{recordListTitle}</div>
<div className='body'>
{
isRecordListLoading ?
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} />} />
</div>
: (dataSource?.length) > 0 ?
<Space direction='vertical' size={10} >
{dataSource?.map((record, index) => {
if (index > 2) return
return (<WarningRecordCard
key={record?.id}
record={record}
onRecordClick={(record) => { onRecordClick?.(record) }}
selectedRecordId={selectedRecordId}
cardStyle={{ width: 300, height: 264, ...cardStyle }}
imgStyle={{ width: 280, height: 169, ...imgStyle }}
/>)
}
)}
</Space>
:
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Empty description="暂无数据" />
</div>
}
</div>
{/* 弹窗 绑定ref 后可以调用 handleCancel方法关闭弹窗 show方法打开弹窗 */}
<ViewLargerImageModal ref={viewLargerImageModalRef} downloadImg={handleDownloadImg} title={largeImageTitle} />
</div>
)
};
export default WarningRecordList;

View File

@ -0,0 +1,20 @@
.zhst-biz-warning-record-list {
display: flex;
flex-direction: column;
border-left: solid 1px #00000026;
width: 320px;
.header {
width: 100%;
height: 48px;
background-color: #EFF2F4;
padding: 10px 20px;
box-sizing: border-box;
}
.body {
padding: 10px;
overflow: hidden;
flex: 1;
}
}

View File

@ -0,0 +1,2 @@
import WarningRecordList from './WarningRecordList'
export default WarningRecordList

View File

@ -0,0 +1,71 @@
import React, { useState } from 'react';
import { Segmented } from 'antd';
import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
import { VideoPlayerCard, VideoPlayerCardProps } from '@zhst/biz';
import './index.less'
import { theme } from 'antd/lib';
type Size = 'large' | 'small'
interface WindowToggleProps {
dataSource?: VideoPlayerCardProps[];
handleWindowClick?: (key?: string) => void;
handleCloseButtonClick?: (key?: string) => void;
selectedWindowKey?: string;
}
export const WindowToggle: React.FC<WindowToggleProps> = (props) => {
const { dataSource = [], handleWindowClick, handleCloseButtonClick, selectedWindowKey } = props
const [size, setSize] = useState<Size>("large");
const { useToken } = theme
const { token } = useToken()
const getLabelStyle = (isSelected: boolean) => ({
padding: "0 11px", background: "#fff",
...(isSelected ? { background: token.colorPrimary, color: '#fff' } : {}),
});
return (
<div className='zhst-biz-window-toggle'>
{/* 切换按钮 */}
<div className='header'>
<Segmented
defaultValue='large'
options={[
{ value: 'large', label: <div style={getLabelStyle(size === 'large')}><BarsOutlined /></div> },
{ value: 'small', label: <div style={getLabelStyle(size === 'small')}><AppstoreOutlined /></div> },
]}
onChange={(value) => {
// 当一个窗口时 默认 selectedkey 第一条数据的 windowkey
if (value === 'large' && dataSource.length > 0) {
const { windowKey } = dataSource[0]
handleWindowClick?.(windowKey)
}
setSize(value as Size)
}}
/>
</div>
<div className='body'>
{
dataSource?.map((item, index) => {
if (size === "large" && index > 0) return
return (
<VideoPlayerCard
key={item.windowKey}
selectedWindowKey={selectedWindowKey}
size={size} {...item}
handleWindowClick={handleWindowClick}
handleCloseButtonClick={handleCloseButtonClick}
/>)
})
}
</div>
</div>
);
};
export default WindowToggle;

View File

@ -0,0 +1,45 @@
.zhst-biz-window-toggle {
display: flex;
flex-direction: column;
flex: 1;
.header {
width: 100%;
height: 48px;
background-color: #EFF2F4;
padding: 10px 20px;
box-sizing: border-box;
.ant-segmented {
padding: 0;
.ant-segmented-group {
border-radius: 4px;
overflow: hidden;
.ant-segmented-item {
border-radius: 0;
.ant-segmented-item-label {
padding: 0;
}
}
}
}
}
.body {
flex: 1;
width: 100%;
background-color: #E5EAEC;
padding: 10px;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
>div {
margin: 10px;
}
}
}

View File

@ -0,0 +1,2 @@
import WindowToggle from './WindowToggle'
export default WindowToggle

View File

@ -0,0 +1,161 @@
import React, { useState } from 'react';
import { IRecord, RealTimeMonitor, VideoPlayerCardProps, useViewLargerImageModal } from '@zhst/biz';
import { videoData, warningData } from './mock';
import { Space } from 'antd';
import dayjs from 'dayjs'
import './index.less'
export default () => {
// 必须设置初始数据 用于渲染 4个窗口
const initialVideoDataSource: VideoPlayerCardProps[] = [
{
windowKey: 'first-window',
},
{
windowKey: 'second-window',
},
{
windowKey: 'third-window',
},
{
windowKey: 'forth-window',
}
]
const [videoDataSource, setVideoDataSource] = useState<VideoPlayerCardProps[]>(initialVideoDataSource);
const [warningDataSource, setWarningDataSource] = useState<IRecord[]>();
const [selectedWindowKey, setSelectedWindowKey] = useState<string | undefined>('first-window');
const [selectedRecordId, setSelectedRecordId] = React.useState<string | undefined>()
const [isRecordListLoading, setIsRecordListLoading] = React.useState<boolean>(false)
// 把弹窗的ref 拿出来
const viewLargerImageModalRef = useViewLargerImageModal()
const handleWindowClick = (key?: string) => {
setSelectedWindowKey(key)
}
const clearWindowData = (key?: string) => {
// 当关闭窗口时 也要刷新 右侧 预警记录接口 不要忘记
setVideoDataSource((pre) => {
const newVideoDataSource = pre.map((item) => {
if (item.windowKey === key) {
return { windowKey: key };
}
return item; // 保持不变
});
return newVideoDataSource
})
}
const handleDownloadImg = (imgSrc?: string) => {
console.log(imgSrc)
// 可以调用 下面 方法关闭弹窗
// viewLargerImageModalRef.current?.handleCancel()
}
const onRecordClick = (record?: IRecord) => {
// 点击的时候把数据 拿过来处理一下传给大图弹框
const { imgSrc, warningType, boxId, position, cabietId, warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {}
const formattedDate = warningTimestamp ? dayjs(warningTimestamp).format(warningTimeFormat) : '';
const warningTimeShow = warningTime ? warningTime : formattedDate
//用于渲染右侧的 信息
const warningData = [
{ label: '预警类型', value: warningType },
{ label: '预警时间', value: warningTimeShow },
{ label: '盒子', value: boxId },
{ label: '点位', value: position },
{ label: '柜子ID', value: cabietId },
]
// 调用这个方法打开弹框
viewLargerImageModalRef?.current?.show({ imgSrc: imgSrc, warningData: warningData })
setSelectedRecordId(record?.id)
}
const mockData = () => {
// 请求时需要对应窗口为loading 状态
setVideoDataSource((pre) => {
const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => {
// 开启loading
if (item.windowKey === selectedWindowKey) {
return { ...item, isWindowLoading: true, title: '' }
}
return item
});
return newVideoDataSource
})
// 模拟 视频数据请求
setTimeout(() => {
// 对后端返回数据进行处理 组装一套符合属性的 数据
const newVideoData: VideoPlayerCardProps = { imgSrc: videoData.imgSrc, title: videoData.title, }
setVideoDataSource((pre) => {
const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => {
// 传给 选中的视频窗口
if (item.windowKey === selectedWindowKey) {
return { ...item, ...newVideoData }
}
return item
});
return newVideoDataSource
})
setVideoDataSource((pre) => {
const newVideoDataSource: VideoPlayerCardProps[] = pre.map((item) => {
// 关闭loading
if (item.windowKey === selectedWindowKey) {
return { ...item, isWindowLoading: false }
}
return item
});
return newVideoDataSource
})
}, 3000);
// 模拟 预警记录数据请求
setIsRecordListLoading(true)
setTimeout(() => {
const newWarningDataSource: IRecord[] = warningData.map(o => {
return {
imgSrc: o.imgSrc,
id: o.id,
warningType: o.warningType,
boxId: o.boxId,
position: o.position,
cabietId: o.cabietId,
//,`柜子ID: ${o.cabietId}`
warningInfo: [`盒子${o.boxId}`, `位置${o.position}`, `柜子ID${o.cabietId}`],
// cabietText: `柜子ID: ${o.cabietId}`,
warningTimestamp: o.warningTimestamp,
}
})
setWarningDataSource(newWarningDataSource)
setIsRecordListLoading(false)
}, 1000)
}
return (
<Space size={[8, 16]} direction="vertical">
<RealTimeMonitor
selectedWindowKey={selectedWindowKey}
videoDataSource={videoDataSource}
handleWindowClick={handleWindowClick}
handleCloseButtonClick={clearWindowData}
warningDataSource={warningDataSource}
handleDownloadImg={handleDownloadImg}
onRecordClick={onRecordClick}
selectedRecordId={selectedRecordId}
viewLargerImageModalRef={viewLargerImageModalRef}
isRecordListLoading={isRecordListLoading}
recordListTitle="监控预警记录" />
<button onClick={() => { mockData() }}></button>
</Space>
)
}

View File

@ -0,0 +1,5 @@
#realtimemonitor-demo-base {
.dumi-default-previewer-demo>div {
width: 100%;
}
}

View File

@ -0,0 +1,65 @@
export const videoData = {
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
// videoSrc: 'ws://10.0.0.7:9033/flv/File/test/test_h264_1.mp4.flv?ip=127.0.0.1',
title: `盒子1 点位1`
}
export const warningData = [
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '156156ewr1',
warningType: '火焰识别',
boxId: '2',
position: '2',
cabietId: '002',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
},
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '156156ewr155',
warningType: '火焰识别',
boxId: '1',
position: '1',
cabietId: '001',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
},
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '156156rew155',
warningType: '火焰识别',
boxId: '3',
position: '3',
cabietId: '004',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
},
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '15615615ew5',
warningType: '火焰识别',
boxId: '4',
position: '4',
cabietId: '004',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
},
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '15615615sdf5',
warningType: '火焰识别',
boxId: '5',
position: '5',
cabietId: '005',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
}
]

View File

@ -0,0 +1,36 @@
---
group: 数据展示
category: Components
subtitle: 实时监控页面
title: RealTimeMonitor 实时监控页面
---
# RealTimeMonitor 实时监控页面
<code src="./demo/base.tsx"></code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| videoDataSource | 用于渲染 每个窗口的数据 | VideoPlayerCardProps[] | 需要传一组默认值用于窗口的渲染| - |
| warningDataSource | 用于渲染 预警记录的数据 | WindowProps[] | 需要传一组默认值用于窗口的渲染| - |
| handleWindowClick | 用于获取窗口的 windowKey 方便更新对应窗口数据 | (key?: string) => void; | - | - |
| handleCloseButtonClick | 用于点击窗口关闭按钮的事件 | (key?: string) => void; | - | - |
| selectedWindowKey | 选中的窗口的 key 用于控制 选中边框样式 |string| - | - |
| handleDownloadImg | 用于处理查看大图下载图片的事件 | (key?: string) => void; | - | - |
| onRecordClick | 用于处理预警记录卡片点击事件事件 | (key?: string) => void; | - | - |
| selectedRecordId| 用于判断是否显示选中样式 |string| - | - |
| isRecordListLoading | 判断预警记录列表loading状态 |boolean| - | - |
| recordListTitle | 预警记录列表的标题 | string | - | - |
| viewLargerImageModalRef | 查看大图弹窗的ref | viewLargerImageModalRef={viewLargerImageModalRef}| | - |
| viewLargerImageModalRef | 查看大图弹窗的ref |React.RefObject\<ViewLargerImageModalRef\>| - | - |

View File

@ -0,0 +1,2 @@
import RealTimeMonitor from './RealTimeMonitor'
export default RealTimeMonitor

View File

@ -0,0 +1,101 @@
import { Card, Space, CardProps, Spin, Button } from 'antd';
import { theme } from 'antd/lib';
import { VideoPlayer, type VideoViewRef } from '@zhst/meta';
import React, { useState, useEffect, ReactNode, useRef } from 'react';
import { CloseOutlined, LoadingOutlined } from '@ant-design/icons';
import './index.less'
export interface VideoPlayerCardProps {
windowKey?: string;
selectedWindowKey?: string;
showType?: 'video' | "image";
imgSrc?: string;
videoSrc?: string;
cardProps?: CardProps;
errorReasonText?: string;
isWindowLoading?: boolean;
size?: 'large' | 'small';
title?: string | ReactNode
handleCloseButtonClick?: (key?: string) => void;
handleWindowClick?: (key?: string) => void;
}
export const VideoPlayerCard: React.FC<VideoPlayerCardProps> = (props) => {
const componentName = `zhst-biz-video-player-card`;
const { showType, imgSrc, videoSrc, cardProps, isWindowLoading, errorReasonText, size, title, handleCloseButtonClick, handleWindowClick, windowKey, selectedWindowKey = '' } = props;
const [cardContent, setCardContent] = useState<JSX.Element | null>(null);
const { useToken } = theme
const { token } = useToken()
const videoRef = useRef<VideoViewRef>(null)
const selectedBorderStyle = {
border: `2px solid ${token.colorPrimary}`, boxShadow: " 0px 2px 9px 0px rgba(0,0,0,0.16)"
}
const cardStyle: React.CSSProperties = {
...(size === 'large' ? { height: 931 } : { height: 456, cursor: 'pointer' }),
...(size === 'small' && selectedWindowKey === windowKey ? selectedBorderStyle : {})
};
const videoPlayerCardStyle = size === 'small' ? { width: "calc(50% - 20px)" } : { flex: 1 }
useEffect(() => {
if (!isWindowLoading && (videoSrc || imgSrc)) {
let contentElement: JSX.Element | null = null;
if (videoSrc) {
contentElement = (
<VideoPlayer ref={videoRef} url={videoSrc} />
);
videoRef.current?.setShowCrop(true)
} else if (imgSrc) {
contentElement = (
<img
alt="首帧图"
src={imgSrc}
style={{ width: "100%", height: "100%", display: 'block' }}
/>
);
}
setCardContent(contentElement);
} else {
setCardContent(null)
}
}, [showType, imgSrc, videoSrc, isWindowLoading]);
return (
<div className={componentName} onClick={() => { handleWindowClick?.(windowKey) }} style={videoPlayerCardStyle}>
<Card
title={
<Space style={{ width: "100%", justifyContent: "space-between" }}>
<div>{title}</div>
<div className="card-close-button">
<Button type="text" onClick={() => { handleCloseButtonClick?.(windowKey) }} >
<CloseOutlined />
</Button>
</div>
</Space>}
style={{ display: "flex", flexDirection: "column", borderRadius: 4, overflow: "hidden", ...cardStyle }}
bodyStyle={{ flex: 1 }}
{...cardProps}
>
{cardContent ? (
<>
{cardContent}
</>
) : (
<div style={{ backgroundColor: '#000', height: '100%', display: 'flex', padding: '20px', boxSizing: 'border-box' }}>
{
isWindowLoading ?
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} />} />
</div>
: !!errorReasonText && <span style={{ color: token.colorError }}>{errorReasonText}</span>
}
</div>
)}
{/* 其他内容 */}
</Card>
</div>
);
};
export default VideoPlayerCard;

View File

@ -0,0 +1,19 @@
import React from 'react';
import { VideoPlayerCard, type VideoPlayerCardProps } from '@zhst/biz';
import { Space } from 'antd';
const mockVideoPlayerCardProps: VideoPlayerCardProps = {
showType: 'image',
// videoSrc: 'https://example.com/video.mp4',
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png', // 如果需要在没有视频时显示图片封面
size: 'small',
};
export default () => {
return (
<Space size={[8, 16]} direction="vertical">
<VideoPlayerCard {...mockVideoPlayerCardProps} />
</Space>
)
}

View File

@ -0,0 +1,30 @@
.zhst-biz-video-player-card {
.ant-card-head {
padding: 0 20px;
}
.ant-card-body {
padding: 0;
overflow: hidden;
border-radius: 0;
.zhst-image__video-view {
height: 100%;
}
}
.card-close-button {
.ant-btn {
padding: 0 3px;
height: 22px;
color: #00000073;
}
.ant-btn:hover {
padding: 0 3px;
height: 22px;
color: #000000e0;
}
}
}

View File

@ -0,0 +1,34 @@
---
group: 数据展示
category: Components
subtitle: 视频播放卡片
title: VideoPlayerCard 视频播放卡片
---
# VideoPlayerCard 视频播放卡片
<code src="./demo/base.tsx"></code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| windowKey | 每个卡片的唯一标识 | string | - | - |
| selectedWindowKey | 选中的窗口key | string | - | - |
| imgSrc | 图片地址 | string | - | - |
| videoSrc | 视频地址 | string | - | - |
| errorReasonText | 加载失败的错误提示 | string | - | - |
| isWindowLoading | 判断是否显示loading | boolean | - | - |
| size | 设置窗口大小 | 'large' \| 'small' | - | - |
| title | 设置窗口标题 | string \| ReactNode | - | - |
| handleCloseButtonClick | 处理关闭按钮事件 | (key?: string) => void| - | - |
| handleWindowClick | 处理窗口点击事件 | (key?: string) => void| - | - |

View File

@ -0,0 +1,3 @@
import VideoPlayerCard from './VideoPlayerCard'
export type { VideoPlayerCardProps } from './VideoPlayerCard'
export default VideoPlayerCard

View File

@ -0,0 +1,86 @@
import React, { useImperativeHandle, useRef, useState, forwardRef } from 'react';
import { Modal, ModalProps, Space, SpaceProps } from 'antd';
import theme from 'antd/lib/theme';
import { DownloadOutlined } from '@ant-design/icons';
import './index.less'
type ViewLargerImageModalParams = {
imgSrc?: string;
warningData?: {
label?: string;
value?: string;
}[];
};
export interface ViewLargerImageModalRef {
show: (params?: ViewLargerImageModalParams) => void;
handleCancel: () => void;
}
export interface ViewLargerImageModalProps {
imgStyle?: React.CSSProperties;
downloadImg?: (imgSrc?: string) => void;
title?: string;
downloadText?: string;
modalProps?: ModalProps
spaceProps?: SpaceProps;
}
export const ViewLargerImageModal = forwardRef<ViewLargerImageModalRef, ViewLargerImageModalProps>(
(props, ref) => {
const { modalProps, downloadImg, imgStyle, title = '预警大图', downloadText = '下载大图', spaceProps } = props
const { useToken } = theme
const { token } = useToken()
const [open, setOpen] = useState<boolean>(false);
const [imgSrc, setImgSrc] = useState<string>();
const [warningData, setWarningData] = useState<ViewLargerImageModalParams['warningData']>();
const handleCancel = () => {
setOpen(false);
}
useImperativeHandle(ref, () => {
return {
show: (_params) => {
setOpen(true);
setImgSrc(_params?.imgSrc)
setWarningData(_params?.warningData)
},
handleCancel
};
});
return (
<Modal
className='zhst-biz-view-warning-larger-image-modal'
open={open}
destroyOnClose
title={title}
width="1029px"
footer={null}
onCancel={handleCancel}
{...modalProps}
>
<Space size={0} styles={{ item: { backgroundColor: '#F6F9FAFF' } }} {...spaceProps}>
<img alt={title} src={imgSrc} style={{ width: 789, height: 444, display: 'block', ...imgStyle }} />
<div className='right-context'>
{warningData?.map(({ label, value }) => (
<div key={label} >
<span className='context-key'>{`${label}: `}</span>
{value}
</div>
))}
{imgSrc && downloadImg && <div className='img-download' style={{ color: token.colorPrimary }} onClick={() => downloadImg?.(imgSrc)} ><DownloadOutlined /><span style={{ paddingLeft: 3 }}>{downloadText}</span></div>}
</div>
</Space>
</Modal>
);
}
)
export default ViewLargerImageModal;
export const useViewLargerImageModal = () => {
return useRef<ViewLargerImageModalRef>(null);
};

View File

@ -0,0 +1,94 @@
import React from 'react';
import { ViewLargerImageModal, WarningRecordCard, IRecord, useViewLargerImageModal } from '@zhst/biz';
import { Space } from 'antd';
import dayjs from 'dayjs';
// 结合预警图列表 演示查看预警大图的使用 例如 后端返回这样的数据结构
const backEndData = [
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '1561561',
warningType: '火焰识别',
boxId: '2',
position: '2',
cabietId: '002',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
},
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '156156155',
warningType: '火焰识别',
boxId: '1',
position: '1',
cabietId: '001',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
}
]
// 前端处理数据结构
const dataSource = backEndData.map(o => {
return {
imgSrc: o.imgSrc,
id: o.id,
warningType: o.warningType,
boxId: o.boxId,
position: o.position,
cabietId: o.cabietId,
//,`柜子ID ${o.cabietId}`
warningInfo: [`盒子${o.boxId}`, `位置${o.position}`],
cabietText: `柜子ID: ${o.cabietId}`,
warningTimestamp: o.warningTimestamp,
}
})
export default () => {
const [selectedRecordId, setSelectedRecordId] = React.useState<string | undefined>()
// 把弹窗的ref 拿出来
const viewLargerImageModalRef = useViewLargerImageModal()
const handleDownloadImg = () => {
console.log('download')
// 可以调用 下面 方法关闭弹窗
// viewLargerImageModalRef.current?.handleCancel()
}
const handleClick = (record?: IRecord) => {
// 点击的时候把数据 拿过来处理一下传给大图弹框
const { imgSrc, warningType, boxId, position, cabietId, warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {}
const formattedDate = warningTimestamp ? dayjs(warningTimestamp).format(warningTimeFormat) : '';
const warningTimeShow = warningTime ? warningTime : formattedDate
//用于渲染右侧的 信息
const warningData = [
{ label: '预警类型', value: warningType },
{ label: '预警时间', value: warningTimeShow },
{ label: '盒子', value: boxId },
{ label: '点位', value: position },
{ label: '柜子ID', value: cabietId },
]
// 调用这个方法打开弹框
viewLargerImageModalRef?.current?.show({ imgSrc: imgSrc, warningData: warningData })
setSelectedRecordId(record?.id)
}
return (
<>
<Space size={[8, 16]} >
{
dataSource?.map((record) => <WarningRecordCard key={record?.id} record={record} onRecordClick={(record) => { handleClick(record) }} selectedRecordId={selectedRecordId} />)
}
</Space>
{/* 弹窗 绑定ref 后可以调用 handleCancel方法关闭弹窗 show方法打开弹窗 */}
<ViewLargerImageModal ref={viewLargerImageModalRef} downloadImg={handleDownloadImg} title="预警大图" />
</>
)
}

View File

@ -0,0 +1,62 @@
.zhst-biz-view-warning-larger-image-modal {
font-family: MicrosoftYaHei;
.ant-modal-content {
padding: 0;
height: 492px;
border-radius: 6px;
overflow: hidden;
.ant-modal-close {
top: 14px;
right: 16px;
}
.ant-modal-header {
height: 48px;
line-height: 48px;
margin-bottom: 0;
.ant-modal-title {
height: 100%;
line-height: 48px;
font-weight: bold;
padding-left: 20px;
}
}
.ant-modal-body {
height: 444px;
>div {
width: 100%;
height: 100%;
align-items: flex-start;
>div:nth-child(2) {
position: relative;
flex: 1;
box-sizing: border-box;
height: 100%;
padding: 30px 16px;
.right-context>div {
margin-bottom: 20px;
}
.right-context .context-key {
font-weight: bold;
}
.img-download {
position: absolute;
bottom: 0;
cursor: pointer;
}
}
}
}
}
}

View File

@ -0,0 +1,28 @@
---
group: 数据展示
category: Components
subtitle: 查看大图弹窗
title: ViewLargerImageModal 查看大图弹窗
---
# ViewLargerImageModal 查看大图弹窗
<code src="./demo/base.tsx"></code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| show() |通过 ref 用于开启弹窗 可以将点击的 记录传给弹窗| | | |
| handleCancel() | 通过 ref 用于关闭弹窗 | | | |
| imgSrc | 图片地址 |string | | |
| contextData | 大图显示的数据 | | | |
| imgStyle | 用于修改图片样式 | | | |
| downloadImg | 传入下载图片的方法 | | | |
| title | 弹窗标题 | string | | |
| downloadText | 下载按钮文本 | string | | |
| modalProps | 弹窗属性 | | | |

View File

@ -0,0 +1,4 @@
import ViewLargerImageModal, { useViewLargerImageModal } from './ViewLargerImageModal'
export type { ViewLargerImageModalRef, ViewLargerImageModalProps } from './ViewLargerImageModal'
export default ViewLargerImageModal;
export { useViewLargerImageModal };

View File

@ -0,0 +1,109 @@
import { Card, Space, Divider, CardProps } from 'antd';
import { theme } from 'antd/lib';
import React from 'react';
import dayjs from 'dayjs';
import './index.less'
export interface IRecord {
imgSrc?: string;
id?: string;
/**
*
*/
warningType?: string;
/*
*/
warningInfo?: string[]
/*
id
/*
ID
*/
boxId: string;
/*
*/
position: string;
/*
id
*/
cabietId?: string;
/*
/*
id
*/
cabietText?: string;
/*
*/
warningTime?: string;
/*
*/
warningTimestamp?: string | number
/*
@default YYYY-MM-DD HH:mm:ss
*/
warningTimeFormat?: string;
};
export interface WarningRecordCardProps {
record?: IRecord;
onRecordClick?: (record?: IRecord) => void;
style?: React.CSSProperties;
cardProps?: CardProps;
selectedRecordId?: string;
cardStyle?: React.CSSProperties;
imgStyle?: React.CSSProperties;
};
export const WarningRecordCard: React.FC<WarningRecordCardProps> = (props) => {
const componentName = `zhst-biz-warning-record-card`;
const { record, onRecordClick, style, cardProps, selectedRecordId, cardStyle, imgStyle } = props;
const { imgSrc, id, warningType, warningInfo = [], cabietText, warningTime, warningTimestamp, warningTimeFormat = 'YYYY-MM-DD HH:mm:ss' } = record || {}
const formattedDate = warningTimestamp ? dayjs(warningTimestamp).format(warningTimeFormat) : '';
const warningTimeShow = warningTime ? warningTime : formattedDate
const { useToken } = theme
const { token } = useToken()
const selectedBorderStyle = {
border: `2px solid ${token.colorPrimary}`, boxShadow: " 0px 2px 9px 0px rgba(0,0,0,0.16)"
}
const selectedCardStyle: React.CSSProperties = {
...(selectedRecordId === record?.id ? selectedBorderStyle : {})
};
const handleClick = () => {
onRecordClick?.(record);
};
return (
<div className={componentName} key={id} onClick={handleClick} style={style}>
<Card
cover={<img alt="预警图" src={imgSrc} style={{ width: 336, height: 203, borderRadius: 0, ...imgStyle }} />}
style={{ width: 356, height: 302, padding: 10, borderRadius: 4, ...selectedCardStyle, ...cardStyle }}
{...cardProps}
>
<div className='left-context'>
<div className="warning-type">{warningType}</div>
<Space size={0} split={<Divider type="vertical" />}>
{warningInfo?.map((item, index) => (
<div key={index} className="info-item">
{item}
</div>
))}
</Space>
<div className='warning-time'>{warningTimeShow}</div>
</div>
<div className='cabietInfo' >{cabietText}</div>
</Card>
</div>
);
};
export default WarningRecordCard;

View File

@ -0,0 +1,56 @@
import React from 'react';
import { WarningRecordCard } from '@zhst/biz';
import { Space } from 'antd';
// 例如 后端返回这样的数据结构
const backEndData = [
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '1561561',
warningType: '火焰识别',
boxId: '2',
position: '2',
cabietId: '002',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
},
{
imgSrc: 'https://i.yourimageshare.com/lRHiD2UnAT.png',
id: '156156155',
warningType: '火焰识别',
boxId: '1',
position: '1',
cabietId: '001',
// warningTime: "2023-03-01 ",
warningTimestamp: Date.now(),
// warningTimeFormat:"YYYY-MM-DD"
}
]
// 前端处理数据结构
const dataSource = backEndData.map(o => {
return {
imgSrc: o.imgSrc,
id: o.id,
warningType: o.warningType,
boxId: o.boxId,
position: o.position,
cabietId: o.cabietId,
warningInfo: [`盒子${o.boxId}`, `位置${o.position}`, `柜子ID${o.cabietId}`],
// cabietText: `柜子ID: ${o.cabietId}`,
warningTimestamp: o.warningTimestamp,
}
})
export default () => {
return (
<Space size={[8, 16]} direction="vertical">
{
dataSource?.map((record) => <WarningRecordCard key={record?.id} record={record} />)
}
</Space>
)
}

View File

@ -0,0 +1,27 @@
.zhst-biz-warning-record-card {
cursor: pointer;
.ant-card-body {
padding: 0;
font-family: MicrosoftYaHei;
line-height: 19px;
display: flex;
margin-top: 10px;
.left-context {
flex: 1;
>div {
margin-top: 6px;
}
>div:nth-child(1) {
margin-top: 0;
}
}
.warning-type {
font-weight: bold;
}
}
}

View File

@ -0,0 +1,30 @@
---
group: 数据展示
category: Components
subtitle: 预警记录卡片
title: WarningRecordCard 预警记录卡片
---
# WarningRecordCard 预警记录卡片
<code src="./demo/base.tsx"></code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| imgSrc | 图片src | string | - | - |
| id | 数据的唯一id 用于key 传值| string | - | - |
| warningType | 预警类型 | string | - | - |
| warningInfo | 盒子 点位 柜子 等信息 | string[] | - | - |
| cabietText | 右侧 柜子信息 | string | - | - |
| warningTime | 预警时间 格式化后的时间字符串 | string | - | - |
| warningTimestamp | 预警时间戳 | string \| number | - | - |
| warningTimeFormat | 预警时间格式 | string | YYYY-MM-DD HH:mm:ss | - |

View File

@ -0,0 +1,3 @@
import WarningRecordCard from './WarningRecordCard'
export type { IRecord, WarningRecordCardProps} from './WarningRecordCard'
export default WarningRecordCard

View File

@ -1,21 +1,11 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { InputProps, Tabs, TabsProps, TreeDataNode, TreeProps } from 'antd' import { Tabs, TabsProps } from 'antd'
import BoxPanel from './components/boxPanel'; import BoxPanel from './components/boxPanel';
import { ModalFormProps } from '@ant-design/pro-components'; import type { BoxPanelProps } from './components/boxPanel';
export interface BoxSelectTreeProps { export interface BoxSelectTreeProps extends BoxPanelProps {
boxDataSource: TreeDataNode[]
data: TreeDataNode[]
onSearch?: (e: any) => void // 搜索
onItemSelect?: TreeProps['onSelect']
onItemCheck?: TreeProps['onCheck']
onTabChange?: (e: any) => void onTabChange?: (e: any) => void
onBoxBatchDelete?: (data?: any) => void
onBoxDelete?: (data?: any) => void
onCreateSubmit?: ModalFormProps['onFinish']
tabsProps?: TabsProps tabsProps?: TabsProps
searchInputProps?: InputProps
treeProps?: TreeProps
} }
const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => { const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
@ -29,15 +19,17 @@ const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
onBoxBatchDelete, onBoxBatchDelete,
onBoxDelete, onBoxDelete,
onCreateSubmit, onCreateSubmit,
onClockClick,
onImport,
onCreate,
tabsProps, tabsProps,
searchInputProps, searchInputProps,
treeProps treeProps,
customImport,
showOptions,
extraBtns,
} = props } = props
const onChange = (key: string) => {
onTabChange?.(key)
};
const items: TabsProps['items'] = [ const items: TabsProps['items'] = [
{ {
key: '1', key: '1',
@ -48,12 +40,18 @@ const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
boxDataSource={boxDataSource} boxDataSource={boxDataSource}
treeProps={treeProps} treeProps={treeProps}
data={data} data={data}
onCreate={onCreate}
onCreateSubmit={onCreateSubmit} onCreateSubmit={onCreateSubmit}
onBoxBatchDelete={onBoxBatchDelete} onBoxBatchDelete={onBoxBatchDelete}
onBoxDelete={onBoxDelete} onBoxDelete={onBoxDelete}
onSearch={onSearch} onSearch={onSearch}
onItemCheck={onItemCheck} onItemCheck={onItemCheck}
onItemSelect={onItemSelect} onItemSelect={onItemSelect}
showOptions={showOptions}
customImport={customImport}
extraBtns={extraBtns}
onClockClick={onClockClick}
onImport={onImport}
/> />
) )
}, },
@ -66,12 +64,18 @@ const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
searchInputProps={searchInputProps} searchInputProps={searchInputProps}
treeProps={treeProps} treeProps={treeProps}
data={data} data={data}
onCreate={onCreate}
onBoxBatchDelete={onBoxBatchDelete} onBoxBatchDelete={onBoxBatchDelete}
onCreateSubmit={onCreateSubmit} onCreateSubmit={onCreateSubmit}
onBoxDelete={onBoxDelete} onBoxDelete={onBoxDelete}
onSearch={onSearch} onSearch={onSearch}
onItemCheck={onItemCheck} onItemCheck={onItemCheck}
onItemSelect={onItemSelect} onItemSelect={onItemSelect}
showOptions={showOptions}
customImport={customImport}
extraBtns={extraBtns}
onClockClick={onClockClick}
onImport={onImport}
/> />
) )
}, },
@ -82,7 +86,7 @@ const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
defaultActiveKey="1" defaultActiveKey="1"
centered centered
items={items} items={items}
onChange={onChange} onChange={onTabChange}
tabBarGutter={0} tabBarGutter={0}
indicator={{ size: (origin) => origin, align: 'center' }} indicator={{ size: (origin) => origin, align: 'center' }}
{...tabsProps} {...tabsProps}

View File

@ -1,14 +1,16 @@
import React, { FC, useState, useRef } from 'react'; import React, { FC, useState, useRef } from 'react';
import{ Button, Divider, Input, Space, TreeDataNode } from 'antd' import{ Button, Divider, Input, Space, TreeDataNode } from 'antd'
import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } from '@ant-design/pro-components' import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } from '@ant-design/pro-components'
import { DiffOutlined, SwitcherOutlined } from '@ant-design/icons' import { ClockCircleOutlined, CloseCircleOutlined, DiffOutlined, FolderAddOutlined, ImportOutlined, SwitcherOutlined } from '@ant-design/icons'
import type { TreeProps, InputProps } from 'antd'; import type { TreeProps, InputProps } from 'antd';
import type { BoxTreeProps } from '../../../tree';
import TreeTransferModal from '../../../treeTransferModal' import TreeTransferModal from '../../../treeTransferModal'
import BoxTree from '../../../tree'; import BoxTree from '../../../tree';
export interface BoxGroupPanelProps { export interface BoxPanelProps {
searchInputProps?: InputProps searchInputProps?: InputProps
treeProps?: TreeProps showOptions?: boolean
treeProps?: Partial<BoxTreeProps>
data: TreeDataNode[] data: TreeDataNode[]
boxDataSource: TreeDataNode[] boxDataSource: TreeDataNode[]
handleImport?: () => void handleImport?: () => void
@ -18,11 +20,19 @@ export interface BoxGroupPanelProps {
onBoxBatchDelete?: (data?: any) => void onBoxBatchDelete?: (data?: any) => void
onBoxDelete?: (data?: any) => void onBoxDelete?: (data?: any) => void
onCreateSubmit?: ModalFormProps['onFinish'] onCreateSubmit?: ModalFormProps['onFinish']
onClockClick?: () => void
onImport?: () => void
onBatch?: () => void
onCreate?: () => void
customImport?: any
extraBtns?: any
} }
const BoxGroupPanel: FC<BoxGroupPanelProps> = (props) => { const BoxPanel: FC<BoxPanelProps> = (props) => {
const { const {
searchInputProps, searchInputProps,
showOptions = true,
extraBtns,
data = [], data = [],
onSearch, onSearch,
treeProps, treeProps,
@ -31,7 +41,12 @@ const BoxGroupPanel: FC<BoxGroupPanelProps> = (props) => {
onCreateSubmit, onCreateSubmit,
onBoxBatchDelete, onBoxBatchDelete,
onBoxDelete, onBoxDelete,
boxDataSource onClockClick,
onImport,
onBatch,
onCreate,
boxDataSource,
customImport
} = props } = props
const [isTreeCheckable, setIsTreeCheckable] = useState(false) const [isTreeCheckable, setIsTreeCheckable] = useState(false)
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]); const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
@ -40,8 +55,7 @@ const BoxGroupPanel: FC<BoxGroupPanelProps> = (props) => {
const createFormRef = useRef< const createFormRef = useRef<
ProFormInstance<{ ProFormInstance<{
name: string; name: string;
company?: string; boxList?: any[];
useMode?: string;
}> }>
>() >()
@ -75,89 +89,122 @@ const BoxGroupPanel: FC<BoxGroupPanelProps> = (props) => {
setTargetItems(pre => pre.filter(o => o.key !== key)) setTargetItems(pre => pre.filter(o => o.key !== key))
} }
const onOk = (data: any) => { // 盒子点击确定
console.log('data', data) const onBoxChoiceOk = async (data: any) => {
createFormRef.current?.setFieldValue('boxList', data)
createFormRef.current?.setFieldValue('boxName', 123)
console.log(createFormRef.current?.getFieldValue('boxList'))
setBoxChoiceOpen(false)
} }
const onReset = () => { // 盒子选择重置
const onBoxChoiceReset = () => {
setCheckedKeys([]) setCheckedKeys([])
setTargetItems([]) setTargetItems([])
} }
return ( return (
<div style={{ padding: '0 16px' }}> <div style={{ padding: '0 16px' }}>
{/* 盒子选择弹框 */}
<TreeTransferModal <TreeTransferModal
open={boxChoiceOpen} open={boxChoiceOpen}
onCancel={() => setBoxChoiceOpen(false)} onCancel={() => setBoxChoiceOpen(false)}
onRadioChange={(val) => console.log('radio', val)} // 顶部 radio 事件 onRadioChange={(e) => console.log('radio', e.target.value)} // 顶部 radio 事件
dataSource={boxDataSource} // 数据源 dataSource={boxDataSource} // 数据源
targetItems={targetItems} // 右侧选中项 targetItems={targetItems} // 右侧选中项
checkedKeys={checkedKeys} // 左侧选中 checkedKeys={checkedKeys} // 左侧选中
onReset={onReset} // 重置按钮事件 onReset={onBoxChoiceReset} // 重置按钮事件
onOk={onOk} // 确定按钮事件 onOk={onBoxChoiceOk} // 确定按钮事件
onTreeCheck={onTreeCheck} // 树check选中事件 onTreeCheck={onTreeCheck} // 树check选中事件
onItemDelete={onItemDelete} // 右侧点击删除事件 onItemDelete={onItemDelete} // 右侧点击删除事件
/> />
<Space size={12} direction='vertical'> <Space size={12} direction='vertical' style={{ width: '100%' }}>
<Space> <Space size={4} style={{ width: '100%', justifyContent: 'space-between' }} >
<Input size='middle' onChange={(e) => onSearch?.(e)} placeholder='请输入盒子名称' {...searchInputProps} /> <Input size='middle' onChange={(e) => onSearch?.(e)} placeholder='请输入盒子名称' {...searchInputProps} />
<Button style={{ width: '80px' }} type='primary' ></Button> {customImport || (
<>
<Button type="text" onClick={() => onBatch?.() || handleCheckable()} icon={isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />} />
<Button type="text" onClick={() => onClockClick?.()} icon={<ClockCircleOutlined />} />
</>
)}
</Space> </Space>
<Space align='center'> {/* 是否显示操作按钮 */}
<ModalForm {showOptions && (
width={'600px'} <>
formRef={createFormRef} <Space align='center'>
title="新建组" <Button type='text' style={{ padding: '4px 8px' }} onClick={() => onImport?.()} icon={<ImportOutlined />} ></Button>
modalProps={{ destroyOnClose: true }} <Divider type="vertical" style={{ margin: '8px 0' }} />
layout='horizontal' {onCreate ?
labelCol={{ span: 6 }} (
wrapperCol={{ span: 18 }} <Button onClick={onCreate} type='text' style={{ padding: '4px 8px' }} icon={<FolderAddOutlined />} ></Button>
trigger={<Button type='link' ></Button>} ) : (
submitter={{ <ModalForm<{
searchConfig: { name: string
submitText: '确定', boxList?: any[]
resetText: '取消', }>
}, width={'600px'}
}} open={onCreate ? false : undefined}
onFinish={onCreateSubmit} formRef={createFormRef}
> title="新建组"
<ProFormText modalProps={{ destroyOnClose: true }}
rules={[ layout='horizontal'
{ labelCol={{ span: 6 }}
required: true, wrapperCol={{ span: 18 }}
}, trigger={<Button type='text' style={{ padding: '4px 8px' }} icon={<FolderAddOutlined />} ></Button>}
]} submitter={{
width="md" searchConfig: {
name="name" submitText: '确定',
label="盒子组名称" resetText: '取消',
placeholder="请输入盒子名称" },
/> }}
<ProFormText onFinish={onCreateSubmit}
width="md" >
name="boxList" <ProFormText
label="盒子选择" rules={[
placeholder="已选择0个盒子" {
fieldProps={{ required: true,
readOnly: true, max: 20,
suffix: ( },
<Space> {
<a onClick={() => { pattern: /^[^\s]*$/g,
createFormRef.current?.setFieldValue('boxList', null) message: '禁止输入空格'
}} ></a> }
<a onClick={() => setBoxChoiceOpen(true)}></a> ]}
</Space> 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' }} />
</ModalForm> {/* @ts-ignore */}
<Divider type="vertical" /> <Button danger type='text' style={{ padding: '4px 8px' }} icon={<CloseCircleOutlined />} disabled={treeProps?.checkedKeys?.length <= 0} onClick={onBoxBatchDelete} ></Button>
{/* @ts-ignore */} </Space>
<Button danger type='link' disabled={treeProps?.checkedKeys?.length <= 0} onClick={onBoxBatchDelete} ></Button> <Divider style={{ margin: 0 }} />
<Divider type="vertical" /> </>
<Button type="link" onClick={() => handleCheckable()} icon={isTreeCheckable ? <DiffOutlined /> : <SwitcherOutlined />} /> )}
<Button type="link" onClick={() => handleCheckable()} icon={isTreeCheckable ? <DiffOutlined /> : <SwitcherOutlined />} /> {extraBtns}
</Space>
<Divider style={{ margin: 0 }} />
<BoxTree <BoxTree
treeCheckable={isTreeCheckable} treeCheckable={isTreeCheckable}
data={data} data={data}
@ -171,4 +218,4 @@ const BoxGroupPanel: FC<BoxGroupPanelProps> = (props) => {
) )
} }
export default BoxGroupPanel export default BoxPanel

View File

@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { boxDataSource } from './mock'
interface DataNode {
title: string;
key: string;
isLeaf?: boolean;
children?: DataNode[];
}
const initTreeData: DataNode[] = [
{ title: '盒子组1', key: '0' },
{ title: '盒子组2', key: '1' },
{ title: '盒子组3', key: '2', isLeaf: true },
];
/**
*
* @param list
* @param key
* @param children
* @returns newList
*/
const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] =>
list.map((node) => {
if (node.key === key) {
return {
...node,
children,
};
}
if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
});
const demo = () => {
const [treeData, setTreeData] = useState(initTreeData);
const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const onLoadData = ({ key, children }: any) =>
new Promise<void>((resolve) => {
if (children) {
resolve();
return;
}
setTimeout(() => {
setTreeData((origin) =>
updateTreeData(origin, key, [
{ title: '盒子', key: `${key}-0` },
{ title: '盒子', key: `${key}-1` },
]),
);
resolve();
}, 1000);
});
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={treeData}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
loadData: (_params) => onLoadData(_params),
checkedKeys
}}
/>
</div>
);
};
export default demo;

View File

@ -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 { Select, TreeProps, Modal, Checkbox } from 'antd'; import { Select, TreeProps, Modal, Checkbox } from 'antd';
import { BOX_TYPE_LIST } from '../../utils/constants';
const { Option } = Select const { Option } = Select
const demo = () => { const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [searchType, setSearchType] = useState('1') const [searchType, setSearchType] = useState('1')
const [searchVal, setSearchVal] = useState('') const [searchVal, setSearchVal] = useState('')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]); const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
@ -32,18 +34,25 @@ const demo = () => {
} }
return ( return (
<div style={{ border: '1px solid #ccc', width: '320px' }}> <div style={{ border: '1px solid #ccc', width: '320px', minHeight: '900px' }}>
{contextHolder} {contextHolder}
<BoxSelectTree <BoxSelectTree
data={treeData} data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource} boxDataSource={boxDataSource}
onSearch={e => console.log('搜索', e)} onSearch={e => console.log('搜索', e)}
onCreateSubmit={async () => { return true }} onCreateSubmit={async (_data) => {
console.log('新建盒子', _data)
return true
}}
onItemCheck={onTreeCheck} onItemCheck={onTreeCheck}
onItemSelect={e => console.log('onItemSelect', e)} onItemSelect={e => console.log('onItemSelect', e)}
onTabChange={e => console.log('tabChange', e)} onTabChange={val => setActiveKey(val)}
onBoxDelete={data => console.log('盒子删除', data)}
onBoxBatchDelete={onBoxBatchDelete} onBoxBatchDelete={onBoxBatchDelete}
onClockClick={() => console.log('时钟点击事件')}
onImport={() => console.log('导入盒子')}
tabsProps={{
activeKey,
}}
searchInputProps={{ searchInputProps={{
addonBefore: ( addonBefore: (
<Select <Select
@ -54,15 +63,20 @@ const demo = () => {
}} }}
style={{ width: '72px' }} style={{ width: '72px' }}
> >
<Option value="1"></Option> {BOX_TYPE_LIST.map(item => (
<Option value="2"></Option> <Option value={item.value}>{item.label}</Option>
))}
</Select> </Select>
), ),
onChange: e => setSearchVal(e.target.value), onChange: e => setSearchVal(e.target.value),
value: searchVal value: searchVal
}} }}
treeProps={{ treeProps={{
checkedKeys onItemDelete: data => console.log('删除', data),
onItemSetting: data => console.log('配置事件', data),
onItemRename: (data) =>console.log('重命名', data),
onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data),
checkedKeys,
}} }}
/> />
</div> </div>

View File

@ -0,0 +1,28 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { Button } from 'antd';
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}
extraBtns={<Button type="dashed" style={{ color: 'green' }}></Button>}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
/>
</div>
);
};
export default demo;

View File

@ -1,6 +1,6 @@
import { TreeDataNode } from "antd"; import { TreeData } from "@zhst/biz";
export const treeData: TreeDataNode[] = [ export const boxDataSource: TreeData[] = [
{ {
title: '全部盒子', title: '全部盒子',
key: '0-0', key: '0-0',
@ -12,10 +12,12 @@ export const treeData: TreeDataNode[] = [
{ {
title: '摄像头1', title: '摄像头1',
key: '0-0-0-0', key: '0-0-0-0',
isCamera: true
}, },
{ {
title: '摄像头2', title: '摄像头2',
key: '0-0-0-1', key: '0-0-0-1',
isCamera: true
}, },
], ],
}, },
@ -25,7 +27,8 @@ export const treeData: TreeDataNode[] = [
children: [ children: [
{ {
title: '摄像头4', title: '摄像头4',
key: '0-0-1-0' key: '0-0-1-0',
isCamera: true
} }
], ],
}, },
@ -34,36 +37,18 @@ export const treeData: TreeDataNode[] = [
]; ];
export const boxDataSource: TreeDataNode[] = [ 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-0', key: '0-1-3',
title: '分组0-0', title: '分组0-1-3',
isLeaf: false,
checkable: false,
},
{
key: '0-1',
title: '分组0-1',
isLeaf: false, isLeaf: false,
children: [ children: [
{ key: '0-1-0', title: '分组0-1-0', isLeaf: true, checkable: false }, { key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true, isCamera: true },
{ key: '0-1-1', title: '分组0-1-1', isLeaf: true, checkable: false }, { key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true, isCamera: true },
{ key: '0-1-2', title: '分组0-1-2', isLeaf: true, checkable: false }, { key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true, isCamera: true },
{
key: '0-1-3',
title: '分组0-1-3',
isLeaf: false,
children: [
{ key: '0-1-3-1', title: '分组0-1-3-1', isLeaf: true },
{ key: '0-1-3-2', title: '分组0-1-3-2', isLeaf: true },
{ key: '0-1-3-3', title: '分组0-1-3-3', isLeaf: true },
],
},
], ],
}, },
{ key: '0-2', title: '分组0-2', isLeaf: false, checkable: false, },
{ key: '0-3', title: '分组0-3', isLeaf: false, checkable: false, },
{ key: '0-4', title: '分组0-4', isLeaf: false, checkable: false, },
{ key: '0-5', title: '分组0-4', isLeaf: false, checkable: false, },
{ key: '0-6', title: '分组0-4', isLeaf: false, checkable: false, },
]; ];

View File

@ -0,0 +1,61 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { Button, Select, TreeProps } from 'antd';
import { FilterOutlined } from '@ant-design/icons';
import { BOX_TYPE_LIST } from '../../utils/constants';
import { treeData, boxDataSource } from './mock'
const { Option } = Select
const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [searchType, setSearchType] = useState('1')
const [searchVal, setSearchVal] = useState('')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const onTreeCheck: TreeProps['onCheck'] = (keys: any) => {
setCheckedKeys(keys)
}
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
onSearch={e => console.log('搜索', e)}
onItemCheck={onTreeCheck}
onItemSelect={e => console.log('onItemSelect', e)}
onTabChange={val => setActiveKey(val)}
onBoxDelete={data => console.log('盒子删除', data)}
showOptions={false}
tabsProps={{
activeKey,
}}
customImport={<Button type="text" icon={<FilterOutlined />} />}
searchInputProps={{
addonBefore: (
<Select
value={searchType}
onChange={_type => {
setSearchType(_type)
setSearchVal('')
}}
style={{ width: '72px' }}
>
{BOX_TYPE_LIST.map(item => (
<Option value={item.value}>{item.label}</Option>
))}
</Select>
),
onChange: e => setSearchVal(e.target.value),
value: searchVal
}}
treeProps={{
checkedKeys
}}
/>
</div>
);
};
export default demo;

View File

@ -13,7 +13,11 @@ group:
## 代码演示 ## 代码演示
<code src="./demo/basic.tsx">基本用法</code> <code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/extraBtns.tsx">自定义其它按钮</code>
<code src="./demo/noOptions.tsx">不显示其它按钮</code>
<code src="./demo/async.tsx">异步加载数据</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
@ -24,3 +28,11 @@ group:
| tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - | | tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - |
| searchInputProps | 搜索框的Props | antd的Input组件 | - | - | | searchInputProps | 搜索框的Props | antd的Input组件 | - | - |
| onTabChange | tab切换监听 | function: (e) => void | - | - | | onTabChange | tab切换监听 | function: (e) => void | - | - |
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | - |
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | - |
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | - |
| onImport | 监听导入盒子点击事件 | function: () => void | - | - |
| onClockClick | 监听时钟点击事件 | function: () => void | - | - |
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | - |
| showOptions | 展示其它功能按钮 | boolean | true | - |

View File

@ -1,3 +1,5 @@
import BoxSelectTree from './boxSelectTree'; import BoxSelectTree from './boxSelectTree';
export type { BoxSelectTreeProps } from './boxSelectTree'
export default BoxSelectTree; export default BoxSelectTree;

View File

@ -1,5 +1,18 @@
export { default as BigImageModal } from './BigImageModal' export { default as BigImageModal } from './BigImageModal'
export type { BigImageModalProps } from './BigImageModal'
export { default as BoxSelectTree } from './boxSelectTree' export { default as BoxSelectTree } 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 { default as TreeTransfer } from './treeTransfer' export { default as TreeTransfer } 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 { default as WarningRecordCard } from './WarningRecordCard'
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard'
export type { ViewLargerImageModalRef, ViewLargerImageModalProps } from './ViewLargerImageModal'
export { default as ViewLargerImageModal, useViewLargerImageModal } from './ViewLargerImageModal'
export type { VideoPlayerCardProps } from './VideoPlayerCard'
export { default as VideoPlayerCard } from './VideoPlayerCard'
export { default as RealTimeMonitor } from './RealTimeMonitor'

View File

@ -1,70 +1,122 @@
import React, { FC } from 'react'; import React, { FC, useState } from 'react';
import { Tree, Badge, TreeDataNode, Space, TreeProps } from 'antd'; import { Tree, Badge, TreeDataNode, Space, TreeProps } from 'antd';
import theme from 'antd/es/theme'
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 { ModalForm, ProFormText } from '@ant-design/pro-components';
import './index.less' import './index.less'
const componentName = 'zhst-biz-tree' const componentName = 'zhst-biz-tree'
const { useToken } = theme
export interface BoxTreeProps extends TreeProps { export interface BoxTreeProps extends TreeProps {
data: TreeDataNode[] data: TreeDataNode[]
treeCheckable?: boolean treeCheckable?: boolean
showItemOption?: boolean showItemOption?: boolean
treeProps?: TreeProps customOptions?: any;
onItemCheck?: TreeProps['onCheck'] onItemCheck?: TreeProps['onCheck']
onItemSelect?: TreeProps['onSelect'] onItemSelect?: TreeProps['onSelect']
onItemSetting?: (_data: any) => void onItemSetting?: (_data: any) => void
onItemDelete?: (_data: any) => void onItemDelete?: (_data: any) => void
onRenameFinish?: (_data: any, _nodeData: any) => Promise<any> onItemRename?: (_nodeData: any) => void
onItemRenameFinish?: (_data: any, _nodeData: any) => Promise<any>
} }
const boxTree: FC<BoxTreeProps> = (props) => { const boxTree: FC<BoxTreeProps> = (props) => {
const { onItemSelect, onItemCheck, onItemSetting, onItemDelete, data = [], showItemOption = true, treeCheckable = false, onRenameFinish } = props const {
onItemSelect,
onItemCheck,
onItemSetting,
onItemDelete,
data = [],
showItemOption = true,
treeCheckable = false,
onItemRename,
onItemRenameFinish,
customOptions
} = props
const { token } = useToken()
const [checkedItem, setCheckedItem] = useState<React.Key>('')
const cameraStatus = new Map([
['0', 'error'],
['1', 'success'],
['3', 'processing'],
['4', 'default'],
])
return ( return (
<Tree <Tree
checkable={treeCheckable} checkable={treeCheckable}
blockNode blockNode
onSelect={onItemSelect} onSelect={(selectedKeys, info) => {
setCheckedItem(selectedKeys[0])
onItemSelect?.(selectedKeys, info)
}}
onCheck={onItemCheck} onCheck={onItemCheck}
treeData={data} treeData={data}
titleRender={(_nodeData) => { titleRender={(_nodeData) => {
return ( return (
<div className={`${componentName}-item-render`}> <div className={`${componentName}-item-render`}>
{!_nodeData.children && <Badge style={{ marginRight: '6px' }} status="success" />} {/* @ts-ignore */}
{_nodeData.title as any} {!_nodeData.children && _nodeData.isCamera && <Badge style={{ marginRight: '6px' }} status={cameraStatus.get(_nodeData.status || '4')} />}
{showItemOption && <Space className={`${componentName}-item-render_right`} style={{ float:'right' }} > <span
<ModalForm // @ts-ignore
title="重命名" style={(checkedItem === _nodeData.key) && _nodeData.isCamera ? {
width={600} color: token.colorPrimary
modalProps={{ destroyOnClose: true }} } : {}}
layout='horizontal' >
labelCol={{ span: 6 }} {_nodeData.title as any}
wrapperCol={{ span: 18 }} </span>
trigger={<EditOutlined />} {showItemOption && (
submitter={{ <Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
searchConfig: { {customOptions || (
submitText: '确定', <>
resetText: '取消', <ModalForm
}, title="重命名"
}} width={600}
onFinish={async (value) => onRenameFinish?.(value, _nodeData)} modalProps={{ destroyOnClose: true }}
> layout='horizontal'
<ProFormText labelCol={{ span: 6 }}
rules={[ wrapperCol={{ span: 18 }}
{ trigger={<EditOutlined onClick={(e) => {
required: true, e.preventDefault();
}, e.stopPropagation();
]} onItemRename?.(_nodeData)
width="md" }} />}
name="name" submitter={{
label="盒子名称" searchConfig: {
placeholder="请输入盒子名称" submitText: '确定',
/> resetText: '取消',
</ModalForm> },
<SettingOutlined onClick={() => onItemSetting?.(_nodeData)} /> }}
<CloseOutlined onClick={() => onItemDelete?.(_nodeData)} /> onFinish={async (value) => onItemRenameFinish?.(value, _nodeData)}
</Space>} >
<ProFormText
rules={[
{
required: true,
},
]}
width="md"
name="name"
label="盒子名称"
placeholder="请输入盒子名称"
/>
</ModalForm>
<SettingOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onItemSetting?.(_nodeData)}
} />
<CloseOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onItemDelete?.(_nodeData)
}} />
</>
)}
</Space>
)}
</div> </div>
) )
}} }}

View File

@ -0,0 +1,23 @@
import React from 'react';
import { Tree } from '@zhst/biz';
import { LoadingOutlined, PlayCircleOutlined, SettingOutlined } from '@ant-design/icons';
import { treeData } from './mock'
const demo = () => {
return (
<div style={{ width: '320px' }}>
<Tree
data={treeData}
customOptions={(
<>
<PlayCircleOutlined />
<SettingOutlined />
<LoadingOutlined />
</>
)}
/>
</div>
);
};
export default demo;

View File

@ -14,8 +14,11 @@ const demo = () => {
<div> <div>
{title} {title}
<div style={{ float: 'right' }} > <div style={{ float: 'right' }} >
<Tooltip placement="right" title={'存在0个'}> <Tooltip
<a >0</a> placement="right"
title={'存在0个'}
>
<a>0</a>
</Tooltip> </Tooltip>
</div> </div>
</div> </div>

View File

@ -14,8 +14,15 @@ group:
<code src="./demo/basic.tsx">基本用法</code> <code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/customTitleRender.tsx">自定义渲染界面</code> <code src="./demo/customTitleRender.tsx">自定义渲染界面</code>
<code src="./demo/noOption.tsx">不展示配置项</code> <code src="./demo/noOption.tsx">不展示配置项</code>
<code src="./demo/customOptions.tsx">自定义配置项</code>
## API
**额外参数可以参考 antd - Tree 组件**
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| data | 数据源 | Array[] | [] | - | | data | 数据源 | Array[] | [] | - |
| titleRender | 子项自定义 | ReactNode、 undefined | - | - |
| showItemOption | 是否显示额外配置功能 | boolean | true | - |
| customOptions | 自定义配置项 | any | - | - |

View File

@ -1,3 +1,16 @@
import { TreeDataNode } from 'antd';
import BoxTree from './boxTree'; import BoxTree from './boxTree';
export interface TreeData extends TreeDataNode {
children?: TreeDataNode['children'] & {
isCamera?: boolean
/**
* 0- 1- 2- 3-
*/
status?: '0' | '1' | '2' | '3'
}[]
}
export type { BoxTreeProps } from './boxTree'
export default BoxTree; export default BoxTree;

View File

@ -13,6 +13,8 @@ group:
<code src="./demo/basic.tsx">基本用法</code> <code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/withModal.tsx">和Modal组合使用</code> <code src="./demo/withModal.tsx">和Modal组合使用</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| data | 数据源 | Array[] | [] | - | | data | 数据源 | Array[] | [] | - |

View File

@ -1,3 +1,7 @@
import TreeTransfer from "./TreeTransfer"; import TreeTransfer from "./TreeTransfer";
export type { TreeTransferProps } from './TreeTransfer'
export * from './treeTransferHelper'
export default TreeTransfer export default TreeTransfer

View File

@ -1,7 +1,7 @@
import React, { FC, useState } from 'react'; import React, { FC, useState } from 'react';
import { Modal, ModalProps, Radio, RadioGroupProps, Select, SelectProps, TransferProps, TreeDataNode, TreeProps } from 'antd';
import TreeTransfer from '../treeTransfer'; import TreeTransfer from '../treeTransfer';
import { Modal, ModalProps, Radio, RadioGroupProps, TransferProps, TreeDataNode } from 'antd'; import { ALL_LIST, BOX_TYPE_LIST } from '../utils/constants';
import { TreeProps } from 'antd/lib';
export interface TreeTransferModalProps { export interface TreeTransferModalProps {
dataSource: TreeDataNode[] dataSource: TreeDataNode[]
@ -17,7 +17,10 @@ export interface TreeTransferModalProps {
open?: boolean open?: boolean
onCancel?: ModalProps['onCancel'] onCancel?: ModalProps['onCancel']
onRadioChange?: RadioGroupProps['onChange'] onRadioChange?: RadioGroupProps['onChange']
onSelect?: SelectProps['onSelect']
modalProps?: ModalProps modalProps?: ModalProps
radioProps?: RadioGroupProps
selectProps?: SelectProps
} }
const TreeTransferModal: FC<TreeTransferModalProps> = (props) => { const TreeTransferModal: FC<TreeTransferModalProps> = (props) => {
@ -31,11 +34,14 @@ const TreeTransferModal: FC<TreeTransferModalProps> = (props) => {
onReset, onReset,
onRadioChange, onRadioChange,
onTreeCheck, onTreeCheck,
onSelect,
targetItems, targetItems,
modalProps, modalProps,
radioProps,
selectProps,
} = props } = props
const [type, setType] = useState('box') const [type, setType] = useState('1')
return ( return (
<Modal <Modal
@ -48,17 +54,25 @@ const TreeTransferModal: FC<TreeTransferModalProps> = (props) => {
{...modalProps} {...modalProps}
> >
<div> <div>
<Radio.Group <div>
onChange={e => { <Radio.Group
setType(e.target.value) onChange={e => {
onRadioChange?.(e) setType(e.target.value)
}} onRadioChange?.(e)
style={{ marginLeft: '24px', padding: '20px 0' }} }}
value={type} style={{ marginLeft: '24px', padding: '20px 0' }}
> value={type}
<Radio value={'box'}></Radio> options={BOX_TYPE_LIST}
<Radio value={'boxGroup'}></Radio> {...radioProps}
</Radio.Group> />
<Select
defaultValue={''}
style={{ marginLeft: 200, width: 150 }}
options={ALL_LIST}
onSelect={onSelect}
{...selectProps}
/>
</div>
{type === 'box' ? {type === 'box' ?
( (
<TreeTransfer <TreeTransfer

View File

@ -7,7 +7,7 @@ import { boxDataSource } from './mock'
const App: React.FC = () => { const App: React.FC = () => {
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]); const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [checkedKeys, setCheckedKeys] = useState<string[]>([]); const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const [open, setOpen] = useState(false) const [open, setOpen] = useState(true)
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => { const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => {
let _targetItems: TreeDataNode[] = [] let _targetItems: TreeDataNode[] = []
@ -47,7 +47,7 @@ const App: React.FC = () => {
<TreeTransferModal <TreeTransferModal
open={open} open={open}
onCancel={() => setOpen(false)} onCancel={() => setOpen(false)}
onRadioChange={() => setOpen(false)} // 顶部 radio 事件 onRadioChange={(e) => console.log('radioChange', e)} // 顶部 radio 事件
dataSource={boxDataSource} // 数据源 dataSource={boxDataSource} // 数据源
targetItems={targetItems} // 右侧选中项 targetItems={targetItems} // 右侧选中项
checkedKeys={checkedKeys} // 左侧选中 checkedKeys={checkedKeys} // 左侧选中

View File

@ -12,6 +12,8 @@ group:
<code src="./demo/basic.tsx">基本用法</code> <code src="./demo/basic.tsx">基本用法</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| data | 数据源 | Array[] | [] | - | | data | 数据源 | Array[] | [] | - |

View File

@ -1,3 +1,5 @@
import TreeTransferModal from './TreeTransferModal' import TreeTransferModal from './TreeTransferModal'
export type { TreeTransferModalProps } from './TreeTransferModal'
export default TreeTransferModal export default TreeTransferModal

View File

@ -1,99 +0,0 @@
import { useMemo } from 'react';
import { throttle as loadshThrottle, noop } from '@zhst/func';
import { useDeepEffect, useLatest } from '@zhst/hooks';
import ws from './ws';
const getSelf = (v: any) => v;
export const SocketApi = {
CameraTaskStatue: 'singer.DeviceService/SubScribeCameraTaskStatus',
DeviceStatus: 'singer.TaskManagerService/SubscribeTaskStatus',
SubscribeSolutionDeploy: 'singer.SolutionManagerService/SubscribeSolutionDeploy',
SubscribeTasksSummary: 'singer.TaskManagerService/SubscribeTasksSummary',
MonitorSubscribeResult: 'singer.MonitorService/MonitorSubscribeResult',
MonitorSubscribeStatus: 'singer.MonitorService/MonitorSubscribeStatus',
SubscribeArchiveGroupUpload: 'singer.ArchiveGroupService/CreateArchiveByImport',
SubscribeJointTask: 'singer.JointTaskService/SubscribeJointTask',
SubscribeGroupFragment: 'singer.VideoService/SubscribeVideoFragmentStatus', // 监听视频分片状态变化,包括新建、删除、变化
SubscribeGroup: 'singer.VideoService/SubscribeGroup', // 监听视频分组状态变化,列表变化也通知
SubscribeStreamEvent: 'singer.MediaManagerService/SubscribeStreamEvent', // 监听视频分组状态变化,列表变化也通知
};
type ApiKeys = keyof typeof SocketApi;
export default (
topic: ApiKeys,
iterator: any = noop,
opt?: {
req?: { [key: string]: any };
throttle?: number;
parseData?: boolean;
beforeLoopTmp?: Function;
shouldBreak: boolean;
forceRefresh: any;
close?: boolean;
}
) => {
const {
req = {},
throttle = 0,
beforeLoopTmp = getSelf,
shouldBreak = false,
forceRefresh,
close,
} = opt || {};
// 带上token
const reqstring = useMemo(() => {
const newReq = {
...req,
extraHeaders: {
authorization: localStorage.getItem('USER-TOKEN'),
},
};
return JSON.stringify(newReq);
}, [req]);
const latestIterator = useLatest(iterator);
useDeepEffect(() => {
if (close) {
return;
}
//控制socket 请求发送
if (shouldBreak) {
return;
}
//去抖动
let tmpData: any = [];
const throttleUpdate = loadshThrottle(() => {
if (tmpData.length == 0) {
return;
}
const _tmpData = beforeLoopTmp(tmpData);
latestIterator.current(_tmpData); //加了throttle 数据就变成数组
tmpData = [];
}, throttle);
const unSubscribe = ws.subscribe(SocketApi[topic], reqstring, (socketData: any) => {
try {
if (!throttle) {
latestIterator.current(socketData);
} else {
tmpData.push(socketData);
throttleUpdate();
}
} catch (error) {
console.error('useSocke:', error);
}
});
return () => {
try {
unSubscribe();
throttleUpdate.cancel();
} catch (error) {
console.error(error);
throw error;
}
};
}, [topic, reqstring, shouldBreak, forceRefresh, close]);
};

View File

@ -1,23 +0,0 @@
//@ts-nocheck
import channel from './ws';
const startChannel = (topic, req, callback) => {
// 带上token
let reqstring = JSON.stringify({
...req,
extraHeaders: {
authorization: localStorage.getItem('USER-TOKEN'),
},
});
let unSubscribe = channel.subscribe(topic, reqstring, (socketData) => {
let shouldStop = callback(socketData);
if (shouldStop) {
unSubscribe?.();
}
});
return unSubscribe;
};
export default startChannel;

View File

@ -1,222 +0,0 @@
// @ts-nocheck
import { v4 as uuidv4 } from 'uuid';
import { has } from '@zhst/func';
import io from 'socket.io-client';
import { SOCKET_HOST } from '@common/constants';
const EMITSTATE = {
NOT_CONNECT: 0,
WAITING: 1,
CONNECT: 2,
};
const initRetryTime = 0;
const intervalTime = 5 * 1000; //下次重试增加时间
const maxIntervalTime = 1 * 60 * 60 * 1000; //最大重试时间1小时
class Channel {
/**
* io
*/
ioIns;
/**
*
*/
listeners = [
// {
// topic: "",
// req: "",
// suInfo: {},
// hasEmit: false,//是否已经订阅
// lastRetryInterval: 0,
// handles: {
// }
// }
];
/**
* /
*/
subscribeListenerId = [];
unSubscribeListenerId = [];
init = () => {
const ioIns = (this.ioIns = io(SOCKET_HOST, {
reconnection: true,
transports: ['websocket'],
forceNew: true,
}));
ioIns.on('connect', (...arg: any) => {
console.debug('connect', arg);
this.ioIns = ioIns;
this.listeners.forEach((v) => {
this.doEmit(v['topic'], v['req'], v['id']);
});
});
ioIns.on('event', (...arg: any) => {
console.debug('event', arg);
});
ioIns.on('disconnect', (...arg: any) => {
console.debug('disconnect', arg);
this.subscribeListenerId = [];
this.unSubscribeListenerId = [];
});
ioIns.on('reconnect', (...arg: any) => {
console.debug('reconnect', arg);
this.listeners.forEach((v) => {
v['hasEmit'] = EMITSTATE.NOT_CONNECT;
this.doEmit(v['topic'], v['req'], v['id']);
});
});
};
retry = (listener: { [x: string]: any; lastRetryInterval: number | undefined; intervalId: NodeJS.Timeout; } | undefined) => {
//重试逻辑
const intervalId = setTimeout(() => {
const hasExit = this.listeners.find(
(v) => v['topic'] === listener?.['topic'] && v['req'] === listener['req']
);
if (!hasExit) return;
listener['hasEmit'] = EMITSTATE.NOT_CONNECT;
this.doEmit(listener['topic'], listener['req'], listener['id']);
}, listener.lastRetryInterval);
listener.intervalId = intervalId;
listener.lastRetryInterval =
intervalTime + listener.lastRetryInterval > maxIntervalTime
? maxIntervalTime
: intervalTime + listener.lastRetryInterval;
};
doEmit = (topic, req, listenerId) => {
if (!this.ioIns) {
this.init();
}
//订阅过就不订阅了
const hasEmit = this.listeners.find(
(v) => v['topic'] === topic && v['req'] === req && v['hasEmit'] !== EMITSTATE.NOT_CONNECT
);
if (hasEmit) {
return;
}
const listener = this.listeners.find((v) => v['topic'] === topic && v['req'] === req);
listener['hasEmit'] = EMITSTATE.WAITING;
this.ioIns?.emit?.(topic, req, (data) => {
console.info('emit', topic, req, data);
const suInfo = JSON.parse(data);
if (has(suInfo, 'Error.code')) {
if (suInfo.Error.code === 500) {
//后端出错
this.retry(listener);
}
return;
}
// console.debug('SUBSCRIBE', listenerId, topic, req, suInfo);
this.subscribeListenerId.push(listenerId);
// debugger
//重新找一遍topic
const currentTopicIndex = this.listeners.findIndex(
(v) => v['topic'] === topic && v['req'] === req && v['id'] === listenerId
);
if (currentTopicIndex == -1) {
// 不存在说明listener取消了 直接反订阅
this.ioIns.emit('UnSubscribe', JSON.stringify(suInfo), (data) => {
this.unSubscribeListenerId.push(listenerId);
console.debug('UNSUBSCRIBE', listenerId, topic, req, data);
});
return;
}
if (!suInfo['SubscribeID']) {
this.listeners.splice(currentTopicIndex, 0);
} else {
this.listeners[currentTopicIndex]['suInfo'] = suInfo;
this.listeners[currentTopicIndex]['hasEmit'] = EMITSTATE.CONNECT;
}
this.ioIns?.on?.(suInfo['SubscribeID'], (data) => {
console.info('on', suInfo['SubscribeID'], data);
try {
const socketData = JSON.parse(data);
if (has(socketData, 'Error.code')) {
if (socketData.Error.code === 500) {
//后端出错
this.retry(listener);
}
return;
}
const { handles = {} } =
this.listeners.find((v) => v['topic'] === topic && v['req'] === req) || {};
Object.keys(handles).forEach((key) => {
try {
//后面观察 为什么delete后在foreach
const func = handles[key];
if (!func) return;
func(socketData);
} catch (error) {
console.error(error);
}
});
} catch (error) {
console.debug('error', error);
}
});
});
};
subscribe(topic, req, handle) {
const handleId = uuidv4();
const listenerId = uuidv4();
const listener = this.listeners.find((v) => v['topic'] === topic && v['req'] === req);
if (listener) {
listener['handles'][handleId] = handle;
} else {
this.listeners.push({
topic: topic,
req: req,
suInfo: {},
id: listenerId,
hasEmit: EMITSTATE.NOT_CONNECT,
lastRetryInterval: initRetryTime,
handles: {
[`${handleId}`]: handle,
},
});
//未注册过 则去订阅
this.doEmit(topic, req, listenerId);
}
return this.unSubscribe.bind(this, topic, req, handleId, listenerId);
}
unSubscribe(topic, req, handleId, listenerId) {
const listener = this.listeners.find(
(v) => v['topic'] === topic && v['req'] === req && v['id'] === listenerId
);
const { handles = {}, suInfo } = listener || {};
if (handles[handleId]) {
delete handles[handleId];
//如果没有其他订阅就删除
if (Object.keys(handles).length === 0) {
if (this.intervalId) {
clearTimeout(this.intervalId);
}
if (listener['hasEmit'] === EMITSTATE['CONNECT']) {
this.ioIns.emit('UnSubscribe', JSON.stringify(suInfo), (data) => {
this.unSubscribeListenerId.push(listenerId);
console.debug('UNSUBSCRIBE', listener['id'], topic, req, data);
});
}
this.listeners = this.listeners.filter((v) => !(v['topic'] === topic && v['req'] === req));
}
}
}
}
//单例
const channelIns = new Channel();
//暴露实例 调试用
window.__channel__ = channelIns;
export default channelIns;

View File

@ -25,3 +25,13 @@ export const DeviceTab = {
REAL_CAMERA_ONLYFACE: 7, REAL_CAMERA_ONLYFACE: 7,
REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8, // 只有普通摄像头,没有人脸、没有盒子、直连 REAL_CAMERA_NOFACE_NOBOX_NODIRECONNECT: 8, // 只有普通摄像头,没有人脸、没有盒子、直连
}; };
// 盒子 Tab 切换
export const BOX_TYPE_LIST = [
{ value: '1', label: '盒子' },
{ value: '2', label: '盒子组' }
]
export const ALL_LIST = [
{ value: '', label: '全部' }
]

View File

@ -1,5 +1,43 @@
# @zhst/utils # @zhst/utils
## 0.7.5
### Patch Changes
- Updated dependencies
- @zhst/request@0.8.4
## 0.7.4
### Patch Changes
- 新增 tree 组件的监听事件,优化 meta 全选的回调事件监听
- Updated dependencies
- @zhst/request@0.8.3
## 0.7.3
### Patch Changes
- 修改 boxSelectTree 类型提示
- Updated dependencies
- @zhst/request@0.8.2
## 0.7.2
### Patch Changes
- 优化 boxSelectTree 组件,添加可以自定义配置按钮功能
- Updated dependencies
- @zhst/request@0.8.1
## 0.7.1
### Patch Changes
- Updated dependencies
- @zhst/request@0.8.0
## 0.7.0 ## 0.7.0
### Minor Changes ### Minor Changes

View File

@ -1,358 +1 @@
var _ALGORITHM_VERSION; export default {};
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 _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread 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 _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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 _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(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { cloneDeep, get, isNull } from 'lodash-es';
import { dataURLToBlob } from "../file";
var proto = {
Common: {
AlgorithmVersion: {
VERSION_REID_HEAD_ATTR: '形体',
VERSION_FACE: '人脸',
VERSION_NON_MOTOR_VEHICLE: '非机动车'
}
}
};
export var ALGORITHM_VERSION = (_ALGORITHM_VERSION = {}, _defineProperty(_ALGORITHM_VERSION, '7', '形体'), _defineProperty(_ALGORITHM_VERSION, '4', '人脸'), _defineProperty(_ALGORITHM_VERSION, '6', '非机动车'), _ALGORITHM_VERSION);
export var algorithmVersions = _toConsumableArray(Object.keys(ALGORITHM_VERSION));
export var getBikeExtendRect = function getBikeExtendRect(rect, maxW) {
var newRect = _objectSpread({}, rect);
//向上扩大一倍
var oldY = cloneDeep(rect.y);
newRect.y = newRect.y - newRect.h < 0 ? 0 : newRect.y - newRect.h;
newRect.h += oldY - newRect.y;
var newX = Math.round(newRect.x - newRect.w * 0.15);
if (newX < 0) {
newX = 0;
}
var newW = newRect.x - newX + newRect.w + Math.round(newRect.w * 0.15);
if (newX + newW > maxW) {
newW = maxW - newX;
}
newRect.x = newX;
newRect.w = newW;
return newRect;
};
export var getOtherExtendRect = function getOtherExtendRect(srcRect, maxW, maxH, type) {
var wExtendRadio = 0.25;
var upExtendRadio = 0.25;
var downExtendRadio = 0.25;
var fixPersonExtend = true;
var nx = 0;
var nw = 0;
nx = srcRect.x - Math.round(srcRect.w * wExtendRadio);
if (nx < 0) {
nx = 0;
}
nw = srcRect.x - nx + srcRect.w + Math.round(srcRect.w * wExtendRadio);
if (nx + nw > maxW) {
nw = maxW - nx;
}
var ny = 0;
var nh = 0;
ny = srcRect.y - Math.round(upExtendRadio * srcRect.h);
if (ny < 0) {
ny = 0;
}
nh = srcRect.y - ny + srcRect.h + Math.round(srcRect.h * downExtendRadio);
if (ny + nh > maxH) {
nh = maxH - ny;
}
var newRect = {
x: nx,
y: ny,
w: nw,
h: nh
};
if ((type === proto.Common.AlgorithmVersion.VERSION_REID_HEAD_ATTR || type === proto.Common.AlgorithmVersion.VERSION_FACE) && fixPersonExtend) {
var fixW = Math.round(nh * 0.75);
if (nw < fixW) {
// 应该扩展宽度
var newX = nx + Math.round(nw / 2.0 - 0.5 * fixW);
if (newX < 0) {
newX = 0;
}
var newW = fixW;
if (newW + newX > maxW) {
newW = maxW - newX;
}
newRect = {
x: newX,
y: ny,
w: newW,
h: nh
};
} else if (nw > fixW) {
// 应该扩展高度
var fixH = Math.round(nw * 1.333);
var newY = ny + Math.round(nh / 2.0 - 0.5 * fixH);
if (newY < 0) {
newY = 0;
}
var newH = fixH;
if (newY + newH > maxH) {
newH = maxH - newY;
}
newRect = {
x: nx,
y: newY,
w: nw,
h: newH
};
}
}
return newRect;
};
export var getNormalization = function getNormalization(srcRect, maxW, maxH) {
var newRect = _objectSpread({}, srcRect);
newRect.x = srcRect.x / maxW;
newRect.y = srcRect.y / maxH;
newRect.w = srcRect.w / maxW;
newRect.h = srcRect.h / maxH;
if (newRect.x + newRect.w > 1) {
newRect.w = 1 - newRect.x;
}
if (newRect.y + newRect.h > 1) {
newRect.h = 1 - newRect.y;
}
return newRect;
};
//传入od框 穿出 od扩展框
export var getExtendRect = function getExtendRect(normalizationRect, imgW, imgH, type) {
var rect = {
x: normalizationRect.x * imgW,
y: normalizationRect.y * imgH,
w: normalizationRect.w * imgW,
h: normalizationRect.h * imgH
};
var newRect;
if (type === proto.Common.AlgorithmVersion.VERSION_NON_MOTOR_VEHICLE) {
newRect = getBikeExtendRect(rect, imgW);
} else {
newRect = getOtherExtendRect(rect, imgW, imgH, type);
}
newRect = getNormalization(newRect, imgW, imgH);
return newRect;
};
export var getTransformRect = function getTransformRect(image, transform, rect) {
var canvasRect = {
x: rect.x,
y: rect.y,
x2: rect.x + rect.w,
y2: rect.h + rect.y
};
//1.转成缩放前的坐标
var translateX = transform.translateX,
translateY = transform.translateY,
scale = transform.scale,
rotate = transform.rotate;
var originAxisRect = {
x: (canvasRect.x - translateX) / scale,
y: (canvasRect.y - translateY) / scale,
x2: (canvasRect.x2 - translateX) / scale,
y2: (canvasRect.y2 - translateY) / scale
};
//2.转成图片坐标
//不考虑旋转 图片原点就是坐标原点
var imgAxisRect = originAxisRect;
//但是旋转90度后图片不在坐标原点 加上这部分diff
if (rotate % 180 !== 0) {
//90度调整偏移量
var offsetX = -(image.height - image.width) / 2;
var offsetY = -(image.width - image.height) / 2;
imgAxisRect = {
x: originAxisRect.x - offsetX,
y: originAxisRect.y - offsetY,
x2: originAxisRect.x2 - offsetX,
y2: originAxisRect.y2 - offsetY
};
}
//3.限制框不要超出图片
var imgW = image.width;
var imgH = image.height;
if (rotate % 180 !== 0) {
var _ref = [imgH, imgW];
imgW = _ref[0];
imgH = _ref[1];
}
imgAxisRect.x = Math.min(imgW, Math.max(imgAxisRect.x, 0));
imgAxisRect.y = Math.min(imgH, Math.max(imgAxisRect.y, 0));
imgAxisRect.x2 = Math.min(imgW, Math.max(imgAxisRect.x2, 0));
imgAxisRect.y2 = Math.min(imgH, Math.max(imgAxisRect.y2, 0));
//获取归一化坐标
var endRect = {
x: imgAxisRect.x2 > imgAxisRect.x ? imgAxisRect.x : imgAxisRect.x2,
y: imgAxisRect.y2 > imgAxisRect.y ? imgAxisRect.y : imgAxisRect.y2,
w: Math.abs(imgAxisRect.x2 - imgAxisRect.x),
h: Math.abs(imgAxisRect.y2 - imgAxisRect.y)
};
return getNormalization(endRect, imgW, imgH);
};
//旋转图片后转成file 对象
export var getRotateImg = function getRotateImg(image, rotate) {
var _commonCanvas$parentN;
var imgW = image.width;
var imgH = image.height;
if (rotate % 180 !== 0) {
var _ref2 = [imgH, imgW];
imgW = _ref2[0];
imgH = _ref2[1];
}
var commonCanvas = document.createElement('canvas');
commonCanvas.width = imgW;
commonCanvas.height = imgH;
commonCanvas.style.display = 'none';
document.body.appendChild(commonCanvas);
var commonCtx = commonCanvas.getContext('2d');
//移动到图片中心 旋转
commonCtx === null || commonCtx === void 0 || commonCtx.save();
if (rotate % 180 !== 0) {
//90度调整偏移量
commonCtx === null || commonCtx === void 0 || commonCtx.translate((image.height - image.width) / 2, (image.width - image.height) / 2);
}
commonCtx === null || commonCtx === void 0 || commonCtx.translate(image.width / 2, image.height / 2);
commonCtx === null || commonCtx === void 0 || commonCtx.rotate(rotate / 180 * Math.PI);
commonCtx === null || commonCtx === void 0 || commonCtx.translate(-image.width / 2, -image.height / 2);
commonCtx === null || commonCtx === void 0 || commonCtx.drawImage(image, 0, 0);
commonCtx === null || commonCtx === void 0 || commonCtx.restore();
var dataUrl = commonCanvas.toDataURL('image/jpeg');
var blobData = dataURLToBlob(dataUrl);
var file = new window.File([blobData], "".concat(new Date().getTime()), {
type: 'image/jpeg'
});
(_commonCanvas$parentN = commonCanvas.parentNode) === null || _commonCanvas$parentN === void 0 || _commonCanvas$parentN.removeChild(commonCanvas);
return file;
};
/**
* 格式化工具
* @param originData
* @returns
*/
export var getOdRect = function getOdRect(originData) {
var data = get(originData, 'objects', []).filter(function (v) {
return !isNull(get(v, 'infoOnSource.bboxInFrame.bboxRatio'));
}).map(function (v, index) {
// objectId==0 特征没有提取到过滤1掉
var rect = get(v, 'infoOnSource.bboxInFrame.bboxRatio');
var extendBox = get(v, 'infoOnSource.bboxInFrame.extendBoxRatio');
var frameTimestamp = get(v, 'timestamp'); //时间戳创建档案的时候需要
var qualityScore = get(v, 'qualityScore');
var algorithmVersion = get(v, 'objectType') === 'OBJECT_TYPE_PEDESTRAIN' ? 'VERSION_REID_HEAD_ATTR' : get(v, 'objectType') === 'OBJECT_TYPE_FACE' ? 'VERSION_FACE' : 'VERSION_REID_HEAD_ATTR';
var featureData = get(v, 'feature', []).filter(function (v) {
return v.type === 'FEATURE_TYPE_BYTE';
});
var objectRectIndex = algorithmVersion === 'VERSION_FACE' ? 0 : 1;
var objectType = get(v, 'objectType');
var objectId = get(v, 'objectIndex.objectId');
var sourceObjectId = get(v, 'sourceObjectId');
return {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
// faceCorrectImage: faceCorrectImage,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
featureData: get(featureData, '0.featureByte'),
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId: objectId,
frameTimestamp: frameTimestamp,
sourceObjectId: sourceObjectId,
extendBox: extendBox
};
});
if (data.length > 0) {
data = data.filter(function (v) {
return v.objectId !== '0';
});
} else {
throw new Error('empty');
}
return data;
};
//档案库od
export var getOdRectV2 = function getOdRectV2(originData) {
var resp = originData.odv2Result[0];
var subObjects = []; //形体
var data = get(resp, 'objects', []).filter(function (v) {
return !isNull(get(v, 'subObjects[0].infoOnSource.bboxInFrame.bboxRatio'));
}).map(function (v, index) {
var rect = get(v, 'infoOnSource.bboxInFrame.bboxRatio');
var qualityScore = get(v, 'qualityScore');
var algorithmVersion = get(v, 'objectType');
var featrueData = get(v, 'feature', []).filter(function (v) {
return v.name === 'feature-body' || v.name === 'feature-face';
});
var objectRectIndex = algorithmVersion === 'OBJECT_TYPE_FACE' ? 0 : 1;
var objectType = get(v, 'objectType');
var objectId = get(v, 'objectIndex.objectId');
//如果存在subObjects的数组不为null表示形体里面带人脸人脸的od框也要显示出来
if (get(v, 'subObjects', []).length) {
get(v, 'subObjects', []).forEach(function (e) {
var rect = get(e, 'infoOnSource.bboxInFrame.bboxRatio');
var qualityScore = get(e, 'qualityScore');
var algorithmVersion = get(e, 'objectType');
var featrueData = get(e, 'feature', []).filter(function (v) {
return v.name === 'feature-body' || v.name === 'feature-face';
});
var objectRectIndex = algorithmVersion === 'OBJECT_TYPE_FACE' ? 0 : 1;
var objectType = get(e, 'objectType');
var objectId = get(e, 'objectIndex.objectId');
subObjects.push({
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
featrueData: featrueData.length ? featrueData[0].featureByte : '',
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId: objectId
});
});
}
return {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
featrueData: featrueData[0].featureByte,
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId: objectId
};
});
var brr = data.concat(subObjects).map(function (v, vs) {
if (String(v.id)) {
v.id = vs;
}
return v;
});
if (brr.length > 0) {
console.log(brr, 'data111');
} else {
throw new Error('empty');
}
console.log(brr);
return brr;
};

View File

@ -26,15 +26,12 @@ export var formateDuration = function formateDuration(diff) {
if (minutes > 0) { if (minutes > 0) {
returnStr = minutes + '分'; //+ returnStr; returnStr = minutes + '分'; //+ returnStr;
} }
if (hours > 0) { if (hours > 0) {
returnStr = hours + '小时'; // + returnStr; returnStr = hours + '小时'; // + returnStr;
} }
if (days > 0) { if (days > 0) {
returnStr = days + '天'; //+ returnStr; returnStr = days + '天'; //+ returnStr;
} }
return returnStr; return returnStr;
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "@zhst/func", "name": "@zhst/func",
"version": "0.7.0", "version": "0.7.5",
"description": "函数合集", "description": "函数合集",
"keywords": [ "keywords": [
"hooks" "hooks"

View File

@ -1,367 +1 @@
import { cloneDeep, get, isNull } from 'lodash-es'; export default {}
import { dataURLToBlob } from '../file';
import { IOdRectOrigin, Rect } from '@zhst/types';
const proto = {
Common: {
AlgorithmVersion: {
VERSION_REID_HEAD_ATTR: '形体',
VERSION_FACE: '人脸',
VERSION_NON_MOTOR_VEHICLE: '非机动车',
},
},
};
export const ALGORITHM_VERSION = {
['7']: '形体',
['4']: '人脸',
['6']: '非机动车',
};
export const algorithmVersions = [...Object.keys(ALGORITHM_VERSION)]
export const getBikeExtendRect = (rect: Rect, maxW: number) => {
const newRect = { ...rect };
//向上扩大一倍
const oldY = cloneDeep(rect.y);
newRect.y = newRect.y - newRect.h < 0 ? 0 : newRect.y - newRect.h;
newRect.h += oldY - newRect.y;
let newX = Math.round(newRect.x - newRect.w * 0.15);
if (newX < 0) {
newX = 0;
}
let newW = newRect.x - newX + newRect.w + Math.round(newRect.w * 0.15);
if (newX + newW > maxW) {
newW = maxW - newX;
}
newRect.x = newX;
newRect.w = newW;
return newRect;
};
export const getOtherExtendRect = (srcRect: Rect, maxW: number, maxH: number, type: string) => {
const wExtendRadio = 0.25;
const upExtendRadio = 0.25;
const downExtendRadio = 0.25;
const fixPersonExtend = true;
let nx = 0;
let nw = 0;
nx = srcRect.x - Math.round(srcRect.w * wExtendRadio);
if (nx < 0) {
nx = 0;
}
nw = srcRect.x - nx + srcRect.w + Math.round(srcRect.w * wExtendRadio);
if (nx + nw > maxW) {
nw = maxW - nx;
}
let ny = 0;
let nh = 0;
ny = srcRect.y - Math.round(upExtendRadio * srcRect.h);
if (ny < 0) {
ny = 0;
}
nh = srcRect.y - ny + srcRect.h + Math.round(srcRect.h * downExtendRadio);
if (ny + nh > maxH) {
nh = maxH - ny;
}
let newRect = {
x: nx,
y: ny,
w: nw,
h: nh,
};
if (
(type === proto.Common.AlgorithmVersion.VERSION_REID_HEAD_ATTR ||
type === proto.Common.AlgorithmVersion.VERSION_FACE) &&
fixPersonExtend
) {
const fixW = Math.round(nh * 0.75);
if (nw < fixW) {
// 应该扩展宽度
let newX = nx + Math.round(nw / 2.0 - 0.5 * fixW);
if (newX < 0) {
newX = 0;
}
let newW = fixW;
if (newW + newX > maxW) {
newW = maxW - newX;
}
newRect = {
x: newX,
y: ny,
w: newW,
h: nh,
};
} else if (nw > fixW) {
// 应该扩展高度
const fixH = Math.round(nw * 1.333);
let newY = ny + Math.round(nh / 2.0 - 0.5 * fixH);
if (newY < 0) {
newY = 0;
}
let newH = fixH;
if (newY + newH > maxH) {
newH = maxH - newY;
}
newRect = {
x: nx,
y: newY,
w: nw,
h: newH,
};
}
}
return newRect;
};
export const getNormalization = (srcRect: Rect, maxW: number, maxH: number) => {
const newRect = {
...srcRect,
};
newRect.x = srcRect.x / maxW;
newRect.y = srcRect.y / maxH;
newRect.w = srcRect.w / maxW;
newRect.h = srcRect.h / maxH;
if (newRect.x + newRect.w > 1) {
newRect.w = 1 - newRect.x;
}
if (newRect.y + newRect.h > 1) {
newRect.h = 1 - newRect.y;
}
return newRect;
};
//传入od框 穿出 od扩展框
export const getExtendRect = (normalizationRect: Rect, imgW: number, imgH: number, type: string) => {
const rect = {
x: normalizationRect.x * imgW,
y: normalizationRect.y * imgH,
w: normalizationRect.w * imgW,
h: normalizationRect.h * imgH,
};
let newRect;
if (type === proto.Common.AlgorithmVersion.VERSION_NON_MOTOR_VEHICLE) {
newRect = getBikeExtendRect(rect, imgW);
} else {
newRect = getOtherExtendRect(rect, imgW, imgH, type);
}
newRect = getNormalization(newRect, imgW, imgH);
return newRect;
};
export const getTransformRect = (image: { height: number; width: number; }, transform: { translateX: any; translateY: any; scale: any; rotate: any; }, rect: Rect) => {
const canvasRect = {
x: rect.x,
y: rect.y,
x2: rect.x + rect.w,
y2: rect.h + rect.y,
};
//1.转成缩放前的坐标
const { translateX, translateY, scale, rotate } = transform;
const originAxisRect = {
x: (canvasRect.x - translateX) / scale,
y: (canvasRect.y - translateY) / scale,
x2: (canvasRect.x2 - translateX) / scale,
y2: (canvasRect.y2 - translateY) / scale,
};
//2.转成图片坐标
//不考虑旋转 图片原点就是坐标原点
let imgAxisRect = originAxisRect;
//但是旋转90度后图片不在坐标原点 加上这部分diff
if (rotate % 180 !== 0) {
//90度调整偏移量
const offsetX = -(image.height - image.width) / 2;
const offsetY = -(image.width - image.height) / 2;
imgAxisRect = {
x: originAxisRect.x - offsetX,
y: originAxisRect.y - offsetY,
x2: originAxisRect.x2 - offsetX,
y2: originAxisRect.y2 - offsetY,
};
}
//3.限制框不要超出图片
let imgW = image.width;
let imgH = image.height;
if (rotate % 180 !== 0) {
[imgW, imgH] = [imgH, imgW];
}
imgAxisRect.x = Math.min(imgW, Math.max(imgAxisRect.x, 0));
imgAxisRect.y = Math.min(imgH, Math.max(imgAxisRect.y, 0));
imgAxisRect.x2 = Math.min(imgW, Math.max(imgAxisRect.x2, 0));
imgAxisRect.y2 = Math.min(imgH, Math.max(imgAxisRect.y2, 0));
//获取归一化坐标
const endRect = {
x: imgAxisRect.x2 > imgAxisRect.x ? imgAxisRect.x : imgAxisRect.x2,
y: imgAxisRect.y2 > imgAxisRect.y ? imgAxisRect.y : imgAxisRect.y2,
w: Math.abs(imgAxisRect.x2 - imgAxisRect.x),
h: Math.abs(imgAxisRect.y2 - imgAxisRect.y),
};
return getNormalization(endRect, imgW, imgH);
};
//旋转图片后转成file 对象
export const getRotateImg = (image: HTMLImageElement, rotate: number) => {
let imgW = image.width;
let imgH = image.height;
if (rotate % 180 !== 0) {
[imgW, imgH] = [imgH, imgW];
}
const commonCanvas = document.createElement('canvas');
commonCanvas.width = imgW;
commonCanvas.height = imgH;
commonCanvas.style.display = 'none';
document.body.appendChild(commonCanvas);
const commonCtx = commonCanvas.getContext('2d');
//移动到图片中心 旋转
commonCtx?.save();
if (rotate % 180 !== 0) {
//90度调整偏移量
commonCtx?.translate((image.height - image.width) / 2, (image.width - image.height) / 2);
}
commonCtx?.translate(image.width / 2, image.height / 2);
commonCtx?.rotate((rotate / 180) * Math.PI);
commonCtx?.translate(-image.width / 2, -image.height / 2);
commonCtx?.drawImage(image, 0, 0);
commonCtx?.restore();
const dataUrl = commonCanvas.toDataURL('image/jpeg');
const blobData = dataURLToBlob(dataUrl);
const file = new window.File([blobData], `${new Date().getTime()}`, {
type: 'image/jpeg',
});
commonCanvas.parentNode?.removeChild(commonCanvas);
return file;
};
/**
*
* @param originData
* @returns
*/
export const getOdRect = (originData: IOdRectOrigin) => {
let data = get(originData, 'objects', [])
.filter((v: any) => !isNull(get(v, 'infoOnSource.bboxInFrame.bboxRatio')))
.map((v: any, index: any) => {
// objectId==0 特征没有提取到过滤1掉
const rect = get(v, 'infoOnSource.bboxInFrame.bboxRatio');
const extendBox = get(v, 'infoOnSource.bboxInFrame.extendBoxRatio');
const frameTimestamp = get(v, 'timestamp'); //时间戳创建档案的时候需要
const qualityScore = get(v, 'qualityScore');
const algorithmVersion =
get(v, 'objectType') === 'OBJECT_TYPE_PEDESTRAIN'
? 'VERSION_REID_HEAD_ATTR'
: get(v, 'objectType') === 'OBJECT_TYPE_FACE'
? 'VERSION_FACE'
: 'VERSION_REID_HEAD_ATTR';
const featureData = get(v, 'feature', []).filter(
(v: any) => v.type === 'FEATURE_TYPE_BYTE'
);
const objectRectIndex = algorithmVersion === 'VERSION_FACE' ? 0 : 1;
const objectType = get(v, 'objectType');
const objectId = get(v, 'objectIndex.objectId');
const sourceObjectId = get(v, 'sourceObjectId');
return {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
// faceCorrectImage: faceCorrectImage,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
featureData: get(featureData, '0.featureByte'),
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId: objectId,
frameTimestamp: frameTimestamp,
sourceObjectId: sourceObjectId,
extendBox: extendBox,
};
});
if (data.length > 0) {
data = data.filter((v: { objectId: string; }) => v.objectId !== '0');
} else {
throw new Error('empty');
}
return data;
};
//档案库od
export const getOdRectV2 = (originData: { odv2Result: any[]; }) => {
const resp = originData.odv2Result[0];
const subObjects: { x: any; y: any; w: any; h: any; id: any; qualityScore: any; algorithmVersion: any; featrueData: any; objectRectIndex: number; objectType: any; objectId: any; }[] = []; //形体
const data = get(resp, 'objects', [])
.filter((v: any) => !isNull(get(v, 'subObjects[0].infoOnSource.bboxInFrame.bboxRatio')))
.map((v: any, index: any) => {
const rect = get(v, 'infoOnSource.bboxInFrame.bboxRatio');
const qualityScore = get(v, 'qualityScore');
const algorithmVersion = get(v, 'objectType');
const featrueData = get(v, 'feature', []).filter(
(v: { name: string; }) => v.name === 'feature-body' || v.name === 'feature-face'
);
const objectRectIndex = algorithmVersion === 'OBJECT_TYPE_FACE' ? 0 : 1;
const objectType = get(v, 'objectType');
const objectId = get(v, 'objectIndex.objectId');
//如果存在subObjects的数组不为null表示形体里面带人脸人脸的od框也要显示出来
if (get(v, 'subObjects', []).length) {
get(v, 'subObjects', []).forEach((e: any) => {
const rect = get(e, 'infoOnSource.bboxInFrame.bboxRatio');
const qualityScore = get(e, 'qualityScore');
const algorithmVersion = get(e, 'objectType');
const featrueData = get(e, 'feature', []).filter(
(v: { name: string; }) => v.name === 'feature-body' || v.name === 'feature-face'
);
const objectRectIndex = algorithmVersion === 'OBJECT_TYPE_FACE' ? 0 : 1;
const objectType = get(e, 'objectType');
const objectId = get(e, 'objectIndex.objectId');
subObjects.push({
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
featrueData: featrueData.length ? featrueData[0].featureByte : '',
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId,
});
});
}
return {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
featrueData: featrueData[0].featureByte,
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId: objectId,
};
});
const brr = data.concat(subObjects).map((v: { id: any; }, vs: any) => {
if (String(v.id)) {
v.id = vs;
}
return v;
});
if (brr.length > 0) {
console.log(brr, 'data111');
} else {
throw new Error('empty');
}
console.log(brr);
return brr;
};

View File

@ -1,5 +1,45 @@
# @zhst/hooks # @zhst/hooks
## 0.8.4
### Patch Changes
- @zhst/func@0.7.5
## 0.8.3
### Patch Changes
- 新增 tree 组件的监听事件,优化 meta 全选的回调事件监听
- Updated dependencies
- @zhst/func@0.7.4
## 0.8.2
### Patch Changes
- 修改 boxSelectTree 类型提示
- Updated dependencies
- @zhst/func@0.7.3
## 0.8.1
### Patch Changes
- 优化 boxSelectTree 组件,添加可以自定义配置按钮功能
- Updated dependencies
- @zhst/func@0.7.2
## 0.8.0
### Minor Changes
- @zhst/biz 优化数组件
### Patch Changes
- @zhst/func@0.7.1
## 0.7.0 ## 0.7.0
### Minor Changes ### Minor Changes

View File

@ -48,4 +48,4 @@ export var useActivateState = function useActivateState() {
}); });
return isActive; return isActive;
}; };
export default useActivateWrapper; export default useActivateWrapper;

View File

@ -2,8 +2,8 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
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 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 _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 _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(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } 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); }
import { useLatest } from 'ahooks'; import { useLatest } from 'ahooks';
import events from 'events'; import events from 'events';
import { get, isArray } from '@zhst/func'; import { get, isArray } from '@zhst/func';

View File

@ -1,6 +1,6 @@
{ {
"name": "@zhst/hooks", "name": "@zhst/hooks",
"version": "0.7.0", "version": "0.8.4",
"description": "hooks合集", "description": "hooks合集",
"keywords": [ "keywords": [
"hooks" "hooks"

View File

@ -1,5 +1,74 @@
# @zhst/utils # @zhst/utils
## 0.9.1
### Patch Changes
- 添加数据为空时自定义
- Updated dependencies
- @zhst/meta@0.9.1
## 0.9.0
### Minor Changes
- 修改 metad 大图组建
### Patch Changes
- Updated dependencies
- @zhst/meta@0.9.0
## 0.8.5
### Patch Changes
- @zhst/func@0.7.5
- @zhst/hooks@0.8.4
- @zhst/meta@0.8.5
- 修改大图组组建的手动绘制截图逻辑
- Updated dependencies
- @zhst/meta@0.8.5
## 0.8.4
### Patch Changes
- 新增 tree 组件的监听事件,优化 meta 全选的回调事件监听
- Updated dependencies
- @zhst/hooks@0.8.3
- @zhst/func@0.7.4
- @zhst/meta@0.8.4
## 0.8.3
### Patch Changes
- 修改 boxSelectTree 类型提示
- Updated dependencies
- @zhst/hooks@0.8.2
- @zhst/func@0.7.3
- @zhst/meta@0.8.3
## 0.8.2
### Patch Changes
- 优化 boxSelectTree 组件,添加可以自定义配置按钮功能
- Updated dependencies
- @zhst/hooks@0.8.1
- @zhst/func@0.7.2
- @zhst/meta@0.8.2
## 0.8.1
### Patch Changes
- Updated dependencies
- @zhst/hooks@0.8.0
- @zhst/func@0.7.1
- @zhst/meta@0.8.1
## 0.8.0 ## 0.8.0
### Minor Changes ### Minor Changes

View File

@ -13,10 +13,9 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
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 _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 _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; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import React, { useEffect, useState, useCallback, useRef, useImperativeHandle } from 'react'; import React, { useEffect, useState, useRef, useImperativeHandle } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useLatest } from '@zhst/hooks'; import { get, pick, isNull, generateImg, dataURLToBlob, getTransforms, addEventListenerWrapper, getFileByRect } from '@zhst/func';
import { get, pick, isNull, generateImg, dataURLToBlob, getOdRect, getExtendRect, getTransformRect, getRotateImg, getTransforms, addEventListenerWrapper, getFileByRect } from '@zhst/func';
import Align from 'rc-align'; import Align from 'rc-align';
import { Button, Empty } from '..'; import { Button, Empty } from '..';
import Icon from "../iconfont"; import Icon from "../iconfont";
@ -24,6 +23,7 @@ import { Cropper, Viewer, EVENT_VIEWER_TRANSFORM_CHANGE, EVENT_VIEWER_READY, EVE
import BtnGroup from "./components/BtnGroup"; import BtnGroup from "./components/BtnGroup";
import "./index.less"; import "./index.less";
import { defaultAlignOption, CROP_TYPE } from "../utils/constants"; import { defaultAlignOption, CROP_TYPE } from "../utils/constants";
import { getOdRect, getExtendRect, getTransformRect, getRotateImg } from "./bigImagePreviewHelper";
var componentName = "zhst-image__img-view"; var componentName = "zhst-image__img-view";
var cropBtnDataSource = [{ var cropBtnDataSource = [{
key: 'close', key: 'close',
@ -55,7 +55,7 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
var width = props.width, var width = props.width,
height = props.height, height = props.height,
_props$showScore = props.showScore, _props$showScore = props.showScore,
showScore = _props$showScore === void 0 ? true : _props$showScore, showScore = _props$showScore === void 0 ? false : _props$showScore,
data = props.data, data = props.data,
_props$showOpt = props.showOpt, _props$showOpt = props.showOpt,
showOpt = _props$showOpt === void 0 ? false : _props$showOpt, showOpt = _props$showOpt === void 0 ? false : _props$showOpt,
@ -67,14 +67,21 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
screenshotButtonRender = _props$screenshotButt2 === void 0 ? function () { screenshotButtonRender = _props$screenshotButt2 === void 0 ? function () {
return /*#__PURE__*/React.createElement("div", { return /*#__PURE__*/React.createElement("div", {
style: { style: {
color: '#fff' color: '#fff',
width: '80px',
top: 0,
fontSize: 12
} }
}, "\u56DE\u8C03DOM"); }, "\u56DE\u8C03DOM");
} : _props$screenshotButt2, } : _props$screenshotButt2,
_props$hideLeftTopBtn = props.hideLeftTopBtn, _props$hideLeftTopBtn = props.hideLeftTopBtn,
hideLeftTopBtn = _props$hideLeftTopBtn === void 0 ? true : _props$hideLeftTopBtn, hideLeftTopBtn = _props$hideLeftTopBtn === void 0 ? true : _props$hideLeftTopBtn,
onDraw = props.onDraw,
_props$viewOption = props.viewOption, _props$viewOption = props.viewOption,
viewOption = _props$viewOption === void 0 ? {} : _props$viewOption; viewOption = _props$viewOption === void 0 ? {} : _props$viewOption,
type = props.type,
hideTypeBtns = props.hideTypeBtns,
customEmpty = props.customEmpty;
var imageKey = data.imageKey, var imageKey = data.imageKey,
attachImg = data.attachImg, attachImg = data.attachImg,
odRect = data.odRect, odRect = data.odRect,
@ -82,22 +89,15 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
_data$objects = data.objects, _data$objects = data.objects,
objects = _data$objects === void 0 ? [] : _data$objects; objects = _data$objects === void 0 ? [] : _data$objects;
var imgContainerRef = React.useRef(null); var imgContainerRef = React.useRef(null);
var _useState = useState(false),
_useState2 = _slicedToArray(_useState, 2),
isReady = _useState2[0],
setIsReady = _useState2[1];
var init = useCallback(function ($container) {
imgContainerRef.current = $container;
setIsReady(true);
}, []);
// ============================= viewer ========================= // ============================= viewer =========================
var imgInsRef = useRef(null); var imgInsRef = useRef(null);
var _useState3 = useState(false), var _useState = useState(false),
_useState4 = _slicedToArray(_useState3, 2), _useState2 = _slicedToArray(_useState, 2),
isImgReady = _useState4[0], isImgReady = _useState2[0],
setIsImgReady = _useState4[1]; setIsImgReady = _useState2[1];
useEffect(function () { useEffect(function () {
if (!isReady || !(imgContainerRef !== null && imgContainerRef !== void 0 && imgContainerRef.current)) return; if (!(imgContainerRef !== null && imgContainerRef !== void 0 && imgContainerRef.current)) return;
var handleReady = addEventListenerWrapper(imgContainerRef.current, EVENT_VIEWER_READY, function () { var handleReady = addEventListenerWrapper(imgContainerRef.current, EVENT_VIEWER_READY, function () {
setIsImgReady(true); setIsImgReady(true);
}); });
@ -113,7 +113,7 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
imgInsRef === null || imgInsRef === void 0 || (_imgInsRef$current = imgInsRef.current) === null || _imgInsRef$current === void 0 || (_imgInsRef$current$de = _imgInsRef$current.destroy) === null || _imgInsRef$current$de === void 0 || _imgInsRef$current$de.call(_imgInsRef$current); imgInsRef === null || imgInsRef === void 0 || (_imgInsRef$current = imgInsRef.current) === null || _imgInsRef$current === void 0 || (_imgInsRef$current$de = _imgInsRef$current.destroy) === null || _imgInsRef$current$de === void 0 || _imgInsRef$current$de.call(_imgInsRef$current);
imgInsRef.current = null; imgInsRef.current = null;
}; };
}, [isReady, imageKey]); }, [imageKey]);
// ============================= viewer操作按钮 ========================= // ============================= viewer操作按钮 =========================
var handleOptClick = function handleOptClick(v) { var handleOptClick = function handleOptClick(v) {
@ -134,42 +134,42 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
// ============================= cropper ========================= // ============================= cropper =========================
// 手动截图相关参数 // 手动截图相关参数
var cropInsRef = useRef(null); var cropInsRef = useRef(null);
var _useState5 = useState(showOpt), var _useState3 = useState(showOpt),
_useState4 = _slicedToArray(_useState3, 2),
showCrop = _useState4[0],
setShowCrop = _useState4[1];
var _useState5 = useState(type || CROP_TYPE['AUTO']),
_useState6 = _slicedToArray(_useState5, 2), _useState6 = _slicedToArray(_useState5, 2),
showCrop = _useState6[0], cropType = _useState6[0],
setShowCrop = _useState6[1]; setCropType = _useState6[1];
var _useState7 = useState(CROP_TYPE['AUTO']),
_useState8 = _slicedToArray(_useState7, 2),
cropType = _useState8[0],
setCropType = _useState8[1];
// 自动截图相关参数 // 自动截图相关参数
var _useState7 = useState([]),
_useState8 = _slicedToArray(_useState7, 2),
odList = _useState8[0],
setOdList = _useState8[1];
var _useState9 = useState([]), var _useState9 = useState([]),
_useState10 = _slicedToArray(_useState9, 2), _useState10 = _slicedToArray(_useState9, 2),
odList = _useState10[0], extendOdList = _useState10[0],
setOdList = _useState10[1]; setExtendOdList = _useState10[1];
var _useState11 = useState([]), var _useState11 = useState(null),
_useState12 = _slicedToArray(_useState11, 2), _useState12 = _slicedToArray(_useState11, 2),
extendOdList = _useState12[0], selectRectId = _useState12[0],
setExtendOdList = _useState12[1]; setSelectRectId = _useState12[1];
var _useState13 = useState(null),
_useState14 = _slicedToArray(_useState13, 2),
selectRectId = _useState14[0],
setSelectRectId = _useState14[1];
// 定位按钮相关参数 // 定位按钮相关参数
var alginContainerRef = useRef(null); var alginContainerRef = useRef(null);
var alignRef = useRef(null); var alignRef = useRef(null);
var _useState15 = useState(null), var _useState13 = useState(null),
_useState16 = _slicedToArray(_useState15, 2), _useState14 = _slicedToArray(_useState13, 2),
cropRect = _useState16[0], cropRect = _useState14[0],
setCropRect = _useState16[1]; setCropRect = _useState14[1];
// 选中的版本号 // 选中的版本号
var _useState17 = useState(null), var _useState15 = useState(null),
_useState18 = _slicedToArray(_useState17, 2), _useState16 = _slicedToArray(_useState15, 2),
selectAlgorithmVersion = _useState18[0], selectAlgorithmVersion = _useState16[0],
setSelectAlgorithmVersion = _useState18[1]; setSelectAlgorithmVersion = _useState16[1];
var handlerCropStartRef = useRef(null); var handlerCropStartRef = useRef(null);
var handlerCropEndRef = useRef(null); var handlerCropEndRef = useRef(null);
var handleShapeSelectRef = useRef(null); var handleShapeSelectRef = useRef(null);
@ -185,6 +185,8 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
cropInsRef.current = null; cropInsRef.current = null;
}; };
}, [isImgReady, showCrop, cropType, imageKey]); }, [isImgReady, showCrop, cropType, imageKey]);
// 初始化页面的绘制矩形
var initData = function initData(_objects) { var initData = function initData(_objects) {
var imgIns = imgInsRef.current; var imgIns = imgInsRef.current;
var _odRect = odRect; var _odRect = odRect;
@ -210,7 +212,7 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
var shapeList = odList.map(function (rect) { var shapeList = odList.map(function (rect) {
return _objectSpread(_objectSpread({}, rect), {}, { return _objectSpread(_objectSpread({}, rect), {}, {
selectAble: true, selectAble: true,
id: rect['id'], id: ['id'],
algorithmVersion: rect.algorithmVersion algorithmVersion: rect.algorithmVersion
}); });
}); });
@ -223,28 +225,49 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
}); });
setExtendOdList(extendRect); setExtendOdList(extendRect);
imgIns.replaceShape(shapeList); imgIns.replaceShape(shapeList);
handleShapeSelectRef.current = addEventListenerWrapper(imgContainerRef.current, 'shape-select', function (e) { // 框选监听事件
var id = e.detail; handleShapeSelectRef.current = addEventListenerWrapper(imgContainerRef.current, 'shape-select', /*#__PURE__*/function () {
setSelectRectId(id); var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(e) {
var selectShape = shapeList.find(function (v) { var id, selectShape, axisRect, _rect2;
return v['id'] === id; return _regeneratorRuntime().wrap(function _callee$(_context) {
}); while (1) switch (_context.prev = _context.next) {
if (selectShape) { case 0:
setSelectAlgorithmVersion(selectShape['algorithmVersion']); id = e.detail;
//换算成屏幕坐标 setSelectRectId(id);
var axisRect = imgIns.imgRectAxisToCanvasAxisRect(selectShape); selectShape = shapeList.find(function (v) {
var _rect = { return v['id'] === id;
x: axisRect.x2 > axisRect.x ? axisRect.x : axisRect.x2, });
y: axisRect.y2 > axisRect.y ? axisRect.y : axisRect.y2, if (selectShape) {
w: Math.abs(axisRect.x2 - axisRect.x), setSelectAlgorithmVersion(selectShape['algorithmVersion']);
h: Math.abs(axisRect.y2 - axisRect.y) //换算成屏幕坐标
}; axisRect = imgIns.imgRectAxisToCanvasAxisRect(selectShape);
setCropRect(_rect); _rect2 = {
} else { x: axisRect.x2 > axisRect.x ? axisRect.x : axisRect.x2,
// @ts-ignore y: axisRect.y2 > axisRect.y ? axisRect.y : axisRect.y2,
setCropRect(null); w: Math.abs(axisRect.x2 - axisRect.x),
} h: Math.abs(axisRect.y2 - axisRect.y)
}); };
setCropRect(_rect2);
onDraw === null || onDraw === void 0 || onDraw({
rectList: [_rect2],
extendRectList: [_rect2],
imgKey: imageKey,
selectIndex: id
});
} else {
// @ts-ignore
setCropRect(null);
}
case 4:
case "end":
return _context.stop();
}
}, _callee);
}));
return function (_x) {
return _ref.apply(this, arguments);
};
}());
}; };
// @ts-ignore // @ts-ignore
var rect = getOdRect({ var rect = getOdRect({
@ -263,18 +286,41 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
setSelectAlgorithmVersion(null); setSelectAlgorithmVersion(null);
setCropRect(null); setCropRect(null);
}); });
handlerCropEndRef.current = addEventListenerWrapper(imgContainerRef.current, EVENT_CROP_END, function (event) { handlerCropEndRef.current = addEventListenerWrapper(imgContainerRef.current, EVENT_CROP_END, /*#__PURE__*/function () {
var _alignRef$current, _alignRef$current$for; var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(event) {
var data = event.detail; var _alignRef$current, _alignRef$current$for;
setSelectAlgorithmVersion(null); var data, _rect, _cropData;
setCropRect({ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
x: data.left, while (1) switch (_context2.prev = _context2.next) {
y: data.top, case 0:
w: data.width, data = event.detail;
h: data.height setSelectAlgorithmVersion(null);
}); _rect = {
alignRef === null || alignRef === void 0 || (_alignRef$current = alignRef.current) === null || _alignRef$current === void 0 || (_alignRef$current$for = _alignRef$current.forceAlign) === null || _alignRef$current$for === void 0 || _alignRef$current$for.call(_alignRef$current); x: data.left,
}); y: data.top,
w: data.width,
h: data.height
};
setCropRect(_rect);
_context2.next = 6;
return getCropInfo({
type: cropType,
rect: _rect
});
case 6:
_cropData = _context2.sent;
onDraw === null || onDraw === void 0 || onDraw(_cropData);
alignRef === null || alignRef === void 0 || (_alignRef$current = alignRef.current) === null || _alignRef$current === void 0 || (_alignRef$current$for = _alignRef$current.forceAlign) === null || _alignRef$current$for === void 0 || _alignRef$current$for.call(_alignRef$current);
case 9:
case "end":
return _context2.stop();
}
}, _callee2);
}));
return function (_x2) {
return _ref2.apply(this, arguments);
};
}());
cropInsRef.current = new Cropper(imgContainerRef.current, { cropInsRef.current = new Cropper(imgContainerRef.current, {
showMask: true, showMask: true,
viewer: imgIns viewer: imgIns
@ -283,26 +329,25 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
}; };
// 获取框选的截图框信息 // 获取框选的截图框信息
var latestCropType = useLatest(cropType);
var latestCropRect = useLatest(cropRect);
var getCropInfo = /*#__PURE__*/function () { var getCropInfo = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(cb) { var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(opt) {
var cropType, cropRect, imgIns, transform, newImgKey, rectList, extendRectList, selectIndex, shapes, shapeIds, newRect, _data; var type, rect, cropType, cropRect, imgIns, transform, newImgKey, rectList, extendRectList, selectIndex, shapes, shapeIds, newRect, _data, data;
return _regeneratorRuntime().wrap(function _callee3$(_context3) { return _regeneratorRuntime().wrap(function _callee5$(_context5) {
while (1) switch (_context3.prev = _context3.next) { while (1) switch (_context5.prev = _context5.next) {
case 0: case 0:
cropType = latestCropType.current; type = opt.type, rect = opt.rect;
cropRect = latestCropRect.current; cropType = type;
cropRect = rect;
imgIns = imgInsRef.current; imgIns = imgInsRef.current;
transform = imgIns.targetTransform; transform = imgIns.targetTransform;
newImgKey = imageKey; newImgKey = imageKey;
rectList = []; rectList = [];
extendRectList = []; extendRectList = [];
selectIndex = 0; selectIndex = 0;
_context3.t0 = cropType; _context5.t0 = cropType;
_context3.next = _context3.t0 === CROP_TYPE['AUTO'] ? 11 : 17; _context5.next = _context5.t0 === CROP_TYPE['AUTO'] ? 12 : 18;
break; break;
case 11: case 12:
shapes = imgIns.getSelectShape(); shapes = imgIns.getSelectShape();
shapeIds = shapes.map(function (v) { shapeIds = shapes.map(function (v) {
return v['id']; return v['id'];
@ -331,8 +376,8 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
selectIndex = rectList.findIndex(function (v) { selectIndex = rectList.findIndex(function (v) {
return v['id'] === selectRectId; return v['id'] === selectRectId;
}); });
return _context3.abrupt("break", 22); return _context5.abrupt("break", 23);
case 17: case 18:
//获取旋转过的坐标 //获取旋转过的坐标
// @ts-ignore // @ts-ignore
newRect = getTransformRect(imgIns.image, transform, cropRect); //判断是不是旋转过 newRect = getTransformRect(imgIns.image, transform, cropRect); //判断是不是旋转过
@ -342,40 +387,40 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
} }
rectList.push(newRect); rectList.push(newRect);
extendRectList.push(newRect); extendRectList.push(newRect);
return _context3.abrupt("break", 22); return _context5.abrupt("break", 23);
case 22: case 23:
_context3.next = 24; _context5.next = 25;
return Promise.all(extendRectList.map( /*#__PURE__*/function () { return Promise.all(extendRectList.map( /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(rect, index) { var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(rect, index) {
var file, imgKey; var file, imgKey;
return _regeneratorRuntime().wrap(function _callee$(_context) { return _regeneratorRuntime().wrap(function _callee3$(_context3) {
while (1) switch (_context.prev = _context.next) { while (1) switch (_context3.prev = _context3.next) {
case 0: case 0:
_context.next = 2; _context3.next = 2;
return getFileByRect(imgIns.image, rect); return getFileByRect(imgIns.image, rect);
case 2: case 2:
file = _context.sent; file = _context3.sent;
imgKey = file; imgKey = file;
extendRectList[index] = _objectSpread(_objectSpread({}, rect), {}, { extendRectList[index] = _objectSpread(_objectSpread({}, rect), {}, {
imgKey: imgKey imgKey: imgKey
}); });
case 5: case 5:
case "end": case "end":
return _context.stop(); return _context3.stop();
} }
}, _callee); }, _callee3);
})); }));
return function (_x2, _x3) { return function (_x4, _x5) {
return _ref2.apply(this, arguments); return _ref4.apply(this, arguments);
}; };
}())); }()));
case 24: case 25:
_context3.next = 26; _context5.next = 27;
return Promise.all(rectList.map( /*#__PURE__*/function () { return Promise.all(rectList.map( /*#__PURE__*/function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(rect, index) { var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(rect, index) {
var faceCorrectImage, faceCorrectImageKey, base64, blobData, file, newRect; var faceCorrectImage, faceCorrectImageKey, base64, blobData, file, newRect;
return _regeneratorRuntime().wrap(function _callee2$(_context2) { return _regeneratorRuntime().wrap(function _callee4$(_context4) {
while (1) switch (_context2.prev = _context2.next) { while (1) switch (_context4.prev = _context4.next) {
case 0: case 0:
faceCorrectImage = rect['faceCorrectImage']; faceCorrectImage = rect['faceCorrectImage'];
if (faceCorrectImage) { if (faceCorrectImage) {
@ -391,35 +436,30 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
rectList[index] = newRect; rectList[index] = newRect;
case 5: case 5:
case "end": case "end":
return _context2.stop(); return _context4.stop();
} }
}, _callee2); }, _callee4);
})); }));
return function (_x4, _x5) { return function (_x6, _x7) {
return _ref3.apply(this, arguments); return _ref5.apply(this, arguments);
}; };
}())); }()));
case 26: case 27:
cb === null || cb === void 0 || cb({ data = {
rectList: rectList, rectList: rectList,
extendRectList: extendRectList, extendRectList: extendRectList,
selectIndex: selectIndex, selectIndex: selectIndex,
imgKey: newImgKey imgKey: newImgKey
}); };
return _context3.abrupt("return", { return _context5.abrupt("return", data);
rectList: rectList, case 29:
extendRectList: extendRectList,
selectIndex: selectIndex,
imgKey: newImgKey
});
case 28:
case "end": case "end":
return _context3.stop(); return _context5.stop();
} }
}, _callee3); }, _callee5);
})); }));
return function getCropInfo(_x) { return function getCropInfo(_x3) {
return _ref.apply(this, arguments); return _ref3.apply(this, arguments);
}; };
}(); }();
@ -439,23 +479,14 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
}; };
// ============================= attact img ========================= // ============================= attact img =========================
var _useState19 = useState(0), var _useState17 = useState(0),
_useState18 = _slicedToArray(_useState17, 2),
selectAttachImgIndex = _useState18[0],
setSelectAttachImgIndex = _useState18[1];
var _useState19 = useState(false),
_useState20 = _slicedToArray(_useState19, 2), _useState20 = _slicedToArray(_useState19, 2),
selectAttachImgIndex = _useState20[0], isZoomin = _useState20[0],
setSelectAttachImgIndex = _useState20[1]; setIsZoomin = _useState20[1];
var _useState21 = useState(false),
_useState22 = _slicedToArray(_useState21, 2),
isZoomin = _useState22[0],
setIsZoomin = _useState22[1];
/**
* 修改当前图片预览下标
* @param diff 跳转强度 正向后翻负值向前翻
*/
var handleChangeIndex = function handleChangeIndex(cb) {
if (!imageKey) return;
cb === null || cb === void 0 || cb();
};
// ============================== Ref =============================== // ============================== Ref ===============================
useImperativeHandle(ref, function () { useImperativeHandle(ref, function () {
@ -463,8 +494,7 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
imgInsRef: imgInsRef, imgInsRef: imgInsRef,
setShowCrop: setShowCrop, setShowCrop: setShowCrop,
initData: initData, initData: initData,
getCropInfo: getCropInfo, getCropInfo: getCropInfo
handleChangeIndex: handleChangeIndex
}; };
}); });
return /*#__PURE__*/React.createElement("div", { return /*#__PURE__*/React.createElement("div", {
@ -475,13 +505,14 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
} }
}, imageKey ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { }, imageKey ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
className: classNames("".concat(componentName, "-main"), cropType === CROP_TYPE['AUTO'] && "".concat(componentName, "-main--cursor")), className: classNames("".concat(componentName, "-main"), cropType === CROP_TYPE['AUTO'] && "".concat(componentName, "-main--cursor")),
ref: init ref: imgContainerRef
// style={{ width: width, height: height }}
}), !hideLeftTopBtn && /*#__PURE__*/React.createElement(BtnGroup, { }), !hideLeftTopBtn && /*#__PURE__*/React.createElement(BtnGroup, {
className: classNames("".concat(componentName, "-opt")), className: classNames("".concat(componentName, "-opt")),
dataSource: operateBtnDataSource, dataSource: operateBtnDataSource,
onClick: handleOptClick, onClick: handleOptClick,
placement: "left" placement: "left"
}), showCrop && /*#__PURE__*/React.createElement(BtnGroup, { }), !hideTypeBtns && showCrop && /*#__PURE__*/React.createElement(BtnGroup, {
circle: true, circle: true,
className: classNames("".concat(componentName, "-crop-opt")), className: classNames("".concat(componentName, "-crop-opt")),
dataSource: cropBtnDataSource, dataSource: cropBtnDataSource,
@ -515,8 +546,8 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
className: classNames("".concat(componentName, "-attach"), isZoomin && "".concat(componentName, "-attach--zoomin"), "".concat(componentName, "-attach--fixed"), isZoomin && "".concat(componentName, "-attach--zoomin--fixed")) className: classNames("".concat(componentName, "-attach"), isZoomin && "".concat(componentName, "-attach--zoomin"), "".concat(componentName, "-attach--fixed"), isZoomin && "".concat(componentName, "-attach--zoomin--fixed"))
}, /*#__PURE__*/React.createElement("div", { }, /*#__PURE__*/React.createElement("div", {
className: classNames("".concat(componentName, "-attach__tab")) className: classNames("".concat(componentName, "-attach__tab"))
}, showAttachImgLabel ? attachImg === null || attachImg === void 0 ? void 0 : attachImg.map(function (_ref4, index) { }, showAttachImgLabel ? attachImg === null || attachImg === void 0 ? void 0 : attachImg.map(function (_ref6, index) {
var label = _ref4.label; var label = _ref6.label;
return /*#__PURE__*/React.createElement("div", { return /*#__PURE__*/React.createElement("div", {
key: index, key: index,
className: classNames("".concat(componentName, "-attach__tab-item"), selectAttachImgIndex === index && "".concat(componentName, "-attach__tab-item--select")), className: classNames("".concat(componentName, "-attach__tab-item"), selectAttachImgIndex === index && "".concat(componentName, "-attach__tab-item--select")),
@ -550,15 +581,25 @@ export var BigImagePreview = /*#__PURE__*/React.forwardRef(function (props, ref)
draggable: "false", draggable: "false",
className: classNames("".concat(componentName, "-attach__img"), "".concat(componentName, "-attach__img--fixed")), className: classNames("".concat(componentName, "-attach__img"), "".concat(componentName, "-attach__img--fixed")),
src: get(attachImg, "".concat(selectAttachImgIndex, ".url"), '') src: get(attachImg, "".concat(selectAttachImgIndex, ".url"), '')
})), showScore && /*#__PURE__*/React.createElement("div", { })), (showScore || score) && /*#__PURE__*/React.createElement("div", {
style: { style: {
bottom: 20 bottom: 20
}, },
className: classNames("".concat(componentName, "__face-score")) className: classNames("".concat(componentName, "__face-score"))
}, "\u4EBA\u8138\u8D28\u91CF\u5206\uFF1A".concat(Number(score).toFixed(2)))) : /*#__PURE__*/React.createElement(Empty, { }, "\u4EBA\u8138\u8D28\u91CF\u5206\uFF1A".concat(Number(score).toFixed(2)))) : /*#__PURE__*/React.createElement("div", {
style: {
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
}, customEmpty || /*#__PURE__*/React.createElement(Empty, {
style: {
margin: 0
},
image: Empty.PRESENTED_IMAGE_SIMPLE, image: Empty.PRESENTED_IMAGE_SIMPLE,
description: "\u6682\u65E0\u6570\u636E" description: "\u6682\u65E0\u6570\u636E"
})); })));
}); });
BigImagePreview.displayName = 'BigImagePreview'; BigImagePreview.displayName = 'BigImagePreview';
export default BigImagePreview; export default BigImagePreview;

View File

@ -0,0 +1,270 @@
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 _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread 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 _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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 _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); }
import { cloneDeep, dataURLToBlob, get, isNull } from "@zhst/func";
var proto = {
Common: {
AlgorithmVersion: {
VERSION_REID_HEAD_ATTR: '形体',
VERSION_FACE: '人脸',
VERSION_NON_MOTOR_VEHICLE: '非机动车'
}
}
};
export var ALGORITHM_VERSION = _defineProperty(_defineProperty(_defineProperty({}, '7', '形体'), '4', '人脸'), '6', '非机动车');
export var algorithmVersions = _toConsumableArray(Object.keys(ALGORITHM_VERSION));
/**
* 格式化工具
* @param originData
* @returns
*/
export var getOdRect = function getOdRect(originData) {
var data = get(originData, 'objects', []).filter(function (v) {
return !isNull(get(v, 'bboxRatio')) || get(v, 'objectIndex.objectId') !== '0';
}).map(function (v, index) {
var rect = get(v, 'bboxRatio');
var extendBox = get(v, 'extendBoxRatio');
var frameTimestamp = get(v, 'timestamp'); //时间戳创建档案的时候需要
var qualityScore = get(v, 'qualityScore');
var algorithmVersion = get(v, 'objectType') === 'OBJECT_TYPE_PEDESTRAIN' ? 'VERSION_REID_HEAD_ATTR' : get(v, 'objectType') === 'OBJECT_TYPE_FACE' ? 'VERSION_FACE' : 'VERSION_REID_HEAD_ATTR';
var objectRectIndex = algorithmVersion === 'VERSION_FACE' ? 0 : 1;
var objectType = get(v, 'objectType');
var objectId = get(v, 'objectIndex.objectId');
var sourceObjectId = get(v, 'sourceObjectId');
return {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
id: index,
qualityScore: qualityScore,
algorithmVersion: algorithmVersion,
objectRectIndex: objectRectIndex,
objectType: objectType,
objectId: objectId,
frameTimestamp: frameTimestamp,
sourceObjectId: sourceObjectId,
extendBox: extendBox
};
});
return data;
};
export var getNormalization = function getNormalization(srcRect, maxW, maxH) {
var newRect = _objectSpread({}, srcRect);
newRect.x = srcRect.x / maxW;
newRect.y = srcRect.y / maxH;
newRect.w = srcRect.w / maxW;
newRect.h = srcRect.h / maxH;
if (newRect.x + newRect.w > 1) {
newRect.w = 1 - newRect.x;
}
if (newRect.y + newRect.h > 1) {
newRect.h = 1 - newRect.y;
}
return newRect;
};
//传入od框 穿出 od扩展框
export var getExtendRect = function getExtendRect(normalizationRect, imgW, imgH, type) {
var rect = {
x: normalizationRect.x * imgW,
y: normalizationRect.y * imgH,
w: normalizationRect.w * imgW,
h: normalizationRect.h * imgH
};
var newRect;
if (type === proto.Common.AlgorithmVersion.VERSION_NON_MOTOR_VEHICLE) {
newRect = getBikeExtendRect(rect, imgW);
} else {
newRect = getOtherExtendRect(rect, imgW, imgH, type);
}
newRect = getNormalization(newRect, imgW, imgH);
return newRect;
};
export var getBikeExtendRect = function getBikeExtendRect(rect, maxW) {
var newRect = _objectSpread({}, rect);
//向上扩大一倍
var oldY = cloneDeep(rect.y);
newRect.y = newRect.y - newRect.h < 0 ? 0 : newRect.y - newRect.h;
newRect.h += oldY - newRect.y;
var newX = Math.round(newRect.x - newRect.w * 0.15);
if (newX < 0) {
newX = 0;
}
var newW = newRect.x - newX + newRect.w + Math.round(newRect.w * 0.15);
if (newX + newW > maxW) {
newW = maxW - newX;
}
newRect.x = newX;
newRect.w = newW;
return newRect;
};
export var getOtherExtendRect = function getOtherExtendRect(srcRect, maxW, maxH, type) {
var wExtendRadio = 0.25;
var upExtendRadio = 0.25;
var downExtendRadio = 0.25;
var fixPersonExtend = true;
var nx = 0;
var nw = 0;
nx = srcRect.x - Math.round(srcRect.w * wExtendRadio);
if (nx < 0) {
nx = 0;
}
nw = srcRect.x - nx + srcRect.w + Math.round(srcRect.w * wExtendRadio);
if (nx + nw > maxW) {
nw = maxW - nx;
}
var ny = 0;
var nh = 0;
ny = srcRect.y - Math.round(upExtendRadio * srcRect.h);
if (ny < 0) {
ny = 0;
}
nh = srcRect.y - ny + srcRect.h + Math.round(srcRect.h * downExtendRadio);
if (ny + nh > maxH) {
nh = maxH - ny;
}
var newRect = {
x: nx,
y: ny,
w: nw,
h: nh
};
if ((type === proto.Common.AlgorithmVersion.VERSION_REID_HEAD_ATTR || type === proto.Common.AlgorithmVersion.VERSION_FACE) && fixPersonExtend) {
var fixW = Math.round(nh * 0.75);
if (nw < fixW) {
// 应该扩展宽度
var newX = nx + Math.round(nw / 2.0 - 0.5 * fixW);
if (newX < 0) {
newX = 0;
}
var newW = fixW;
if (newW + newX > maxW) {
newW = maxW - newX;
}
newRect = {
x: newX,
y: ny,
w: newW,
h: nh
};
} else if (nw > fixW) {
// 应该扩展高度
var fixH = Math.round(nw * 1.333);
var newY = ny + Math.round(nh / 2.0 - 0.5 * fixH);
if (newY < 0) {
newY = 0;
}
var newH = fixH;
if (newY + newH > maxH) {
newH = maxH - newY;
}
newRect = {
x: nx,
y: newY,
w: nw,
h: newH
};
}
}
return newRect;
};
export var getTransformRect = function getTransformRect(image, transform, rect) {
var canvasRect = {
x: rect.x,
y: rect.y,
x2: rect.x + rect.w,
y2: rect.h + rect.y
};
//1.转成缩放前的坐标
var translateX = transform.translateX,
translateY = transform.translateY,
scale = transform.scale,
rotate = transform.rotate;
var originAxisRect = {
x: (canvasRect.x - translateX) / scale,
y: (canvasRect.y - translateY) / scale,
x2: (canvasRect.x2 - translateX) / scale,
y2: (canvasRect.y2 - translateY) / scale
};
//2.转成图片坐标
//不考虑旋转 图片原点就是坐标原点
var imgAxisRect = originAxisRect;
//但是旋转90度后图片不在坐标原点 加上这部分diff
if (rotate % 180 !== 0) {
//90度调整偏移量
var offsetX = -(image.height - image.width) / 2;
var offsetY = -(image.width - image.height) / 2;
imgAxisRect = {
x: originAxisRect.x - offsetX,
y: originAxisRect.y - offsetY,
x2: originAxisRect.x2 - offsetX,
y2: originAxisRect.y2 - offsetY
};
}
//3.限制框不要超出图片
var imgW = image.width;
var imgH = image.height;
if (rotate % 180 !== 0) {
var _ref = [imgH, imgW];
imgW = _ref[0];
imgH = _ref[1];
}
imgAxisRect.x = Math.min(imgW, Math.max(imgAxisRect.x, 0));
imgAxisRect.y = Math.min(imgH, Math.max(imgAxisRect.y, 0));
imgAxisRect.x2 = Math.min(imgW, Math.max(imgAxisRect.x2, 0));
imgAxisRect.y2 = Math.min(imgH, Math.max(imgAxisRect.y2, 0));
//获取归一化坐标
var endRect = {
x: imgAxisRect.x2 > imgAxisRect.x ? imgAxisRect.x : imgAxisRect.x2,
y: imgAxisRect.y2 > imgAxisRect.y ? imgAxisRect.y : imgAxisRect.y2,
w: Math.abs(imgAxisRect.x2 - imgAxisRect.x),
h: Math.abs(imgAxisRect.y2 - imgAxisRect.y)
};
return getNormalization(endRect, imgW, imgH);
};
//旋转图片后转成file 对象
export var getRotateImg = function getRotateImg(image, rotate) {
var _commonCanvas$parentN;
var imgW = image.width;
var imgH = image.height;
if (rotate % 180 !== 0) {
var _ref2 = [imgH, imgW];
imgW = _ref2[0];
imgH = _ref2[1];
}
var commonCanvas = document.createElement('canvas');
commonCanvas.width = imgW;
commonCanvas.height = imgH;
commonCanvas.style.display = 'none';
document.body.appendChild(commonCanvas);
var commonCtx = commonCanvas.getContext('2d');
//移动到图片中心 旋转
commonCtx === null || commonCtx === void 0 || commonCtx.save();
if (rotate % 180 !== 0) {
//90度调整偏移量
commonCtx === null || commonCtx === void 0 || commonCtx.translate((image.height - image.width) / 2, (image.width - image.height) / 2);
}
commonCtx === null || commonCtx === void 0 || commonCtx.translate(image.width / 2, image.height / 2);
commonCtx === null || commonCtx === void 0 || commonCtx.rotate(rotate / 180 * Math.PI);
commonCtx === null || commonCtx === void 0 || commonCtx.translate(-image.width / 2, -image.height / 2);
commonCtx === null || commonCtx === void 0 || commonCtx.drawImage(image, 0, 0);
commonCtx === null || commonCtx === void 0 || commonCtx.restore();
var dataUrl = commonCanvas.toDataURL('image/jpeg');
var blobData = dataURLToBlob(dataUrl);
var file = new window.File([blobData], "".concat(new Date().getTime()), {
type: 'image/jpeg'
});
(_commonCanvas$parentN = commonCanvas.parentNode) === null || _commonCanvas$parentN === void 0 || _commonCanvas$parentN.removeChild(commonCanvas);
return file;
};

View File

@ -2,6 +2,7 @@
position: relative; position: relative;
width: calc(100%); width: calc(100%);
height: 100%; height: 100%;
font-size: 0;
&__face-score { &__face-score {
position: absolute; position: absolute;
@ -62,14 +63,9 @@
height: 202px; height: 202px;
transition: all 200ms; transition: all 200ms;
&--fixed {
}
&--zoomin { &--zoomin {
height: 100%; height: 100%;
&--fixed {
}
} }
&__tab { &__tab {

View File

@ -34,7 +34,7 @@ export default {
try { try {
handler.remove(); handler.remove();
} catch (error) { } catch (error) {
warn('CROP:REMOVE_EVENT_FAIL,', error); warn('CROP:REMOVE_EVENT_FAIL: ', error);
} }
} }
}, },

View File

@ -46,7 +46,9 @@ var VideoPlayer = /*#__PURE__*/forwardRef(function (props, ref) {
screenshotButtonRender = _props$screenshotButt2 === void 0 ? function () { screenshotButtonRender = _props$screenshotButt2 === void 0 ? function () {
return /*#__PURE__*/React.createElement("div", { return /*#__PURE__*/React.createElement("div", {
style: { style: {
color: '#fff' color: '#fff',
width: '80px',
top: 0
} }
}, "\u56DE\u8C03DOM"); }, "\u56DE\u8C03DOM");
} : _props$screenshotButt2, } : _props$screenshotButt2,

View File

@ -1,5 +1,5 @@
export var CROP_TYPE = { export var CROP_TYPE = {
CUSTOM: 'CSUTOM', CUSTOM: 'CUSTOM',
AUTO: 'AUTO' AUTO: 'AUTO'
}; };
export var defaultAlignOption = { export var defaultAlignOption = {

View File

@ -1,6 +1,6 @@
{ {
"name": "@zhst/meta", "name": "@zhst/meta",
"version": "0.8.0", "version": "0.9.1",
"description": "原子组件", "description": "原子组件",
"keywords": [ "keywords": [
"meta", "meta",

View File

@ -1,23 +1,18 @@
import React, { useEffect, ReactElement, useState, useCallback, useRef, useImperativeHandle } from 'react'; import React, { useEffect, ReactElement, useState, useRef, useImperativeHandle } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useLatest } from '@zhst/hooks';
import { import {
get, get,
pick, pick,
isNull, isNull,
generateImg, generateImg,
dataURLToBlob, dataURLToBlob,
getOdRect,
getExtendRect,
getTransformRect,
getRotateImg,
getTransforms, getTransforms,
addEventListenerWrapper, addEventListenerWrapper,
getFileByRect getFileByRect
} from '@zhst/func'; } from '@zhst/func';
import Align from 'rc-align'; import Align from 'rc-align';
import { Button, Empty } from '..'; import { Button, Empty } from '..';
import { type Rect, type IScreenshotButtonProp, type AlignType, type IOdRectOrigin } from '@zhst/types' import { type Rect, type IScreenshotButtonProp, type AlignType } from '@zhst/types'
import Icon from '../iconfont'; import Icon from '../iconfont';
import { import {
Cropper, Cropper,
@ -30,10 +25,15 @@ import {
import BtnGroup from './components/BtnGroup'; import BtnGroup from './components/BtnGroup';
import './index.less' import './index.less'
import { defaultAlignOption, CROP_TYPE } from '../utils/constants' import { defaultAlignOption, CROP_TYPE } from '../utils/constants'
import {
getOdRect,
IOdObject,
getExtendRect,
getTransformRect,
getRotateImg, } from './bigImagePreviewHelper'
const componentName = `zhst-image__img-view`; const componentName = `zhst-image__img-view`;
export interface ViewOption { export interface ViewOption {
/* 图片url */ /* 图片url */
image?: string | HTMLImageElement; image?: string | HTMLImageElement;
@ -67,7 +67,7 @@ export interface ImgViewProps extends React.HTMLAttributes<HTMLElement> {
attachImg?: Array<{ label: string; url: string }>; // 缩略图列表 attachImg?: Array<{ label: string; url: string }>; // 缩略图列表
odRect?: Rect odRect?: Rect
score?: number score?: number
objects?: IOdRectOrigin[] // 图片人物框选 objects?: IOdObject
} }
showAttachImgLabel?: boolean; // 是否显示缩略图 showAttachImgLabel?: boolean; // 是否显示缩略图
showOpt?: boolean; // 是否显示操作面板 showOpt?: boolean; // 是否显示操作面板
@ -77,8 +77,28 @@ export interface ImgViewProps extends React.HTMLAttributes<HTMLElement> {
screenshotButtonAlign?: AlignType; screenshotButtonAlign?: AlignType;
screenshotButtonRender?: (screenshotButtonProp: IScreenshotButtonProp) => ReactElement; screenshotButtonRender?: (screenshotButtonProp: IScreenshotButtonProp) => ReactElement;
hideLeftTopBtn?: boolean; hideLeftTopBtn?: boolean;
onDraw?: (obj: {
rectList: any;
extendRectList: {
x: number;
y: number;
w: number;
h: number;
}[];
selectIndex: number;
imgKey: string;
}) => void
showScore?: boolean // 是否显示相似度 showScore?: boolean // 是否显示相似度
viewOption?: ViewOption; viewOption?: ViewOption;
onRectSelect?: (data: {
rectList: any[];
extendRectList: any[];
selectIndex: number;
imgKey: string;
}) => void
type?: 'CUSTOM' | 'AUTO'
hideTypeBtns?: boolean
customEmpty?: any
} }
export interface ImgViewRef { export interface ImgViewRef {
/* 图片实例 */ /* 图片实例 */
@ -127,14 +147,18 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
const { const {
width, width,
height, height,
showScore = true, showScore = false,
data, data,
showOpt = false, showOpt = false,
showAttachImgLabel = true, showAttachImgLabel = true,
screenshotButtonAlign = defaultAlignOption, screenshotButtonAlign = defaultAlignOption,
screenshotButtonRender = () => <div style={{ color: '#fff' }}>DOM</div>, screenshotButtonRender = () => <div style={{ color: '#fff', width: '80px', top: 0, fontSize: 12 }}>DOM</div>,
hideLeftTopBtn = true, hideLeftTopBtn = true,
viewOption = {} onDraw,
viewOption = {},
type,
hideTypeBtns,
customEmpty,
} = props; } = props;
const { const {
imageKey, imageKey,
@ -144,18 +168,13 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
objects = [], objects = [],
} = data } = data
const imgContainerRef = React.useRef(null); const imgContainerRef = React.useRef(null);
const [isReady, setIsReady] = useState(false);
const init = useCallback(($container: null) => {
imgContainerRef.current = $container;
setIsReady(true);
}, []);
// ============================= viewer ========================= // ============================= viewer =========================
const imgInsRef = useRef<any>(null); const imgInsRef = useRef<any>(null);
const [isImgReady, setIsImgReady] = useState(false); const [isImgReady, setIsImgReady] = useState(false);
useEffect(() => { useEffect(() => {
if (!isReady || !imgContainerRef?.current) return; if (!imgContainerRef?.current) return;
const handleReady = addEventListenerWrapper(imgContainerRef.current, EVENT_VIEWER_READY, () => { const handleReady = addEventListenerWrapper(imgContainerRef.current, EVENT_VIEWER_READY, () => {
setIsImgReady(true); setIsImgReady(true);
}); });
@ -176,7 +195,7 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
imgInsRef?.current?.destroy?.(); imgInsRef?.current?.destroy?.();
imgInsRef.current = null; imgInsRef.current = null;
}; };
}, [isReady, imageKey]); }, [imageKey]);
// ============================= viewer操作按钮 ========================= // ============================= viewer操作按钮 =========================
const handleOptClick = (v: string) => { const handleOptClick = (v: string) => {
@ -197,7 +216,7 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
// 手动截图相关参数 // 手动截图相关参数
const cropInsRef: any = useRef(null); const cropInsRef: any = useRef(null);
const [showCrop, setShowCrop] = useState(showOpt); const [showCrop, setShowCrop] = useState(showOpt);
const [cropType, setCropType] = useState(CROP_TYPE['AUTO']); const [cropType, setCropType] = useState(type || CROP_TYPE['AUTO']);
// 自动截图相关参数 // 自动截图相关参数
const [odList, setOdList] = useState<any>([]); const [odList, setOdList] = useState<any>([]);
@ -227,7 +246,8 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
}; };
}, [isImgReady, showCrop, cropType, imageKey]); }, [isImgReady, showCrop, cropType, imageKey]);
const initData = (_objects: IOdRectOrigin[]) => { // 初始化页面的绘制矩形
const initData = (_objects: IOdObject | never[]) => {
const imgIns = imgInsRef.current; const imgIns = imgInsRef.current;
const _odRect = odRect const _odRect = odRect
//清理crop //清理crop
@ -251,7 +271,8 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
const shapeList = odList.map((rect: { [x: string]: any; algorithmVersion: any; }) => ({ const shapeList = odList.map((rect: { [x: string]: any; algorithmVersion: any; }) => ({
...rect, ...rect,
selectAble: true, selectAble: true,
id: rect['id'], id:
['id'],
algorithmVersion: rect.algorithmVersion, algorithmVersion: rect.algorithmVersion,
})); }));
imgIns.replaceShape(shapeList); imgIns.replaceShape(shapeList);
@ -263,7 +284,8 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
}); });
setExtendOdList(extendRect); setExtendOdList(extendRect);
imgIns.replaceShape(shapeList); imgIns.replaceShape(shapeList);
handleShapeSelectRef.current = addEventListenerWrapper(imgContainerRef.current, 'shape-select', (e: { detail: any; }) => { // 框选监听事件
handleShapeSelectRef.current = addEventListenerWrapper(imgContainerRef.current, 'shape-select', async (e: { detail: any; }) => {
const id = e.detail; const id = e.detail;
setSelectRectId(id); setSelectRectId(id);
const selectShape = shapeList.find((v: { [x: string]: any; }) => v['id'] === id); const selectShape = shapeList.find((v: { [x: string]: any; }) => v['id'] === id);
@ -278,6 +300,7 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
h: Math.abs(axisRect.y2 - axisRect.y), h: Math.abs(axisRect.y2 - axisRect.y),
}; };
setCropRect(rect); setCropRect(rect);
onDraw?.({ rectList: [rect], extendRectList: [rect], imgKey: imageKey, selectIndex: id })
} else { } else {
// @ts-ignore // @ts-ignore
setCropRect(null); setCropRect(null);
@ -298,15 +321,18 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
setSelectAlgorithmVersion(null); setSelectAlgorithmVersion(null);
setCropRect(null); setCropRect(null);
}); });
handlerCropEndRef.current = addEventListenerWrapper(imgContainerRef.current, EVENT_CROP_END, (event: { detail: any; }) => { handlerCropEndRef.current = addEventListenerWrapper(imgContainerRef.current, EVENT_CROP_END, async (event: { detail: any; }) => {
const data = event.detail; const data = event.detail;
setSelectAlgorithmVersion(null); setSelectAlgorithmVersion(null);
setCropRect({ const _rect = {
x: data.left, x: data.left,
y: data.top, y: data.top,
w: data.width, w: data.width,
h: data.height, h: data.height,
}); }
setCropRect(_rect);
const _cropData = await getCropInfo({ type: cropType, rect: _rect })
onDraw?.(_cropData)
alignRef?.current?.forceAlign?.(); alignRef?.current?.forceAlign?.();
}); });
cropInsRef.current = new Cropper(imgContainerRef.current, { cropInsRef.current = new Cropper(imgContainerRef.current, {
@ -317,11 +343,10 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
} }
// 获取框选的截图框信息 // 获取框选的截图框信息
const latestCropType = useLatest(cropType); const getCropInfo = async (opt: { type: string; rect: Rect }) => {
const latestCropRect = useLatest(cropRect); const { type, rect } = opt
const getCropInfo = async (cb: (data: any) => void) => { const cropType = type;
const cropType = latestCropType.current; const cropRect = rect;
const cropRect = latestCropRect.current;
const imgIns = imgInsRef.current; const imgIns = imgInsRef.current;
const transform = imgIns.targetTransform; const transform = imgIns.targetTransform;
let newImgKey = imageKey; let newImgKey = imageKey;
@ -398,8 +423,15 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
rectList[index] = newRect; rectList[index] = newRect;
}) })
); );
cb?.({ rectList, extendRectList, selectIndex, imgKey: newImgKey })
return { rectList, extendRectList, selectIndex, imgKey: newImgKey }; let data = {
rectList,
extendRectList,
selectIndex,
imgKey: newImgKey
}
return data;
}; };
// 操作界面判断 // 操作界面判断
@ -421,22 +453,12 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
const [selectAttachImgIndex, setSelectAttachImgIndex] = useState(0); const [selectAttachImgIndex, setSelectAttachImgIndex] = useState(0);
const [isZoomin, setIsZoomin] = useState(false); const [isZoomin, setIsZoomin] = useState(false);
/**
*
* @param diff
*/
const handleChangeIndex = (cb?: () => void) => {
if (!imageKey) return
cb?.()
}
// ============================== Ref =============================== // ============================== Ref ===============================
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
imgInsRef, imgInsRef,
setShowCrop, setShowCrop,
initData, initData,
getCropInfo, getCropInfo,
handleChangeIndex
})); }));
return ( return (
@ -449,7 +471,8 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
`${componentName}-main`, `${componentName}-main`,
cropType === CROP_TYPE['AUTO'] && `${componentName}-main--cursor` cropType === CROP_TYPE['AUTO'] && `${componentName}-main--cursor`
)} )}
ref={init} ref={imgContainerRef}
// style={{ width: width, height: height }}
/> />
{/* 图片操作 */} {/* 图片操作 */}
{!hideLeftTopBtn && ( {!hideLeftTopBtn && (
@ -460,7 +483,7 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
placement="left" placement="left"
/> />
)} )}
{showCrop && ( {!hideTypeBtns && showCrop && (
<BtnGroup <BtnGroup
circle circle
className={classNames(`${componentName}-crop-opt`)} className={classNames(`${componentName}-crop-opt`)}
@ -562,7 +585,7 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
/> />
</div> </div>
)} )}
{showScore && <div {(showScore || score) && <div
style={{ bottom: 20 }} style={{ bottom: 20 }}
className={classNames(`${componentName}__face-score`)} className={classNames(`${componentName}__face-score`)}
> >
@ -570,7 +593,9 @@ export const BigImagePreview = React.forwardRef<ImgViewRef, ImgViewProps>((props
</div>} </div>}
</> </>
: :
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无数据" /> <div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{customEmpty || <Empty style={{ margin: 0 }} image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无数据" />}
</div>
} }
</div> </div>
); );

View File

@ -0,0 +1,300 @@
import { cloneDeep, dataURLToBlob, get, isNull } from "@zhst/func";
import { ObjectType, Rect } from "@zhst/types";
const proto = {
Common: {
AlgorithmVersion: {
VERSION_REID_HEAD_ATTR: '形体',
VERSION_FACE: '人脸',
VERSION_NON_MOTOR_VEHICLE: '非机动车',
},
},
};
export const ALGORITHM_VERSION = {
['7']: '形体',
['4']: '人脸',
['6']: '非机动车',
};
export const algorithmVersions = [...Object.keys(ALGORITHM_VERSION)]
export interface IOdObject {
objects: {
bboxRatio: Rect
timestamp?: string
qualityScore?: string
extendBoxRatio?: any
objectType?: ObjectType
objectIndex?: {
objectId: string
}
sourceObjectId?: string
}[]
}
/**
*
* @param originData
* @returns
*/
export const getOdRect = (originData: IOdObject) => {
let data = get(originData, 'objects', [])
.filter((v: any) => !isNull(get(v, 'bboxRatio')) || get(v, 'objectIndex.objectId') !== '0' )
.map((v: any, index: any) => {
const rect = get(v, 'bboxRatio');
const extendBox = get(v, 'extendBoxRatio');
const frameTimestamp = get(v, 'timestamp'); //时间戳创建档案的时候需要
const qualityScore = get(v, 'qualityScore');
const algorithmVersion =
get(v, 'objectType') === 'OBJECT_TYPE_PEDESTRAIN'
? 'VERSION_REID_HEAD_ATTR'
: get(v, 'objectType') === 'OBJECT_TYPE_FACE'
? 'VERSION_FACE'
: 'VERSION_REID_HEAD_ATTR';
const objectRectIndex = algorithmVersion === 'VERSION_FACE' ? 0 : 1;
const objectType = get(v, 'objectType');
const objectId = get(v, 'objectIndex.objectId');
const sourceObjectId = get(v, 'sourceObjectId');
return {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
id: index,
qualityScore,
algorithmVersion,
objectRectIndex,
objectType,
objectId,
frameTimestamp,
sourceObjectId,
extendBox,
};
});
return data;
};
export const getNormalization = (srcRect: Rect, maxW: number, maxH: number) => {
const newRect = {
...srcRect,
};
newRect.x = srcRect.x / maxW;
newRect.y = srcRect.y / maxH;
newRect.w = srcRect.w / maxW;
newRect.h = srcRect.h / maxH;
if (newRect.x + newRect.w > 1) {
newRect.w = 1 - newRect.x;
}
if (newRect.y + newRect.h > 1) {
newRect.h = 1 - newRect.y;
}
return newRect;
};
//传入od框 穿出 od扩展框
export const getExtendRect = (normalizationRect: Rect, imgW: number, imgH: number, type: string) => {
const rect = {
x: normalizationRect.x * imgW,
y: normalizationRect.y * imgH,
w: normalizationRect.w * imgW,
h: normalizationRect.h * imgH,
};
let newRect;
if (type === proto.Common.AlgorithmVersion.VERSION_NON_MOTOR_VEHICLE) {
newRect = getBikeExtendRect(rect, imgW);
} else {
newRect = getOtherExtendRect(rect, imgW, imgH, type);
}
newRect = getNormalization(newRect, imgW, imgH);
return newRect;
};
export const getBikeExtendRect = (rect: Rect, maxW: number) => {
const newRect = { ...rect };
//向上扩大一倍
const oldY = cloneDeep(rect.y);
newRect.y = newRect.y - newRect.h < 0 ? 0 : newRect.y - newRect.h;
newRect.h += oldY - newRect.y;
let newX = Math.round(newRect.x - newRect.w * 0.15);
if (newX < 0) {
newX = 0;
}
let newW = newRect.x - newX + newRect.w + Math.round(newRect.w * 0.15);
if (newX + newW > maxW) {
newW = maxW - newX;
}
newRect.x = newX;
newRect.w = newW;
return newRect;
};
export const getOtherExtendRect = (srcRect: Rect, maxW: number, maxH: number, type: string) => {
const wExtendRadio = 0.25;
const upExtendRadio = 0.25;
const downExtendRadio = 0.25;
const fixPersonExtend = true;
let nx = 0;
let nw = 0;
nx = srcRect.x - Math.round(srcRect.w * wExtendRadio);
if (nx < 0) {
nx = 0;
}
nw = srcRect.x - nx + srcRect.w + Math.round(srcRect.w * wExtendRadio);
if (nx + nw > maxW) {
nw = maxW - nx;
}
let ny = 0;
let nh = 0;
ny = srcRect.y - Math.round(upExtendRadio * srcRect.h);
if (ny < 0) {
ny = 0;
}
nh = srcRect.y - ny + srcRect.h + Math.round(srcRect.h * downExtendRadio);
if (ny + nh > maxH) {
nh = maxH - ny;
}
let newRect = {
x: nx,
y: ny,
w: nw,
h: nh,
};
if (
(type === proto.Common.AlgorithmVersion.VERSION_REID_HEAD_ATTR ||
type === proto.Common.AlgorithmVersion.VERSION_FACE) &&
fixPersonExtend
) {
const fixW = Math.round(nh * 0.75);
if (nw < fixW) {
// 应该扩展宽度
let newX = nx + Math.round(nw / 2.0 - 0.5 * fixW);
if (newX < 0) {
newX = 0;
}
let newW = fixW;
if (newW + newX > maxW) {
newW = maxW - newX;
}
newRect = {
x: newX,
y: ny,
w: newW,
h: nh,
};
} else if (nw > fixW) {
// 应该扩展高度
const fixH = Math.round(nw * 1.333);
let newY = ny + Math.round(nh / 2.0 - 0.5 * fixH);
if (newY < 0) {
newY = 0;
}
let newH = fixH;
if (newY + newH > maxH) {
newH = maxH - newY;
}
newRect = {
x: nx,
y: newY,
w: nw,
h: newH,
};
}
}
return newRect;
};
export const getTransformRect = (image: { height: number; width: number; }, transform: { translateX: any; translateY: any; scale: any; rotate: any; }, rect: Rect) => {
const canvasRect = {
x: rect.x,
y: rect.y,
x2: rect.x + rect.w,
y2: rect.h + rect.y,
};
//1.转成缩放前的坐标
const { translateX, translateY, scale, rotate } = transform;
const originAxisRect = {
x: (canvasRect.x - translateX) / scale,
y: (canvasRect.y - translateY) / scale,
x2: (canvasRect.x2 - translateX) / scale,
y2: (canvasRect.y2 - translateY) / scale,
};
//2.转成图片坐标
//不考虑旋转 图片原点就是坐标原点
let imgAxisRect = originAxisRect;
//但是旋转90度后图片不在坐标原点 加上这部分diff
if (rotate % 180 !== 0) {
//90度调整偏移量
const offsetX = -(image.height - image.width) / 2;
const offsetY = -(image.width - image.height) / 2;
imgAxisRect = {
x: originAxisRect.x - offsetX,
y: originAxisRect.y - offsetY,
x2: originAxisRect.x2 - offsetX,
y2: originAxisRect.y2 - offsetY,
};
}
//3.限制框不要超出图片
let imgW = image.width;
let imgH = image.height;
if (rotate % 180 !== 0) {
[imgW, imgH] = [imgH, imgW];
}
imgAxisRect.x = Math.min(imgW, Math.max(imgAxisRect.x, 0));
imgAxisRect.y = Math.min(imgH, Math.max(imgAxisRect.y, 0));
imgAxisRect.x2 = Math.min(imgW, Math.max(imgAxisRect.x2, 0));
imgAxisRect.y2 = Math.min(imgH, Math.max(imgAxisRect.y2, 0));
//获取归一化坐标
const endRect = {
x: imgAxisRect.x2 > imgAxisRect.x ? imgAxisRect.x : imgAxisRect.x2,
y: imgAxisRect.y2 > imgAxisRect.y ? imgAxisRect.y : imgAxisRect.y2,
w: Math.abs(imgAxisRect.x2 - imgAxisRect.x),
h: Math.abs(imgAxisRect.y2 - imgAxisRect.y),
};
return getNormalization(endRect, imgW, imgH);
};
//旋转图片后转成file 对象
export const getRotateImg = (image: HTMLImageElement, rotate: number) => {
let imgW = image.width;
let imgH = image.height;
if (rotate % 180 !== 0) {
[imgW, imgH] = [imgH, imgW];
}
const commonCanvas = document.createElement('canvas');
commonCanvas.width = imgW;
commonCanvas.height = imgH;
commonCanvas.style.display = 'none';
document.body.appendChild(commonCanvas);
const commonCtx = commonCanvas.getContext('2d');
//移动到图片中心 旋转
commonCtx?.save();
if (rotate % 180 !== 0) {
//90度调整偏移量
commonCtx?.translate((image.height - image.width) / 2, (image.width - image.height) / 2);
}
commonCtx?.translate(image.width / 2, image.height / 2);
commonCtx?.rotate((rotate / 180) * Math.PI);
commonCtx?.translate(-image.width / 2, -image.height / 2);
commonCtx?.drawImage(image, 0, 0);
commonCtx?.restore();
const dataUrl = commonCanvas.toDataURL('image/jpeg');
const blobData = dataURLToBlob(dataUrl);
const file = new window.File([blobData], `${new Date().getTime()}`, {
type: 'image/jpeg',
});
commonCanvas.parentNode?.removeChild(commonCanvas);
return file;
};

View File

@ -1,6 +1,6 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Button, Space } from '@zhst/meta' import { Button, ImgViewRef, Space } from '@zhst/meta'
import { BigImagePreview } from '@zhst/meta' import { BigImagePreview } from '@zhst/meta'
@ -25,42 +25,33 @@ const props = {
time: '2022-01-01', // 摄像头拍摄时间 time: '2022-01-01', // 摄像头拍摄时间
objects: [ objects: [
{ {
"infoOnSource": { "bboxRatio": {
"bboxInFrame": { "x": 0.5519352,
"bboxRatio": { "y": 0.2965385,
"x": 0.5519352, "w": 0.05185461,
"y": 0.2965385, "h": 0.24698898
"w": 0.05185461, },
"h": 0.24698898
},
},
},
}, },
{ {
"infoOnSource": { "bboxRatio": {
"bboxInFrame": { "x": 0.58543766,
"bboxRatio": { "y": 0.3203356,
"x": 0.58543766, "w": 0.052037954,
"y": 0.3203356, "h": 0.2664015
"w": 0.052037954, },
"h": 0.2664015
},
},
},
}, },
] ]
} },
} }
export default () => { export default () => {
const imgRef = useRef(null) const imgRef = useRef<ImgViewRef>(null)
return ( return (
<Space size={[8, 16]} direction="vertical"> <Space size={[8, 16]} direction="vertical">
<BigImagePreview {...props} ref={imgRef} /> <BigImagePreview {...props} ref={imgRef} />
<Space> <Space>
<Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}></Button> <Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}></Button>
<Button onClick={() => imgRef.current?.handleChangeIndex(1)}></Button>
<Button onClick={() => imgRef.current?.setShowCrop(false)}></Button> <Button onClick={() => imgRef.current?.setShowCrop(false)}></Button>
</Space> </Space>
</Space> </Space>

View File

@ -0,0 +1,28 @@
import React, { useRef } from 'react';
import { Button, ImgViewRef, Space } from '@zhst/meta'
import { BigImagePreview } from '@zhst/meta'
const props = {
height: '320px',
width: '640px',
data: {
imageKey: '',
},
}
// FIXME需要优化 props
export default () => {
const imgRef = useRef<ImgViewRef>(null)
return (
<Space size={[8, 16]} direction="vertical" style={{ border: '1px solid #ccc' }}>
<BigImagePreview {...props} type="CUSTOM" hideTypeBtns ref={imgRef} />
<Space>
<Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}></Button>
<Button onClick={() => imgRef.current?.setShowCrop(false)}></Button>
</Space>
</Space>
)
}

View File

@ -0,0 +1,35 @@
import React, { useRef } from 'react';
import { Button, Space } from '@zhst/meta'
import { BigImagePreview } from '@zhst/meta'
import type { ImgViewRef } from '@zhst/meta'
const props = {
height: '320px',
width: '640px',
data: {
imageKey: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
odRect:{
"x":0.553125,"y":0.29722223,"w":0.048958335,"h":0.2462963
},
cameraPosition: 'string', // 摄像头位置
time: '2022-01-01', // 摄像头拍摄时间
},
onDraw: (data: any) => console.log('data', data),
hideTypeBtns: true,
}
export default () => {
const imgRef = useRef<ImgViewRef>(null)
return (
<Space size={[8, 16]} direction="vertical">
<BigImagePreview {...props} type="CUSTOM" ref={imgRef} />
<Space>
<Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}></Button>
<Button onClick={() => imgRef.current?.setShowCrop(false)}></Button>
</Space>
</Space>
)
}

View File

@ -2,6 +2,7 @@
position: relative; position: relative;
width: calc(100%); width: calc(100%);
height: 100%; height: 100%;
font-size: 0;
&__face-score { &__face-score {
position: absolute; position: absolute;
@ -62,14 +63,9 @@
height: 202px; height: 202px;
transition: all 200ms; transition: all 200ms;
&--fixed {
}
&--zoomin { &--zoomin {
height: 100%; height: 100%;
&--fixed {
}
} }
&__tab { &__tab {

View File

@ -8,15 +8,60 @@ title: BigImagePreview 大图预览组件
# BigImagePreview 大图预览组件 # BigImagePreview 大图预览组件
<code src="./demo/base.tsx">基本</code> <code src="./demo/base.tsx">基本</code>
<code src="./demo/noAttach.tsx">不展示小图</code>
<code src="./demo/empty.tsx">空数据</code>
## API ## API
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| data | 当前组件数据对象 | IData必填 | - | |
| imageKey | 当前大图链接 | string必填 | - | | | imageKey | 当前大图链接 | string必填 | - | |
| odRect | 圈选矩形参数 | { x: number; y: number; w: number; h: number } | - | |
| height | 高度 | number选填 | - | | | height | 高度 | number选填 | - | |
| width | 宽度 | number选填 | - | | | width | 宽度 | number选填 | - | |
### IData
可以通过组件的 props 获取ts 定义:
```js
import type { ImgViewProps } from '@zhst/meta'
const data: ImgViewProps['data'] = {
imageKey: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
odRect:{
"x":0.553125,"y":0.29722223,"w":0.048958335,"h":0.2462963
},
attachImg: [
{
"url": "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png","label": "形体"
},{
"url": "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
"label": "人脸"
}
],
score: 0.815207, // 人脸质量分
showScore: true, // 人脸质量分
cameraPosition: 'string', // 摄像头位置
time: '2022-01-01', // 摄像头拍摄时间
objects: [
{
"bboxRatio": {
"x": 0.5519352,
"y": 0.2965385,
"w": 0.05185461,
"h": 0.24698898
},
}
]
}
```
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| imageKey | 当前大图链接 | string | - | |
| odRect | 初始化OD | { x: number; y: number; w: number; h: number } | - | |
| attachImg | 缩略图对象 | { url: string; label: string; }[](选填) | - | |
| objects | 多OD状态列表 | IOdRectOrigin[](选填) | - | |
| objects | 多OD状态列表 | IOdRectOrigin[](选填) | - | |
| score | 相似度 | number选填 | - | | | score | 相似度 | number选填 | - | |
| attachImg | 缩略图 | { url: string; label: string; }[](选填) | - | |
| objects | 编辑状态参数 | IOdRectOrigin[](选填) | - | |

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