feat: 初始化项目(待优化ts)

This commit is contained in:
NICE CODE BY DEV 2024-01-12 18:22:58 +08:00
parent 8d8aa13483
commit c4390a34d7
101 changed files with 6155 additions and 2241 deletions

View File

@ -13,6 +13,7 @@ export default defineConfig({
'@zhst/func': path.join(__dirname, 'packages/func/src'),
'@zhst/biz': path.join(__dirname, 'packages/biz/src'),
'@zhst/meta': path.join(__dirname, 'packages/meta/src'),
'@zhst/request': path.join(__dirname, 'packages/request/src'),
},
resolve: {
docDirs: ['docs'],
@ -21,6 +22,9 @@ export default defineConfig({
{ type: 'utils', dir: 'packages/func/src' },
{ type: 'biz', dir: 'packages/biz/src' },
{ type: 'meta', dir: 'packages/meta/src' },
{ type: 'request', dir: 'packages/request/src' },
{ type: 'constants', dir: 'packages/constants/src' },
{ type: 'types', dir: 'packages/types/src' },
],
},
monorepoRedirect: {

View File

@ -20,6 +20,79 @@ features:
description: 原子组件库
---
## 目录结构
<Tree>
<ul>
<li>
docs
<small>全局文档</small>
<ul>
<li>
index.md
<small>这是首页文档</small>
</li>
</ul>
</li>
<li>
packages
<small>组件包目录</small>
<ul>
<li>
biz
<small>业务组件</small>
</li>
<li>
func
<small>函数库</small>
</li>
<li>
hooks
<small>hooks</small>
</li>
<li>
constants
<small>静态枚举值定义</small>
</li>
<li>
meta
<small>元组件</small>
</li>
<li>
request
<small>请求库</small>
</li>
<li>
types
<small>类型定义库</small>
</li>
<li>
material
<small>物料库</small>
</li>
</ul>
</li>
<li>
src
<small>这是 src 文件夹</small>
<ul>
<li>
index.md
<small>这是 index.md</small>
</li>
</ul>
</li>
<li>
.dumirc.ts
<small>文档配置</small>
</li>
<li>
package.json
<small>这是 package.json</small>
</li>
</ul>
</Tree>
## 本文档食用说明
目前在开发中的项目为:@zhst/bizs、@zhst/hooks、@zhst/meta、@zhst/func。

View File

@ -15,7 +15,7 @@
"lib/**/style/*",
"*.less"
],
"main": "lib/index.tsx",
"main": "lib/index.js",
"module": "es/index.js",
"typings": "es/index.d.ts",
"exports": {
@ -32,6 +32,9 @@
"access": "public",
"registry": "http://10.0.0.77:4874"
},
"devDependencies": {
"@zhst/types": "workspace:^"
},
"dependencies": {
"@zhst/func": "workspace:^",
"@zhst/hooks": "workspace:^",

View File

@ -67,256 +67,7 @@
margin-bottom: 24px;
}
}
.zhst-image__img-view {
position: relative;
width: calc(100% - 46px - 46px - 20px - 20px);
height: 100%;
margin: 0 66px;
&-opt {
position: absolute;
z-index: 99;
top: 0;
}
&-crop-opt {
position: absolute;
z-index: 99;
top: 0;
right: 0;
}
&-align {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
&-main {
// height: 532px;
width: 100%;
height: 100%;
&--cursor {
& canvas {
cursor: pointer !important;
}
}
}
&-screenshot {
position: absolute;
z-index: 10;
}
&-attach {
position: absolute;
z-index: 11;
bottom: 0;
// left: 78px;
left: 0;
min-width: 120px;
height: 202px;
transition: all 200ms;
&--fixed {
width: 152px !important;
background-color: #fff;
}
&--zoomin {
height: 532px;
&--fixed {
width: 398px !important;
}
}
&__tab {
position: absolute;
top: 0;
left: 0;
display: flex;
&-item {
display: flex;
width: 30px;
height: 24px;
align-items: center;
justify-content: center;
background: #000;
color: #fff;
cursor: pointer;
font-size: 12px;
opacity: 0.5;
transition: all 200ms;
&--select {
width: 48px;
height: 34px;
background: #09f;
opacity: 1;
}
}
}
&__scale {
position: absolute;
top: 4px;
right: 4px;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 60%);
border-radius: 100%;
cursor: pointer;
line-height: 30px;
text-align: center;
}
&__img {
height: 100%;
object-fit: contain;
&--fixed {
width: 100%;
object-fit: contain;
}
}
}
}
.zhst-image__video-view {
position: relative;
overflow: hidden;
width: 100%;
height: 532px;
background-color: #333;
// &-flv {
// width: 85%;
// }
&-screenshot {
position: absolute;
z-index: 10;
}
&-crop-container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&-align {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
&-opt {
position: absolute;
z-index: 99;
bottom: 0;
display: flex;
width: 100%;
height: 32px;
box-sizing: border-box;
align-items: center;
padding: 0 12px;
background-color: rgb(0 0 0 / 80%);
line-height: 32px;
&>div:first-child {
display: flex;
align-items: center;
margin-right: 12px;
}
&>div:last-child {
display: flex;
align-items: center;
margin-left: 12px;
}
&-range {
display: flex;
height: 32px;
flex: 1;
align-items: center;
line-height: 32px;
text-align: center;
&>div:first-child {
flex: 1;
}
&>div:last-child {
width: 100px;
margin-left: 8px;
color: #fff;
}
}
}
&__player-mask {
position: absolute;
z-index: 99;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgb(4 4 4 / 70%);
&--bg {
z-index: 999;
background-color: rgb(4 4 4 / 100%);
}
i {
cursor: pointer;
}
&-title {
margin-top: 12px;
color: #fff;
text-align: center;
& a {
color: #09f;
cursor: pointer;
text-decoration: underline;
}
}
}
&__icon-wraper {
display: flex;
width: 80px;
height: 80px;
align-items: center;
justify-content: center;
background-color: rgb(255 255 255 / 10%);
border-radius: 50%;
cursor: pointer;
line-height: 80px;
text-align: center;
// &:hover {
// background: #0099ff;
// }
}
}
.zhst-image__tool {
display: flex;
@ -553,57 +304,6 @@
}
}
.zhst-image__btn-group {
// display: flex;
width: 30px;
box-shadow: 0 2px 6px 0 rgb(0 0 0 / 40%);
&__item {
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
background: #000;
&>button {
padding: 0;
color: #fff;
&:hover {
color: #09f;
}
&:active {
color: #09f;
}
&:focus {
color: #fff;
}
&>span {
display: flex;
}
}
&--active {
&>button {
color: #09f;
}
}
}
&--circle {
background-color: none;
box-shadow: none;
}
&--circle &__item {
margin-bottom: 4px;
border-radius: 50%;
}
}
.zhst-image__compater-view {
display: flex;
@ -962,78 +662,6 @@
}
}
.zhst-image__score {
display: flex;
flex-direction: column;
align-items: center;
&-box {
position: relative;
display: flex;
box-sizing: border-box;
align-items: center;
justify-content: center;
margin-bottom: 12px;
border-radius: 50%;
&-bg {
display: flex;
overflow: hidden;
box-sizing: border-box;
align-items: flex-end;
justify-content: center;
border: 2px solid #fff;
background-color: #fff;
border-radius: 50%;
&-inner {
width: 100% !important;
height: 0;
}
}
}
&-score {
position: absolute;
top: 50%;
left: 50%;
color: rgb(0 0 0 / 88%);
font-size: 28px;
transform: translate(-50%, -50%);
}
&-text {
width: 54px;
height: 24px;
color: rgb(0 0 0 / 88%);
font-size: 18px;
line-height: 24px;
}
}
.zhst-image__CornerScore {
position: absolute;
right: 0;
bottom: 0;
width: 56px;
height: 22px;
line-height: 22px;
text-align: right;
vertical-align: bottom;
padding-right: 1px;
background-size: 100%;
background-image: url('./images/percent_background.png');
z-index: 99;
&>span {
padding-right: 6px;
line-height: 22px;
font-size: 12px;
vertical-align: middle;
color: rgba(255, 255, 255, 1);
}
}
.zhst-image__attributePanel {
margin: 0 66px;
background: #f6f6f6;

View File

@ -2,7 +2,7 @@
*
*/
import React from 'react';
import { AlgorithmVersionStr, HumanProperty, ObjectType, Rect, ViewOption, AlignType, IScreenshotButtonProp, ODRECT } from './interface'
import { AlgorithmVersionStr, HumanProperty, ObjectType, Rect, ViewOption, AlignType, IScreenshotButtonProp, ODRECT } from '@zhst/types'
export type TAB_TYPE = 'COMPATER' | 'NORMAL' | 'TRACK';
export type MODEL_TYPE = 'VIDEO' | 'IMAGE';

View File

@ -1,217 +0,0 @@
export type CamerasStatusList = [string[], string[], string[]];
export type Rect = { x: number; y: number; w: number; h: number };
export type StatusList = {
taskOverview: any;
taskIndex: {
deviceId: string;
solutionId: string;
};
}[];
export enum OperationType {
OPERATION_TYPE_UNKNOW, // 未知状态,传该值会报错
OPERATION_TYPE_START,
OPERATION_TYPE_STOP
}
export enum AlgorithmVersion {
VERSION_MGN_BNN, // MGN+BNN算法
VERSION_BNN_PRO, // BNNPRO算法
VERSION_BNN_PRO_ATTR, // BNNPROATTR算法
VERSION_BNN_PRO_ATTR_SCORE, // BNNPROATTR算法
VERSION_FACE, // 人脸算法
VERSION_HEAD, // 头肩算法
VERSION_NON_MOTOR_VEHICLE, // 非机动车的算法
VERSION_REID_HEAD_ATTR, // 形体头肩属性三种特征融合的算法
VERSION_MOTOR_VEHICLE, // 机动车的算法
}
export enum AlgorithmVersionStr {
VERSION_MGN_BNN = 'VERSION_MGN_BNN', // MGN+BNN算法
VERSION_BNN_PRO = 'VERSION_BNN_PRO', // BNNPRO算法
VERSION_BNN_PRO_ATTR = 'VERSION_BNN_PRO_ATTR', // BNNPROATTR算法
VERSION_BNN_PRO_ATTR_SCORE = 'VERSION_BNN_PRO_ATTR_SCORE', // BNNPROATTR算法
VERSION_FACE = 'VERSION_FACE', // 人脸算法
VERSION_HEAD = 'VERSION_HEAD', // 头肩算法
VERSION_NON_MOTOR_VEHICLE = 'VERSION_NON_MOTOR_VEHICLE', // 非机动车的算法
VERSION_REID_HEAD_ATTR = 'VERSION_REID_HEAD_ATTR', // 形体头肩属性三种特征融合的算法
VERSION_MOTOR_VEHICLE = 'VERSION_MOTOR_VEHICLE', // 机动车的算法
}
export enum ObjectType {
OBJECT_TYPE_NULL,
OBJECT_TYPE_PEDESTRAIN,
OBJECT_TYPE_BICYCLE,
OBJECT_TYPE_CAR,
OBJECT_TYPE_MOTORBIKE,
OBJECT_TYPE_AEROPLANE,
OBJECT_TYPE_BUS,
OBJECT_TYPE_TRAIN,
OBJECT_TYPE_TRUCK,
OBJECT_TYPE_MOTOR_RIDER,
OBJECT_TYPE_BIKE_RIDER,
OBJECT_TYPE_MAX,
OBJECT_TYPE_FACE = 101,
}
// 性别
export enum Gender {
GENDER_NONE = 'GENDER_NONE',
GENDER_MAN = 'GENDER_MAN',
GENDER_WOMAN = 'GENDER_WOMAN',
}
// 年龄
export enum Age {
AGE_ALL = 'AGE_ALL',
AGE_ZERO = 'AGE_ZERO',
AGE_OVERENGHTEEN = 'AGE_OVERENGHTEEN',
AGE_OVERSIXTY = 'AGE_OVERSIXTY',
}
// 戴帽子状态
export enum Hat {
HAT_ALL = 'HAT_ALL',
HAT_NONE = 'HAT_NONE',
HAT_OWNED = 'HAT_OWNED',
}
// 颜色
export enum Color {
COLOR_ALL = 'COLOR_ALL',
COLOR_BLACK = 'COLOR_BLACK',
COLOR_WHITE = 'COLOR_WHITE',
COLOR_GRAY = 'COLOR_GRAY',
COLOR_BROWN = 'COLOR_BROWN',
COLOR_PINK = 'COLOR_PINK',
COLOR_REDANDORANGE = 'COLOR_REDANDORANGE',
COLOR_YELLOW = 'COLOR_YELLOW',
COLOR_GREEN = 'COLOR_GREEN',
COLOR_BLUE = 'COLOR_BLUE',
COLOR_PURPLE = 'COLOR_PURPLE',
}
// 背包
export enum Package {
PACKAGE_ALL = 'PACKAGE_ALL',
PACKAGE_HANDBAG = 'PACKAGE_HANDBAG',
PACKAGE_BACKPACK = 'PACKAGE_BACKPACK',
PACKAGE_SHOULDERBAG = 'PACKAGE_SHOULDERBAG',
PACKAGE_OTHER = 'PACKAGE_OTHER',
PACKAGE_NONE = 'PACKAGE_NONE',
}
// 行走模式
export enum WalkPattern {
WALKPATTERN_ALL = 'WALKPATTERN_ALL',
WALKPATTERN_WALK = 'WALKPATTERN_WALK',
WALKPATTERN_RIDING = 'WALKPATTERN_RIDING',
}
// 人类属性
export interface HumanProperty {
age: Age;
downColor: Color;
gender: Gender;
hat: Hat;
package: Package;
upColor: Color;
walkPattern: WalkPattern;
}
// 人类属性枚举
export interface EnumHumanProperty {
Gender: typeof Gender;
Age: typeof Age;
Hat: typeof Hat;
Color: typeof Color;
Package: typeof Package;
WalkPattern: typeof WalkPattern;
}
export interface IScreenshotButtonProp {
model: 'VIDEO' | 'IMAGE';
getCropInfo: () => Promise<RESP>;
setShowCrop: React.Dispatch<React.SetStateAction<boolean>>;
cropType: typeof cropType[number];
selectAlgorithmVersion: number;
}
export interface AlignType {
/**
* move point of source node to align with point of target node.
* Such as ['tr','cc'], align top right point of source node with center point of target node.
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
points?: AlignPoint[];
/**
* offset source node by offset[0] in x and offset[1] in y.
* If offset contains percentage string value, it is relative to sourceNode region.
*/
offset?: number[];
/**
* offset target node by offset[0] in x and offset[1] in y.
* If targetOffset contains percentage string value, it is relative to targetNode region.
*/
targetOffset?: number[];
/**
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
*/
overflow?: {
adjustX?: boolean | number;
adjustY?: boolean | number;
};
/**
* Whether use css right instead of left to position
*/
useCssRight?: boolean;
/**
* Whether use css bottom instead of top to position
*/
useCssBottom?: boolean;
/**
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
* Defaults to false.
*/
useCssTransform?: boolean;
ignoreShake?: boolean;
}
export type ODRECT = {
topleft: {
x: number;
y: number;
};
width: number;
height: number;
};
export interface ViewOption {
/* 图片url */
image?: string | HTMLImageElement;
/* 缩放灵敏度(0,1],default: 0.1 */
wheelZoomRatio?: number;
/*
*
* @default: true
*/
scaleAble?: boolean;
/*
*
* @default: true
*/
dragAble?: boolean;
/*
* fit scale
* @default: false
*/
fitScaleAsMinScale?: boolean;
}

View File

View File

View File

@ -1,6 +1,6 @@
---
nav:
title: 业务组件
title: 其它
order: 1
title: 快速上手
---

View File

View File

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

View File

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

View File

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

View File

@ -343,4 +343,18 @@ export var generateImg = function generateImg(_imgKey) {
imgUrl = '';
}
return imgUrl;
};
/**
* 获取指定字符串后面的部分
* @param imageKey v1_开头的字符串
* @returns
*/
export var getImageKey = function getImageKey(imageKey, preFix) {
var splitIndex = preFix || 'v1_';
if (imageKey.startsWith(splitIndex)) {
return window.atob(imageKey.split(splitIndex)[1]).replace('_', '/');
} else {
return imageKey;
}
};

View File

@ -1,9 +0,0 @@
export * from 'lodash-es';
export * from './file';
export * from './map';
export * from './performance';
export * from './string';
export * from './number';
export * from './time';
export * from './utils';
export * from './camera';

View File

@ -6,4 +6,5 @@ export * from "./string";
export * from "./number";
export * from "./time";
export * from "./utils";
export * from "./camera";
export * from "./camera";
export * from "./math";

View File

@ -1,10 +0,0 @@
/**
*
* @param lngLat
* @returns @object { long, lat }
*/
export declare const fixedLngLat: (lngLat?: string) => string;
export declare const transformLngLat: (lngLat?: string) => {
longitude: number;
latitude: number;
} | null;

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
/**
*
* @param originNumber
* @param accuracy
* @param isCeil
* @returns number
*/
export declare const setNumberAccuracy: (originNumber: number, accuracy?: number, isCeil?: boolean) => number;

View File

@ -1 +0,0 @@
export declare const speedConvert: (bps: number, contertUnit?: number) => string;

View File

@ -1,7 +0,0 @@
export declare const getStrLength: (str: string) => number;
/**
* js截取字符串
* @param str
* @param len: 需要截取的长度
*/
export declare const cutStr: (str: string, len: number) => string | String | undefined;

View File

@ -1,5 +0,0 @@
export declare const formateDuration: (diff: number) => string;
export declare function generateTime(): {
startDateTime: number;
endDateTime: number;
};

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
export { default as isChrome } from './isChrome';
export declare const matchS3Prefix: (str: string) => boolean;
/**
*
* @param fileName
* @returns
*/
export declare const getFileSuffix: (fileName: string) => string;
/**
*
* @param type
* @returns
*/
export declare const getImageSuffixByFileType: (type: string) => string;
export declare function getChromeVersion(): number | false;
export declare const nextTick: (func: (value: void) => void | PromiseLike<void>) => void;
export declare const loop: (items: string | any[], callback: (arg0: any) => any) => void;
export declare const addEventListener: (target: any, eventType: string, cb: any, option?: any) => {
remove: () => void;
};

View File

@ -1,3 +1,4 @@
import { isNumber } from 'lodash-es';
import ReactDOM from 'react-dom';
export { default as isChrome } from "./isChrome";
export var matchS3Prefix = function matchS3Prefix(str) {
@ -103,4 +104,41 @@ export var addEventListener = function addEventListener(target, eventType, cb, o
}
}
};
};
};
/**
* Get transforms base on the given object.
* @param {Object} obj - The target object.
* @returns {string} A string contains transform values.
*/
export function getTransforms(_ref) {
var rotate = _ref.rotate,
scaleX = _ref.scaleX,
scaleY = _ref.scaleY,
translateX = _ref.translateX,
translateY = _ref.translateY;
var values = [];
if (isNumber(translateX) && translateX !== 0) {
values.push("translateX(".concat(translateX, "px)"));
}
if (isNumber(translateY) && translateY !== 0) {
values.push("translateY(".concat(translateY, "px)"));
}
// Rotate should come first before scale to match orientation transform
if (isNumber(rotate) && rotate !== 0) {
values.push("rotate(".concat(rotate, "deg)"));
}
if (isNumber(scaleX) && scaleX !== 1) {
values.push("scaleX(".concat(scaleX, ")"));
}
if (isNumber(scaleY) && scaleY !== 1) {
values.push("scaleY(".concat(scaleY, ")"));
}
var transform = values.length ? values.join(' ') : 'none';
return {
WebkitTransform: transform,
msTransform: transform,
transform: transform
};
}

View File

@ -1,3 +0,0 @@
export declare const isBrowser: boolean;
declare const isChrome: () => boolean;
export default isChrome;

View File

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

View File

@ -1,90 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/camera/constants.ts
var constants_exports = {};
__export(constants_exports, {
BOX_DIRECONNECT_PLATFORM_FILTER: () => BOX_DIRECONNECT_PLATFORM_FILTER,
BOX_LIST_KEY: () => BOX_LIST_KEY,
DIRE_CONNECT_KEY: () => DIRE_CONNECT_KEY,
DeviceType: () => DeviceType,
DevicemanagerCameraType: () => DevicemanagerCameraType,
LOCAL_KEY: () => LOCAL_KEY,
OPT: () => OPT,
VmsplatformOpt: () => VmsplatformOpt
});
module.exports = __toCommonJS(constants_exports);
var DeviceType = {
VMS: "vms",
DIR: "dir",
CAMERA: "camera"
};
var LOCAL_KEY = "local";
var DIRE_CONNECT_KEY = "direconnect";
var BOX_LIST_KEY = "boxlist";
var VmsplatformOpt = /* @__PURE__ */ ((VmsplatformOpt2) => {
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_ID"] = 0] = "VMSPLATFORMOPT_ID";
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_PLATFORMNAME"] = 1] = "VMSPLATFORMOPT_PLATFORMNAME";
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_PLUGINNAME"] = 2] = "VMSPLATFORMOPT_PLUGINNAME";
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_IP"] = 3] = "VMSPLATFORMOPT_IP";
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_PORT"] = 4] = "VMSPLATFORMOPT_PORT";
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_USERNAME"] = 5] = "VMSPLATFORMOPT_USERNAME";
VmsplatformOpt2[VmsplatformOpt2["VMSPLATFORMOPT_PASSWORD"] = 6] = "VMSPLATFORMOPT_PASSWORD";
return VmsplatformOpt2;
})(VmsplatformOpt || {});
var OPT = /* @__PURE__ */ ((OPT2) => {
OPT2[OPT2["OR"] = 0] = "OR";
OPT2[OPT2["AND"] = 1] = "AND";
OPT2[OPT2["ORNOT"] = 2] = "ORNOT";
OPT2[OPT2["ANDNOT"] = 3] = "ANDNOT";
return OPT2;
})(OPT || {});
var DevicemanagerCameraType = /* @__PURE__ */ ((DevicemanagerCameraType2) => {
DevicemanagerCameraType2[DevicemanagerCameraType2["DEVICEMANAGER_CAMERA_TYPE_DEFAULT"] = 0] = "DEVICEMANAGER_CAMERA_TYPE_DEFAULT";
DevicemanagerCameraType2[DevicemanagerCameraType2["DEVICEMANAGER_CAMERA_TYPE_NORMAL"] = 1] = "DEVICEMANAGER_CAMERA_TYPE_NORMAL";
DevicemanagerCameraType2[DevicemanagerCameraType2["DEVICEMANAGER_CAMERA_TYPE_1400"] = 97] = "DEVICEMANAGER_CAMERA_TYPE_1400";
DevicemanagerCameraType2[DevicemanagerCameraType2["DEVICEMANAGER_CAMERA_TYPE_DHGRABBER"] = 98] = "DEVICEMANAGER_CAMERA_TYPE_DHGRABBER";
DevicemanagerCameraType2[DevicemanagerCameraType2["DEVICEMANAGER_CAMERA_TYPE_HKGRABBER"] = 99] = "DEVICEMANAGER_CAMERA_TYPE_HKGRABBER";
DevicemanagerCameraType2[DevicemanagerCameraType2["DEVICEMANAGER_CAMERA_TYPE_LOCAL"] = 100] = "DEVICEMANAGER_CAMERA_TYPE_LOCAL";
return DevicemanagerCameraType2;
})(DevicemanagerCameraType || {});
var BOX_DIRECONNECT_PLATFORM_FILTER = {
filtervmsplatformList: [
{
opt: 0 /* OR */,
vmsplatformOpt: 1 /* VMSPLATFORMOPT_PLATFORMNAME */,
value: "direconnect"
},
{
opt: 0 /* OR */,
vmsplatformOpt: 1 /* VMSPLATFORMOPT_PLATFORMNAME */,
value: "boxlist"
}
]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BOX_DIRECONNECT_PLATFORM_FILTER,
BOX_LIST_KEY,
DIRE_CONNECT_KEY,
DeviceType,
DevicemanagerCameraType,
LOCAL_KEY,
OPT,
VmsplatformOpt
});

View File

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

View File

@ -1,143 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/camera/index.ts
var camera_exports = {};
__export(camera_exports, {
deviceIDToDeviceKey: () => deviceIDToDeviceKey,
deviceKeyToDeviceId: () => deviceKeyToDeviceId,
deviceToDeviceKey: () => deviceToDeviceKey,
findCamerasByInDeviceTree: () => findCamerasByInDeviceTree,
getDeviceType: () => getDeviceType,
getVmsIdByDeviceId: () => getVmsIdByDeviceId,
isFaceCamera: () => isFaceCamera
});
module.exports = __toCommonJS(camera_exports);
var import_lodash_es = require("lodash-es");
var import_utils = require("../utils");
var import_constants = require("./constants");
var isFaceCamera = (type) => {
return [
import_constants.DevicemanagerCameraType.DEVICEMANAGER_CAMERA_TYPE_1400,
import_constants.DevicemanagerCameraType.DEVICEMANAGER_CAMERA_TYPE_HKGRABBER,
import_constants.DevicemanagerCameraType.DEVICEMANAGER_CAMERA_TYPE_DHGRABBER
].includes(type);
};
function getDeviceType(value) {
let type;
let isDeviceKey = (0, import_lodash_es.isString)(value);
if (isDeviceKey) {
type = value.split("_")[0];
} else {
if ((0, import_lodash_es.has)(value, "longitude")) {
type = import_constants.DeviceType["CAMERA"];
}
if ((0, import_lodash_es.has)(value, "ip")) {
type = import_constants.DeviceType["VMS"];
}
if (!type) {
type = import_constants.DeviceType["DIR"];
}
}
return type;
}
function deviceIDToDeviceKey(id, type, vmsId) {
if (type == import_constants.DeviceType["DIR"]) {
return `${type}_${id}_${vmsId}`;
} else {
return `${type}_${id}`;
}
}
function deviceToDeviceKey(item) {
let deviceKey = "";
let type = getDeviceType(item);
if (!type) {
console.error("device type is null!");
}
switch (type) {
case import_constants.DeviceType["DIR"]:
{
let dirId = item["dirid"] || item["dirId"];
if (!dirId && dirId !== 0) {
console.error("dirId type is null!");
}
let vmsId = (0, import_lodash_es.get)(item, "extendInfo.vmsPlatformId");
if (!vmsId && vmsId !== 0) {
console.error("vmsId type is null!");
}
deviceKey = `${type}_${dirId}_${vmsId}`;
}
break;
case import_constants.DeviceType["VMS"]:
deviceKey = `${type}_${item["id"]}`;
break;
case import_constants.DeviceType["CAMERA"]:
{
let vmsId = (0, import_lodash_es.get)(item, "extendInfo.vmsPlatformId");
if (!vmsId && vmsId !== 0) {
console.error("vmsId type is null!");
}
deviceKey = `${type}_${item.id}`;
}
break;
}
return deviceKey;
}
function deviceKeyToDeviceId(deviceKey) {
let [type, id] = deviceKey.split("_");
return type === import_constants.DeviceType["DIR"] ? id : Number(id);
}
var getVmsIdByDeviceId = (key) => {
const type = getDeviceType(key);
let vmsId = "";
switch (type) {
case import_constants.DeviceType["CAMERA"]:
case import_constants.DeviceType["DIR"]:
vmsId = key.split("_")[2];
break;
case import_constants.DeviceType["VMS"]:
vmsId = key.split("_")[1];
break;
}
if (!vmsId) {
console.error("vmsid is null!");
}
return vmsId;
};
var findCamerasByInDeviceTree = (ids = [], deviceTree, type = "id") => {
let cameraInfoList = [];
let _ids = ids.map((v) => String(v));
(0, import_utils.loop)(deviceTree, (item) => {
let isCamera = getDeviceType((0, import_lodash_es.get)(item, "key", "")) === import_constants.DeviceType["CAMERA"];
let isMatch = type === "key" ? _ids.includes((0, import_lodash_es.get)(item, "key")) : _ids.includes(`${(0, import_lodash_es.get)(item, "origin.id")}`);
if (isCamera && isMatch) {
cameraInfoList.push(item);
}
});
return cameraInfoList;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
deviceIDToDeviceKey,
deviceKeyToDeviceId,
deviceToDeviceKey,
findCamerasByInDeviceTree,
getDeviceType,
getVmsIdByDeviceId,
isFaceCamera
});

View File

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

View File

@ -1,280 +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/file/index.ts
var file_exports = {};
__export(file_exports, {
base64DecodeImageKey: () => base64DecodeImageKey,
base64toBlob: () => base64toBlob,
dataURLToBlob: () => dataURLToBlob,
downloadPackageImages: () => downloadPackageImages,
fileToBase64: () => fileToBase64,
generateImg: () => generateImg,
getBase64ByImage: () => getBase64ByImage,
getBase64ByUrl: () => getBase64ByUrl,
getBase64Image: () => getBase64Image,
getFileSize: () => getFileSize,
urlToBase64V2: () => urlToBase64V2,
urlToImg: () => urlToImg
});
module.exports = __toCommonJS(file_exports);
var import_base_64 = __toESM(require("base-64"));
var import_jszip = __toESM(require("jszip"));
var import_file_saver = __toESM(require("file-saver"));
var import_utils = require("../utils");
var import_lodash_es = require("lodash-es");
var urlToImg = (url) => {
const resImage = new Promise((resolve) => {
const image = new Image();
image.crossOrigin = "";
image.src = url;
image.onload = () => {
resolve(image);
};
});
return resImage;
};
var base64DecodeImageKey = (base64ImgKey) => {
let tempStr = base64ImgKey;
if ((0, import_utils.matchS3Prefix)(tempStr)) {
tempStr = tempStr.replace(/^v[0-9]_/, "");
tempStr = import_base_64.default.decode(tempStr);
}
const [bucket, ...pathArr] = tempStr.split("_");
return tempStr = `${bucket}/${pathArr.join("_")}`;
};
var getBase64ByUrl = function(src, outputFormat = "image/png") {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.responseType = "arraybuffer";
xhr.onload = function(e) {
if (Number(xhr.status) === 200) {
const uInt8Array = new Uint8Array(xhr.response);
let i = uInt8Array.length;
const binaryString = new Array(i);
while (i--) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
const data = binaryString.join("");
const base64 = window.btoa(data);
const dataUrl = "data:" + (outputFormat || "image/png") + ";base64," + base64;
resolve(dataUrl);
} else {
reject(e);
}
};
xhr.onerror = (e) => {
reject(e);
};
xhr.send();
});
};
var fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
resolve(e == null ? void 0 : e.target.result);
};
reader.onerror = function(e) {
reject(e);
};
});
};
var getBase64Image = (image, width, height) => {
const canvas = document.createElement("canvas");
canvas.width = width !== void 0 ? width : image.width;
canvas.height = height !== void 0 ? height : image.height;
const ctx = canvas.getContext("2d");
ctx == null ? void 0 : ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
const ext = image.src.substring(image.src.lastIndexOf(".") + 1).toLowerCase();
const dataURL = canvas.toDataURL("image/" + ext);
return dataURL;
};
var getBase64ByImage = function(src) {
return new Promise((resolve, reject) => {
const image = new Image();
const timestamp = (/* @__PURE__ */ new Date()).getTime();
const imgUrl = src + "?" + timestamp;
image.src = imgUrl;
image.onload = function() {
function getBase64Image2(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx == null ? void 0 : ctx.drawImage(img, 0, 0, img.width, img.height);
const ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
const dataURL = canvas.toDataURL("image/" + ext);
return dataURL;
}
const base64 = getBase64Image2(image);
resolve(base64);
};
image.onerror = (e) => {
reject(e);
};
});
};
var urlToBase64V2 = (url) => {
return new Promise((resolve, reject) => {
let image = new Image();
image.onload = function() {
var _a;
let canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
(_a = canvas == null ? void 0 : canvas.getContext("2d")) == null ? void 0 : _a.drawImage(image, 0, 0);
let result = canvas.toDataURL("image/png");
resolve(result);
};
const imgUrl = url;
image.setAttribute("crossOrigin", "Anonymous");
image.src = imgUrl;
image.onerror = () => {
reject(new Error("Images fail to load"));
};
}).catch((error) => {
throw new Error(error);
});
};
function base64toBlob(base64) {
if (!base64)
return;
var arr = base64.split(","), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
var downloadPackageImages = async (imgDataList, zipName) => {
let imgDataDownLoadList = [];
let imgBlobList = [];
let imageSuffix = [];
let zip = new import_jszip.default();
let img = zip.folder(zipName);
try {
for (let i2 = 0; i2 < imgDataList.length; i2++) {
let src = imgDataList[i2].url;
let suffix = src.substring(src.lastIndexOf("."));
let base64ByUrl = await urlToBase64V2(imgDataList[i2].url);
if (!base64ByUrl)
continue;
let blob = base64toBlob(base64ByUrl);
imgDataDownLoadList.push(imgDataList[i2]);
imgBlobList.push(blob);
imageSuffix.push(suffix);
}
if (imgBlobList.length === 0)
throw new Error("The number of pictures is zero !");
if (imgBlobList.length > 0) {
for (var i = 0; i < imgBlobList.length; i++) {
img == null ? void 0 : img.file(
imgDataDownLoadList[i].name + (0, import_lodash_es.get)(imageSuffix, `.${i}`, imageSuffix[0]),
// @ts-ignore
imgBlobList[i],
{
base64: true
}
);
}
}
zip.generateAsync({ type: "blob" }).then(function(content) {
import_file_saver.default.saveAs(content, zipName + ".zip");
});
} catch (error) {
throw new Error(error);
}
};
function getFileSize(size) {
if (!size)
return "";
var num = 1024;
if (size < num)
return size + "B";
if (size < Math.pow(num, 2))
return (size / num).toFixed(2) + "K";
if (size < Math.pow(num, 3))
return (size / Math.pow(num, 2)).toFixed(2) + "M";
if (size < Math.pow(num, 4))
return (size / Math.pow(num, 3)).toFixed(2) + "G";
return (size / Math.pow(num, 4)).toFixed(2) + "T";
}
var dataURLToBlob = (dataurl) => {
const arr = dataurl.split(",");
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
};
var generateImg = (_imgKey, host = "http://10.0.0.120") => {
let imgKey = _imgKey;
let imgUrl = "";
if (!imgKey)
return "";
if (/^(http:|https:)/.test(imgKey)) {
return imgKey;
}
try {
if ((0, import_utils.matchS3Prefix)(imgKey)) {
imgKey = base64DecodeImageKey(imgKey);
if (imgKey.endsWith("/")) {
const i = imgKey.substring(0, imgKey.length - 1);
imgKey = i;
}
}
imgUrl = `${host}/file/${imgKey}`;
if (_imgKey.includes("v3")) {
imgUrl = `${host}/minio/${imgKey}`;
}
} catch (error) {
console.error(error);
imgUrl = "";
}
return imgUrl;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
base64DecodeImageKey,
base64toBlob,
dataURLToBlob,
downloadPackageImages,
fileToBase64,
generateImg,
getBase64ByImage,
getBase64ByUrl,
getBase64Image,
getFileSize,
urlToBase64V2,
urlToImg
});

View File

@ -1,9 +0,0 @@
export * from 'lodash-es';
export * from './file';
export * from './map';
export * from './performance';
export * from './string';
export * from './number';
export * from './time';
export * from './utils';
export * from './camera';

View File

@ -1,39 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
module.exports = __toCommonJS(src_exports);
__reExport(src_exports, require("lodash-es"), module.exports);
__reExport(src_exports, require("./file"), module.exports);
__reExport(src_exports, require("./map"), module.exports);
__reExport(src_exports, require("./performance"), module.exports);
__reExport(src_exports, require("./string"), module.exports);
__reExport(src_exports, require("./number"), module.exports);
__reExport(src_exports, require("./time"), module.exports);
__reExport(src_exports, require("./utils"), module.exports);
__reExport(src_exports, require("./camera"), module.exports);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
...require("lodash-es"),
...require("./file"),
...require("./map"),
...require("./performance"),
...require("./string"),
...require("./number"),
...require("./time"),
...require("./utils"),
...require("./camera")
});

View File

@ -1,10 +0,0 @@
/**
*
* @param lngLat
* @returns @object { long, lat }
*/
export declare const fixedLngLat: (lngLat?: string) => string;
export declare const transformLngLat: (lngLat?: string) => {
longitude: number;
latitude: number;
} | null;

View File

@ -1,54 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/map/index.ts
var map_exports = {};
__export(map_exports, {
fixedLngLat: () => fixedLngLat,
transformLngLat: () => transformLngLat
});
module.exports = __toCommonJS(map_exports);
var fixedLngLat = (lngLat) => {
if (!lngLat) {
return "";
}
const lngLatArr = lngLat.split(",");
const longitude = Number(lngLatArr[0]).toFixed(6);
const latitude = Number(lngLatArr[1]).toFixed(6);
if (lngLatArr.length < 2) {
return "";
}
return `${longitude},${latitude}`;
};
var transformLngLat = (lngLat) => {
if (lngLat) {
const lngLatArr = lngLat ? lngLat.split(",") : [];
const longitude = Number(lngLatArr[0]);
const latitude = Number(lngLatArr[1]);
if (lngLatArr.length < 2) {
return null;
}
return { longitude, latitude };
}
return null;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
fixedLngLat,
transformLngLat
});

View File

@ -1,8 +0,0 @@
/**
*
* @param originNumber
* @param accuracy
* @param isCeil
* @returns number
*/
export declare const setNumberAccuracy: (originNumber: number, accuracy?: number, isCeil?: boolean) => number;

View File

@ -1,45 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/number/index.ts
var number_exports = {};
__export(number_exports, {
setNumberAccuracy: () => setNumberAccuracy
});
module.exports = __toCommonJS(number_exports);
var setNumberAccuracy = (originNumber, accuracy = 0, isCeil = true) => {
if (originNumber === 0) {
return 0;
}
let returnData = 0;
if (isCeil) {
returnData = Math.ceil(originNumber / Math.pow(10, accuracy)) * Math.pow(10, accuracy);
} else {
returnData = Math.floor(originNumber / Math.pow(10, accuracy)) * Math.pow(10, accuracy);
}
if (accuracy < 0) {
returnData = Number(returnData.toFixed(-accuracy));
} else {
returnData = Number(returnData.toFixed(0));
}
return returnData;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
setNumberAccuracy
});

View File

@ -1 +0,0 @@
export declare const speedConvert: (bps: number, contertUnit?: number) => string;

View File

@ -1,41 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/performance/index.ts
var performance_exports = {};
__export(performance_exports, {
speedConvert: () => speedConvert
});
module.exports = __toCommonJS(performance_exports);
var speedConvert = (bps, contertUnit = 8) => {
if (bps === void 0)
return `0KB/s`;
const byte = bps / contertUnit;
if (bps > 1024 * 1024 * 1024) {
return `${(byte / 1024 / 1024 / 1024).toFixed(2)}GB/s`;
} else if (byte > 1024 * 1024) {
return `${(byte / 1024 / 1024).toFixed(2)}MB/s`;
} else if (byte > 1024) {
return `${(byte / 1024).toFixed(2)}KB/s`;
}
return `${byte}KB/s`;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
speedConvert
});

View File

@ -1,7 +0,0 @@
export declare const getStrLength: (str: string) => number;
/**
* js截取字符串
* @param str
* @param len: 需要截取的长度
*/
export declare const cutStr: (str: string, len: number) => string | String | undefined;

View File

@ -1,62 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/string/index.ts
var string_exports = {};
__export(string_exports, {
cutStr: () => cutStr,
getStrLength: () => getStrLength
});
module.exports = __toCommonJS(string_exports);
var getStrLength = function(str) {
var realLength = 0, len = str.length, charCode = -1;
for (var i = 0; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode >= 0 && charCode <= 128)
realLength += 1;
else
realLength += 2;
}
return realLength;
};
var cutStr = function cutstr(str, len) {
var str_length = 0;
var str_len = 0;
let str_cut = new String();
str_len = str.length;
for (var i = 0; i < str_len; i++) {
let a = str.charAt(i);
str_length++;
if (escape(a).length > 4) {
str_length++;
}
str_cut = str_cut.concat(a);
if (str_length >= len) {
str_cut = str_cut.concat("...");
return str_cut;
}
}
if (str_length < len) {
return str;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
cutStr,
getStrLength
});

View File

@ -1,5 +0,0 @@
export declare const formateDuration: (diff: number) => string;
export declare function generateTime(): {
startDateTime: number;
endDateTime: number;
};

View File

@ -1,66 +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/time/index.ts
var time_exports = {};
__export(time_exports, {
formateDuration: () => formateDuration,
generateTime: () => generateTime
});
module.exports = __toCommonJS(time_exports);
var import_dayjs = __toESM(require("dayjs"));
var formateDuration = (diff) => {
var days = Math.floor(diff / (24 * 3600 * 1e3));
var leave1 = diff % (24 * 3600 * 1e3);
var hours = Math.floor(leave1 / (3600 * 1e3));
var leave2 = leave1 % (3600 * 1e3);
var minutes = Math.floor(leave2 / (60 * 1e3));
var leave3 = leave2 % (60 * 1e3);
var seconds = Math.round(leave3 / 1e3);
var returnStr = seconds + "秒";
if (minutes > 0) {
returnStr = minutes + "分";
}
if (hours > 0) {
returnStr = hours + "小时";
}
if (days > 0) {
returnStr = days + "天";
}
return returnStr;
};
function generateTime() {
let endDateTime = (0, import_dayjs.default)().endOf("day").unix();
let startDateTime = (0, import_dayjs.default)().startOf("day").unix();
return { startDateTime, endDateTime };
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
formateDuration,
generateTime
});

View File

@ -1,20 +0,0 @@
export { default as isChrome } from './isChrome';
export declare const matchS3Prefix: (str: string) => boolean;
/**
*
* @param fileName
* @returns
*/
export declare const getFileSuffix: (fileName: string) => string;
/**
*
* @param type
* @returns
*/
export declare const getImageSuffixByFileType: (type: string) => string;
export declare function getChromeVersion(): number | false;
export declare const nextTick: (func: (value: void) => void | PromiseLike<void>) => void;
export declare const loop: (items: string | any[], callback: (arg0: any) => any) => void;
export declare const addEventListener: (target: any, eventType: string, cb: any, option?: any) => {
remove: () => void;
};

View File

@ -1,138 +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/utils/index.ts
var utils_exports = {};
__export(utils_exports, {
addEventListener: () => addEventListener,
getChromeVersion: () => getChromeVersion,
getFileSuffix: () => getFileSuffix,
getImageSuffixByFileType: () => getImageSuffixByFileType,
isChrome: () => import_isChrome.default,
loop: () => loop,
matchS3Prefix: () => matchS3Prefix,
nextTick: () => nextTick
});
module.exports = __toCommonJS(utils_exports);
var import_react_dom = __toESM(require("react-dom"));
var import_isChrome = __toESM(require("./isChrome"));
var matchS3Prefix = (str) => {
return /^v[0-9]_/.test(str);
};
var getFileSuffix = (fileName) => {
const splitArr = fileName.split(".");
return splitArr.length < 2 ? "" : splitArr[splitArr.length - 1];
};
var getImageSuffixByFileType = (type) => {
let imageSuffix = "";
switch (type) {
case "jpeg": {
imageSuffix = ".jpg";
break;
}
case "gif": {
imageSuffix = ".gif";
break;
}
case "png": {
imageSuffix = ".png";
break;
}
case "vnd.wap.wbmp": {
imageSuffix = ".wbmp";
break;
}
case "x-up-wpng": {
imageSuffix = ".wpng";
break;
}
case "nbmp": {
imageSuffix = ".nbmp";
break;
}
}
return imageSuffix;
};
function getChromeVersion() {
const arr = navigator.userAgent.split(" ");
let chromeVersion = "";
for (let i = 0; i < arr.length; i++) {
if (/chrome/i.test(arr[i]))
chromeVersion = arr[i];
}
if (chromeVersion) {
return Number(chromeVersion.split("/")[1].split(".")[0]);
} else {
return false;
}
}
var nextTick = (func) => {
if (queueMicrotask) {
queueMicrotask(func);
return;
}
Promise.resolve().then(func);
};
var loop = (items, callback) => {
for (let i = 0; i < items.length; i++) {
const element = items[i];
let isBreak = callback(element);
if (isBreak) {
return;
}
if (element["children"]) {
loop(element["children"], callback);
}
}
};
var addEventListener = (target, eventType, cb, option) => {
const callback = import_react_dom.default.unstable_batchedUpdates ? function run(e) {
import_react_dom.default.unstable_batchedUpdates(cb, e);
} : cb;
if (target.addEventListener) {
target.addEventListener(eventType, callback, option);
}
return {
remove: () => {
if (target.removeEventListener) {
target.removeEventListener(eventType, callback);
}
}
};
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
addEventListener,
getChromeVersion,
getFileSuffix,
getImageSuffixByFileType,
isChrome,
loop,
matchS3Prefix,
nextTick
});

View File

@ -1,3 +0,0 @@
export declare const isBrowser: boolean;
declare const isChrome: () => boolean;
export default isChrome;

View File

@ -1,42 +0,0 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/utils/isChrome.ts
var isChrome_exports = {};
__export(isChrome_exports, {
default: () => isChrome_default,
isBrowser: () => isBrowser
});
module.exports = __toCommonJS(isChrome_exports);
var isBrowser = !!(typeof window !== "undefined" && window);
var isChrome = () => {
const winNav = isBrowser && window.navigator;
const vendorName = winNav && winNav.vendor;
const userAgent = winNav && winNav.userAgent;
const isChromium = isBrowser && typeof chrome !== "undefined";
const isOpera = isBrowser && typeof opr !== "undefined";
const isIEedge = userAgent && userAgent.indexOf("Edge") > -1;
const isIOSChrome = !!(userAgent && userAgent.match("CriOS"));
const isDesktopChrome = isChromium && vendorName === "Google Inc." && !isOpera && !isIEedge;
return isIOSChrome || isDesktopChrome;
};
var isChrome_default = isChrome;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
isBrowser
});

View File

@ -13,10 +13,11 @@
"lib/**/style/*",
"*.less"
],
"main": "lib/index.tsx",
"main": "lib/index.js",
"module": "es/index.js",
"typings": "es/index.d.ts",
"exports": {
"./*": "./*",
"./package.json": "./package.json"
},
"files": [
@ -32,15 +33,19 @@
"registry": "http://10.0.0.77:4874"
},
"dependencies": {
"@zhst/request": "workspace:^",
"base-64": "^1.0.0",
"dayjs": "^1.11.10",
"file-saver": "^2.0.5",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21"
"lodash-es": "^4.17.21",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/base-64": "^1.0.2",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12"
"@types/lodash-es": "^4.17.12",
"@types/uuid": "^9.0.7",
"@zhst/types": "workspace:^"
}
}

View File

@ -2,7 +2,8 @@ import Base64 from 'base-64'
import JSZip from 'jszip';
import FileSaver from 'file-saver';
import { matchS3Prefix } from '../utils'
import { get } from 'lodash-es';
import { get, isString } from 'lodash-es';
import { Rect } from '@zhst/types'
/**
*
@ -296,3 +297,49 @@ export const generateImg: (imgKey: string, host?: string) => string = (_imgKey,
}
return imgUrl;
};
/**
*
* @param imageKey v1_开头的字符串
* @returns
*/
export const getImageKey = (imageKey: string, preFix?: string) => {
const splitIndex = preFix || 'v1_';
if (imageKey.startsWith(splitIndex)) {
return window.atob(imageKey.split(splitIndex)[1]).replace('_', '/');
} else {
return imageKey;
}
};
/**
*
* @param img url链接
* @param odRect
* @returns file
*/
export const getFileByRect = async (img: string, odRect: Rect) => {
let image;
if (isString(img)) {
const url = generateImg(img);
image = await urlToImg(url);
} else {
image = img;
}
const commonCanvas = document.createElement('canvas');
commonCanvas.width = odRect.w * image.width;
commonCanvas.height = odRect.h * image.height;
commonCanvas.style.display = 'none';
document.body.appendChild(commonCanvas);
const commonCtx = commonCanvas.getContext('2d');
commonCtx?.translate(-odRect.x * image.width, -odRect.y * image.height);
commonCtx?.drawImage(image, 0, 0);
const base64 = commonCanvas.toDataURL('image/jpeg');
const blobData = dataURLToBlob(base64);
commonCanvas.parentNode?.removeChild(commonCanvas);
const file = new window.File([blobData], `${new Date().getTime()}`, {
type: 'image/jpeg',
});
return file;
};

View File

@ -7,3 +7,5 @@ export * from './number'
export * from './time'
export * from './utils'
export * from './camera'
export * from './math'
export * from './upload'

View File

@ -0,0 +1,448 @@
import { cloneDeep, get, isNull, isNumber, isString } from 'lodash-es';
import { dataURLToBlob, generateImg, urlToImg } 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, maxW, maxH, type) => {
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 fileKey = 'v1_' + window.btoa('public_' + imgKey.split('public/')[1]);
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) => !isNull(get(v, 'subObjects[0].infoOnSource.bboxInFrame.bboxRatio')))
.map((v, index) => {
const rect = get(v, 'infoOnSource.bboxInFrame.bboxRatio');
const qualityScore = get(v, 'qualityScore');
const algorithmVersion = get(v, 'objectType');
const featrueData = get(v, 'feature', []).filter(
(v) => 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) => {
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;
};
//获取图片
export const getFileByRect = async (img: any, odRect: Rect) => {
let image;
if (isString(img)) {
const url = generateImg(img);
console.log(url, '获取图片');
image = await urlToImg(url);
console.log(image, '获取的图片');
} else {
image = img;
}
const commonCanvas = document.createElement('canvas');
commonCanvas.width = odRect.w * image.width;
commonCanvas.height = odRect.h * image.height;
commonCanvas.style.display = 'none';
document.body.appendChild(commonCanvas);
const commonCtx = commonCanvas.getContext('2d');
commonCtx?.translate(-odRect.x * image.width, -odRect.y * image.height);
commonCtx?.drawImage(image, 0, 0);
const base64 = commonCanvas.toDataURL('image/jpeg');
const blobData = dataURLToBlob(base64);
commonCanvas?.parentNode?.removeChild(commonCanvas);
const file = new window.File([blobData], `${new Date().getTime()}`, {
type: 'image/jpeg',
});
return file;
};
/**
* Get transforms base on the given object.
* @param {Object} obj - The target object.
* @returns {string} A string contains transform values.
*/
export function getTransforms({
rotate,
scaleX,
scaleY,
translateX,
translateY,
}: {
rotate?: number;
scaleX?: number;
scaleY?: number;
translateX?: number;
translateY?: number;
}) {
const values = [];
if (isNumber(translateX) && translateX !== 0) {
values.push(`translateX(${translateX}px)`);
}
if (isNumber(translateY) && translateY !== 0) {
values.push(`translateY(${translateY}px)`);
}
// Rotate should come first before scale to match orientation transform
if (isNumber(rotate) && rotate !== 0) {
values.push(`rotate(${rotate}deg)`);
}
if (isNumber(scaleX) && scaleX !== 1) {
values.push(`scaleX(${scaleX})`);
}
if (isNumber(scaleY) && scaleY !== 1) {
values.push(`scaleY(${scaleY})`);
}
const transform = values.length ? values.join(' ') : 'none';
return {
WebkitTransform: transform,
msTransform: transform,
transform,
};
}

View File

@ -25,3 +25,14 @@ export const setNumberAccuracy = (originNumber: number, accuracy = 0, isCeil = t
}
return returnData;
};
/**
*
* @param number
* @returns @number
*/
export const toRealNumber = (number: any) => {
if (isNaN(number) || number === Infinity) {
return 0;
} else return number;
};

View File

@ -47,3 +47,14 @@ export function generateTime() {
let startDateTime = dayjs().startOf('day').unix();
return { startDateTime, endDateTime };
}
/**
* 00:00
* @param seconds
* @returns
*/
export function formatDurationTime(seconds: number) {
var minutes = Math.floor(seconds / 60) || 0;
var remainingSeconds = Math.floor(seconds % 60);
return (minutes < 10 ? `0${minutes}` : minutes) + ":" + (remainingSeconds < 10 ? "0" : "") + remainingSeconds;
}

View File

@ -0,0 +1,58 @@
import dayjs from "dayjs";
import { getFileSuffix, getImageSuffixByFileType } from "../utils";
import base64 from "base-64";
import { v4 as uuidV4 } from 'uuid'
import { get } from "lodash-es";
import request from '@zhst/request'
//小文件上传走s3
const defaultBucket = 'public';
type uploadOption = {
bucket?: string;
dir?: string;
withSuFuffix?: boolean;
};
export const commonUpload = async (file: File, option: uploadOption = {}, type: string) => {
const { bucket = defaultBucket, dir = 'file', withSuFuffix = false } = option;
const prefix = `${dayjs().format('YYYYMMDD')}`;
const fileSuffix = withSuFuffix ? getFileSuffix(get(file, 'name')) : '';
const fileType = file['type'].split('/', 2);
let imageSuffix = '';
if (fileType['0'] === 'image') {
imageSuffix = getImageSuffixByFileType(fileType['1']);
}
const key = `${prefix}/${dir ? `${dir}/` : ''}${uuidV4()}${!fileSuffix ? '' : `.${fileSuffix}`}`;
let imgKey = `${bucket}_${bucket}_${key}${imageSuffix}`; //后端约定 见https://docs.qq.com/doc/DUklodHNxUGl2U3NM》
if (type === 'upload') {
imgKey = `v1_${base64.encode(imgKey)}`;
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async () => {
if (reader.result) {
await request({
method: 'PUT',
url: '/singer.FileServerService/PutObject',
data: {
version: 1,
bucket: defaultBucket,
objectName: `${bucket}_${key}${imageSuffix}`,
// fileData: reader.result,
putObjectOption: {
contentType: file.type,
},
fileDataBase64: reader.result?.split(';base64,')[1],
},
});
resolve(imgKey);
}
};
});
}
};
export const upload = async (file: File, option: uploadOption = {}) => {
return await commonUpload(file, option, 'upload');
};

View File

@ -1,3 +1,4 @@
import { isNumber } from 'lodash-es';
import ReactDOM from 'react-dom';
export { default as isChrome } from './isChrome';
@ -90,7 +91,7 @@ export const loop = (items: string | any[], callback: (arg0: any) => any) => {
}
};
export const addEventListener = (target: any, eventType: string, cb: any, option?: any) => {
export const addEventListenerWrapper = (target: any, eventType: string, cb: any, option?: any) => {
/* eslint camelcase: 2 */
const callback = ReactDOM.unstable_batchedUpdates
? function run(e: any) {
@ -109,3 +110,54 @@ export const addEventListener = (target: any, eventType: string, cb: any, option
},
};
}
/**
* Get transforms base on the given object.
* @param {Object} obj - The target object.
* @returns {string} A string contains transform values.
*/
export function getTransforms({
rotate,
scaleX,
scaleY,
translateX,
translateY,
}: {
rotate?: number;
scaleX?: number;
scaleY?: number;
translateX?: number;
translateY?: number;
}) {
const values = [];
if (isNumber(translateX) && translateX !== 0) {
values.push(`translateX(${translateX}px)`);
}
if (isNumber(translateY) && translateY !== 0) {
values.push(`translateY(${translateY}px)`);
}
// Rotate should come first before scale to match orientation transform
if (isNumber(rotate) && rotate !== 0) {
values.push(`rotate(${rotate}deg)`);
}
if (isNumber(scaleX) && scaleX !== 1) {
values.push(`scaleX(${scaleX})`);
}
if (isNumber(scaleY) && scaleY !== 1) {
values.push(`scaleY(${scaleY})`);
}
const transform = values.length ? values.join(' ') : 'none';
return {
WebkitTransform: transform,
msTransform: transform,
transform,
};
}

View File

@ -13,7 +13,7 @@
"lib/**/style/*",
"*.less"
],
"main": "lib/index.tsx",
"main": "lib/index.js",
"module": "es/index.js",
"typings": "es/index.d.ts",
"exports": {
@ -31,6 +31,9 @@
"access": "public",
"registry": "http://10.0.0.77:4874"
},
"devDependencies": {
"@zhst/types": "workspace:^"
},
"dependencies": {
"@zhst/func": "workspace:^",
"ahooks": "^3.7.8",

View File

@ -16,7 +16,7 @@
"lib/**/style/*",
"*.less"
],
"main": "lib/index.tsx",
"main": "lib/index.js",
"module": "es/index.js",
"typings": "es/index.d.ts",
"files": [

View File

@ -14,7 +14,7 @@
"lib/**/style/*",
"*.less"
],
"main": "lib/index.tsx",
"main": "lib/index.js",
"module": "es/index.js",
"typings": "es/index.d.ts",
"exports": {
@ -32,13 +32,20 @@
"access": "public",
"registry": "http://10.0.0.77:4874"
},
"devDependencies": {
"@zhst/types": "workspace:^"
},
"dependencies": {
"@turf/boolean-point-in-polygon": "^6.5.0",
"@turf/turf": "^6.5.0",
"@types/downloadjs": "^1.4.6",
"@zhst/func": "workspace:^",
"@zhst/hooks": "workspace:^",
"antd": "^5.12.5",
"classnames": "^2.5.1",
"downloadjs": "^1.4.7",
"flv.js": "^1.6.2",
"rc-align": "^4.0.15",
"rc-util": "^5.38.1",
"react": "^18.0.0",
"react-dom": "^18.0.0"

View File

@ -0,0 +1,51 @@
.zhst-image__btn-group {
// display: flex;
width: 30px;
box-shadow: 0 2px 6px 0 rgb(0 0 0 / 40%);
&__item {
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
background: #000;
&>button {
padding: 0;
color: #fff;
&:hover {
color: #09f;
}
&:active {
color: #09f;
}
&:focus {
color: #fff;
}
&>span {
display: flex;
}
}
&--active {
&>button {
color: #09f;
}
}
}
&--circle {
background-color: none;
box-shadow: none;
}
&--circle &__item {
margin-bottom: 4px;
border-radius: 50%;
}
}

View File

@ -0,0 +1,57 @@
import React, { MouseEvent } from 'react';
import classNames from 'classnames';
import { Button, Tooltip, TooltipProps } from 'antd';
import Icon from '../../../iconfont';
import './index.less';
const componentName = `zhst-image__btn-group`;
export interface BtnGroupProps {
className: string;
dataSource: Array<{ key: string; icon: string; title: string }>;
onClick: (v: string, e: MouseEvent<HTMLElement>) => void;
selectKey?: string;
circle?: boolean;
placement?: TooltipProps['placement'];
}
export const BtnGroup: React.FC<BtnGroupProps> = (props) => {
const { dataSource, onClick, className, circle, selectKey = '', placement = 'right' } = props;
return (
<div
className={classNames(
componentName,
circle && `${componentName}--circle`,
className
)}
>
{dataSource.map(({ key, icon, title }) => {
const isSelect = key === selectKey;
return (
<Tooltip key={key} placement={placement} title={title}>
<div
key={key}
className={classNames(
`${componentName}__item`,
isSelect && `${componentName}__item--active`
)}
>
<Button
type="text"
onClick={(e) => {
onClick(key, e);
}}
>
<Icon size={18} icon={icon} />
</Button>
</div>
</Tooltip>
);
})}
</div>
);
};
BtnGroup.displayName = 'BtnGroup';
export default BtnGroup;

View File

@ -0,0 +1,21 @@
.bigImageWrapper--v2__screenshot {
min-width: 90px;
background: rgb(0 0 0 / 50%);
border-radius: 0;
&>button {
width: 100%;
color: #fff !important;
font-family: 'Microsoft YaHei';
font-size: 12px !important;
border-radius: 0;
&>span {
color: #fff;
}
&:hover {
background-color: #09f !important;
}
}
}

View File

@ -0,0 +1,106 @@
import React from'react'
import { Button } from 'antd';
import classNames from 'classnames';
import type { AlgorithmVersion } from '@zhst/types'
import { IBigImageOpt } from '@zhst/types/BigImageModal'
import './index.less';
const componentName = 'bigImageWrapper--v2';
interface IScreenShotButton {
getCropInfo: () => void
setShowCrop: any
cropType: string
selectAlgorithmVersion: AlgorithmVersion
}
const getScreenshotButtonRender = (arg: {
disableBtn: number[];
onBigImageActionClick: (type: number, item: any) => void;
}) => {
const { disableBtn = [], onBigImageActionClick } = arg;
return (param: IScreenShotButton) => {
const { getCropInfo, setShowCrop, cropType, selectAlgorithmVersion } = param;
let isAuto = cropType === 'AUTO';
return (
<div
className={classNames(`${componentName}__screenshot`)}
style={{
zIndex: 100,
position: 'absolute',
width: '86px',
}}
>
{!disableBtn.includes(IBigImageOpt['ADD_HISTORY_WITH_CROP']) && isAuto && (
<Button
type={'text'}
onClick={async (e) => {
e.stopPropagation();
const image = await getCropInfo();
setShowCrop(false);
onBigImageActionClick(IBigImageOpt['ADD_HISTORY_WITH_CROP'], image);
}}
>
</Button>
)}
{!disableBtn.includes(IBigImageOpt['ADD_HISTORY_WITH_CROP_BODY']) && !isAuto && (
<Button
type={'text'}
onClick={async (e) => {
e.stopPropagation();
const image = await getCropInfo();
setShowCrop(false);
onBigImageActionClick(IBigImageOpt['ADD_HISTORY_WITH_CROP_BODY'], image);
}}
>
</Button>
)}
{!disableBtn.includes(IBigImageOpt['ADD_HISTORY_WITH_CROP_VEHICLE']) && !isAuto && (
<Button
type={'text'}
onClick={async (e) => {
e.stopPropagation();
const image = await getCropInfo();
setShowCrop(false);
onBigImageActionClick(IBigImageOpt['ADD_HISTORY_WITH_CROP_VEHICLE'], image);
}}
>
</Button>
)}
{!disableBtn.includes(IBigImageOpt['ADD_HISTORY_WITH_CROP_ARCHIVE']) &&
selectAlgorithmVersion !== 0 && (
<Button
type={'text'}
onClick={async (e) => {
e.stopPropagation();
let image = await getCropInfo();
setShowCrop(false);
if (!image.rectList[0].algorithmVersion) {
image.rectList[0].algorithmVersion = 0;
image.extendRectList[0].algorithmVersion = 0;
}
onBigImageActionClick(IBigImageOpt['ADD_HISTORY_WITH_CROP_ARCHIVE'], image);
}}
>
</Button>
)}
<Button
type={'text'}
onClick={(e) => {
e.stopPropagation();
setShowCrop(false);
}}
>
退
</Button>
</div>
);
};
};
export default getScreenshotButtonRender;

View File

@ -0,0 +1,129 @@
.zhst-image__img-view {
position: relative;
width: calc(100%);
height: 100%;
&__face-score {
position: absolute;
right: 20px;
bottom: 80px;
color: red;
font-family: 'Microsoft YaHei';
font-size: 19px;
font-weight: bold;
}
&-opt {
position: absolute;
z-index: 99;
top: 0;
}
&-crop-opt {
position: absolute;
z-index: 99;
top: 0;
right: 0;
}
&-align {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
&-main {
width: 100%;
height: 100%;
font-size: 0;
&--cursor {
& canvas {
min-height: 320px;
cursor: pointer;
}
}
}
&-screenshot {
position: absolute;
z-index: 10;
}
&-attach {
position: absolute;
z-index: 11;
bottom: 0;
// left: 78px;
left: 0;
min-width: 120px;
height: 202px;
transition: all 200ms;
&--fixed {
}
&--zoomin {
height: 100%;
&--fixed {
}
}
&__tab {
position: absolute;
top: 0;
left: 0;
display: flex;
&-item {
display: flex;
width: 30px;
height: 24px;
align-items: center;
justify-content: center;
background: #000;
color: #fff;
cursor: pointer;
font-size: 12px;
opacity: 0.5;
transition: all 200ms;
&--select {
width: 48px;
height: 34px;
background: #09f;
opacity: 1;
}
}
}
&__scale {
position: absolute;
top: 4px;
right: 4px;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 60%);
border-radius: 100%;
cursor: pointer;
line-height: 30px;
text-align: center;
}
&__img {
height: 100%;
object-fit: contain;
&--fixed {
width: 100%;
object-fit: contain;
}
}
}
}

View File

@ -0,0 +1,62 @@
---
nav:
title: 元组件
order: 1
group:
title: 通用
order: 3
---
# BigImagePreview 大图预览组件
```jsx
import React, { useState, useRef } from 'react';
import { Button, Space } from 'antd'
import { BigImagePreview } from '@zhst/meta'
const props = {
imageKey:"http://10.0.0.120:30003/file/singer-20240110/1/5/1744894622934503424.jpg",
odRect:{
"x":0.5445312,
"y":0.19166666,
"w":0.08671875,
"h":0.40138888
},
heigth: '500px',
attachImg: [
{
"url": "http://10.0.0.120:30003/file/singer-20240110/1/5/1744894622695428096.jpg","label": "形体"
},{
"url": "http://10.0.0.120:30003/file/singer-20240110/1/5/1744894588427964418.jpg",
"label": "人脸"
}
],
score: 0.891417,
objects: [{"objectIndex":{"objectId":"1745329264976201728","solutionId":"0","deviceId":"0","fragmentId":"0"},"objectType":"OBJECT_TYPE_PEDESTRAIN","sourceObjectId":"0","level":0,"confidence":0.8910453,"pathInfo":null,"frameInfo":{"frameId":"0","frameTimestamp":"1704953898021","width":0,"height":0,"originWidth":0,"originHeight":0,"offsetTime":"0","skipNumber":"0"},"deviceInfo":null,"infoOnSource":{"bboxInSource":null,"bboxInFrame":{"bbox":null,"bboxRatio":{"x":0.69192713,"y":0.53585213,"w":0.031508446,"h":0.12733544},"extendBbox":null,"extendBoxRatio":null},"countInSource":0,"indexInSource":0},"qualityScore":0,"frameImage":null,"objectImage":null,"objectExtImage":null,"feature":[{"name":"feature-body","type":"FEATURE_TYPE_BYTE","featureId":"0","featureByte":"","featureBool":[],"featureUint8":[],"featureUint16":[],"featureUint32":[],"featureUint64":[],"featureInt8":[],"featureInt16":[],"featureInt32":[],"featureInt64":[],"featureFloat32":[],"featureString":[]},{"name":"property-body","type":"FEATURE_TYPE_FLOAT32","featureId":"0","featureByte":"","featureBool":[],"featureUint8":[],"featureUint16":[],"featureUint32":[],"featureUint64":[],"featureInt8":[],"featureInt16":[],"featureInt32":[],"featureInt64":[],"featureFloat32":[0.8251953,0.40527344,0.4567871,0.20178223,0.26220703,0.5083008,0.36499023,0.045196533,0.92822266,0.08300781,0.9663086,0.71533203,0.112976074,0.3935547,0.25048828,0.11694336,0.5317383,0.26293945,0.13134766,0.11657715,0.4868164,0.3647461,0.06903076,0.234375,0.84472656,0.07684326,0.08227539,0.035827637,0.11315918,0.0637207,0.1850586,0.057495117,0.9013672,0.15673828,0.14099121,0.040618896,0.08270264,0.076538086,0.828125,0.055419922,0.038391113,0.03137207,0.07574463,0.08105469,0.05340576,0.023330688,0.25561523,0.05090332,0.17480469,0.73779297,0.49169922,0.63500977,0.033691406],"featureString":[]}],"subObjects":[]},{"objectIndex":{"objectId":"1745329264976201729","solutionId":"0","deviceId":"0","fragmentId":"0"},"objectType":"OBJECT_TYPE_PEDESTRAIN","sourceObjectId":"0","level":0,"confidence":0.74931294,"pathInfo":null,"frameInfo":{"frameId":"0","frameTimestamp":"1704953898021","width":0,"height":0,"originWidth":0,"originHeight":0,"offsetTime":"0","skipNumber":"0"},"deviceInfo":null,"infoOnSource":{"bboxInSource":null,"bboxInFrame":{"bbox":null,"bboxRatio":{"x":0.73869747,"y":0.5493781,"w":0.025924563,"h":0.11845186},"extendBbox":null,"extendBoxRatio":null},"countInSource":0,"indexInSource":0},"qualityScore":0,"frameImage":null,"objectImage":null,"objectExtImage":null,"feature":[{"name":"feature-body","type":"FEATURE_TYPE_BYTE","featureId":"0","featureByte":"","featureBool":[],"featureUint8":[],"featureUint16":[],"featureUint32":[],"featureUint64":[],"featureInt8":[],"featureInt16":[],"featureInt32":[],"featureInt64":[],"featureFloat32":[],"featureString":[]},{"name":"property-body","type":"FEATURE_TYPE_FLOAT32","featureId":"0","featureByte":"","featureBool":[],"featureUint8":[],"featureUint16":[],"featureUint32":[],"featureUint64":[],"featureInt8":[],"featureInt16":[],"featureInt32":[],"featureInt64":[],"featureFloat32":[0.41308594,0.33789062,0.5917969,0.17590332,0.7392578,0.3046875,0.5053711,0.017181396,0.94677734,0.19189453,0.8696289,0.21728516,0.14611816,0.27514648,0.5161133,0.49414062,0.77978516,0.25952148,0.26586914,0.07751465,0.15344238,0.2680664,0.82958984,0.13134766,0.41992188,0.118774414,0.04046631,0.13342285,0.16113281,0.16186523,0.07922363,0.044525146,0.9760742,0.19250488,0.22131348,0.044921875,0.046539307,0.040161133,0.9453125,0.04751587,0.1361084,0.013069153,0.049316406,0.0385437,0.06903076,0.056762695,0.047790527,0.09057617,0.58691406,0.2607422,0.6953125,0.4946289,0.1303711],"featureString":[]}],"subObjects":[]}]
}
export default () => {
const imgRef = useRef(null)
return (
<Space size={[8, 16]} direction="vertical">
<BigImagePreview {...props} ref={imgRef} />
<Space>
<Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}>编辑</Button>
<Button onClick={() => imgRef.current?.setShowCrop(false)}>取消</Button>
</Space>
</Space>
)
}
```
| 参数名 | 参数类型 | 参数说明 |
| ------ | -------- | ---- |
| imageKey | string必填 | 当前大图链接 |
| odRect | { x: number; y: number; w: number; h: number } | 圈选矩形 |
|height|string选填|高度|
|width|string选填|宽度|
|score|string选填|相似度|
|attachImg|{ url: string; label: string; }[](选填)|缩略图|
|objects | IOdRectOrigin[] | 编辑状态参数 |

View File

@ -0,0 +1,540 @@
import React, { useEffect, ReactElement, useState, useCallback, useRef, useImperativeHandle } from 'react';
import classNames from 'classnames';
import { useLatest } from '@zhst/hooks';
import {
get,
pick,
isNull,
generateImg,
dataURLToBlob,
getOdRect,
getExtendRect,
getTransformRect,
getRotateImg,
getTransforms,
addEventListenerWrapper,
upload,
getFileByRect
} from '@zhst/func';
import Align from 'rc-align';
import { Button } from 'antd';
import type { Rect, IScreenshotButtonProp, ODRECT, AlignType, ViewOption, IOdRectOrigin } from '@zhst/types'
import Icon from '../iconfont';
import {
Cropper,
Viewer,
EVENT_VIEWER_TRANSFORM_CHANGE,
EVENT_VIEWER_READY,
EVENT_CROP_START,
EVENT_CROP_END,
} from '../ImageEditor';
import BtnGroup from './components/BtnGroup';
import './index.less'
import getScreenshotButtonRender from './components/ScreenhotButtons';
const componentName = `zhst-image__img-view`;
export const CROP_TYPE = {
CUSTOM: 'CSUTOM',
AUTO: 'AUTO',
};
export const defaultAlignOption = {
points: ['bl', 'br'],
offset: [6, 0],
overflow: {
adjustX: true,
adjustY: true,
},
};
export interface ImgViewProps extends React.HTMLAttributes<HTMLElement> {
imageKey: string; //不在监听url变化 更新走销毁
odRect: ODRECT;
attachImg?: Array<{ label: string; url: string }>; // 缩略图列表
showAttachImgLabel: boolean; // 是否显示缩略图
showOpt: boolean; // 是否显示操作面板
width?: string | number;
height?: string | number;
/* 截图渲染 */
screenshotButtonAlign: AlignType;
screenshotButtonRender: (screenshotButtonProp: IScreenshotButtonProp) => ReactElement;
hideLeftTopBtn?: boolean;
score?: number;
viewOption?: ViewOption;
objects: IOdRectOrigin[]
}
export interface ImgViewRef {
/* 图片实例 */
imgInsRef: React.MutableRefObject<any>;
/* 切换编辑模式 */
setShowCrop: React.Dispatch<React.SetStateAction<boolean>>;
}
const cropBtnDataSource = [
{
key: 'close',
icon: 'icon-danchuangguanbi',
title: '退出',
},
{
key: 'autoCrop',
icon: 'icon-zidong',
title: '智能框选',
},
{
key: 'customCrop',
icon: 'icon-shoudong',
title: '手动框选',
},
];
const operateBtnDataSource = [
{
key: 'zoomOut',
icon: 'icon-fangda',
title: '放大',
},
{
key: 'zoomIn',
icon: 'icon-suoxiao',
title: '缩小',
},
{
key: 'reset',
icon: 'icon-zhongzhi3',
title: '重置图片',
},
];
export const ImgView = React.forwardRef<ImgViewRef, ImgViewProps>((props, ref) => {
const {
imageKey,
width,
height,
odRect,
score = 0,
attachImg = [],
objects = [],
showOpt = false,
showAttachImgLabel = true,
screenshotButtonAlign = defaultAlignOption,
screenshotButtonRender = getScreenshotButtonRender({
onBigImageActionClick: () => {},
disableBtn: [],
}),
hideLeftTopBtn = true,
viewOption = {}
} = props;
const imgContainerRef = React.useRef(null);
const [isReady, setIsReady] = useState(false);
console.log('props', props)
const init = useCallback(($container: null) => {
imgContainerRef.current = $container;
setIsReady(true);
}, []);
// ============================= viewer =========================
const imgInsRef = useRef<any>(null);
const [isImgReady, setIsImgReady] = useState(false);
useEffect(() => {
if (!isReady) return;
const handleReady = addEventListenerWrapper(imgContainerRef.current, EVENT_VIEWER_READY, () => {
setIsImgReady(true);
});
const handleTransformChange = addEventListenerWrapper(
imgContainerRef.current,
EVENT_VIEWER_TRANSFORM_CHANGE,
() => {
}
);
imgInsRef.current = new Viewer(imgContainerRef.current, {
...viewOption,
fitScaleAsMinScale: true,
image: generateImg(imageKey),
});
return () => {
handleReady?.remove();
handleTransformChange?.remove();
imgInsRef?.current?.destroy?.();
imgInsRef.current = null;
};
}, [isReady, imageKey]);
// ============================= viewer操作按钮 =========================
const handleOptClick = (v: string) => {
switch (v) {
case 'zoomOut':
imgInsRef?.current?.scaleTo?.(0.1);
break;
case 'zoomIn':
imgInsRef?.current?.scaleTo?.(-0.1);
break;
case 'reset':
imgInsRef?.current?.reset?.(-0.1);
break;
}
};
// ============================= cropper =========================
// 手动截图相关参数
const cropInsRef = useRef(null);
const [showCrop, setShowCrop] = useState(showOpt);
const [cropType, setCropType] = useState(CROP_TYPE['AUTO']);
// 自动截图相关参数
const [odList, setOdList] = useState([]);
const [extendOdList, setExtendOdList] = useState([]);
const [selectRectId, setSelectRectId] = useState(null);
// 定位按钮相关参数
const aliginContainerRef = useRef(null);
const alignRef = useRef(null);
const [cropRect, setCropRect] = useState<Rect | null>(null);
// 选中的版本号
const [selectAlgorithmVersion, setSelectAlgorithmVersion] = useState(null);
const handlerCropStartRef = useRef(null);
const handlerCropEndRef = useRef(null);
const handleShapeSelectRef = useRef(null);
useEffect(() => {
initData(objects)
return () => {
imgInsRef.current?.clearShape?.();
handlerCropStartRef.current?.remove();
handlerCropEndRef.current?.remove();
handleShapeSelectRef.current?.remove();
cropInsRef?.current?.destroy?.();
cropInsRef.current = null;
};
}, [isImgReady, showCrop, cropType]);
const initData = (_objects: IOdRectOrigin[]) => {
const imgIns = imgInsRef.current;
//清理crop
setCropRect(null);
if (!isImgReady) return;
if (!showCrop) {
imgIns?.addShape?.({
x: get(odRect, 'x', 0),
y: get(odRect, 'y', 0),
w: get(odRect, 'w', 0),
h: get(odRect, 'h', 0),
selectAble: false,
});
return;
}
if (cropType === CROP_TYPE['AUTO']) {
const handleGetOD = (odList: any) => {
const imgSize = imgIns.getImgSize();
const shapeList = odList.map((rect: { [x: string]: any; algorithmVersion: any; }) => ({
...rect,
selectAble: true,
id: rect['id'],
algorithmVersion: rect.algorithmVersion,
}));
imgIns.replaceShape(shapeList);
//顺便吧扩展框拿到
const extendRect = shapeList.map((rect: { algorithmVersion: string; }) => {
const extendRect = getExtendRect(rect, imgSize.w, imgSize.h, rect.algorithmVersion);
return { ...rect, ...extendRect };
});
setExtendOdList(extendRect);
imgIns.replaceShape(shapeList);
handleShapeSelectRef.current = addEventListenerWrapper(imgContainerRef.current, 'shape-select', (e) => {
const id = e.detail;
setSelectRectId(id);
const selectShape = shapeList.find((v) => v['id'] === id);
if (selectShape) {
setSelectAlgorithmVersion(selectShape['algorithmVersion']);
//换算成屏幕坐标
const axisRect = imgIns.imgRectAxisToCanvasAxisRect(selectShape);
const rect = {
x: axisRect.x2 > axisRect.x ? axisRect.x : axisRect.x2,
y: axisRect.y2 > axisRect.y ? axisRect.y : axisRect.y2,
w: Math.abs(axisRect.x2 - axisRect.x),
h: Math.abs(axisRect.y2 - axisRect.y),
};
setCropRect(rect);
} else {
// @ts-ignore
setCropRect(null);
}
});
};
const rect = getOdRect({ objects })
setOdList(rect);
handleGetOD(rect);
}
if (cropType === CROP_TYPE['CUSTOM']) {
// 手动框选状态预先清除imgIns
imgIns?.clearShape?.();
handlerCropStartRef.current = addEventListenerWrapper(imgContainerRef.current, EVENT_CROP_START, (event) => {
setSelectAlgorithmVersion(null);
setCropRect(null);
});
handlerCropEndRef.current = addEventListenerWrapper(imgContainerRef.current, EVENT_CROP_END, (event) => {
const data = event.detail;
setSelectAlgorithmVersion(null);
setCropRect({
x: data.left,
y: data.top,
w: data.width,
h: data.height,
});
alignRef?.current?.forceAlign?.();
});
cropInsRef.current = new Cropper(imgContainerRef.current, {
showMask: true,
viewer: imgIns,
});
}
}
// 获取框选的截图框信息
const latestCropType = useLatest(cropType);
const latestImgKey = useLatest(imageKey);
const latestCropRect = useLatest(cropRect);
const getCropInfo = async () => {
const cropType = latestCropType.current;
const cropRect = latestCropRect.current;
const imgIns = imgInsRef.current;
const transform = imgIns.targetTransform;
let newImgKey = latestImgKey.current;
let rectList = [];
let extendRectList = [];
let selectIndex = 0;
switch (cropType) {
case CROP_TYPE['AUTO']:
const shapes = imgIns.getSelectShape();
const shapeIds = shapes.map((v) => v['id']);
rectList = odList
.filter((v) => shapeIds.includes(v['id']))
.map((item: any) => {
if (
item.algorithmVersion === 'OBJECT_TYPE_FACE' ||
item.objectType === 'OBJECT_TYPE_FACE'
) {
if (!isNull(item.extendBox)) {
return {
...item,
w: get(item, 'extendBox.w'),
h: get(item, 'extendBox.h'),
x: get(item, 'extendBox.x'),
y: get(item, 'extendBox.y'),
};
}
} else {
return item;
}
});
extendRectList = extendOdList
.filter((v) => shapeIds.includes(v['id']))
.map((v) => pick(v, ['x', 'y', 'w', 'h', 'algorithmVersion', 'id']));
selectIndex = rectList.findIndex((v) => v['id'] === selectRectId);
break;
default:
//获取旋转过的坐标
const newRect = getTransformRect(imgIns.image, transform, cropRect);
//判断是不是旋转过
if (get(transform, 'rotate', 0) % 360 != 0) {
const data = getRotateImg(imgIns.image, get(transform, 'rotate', 0));
//在画布上画旋转后的图片
newImgKey = await upload(data);
}
rectList.push(newRect);
extendRectList.push(newRect);
break;
}
//扩展框获取imgkey
await Promise.all(
extendRectList.map(async (rect, index) => {
const file = await getFileByRect(imgIns.image, rect);
const imgKey = await upload(file);
extendRectList[index] = { ...rect, imgKey };
})
);
//人脸图获取矫正图
await Promise.all(
rectList.map(async (rect, index) => {
const faceCorrectImage = rect['faceCorrectImage'];
let faceCorrectImageKey;
if (faceCorrectImage) {
const base64 = `data:image/jpg;base64,${faceCorrectImage}`;
const blobData = dataURLToBlob(base64);
const file = new window.File([blobData], `${new Date().getTime()}`);
faceCorrectImageKey = await upload(file);
}
const newRect = {
...rect,
...(faceCorrectImageKey ? { faceCorrectImageKey } : {}),
};
delete newRect['faceCorrectImage'];
rectList[index] = newRect;
})
);
return { rectList, extendRectList, selectIndex, imgKey: newImgKey };
};
// 操作界面判断
const handleCropBtnClick = (v: string) => {
switch (v) {
case 'close':
setShowCrop(false);
break;
case 'autoCrop':
setCropType(CROP_TYPE['AUTO']);
break;
case 'customCrop':
setCropType(CROP_TYPE['CUSTOM']);
break;
}
};
// ============================= attact img =========================
const showAttachImg = attachImg.length !== 0;
const [selectAttachImgIndex, setSelectAttachImgIndex] = useState(0);
const [isZoomin, setIsZoomin] = useState(false);
// ============================== Ref ===============================
useImperativeHandle(ref, () => ({
imgInsRef,
setShowCrop,
initData,
}));
return (
<div className={classNames(`${componentName}`)} style={{ height, width }}>
{/*场景图大图 */}
<div
className={classNames(
`${componentName}-main`,
cropType === CROP_TYPE['AUTO'] && `${componentName}-main--cursor`
)}
ref={init}
/>
{/* 图片操作 */}
{!hideLeftTopBtn && (
<BtnGroup
className={classNames(`${componentName}-opt`)}
dataSource={operateBtnDataSource}
onClick={handleOptClick}
placement="left"
/>
)}
{showCrop && (
<BtnGroup
circle
className={classNames(`${componentName}-crop-opt`)}
dataSource={cropBtnDataSource}
onClick={handleCropBtnClick}
selectKey={cropType === CROP_TYPE['AUTO'] ? 'autoCrop' : 'customCrop'}
/>
)}
{showCrop && cropRect && screenshotButtonRender && (
<>
<div
ref={aliginContainerRef}
className={classNames(`${componentName}-align`)}
style={Object.assign(
{
width: cropRect.w,
height: cropRect.h,
},
getTransforms({
translateX: cropRect.x,
translateY: cropRect.y,
})
)}
></div>
<Align
ref={alignRef}
monitorWindowResize
align={screenshotButtonAlign}
target={function () {
return aliginContainerRef.current;
}}
>
{screenshotButtonRender({
model: 'IMAGE',
getCropInfo,
setShowCrop,
cropType,
selectAlgorithmVersion,
})}
</Align>
</>
)}
{/* 场景图小图 */}
{showAttachImg && !showCrop && (
<div
className={classNames(
`${componentName}-attach`,
isZoomin && `${componentName}-attach--zoomin`,
`${componentName}-attach--fixed`,
isZoomin && `${componentName}-attach--zoomin--fixed`
)}
>
<div className={classNames(`${componentName}-attach__tab`)}>
{showAttachImgLabel
? attachImg.map(({ label, url }, index) => (
<div
key={index}
className={classNames(
`${componentName}-attach__tab-item`,
selectAttachImgIndex === index && `${componentName}-attach__tab-item--select`
)}
onMouseEnter={() => {
setSelectAttachImgIndex(index);
}}
>
{label}
</div>
))
: null}
</div>
<div className={classNames(`${componentName}-attach__scale`)}>
<Button
type="text"
//绝对定位下onClick事件失效采用onMouseDown
onMouseDown={(e) => {
//如果是左键执行
if (e.button == 0) {
setIsZoomin((pre) => !pre);
}
}}
style={{
color: '#fff',
}}
>
<Icon
styles={{ display: 'flex' }}
icon={isZoomin ? 'icon-cancle_fullscreen' : 'icon-fullscreen'}
/>
</Button>
</div>
<img
draggable="false"
className={classNames(
`${componentName}-attach__img`,
`${componentName}-attach__img--fixed`
)}
src={get(attachImg, `${selectAttachImgIndex}.url`, '')}
/>
</div>
)}
<div
style={{ bottom: 20 }}
className={classNames(`${componentName}__face-score`)}
>{`人脸质量分:${(score as number).toFixed(2)}`}</div>
</div>
);
});
ImgView.displayName = 'ImgView';
export default ImgView;

View File

@ -1,6 +1,6 @@
//@ts-nocheck
import { addClass, removeClass } from 'rc-util/lib/Dom/class';
import { addEventlistener, isNumber, get } from '@zhst/func';
import { addEventListenerWrapper, isNumber, get } from '@zhst/func';
import warn from 'rc-util/lib/warn';
import { getData, getPointer, getOffset, dispatchEvent } from '../utils';
import {
@ -36,13 +36,13 @@ export default {
bind() {
const { container, element, eventHandleList = [], option } = this;
const handleCropStart = addEventlistener(
const handleCropStart = addEventListenerWrapper(
container,
EVENT_POINTER_DOWN,
this.onCropStart.bind(this)
);
eventHandleList.push(handleCropStart);
const handleCropMove = addEventlistener(
const handleCropMove = addEventListenerWrapper(
element.ownerDocument,
EVENT_POINTER_MOVE,
this.onCropMove.bind(this)
@ -51,7 +51,7 @@ export default {
EVENT_POINTER_UP.trim()
.split(REGEXP_SPACES)
.forEach((eventName) => {
const handleCropEnd = addEventlistener(
const handleCropEnd = addEventListenerWrapper(
element.ownerDocument,
eventName,
this.onCropEnd.bind(this)

View File

@ -1,5 +1,5 @@
//@ts-nocheck
import {addEventlistener} from '@zhst/func';
import {addEventListenerWrapper} from '@zhst/func';
import { EVENT_WHEEL } from './constants';
import { EVENT_VIEWER_TRANSFORM_CHANGE } from '../viewer/constants';
@ -10,12 +10,12 @@ export default {
if (this.options.viewer) {
const viewer = this.options.viewer;
//添加缩放事件
const handleWhele = addEventlistener(container, EVENT_WHEEL, this.onWheel.bind(this));
const handleWhele = addEventListenerWrapper(container, EVENT_WHEEL, this.onWheel.bind(this));
eventHandleList.push(handleWhele);
//添加事件监听 获取limit crop box & 渲染canvas
this.onTransformChange(viewer);
this.limited = true;
const handleTransformChange = addEventlistener(
const handleTransformChange = addEventListenerWrapper(
viewer.element,
EVENT_VIEWER_TRANSFORM_CHANGE,
(event) => {

View File

@ -1,5 +1,5 @@
//@ts-nocheck
import { isNumber, get, addEventListener } from '@zhst/func';
import { isNumber, get, addEventListenerWrapper } from '@zhst/func';
import { addClass, removeClass } from 'rc-util/lib/Dom/class.js';
import guid from 'rc-util/lib/guid';
import warn from 'rc-util/lib/warn';
@ -33,19 +33,19 @@ export default {
//图片事件
const scaleAble = get(options, 'scaleAble', true);
if (scaleAble) {
const handleWhele = addEventListener(canvas, EVENT_WHEEL, this.onWheel.bind(this));
const handleWhele = addEventListenerWrapper(canvas, EVENT_WHEEL, this.onWheel.bind(this));
eventHandleList.push(handleWhele);
}
const dragAble = get(options, 'dragAble', true);
if (dragAble) {
const handleDragStart = addEventListener(
const handleDragStart = addEventListenerWrapper(
canvas,
EVENT_POINTER_DOWN,
this.onDragStart.bind(this)
);
eventHandleList.push(addEventListener);
const handleDragMove = addEventListener(
eventHandleList.push(addEventListenerWrapper);
const handleDragMove = addEventListenerWrapper(
element.ownerDocument,
EVENT_POINTER_MOVE,
this.onDragMove.bind(this)
@ -54,7 +54,7 @@ export default {
EVENT_POINTER_UP.trim()
.split(REGEXP_SPACES)
.forEach((eventName) => {
const handleDragEnd = addEventListener(
const handleDragEnd = addEventListenerWrapper(
element.ownerDocument,
eventName,
this.onDragEnd.bind(this)
@ -64,11 +64,11 @@ export default {
}
//rect事件
const handleClick = addEventListener(canvas, EVENT_CLICK, this.onClick.bind(this));
const handleClick = addEventListenerWrapper(canvas, EVENT_CLICK, this.onClick.bind(this));
eventHandleList.push(handleClick);
// const handleLeveal = addEventListener(canvas, EVENT_LEAVEL, this.onLeavel.bind(this));
// const handleLeveal = addEventListenerWrapper(canvas, EVENT_LEAVEL, this.onLeavel.bind(this));
// eventHandleList.push(handleLeveal);
// const handleEnter = addEventListener(canvas, EVENT_ENTER, this.onEnter.bind(this));
// const handleEnter = addEventListenerWrapper(canvas, EVENT_ENTER, this.onEnter.bind(this));
// eventHandleList.push(handleEnter);
},
unbind() {

View File

@ -1,5 +1,5 @@
import React, { useRef, useState, FC, useEffect, useCallback } from 'react'
import { generateImg, get, addEventListener } from '@zhst/func';
import { generateImg, get, addEventListenerWrapper } from '@zhst/func';
import { useUpdateEffect } from '@zhst/hooks';
import { Button } from 'antd';
import classNames from 'classnames'
@ -43,7 +43,7 @@ const CompaterImage: FC<CompaterImageProps> = (props) => {
useEffect(() => {
setShowUrl(url);
const handleTransformChange = addEventListener(
const handleTransformChange = addEventListenerWrapper(
imgContainerRef.current,
'viewer-transform-change',
(event: any) => {

View File

@ -0,0 +1,116 @@
import React, { Component, CSSProperties } from 'react';
import flvjs from 'flv.js';
import { isEqual } from '@zhst/func';
export const FLV_EVENT = flvjs.Events;
export interface VideoPlayerProps {
className: string;
style?: CSSProperties;
type: string;
isLive?: boolean;
cors?: boolean;
withCredentials?: boolean;
playId?: number;
hasAudio?: boolean;
hasVideo?: boolean;
duration?: number;
filesize?: number;
url?: string;
autoPlay?: boolean;
onCreat?: any;
/**
* @see https://github.com/Bilibili/flv.js/blob/master/docs/api.md#config
*/
config: object;
}
export default class VideoPlayer extends Component<VideoPlayerProps, any> {
state = {
curPlayUrl: '',
shouldReinit: false,
};
flvPlayer = null;
videoElement = null;
static getDerivedStateFromProps = (nextProps: { url?: any; playId?: any; }, prevState: { curPlayUrl?: any; playId?: any; }) => {
const { playId = 0 } = nextProps;
const { playId: prePlayId = 0 } = prevState;
if (nextProps.url !== undefined) {
if (!isEqual(nextProps.url, prevState.curPlayUrl) || !isEqual(playId, prePlayId)) {
return {
playId: playId,
curPlayUrl: nextProps.url,
shouldReinit: true,
};
}
}
// 否则对于state不进行任何操作
return null;
};
initFlv = ($video: null) => {
this.videoElement = $video;
const { className, autoPlay = true, config = {}, onCreat, playId, ...others } = this.props;
if ($video) {
if (flvjs.isSupported() && this.props.url && this.props.url !== '') {
const reload = () => {
if (this.flvPlayer && this.flvPlayer.destroy) {
try {
this.flvPlayer?.destroy();
} catch (error) {
console.error(error);
}
}
let flvPlayer = flvjs.createPlayer({ ...others }, config);
flvPlayer.attachMediaElement($video);
flvPlayer.load();
this.flvPlayer = flvPlayer;
this.flvPlayer.reload = reload;
onCreat && onCreat(this.flvPlayer, $video);
};
reload();
onCreat && onCreat(this.flvPlayer, $video);
}
}
};
componentWillUnmount() {
if (this.flvPlayer) {
this.flvPlayer?.unload();
this.flvPlayer?.detachMediaElement();
}
}
componentDidUpdate() {
if (this.state.shouldReinit) {
this.setState({ shouldReinit: false });
this.initFlv(this.videoElement);
}
}
render() {
const { className, style } = this.props;
return (
<video
muted
preload="metadata"
className={className}
// controls={true}
style={Object.assign(
{
width: '100%',
height: '100%',
},
style ? style : {}
)}
ref={this.initFlv}
/>
);
}
}

View File

@ -0,0 +1,53 @@
.zhst-image__video-view__player-mask {
position: absolute;
width: 100%;
height: 100%;
z-index: 99;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgb(4 4 4 / 70%);
&--bg {
z-index: 999;
background-color: rgb(4 4 4 / 100%);
}
i {
cursor: pointer;
}
&-title {
margin-top: 12px;
color: #fff;
text-align: center;
& a {
color: #09f;
cursor: pointer;
text-decoration: underline;
}
}
}
.zhst-image__video-view__icon-wraper {
display: flex;
width: 80px;
height: 80px;
align-items: center;
justify-content: center;
background-color: rgb(255 255 255 / 10%);
border-radius: 50%;
cursor: pointer;
line-height: 80px;
text-align: center;
// &:hover {
// background: #0099ff;
// }
}

View File

@ -0,0 +1,72 @@
import React, { FC } from 'react'
import { Spin } from 'antd'
import classNames from 'classnames'
import Icon from '../../../iconfont'
import './index.less'
const componentName = `zhst-image__video-view`;
export interface ILoading {
status: 'LOADING' | 'COMPLETED' | 'END' | 'ERROR'
reload: () => void
}
const Loading: FC<ILoading> = (props) => {
const { status, reload } = props
return (
<div
className={classNames(`${componentName}__player-mask`)}
onClick={(e) => {
e.stopPropagation();
}}
>
{/* todo图标颜色 */}
{status === 'LOADING' && (
<Spin tip="加载中..." spinning={status === 'LOADING'} />
)}
{status === 'END' && (
<>
<div
onClick={() => {
reload();
}}
className={classNames(`${componentName}__icon-wraper`)}
>
<Icon
styles={{
fill: '#ffffff',
color: '#ffffff',
}}
size={54}
icon={'icon-shuaxin'}
/>
</div>
<div className={classNames(`${componentName}__player-mask-title`)}>
{'点击刷新后,将重新播放'}
</div>
</>
)}
{status === 'ERROR' && (
<>
<div className={classNames(`${componentName}__icon-wraper`)}>
<Icon
styles={{
color: '#ffffff',
}}
size={54}
icon={'icon-jiazaishibai'}
/>
</div>
<div className={classNames(`${componentName}__player-mask-title`)}>
{'视频加载失败,'}
<a onClick={reload}> {'刷新'}</a>
</div>
</>
)}
</div>
)
}
export default Loading

View File

@ -0,0 +1,49 @@
.zhst-image__range {
position: relative;
&--no-slider {
.next-range-slider {
display: none;
}
}
& .next-range .next-range-track {
height: 8px;
margin-top: -4px;
border-radius: 8px;
}
& .next-range .next-range-selected {
height: 8px;
margin-top: -4px;
border-radius: 8px;
}
& .next-range .next-range-slider-inner {
width: 14px;
height: 14px;
border-color: #fff;
margin-top: -7px;
margin-left: -7px;
background-color: #0098ff;
}
& .next-range .next-range-slider {
width: 14px;
height: 14px;
margin-top: -7px;
margin-left: -7px;
}
& .next-range.simulation-click>.next-range-slider-inner {
border: 2px solid #fff !important;
}
& .next-range .next-range-frag.next-range-active .next-range-slider .next-range-slider-inner {
border: 2px solid #fff !important;
}
& .next-range .next-range-slider.next-range-slider-moving .next-range-slider-inner {
border: 2px solid #fff !important;
}
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import classNames from 'classnames';
import { Slider } from 'antd';
import './index.less';
const componentName = `zhst-image__range`;
export interface RangeWrapperProps extends React.HTMLAttributes<HTMLElement> {
showSlider: boolean;
}
export const Range: React.FC<RangeWrapperProps> = (props) => {
const { className, style, showSlider = true, ...others } = props;
return (
<div
style={style}
className={classNames(
`${componentName}`,
!showSlider && `${componentName}--no-slider`,
className
)}
>
{/* @ts-ignore */}
<Slider {...others}></Slider>
</div>
);
};
export default Range;

View File

@ -0,0 +1,75 @@
.zhst-image__video-view {
position: relative;
overflow: hidden;
width: 100%;
height: 532px;
background-color: #333;
// &-flv {
// width: 85%;
// }
&-screenshot {
position: absolute;
z-index: 10;
}
&-crop-container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&-align {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
&-opt {
position: absolute;
z-index: 99;
bottom: 0;
display: flex;
width: 100%;
height: 32px;
box-sizing: border-box;
align-items: center;
padding: 0 12px;
background-color: rgb(0 0 0 / 80%);
line-height: 32px;
&>div:first-child {
display: flex;
align-items: center;
margin-right: 12px;
}
&>div:last-child {
display: flex;
align-items: center;
margin-left: 12px;
}
&-range {
display: flex;
height: 32px;
flex: 1;
align-items: center;
line-height: 32px;
text-align: center;
&>div:first-child {
flex: 1;
}
&>div:last-child {
width: 100px;
margin-left: 8px;
color: #fff;
}
}
}
}

View File

@ -0,0 +1,27 @@
---
nav:
title: 元组件
order: 1
group:
title: 通用
order: 3
---
# VideoPlayer 视频播放
```jsx
import React from 'react';
import { VideoPlayer } from '@zhst/meta'
const props = {
"flvUrl":"ws://10.0.0.120:9033/flv/HaikangNvr/45.flv?ip=10.0.2.103&stime=1705051970&etime=1705051990",
"maxDuration":20,
"showCrop$":false
}
export default () => {
return (
<VideoPlayer {...props} />
)
}
```

View File

@ -0,0 +1,696 @@
import React, { Dispatch, ReactElement, SetStateAction, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import {
noop,
get,
addEventListenerWrapper,
dataURLToBlob,
nextTick,
toRealNumber,
getTransforms,
formatDurationTime
} from '@zhst/func';
import Align from 'rc-align';
import { Rect, IScreenshotButtonProp, AlignType } from '@zhst/types'
import { useLatest, useUpdateEffect, useFullscreen, useUnmount } from '@zhst/hooks';
import classNames from 'classnames';
import download from 'downloadjs';
import { Button, message } from 'antd';
import Icon from '../iconfont';
import {
Cropper,
EVENT_CROP_START,
EVENT_CROP_END,
} from '../ImageEditor';
import FlvPlayer, { FLV_EVENT } from './components/FlvPlayer';
import Range from './components/Progress';
import Loading, { ILoading } from './components/Loading';
import getScreenshotButtonRender from '../BigImagePreview/components/ScreenhotButtons';
import './index.less'
const componentName = `zhst-image__video-view`;
export const CROP_TYPE = {
CUSTOM: 'CSUTOM',
AUTO: 'AUTO',
};
export function getShowStatus(isLoadingVideo: boolean, isEnd: boolean, isError: boolean): ILoading['status'] {
let status = null;
if (isLoadingVideo) {
status = 'LOADING';
}
if (isError) {
status = 'ERROR';
}
if (isEnd) {
status = 'END';
}
return status;
}
export interface VideoViewProps {
/* 播放地址 */
flvUrl: string;
/* 播放总时间 */
maxDuration?: number;
/* 截图渲染 */
screenshotButtonAlign?: AlignType;
screenshotButtonRender?: (screenshotButtonProp: IScreenshotButtonProp) => ReactElement;
/* 默认截图框 */
defautlNormalizationRect?: Rect;
/* 截图回调 */
onCropChange?: (showCrop: boolean, normalizationRect: null | Rect) => void;
}
export interface VideoViewRef {
/* 当前图片模式 */
cropAble: boolean;
setShowCrop: Dispatch<SetStateAction<boolean>>;
downloadVideoframe: () => void;
}
const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
const {
flvUrl,
maxDuration,
screenshotButtonAlign = {
points: ['bl', 'br'],
offset: [6, 0],
overflow: {
adjustX: true,
adjustY: true,
},
},
screenshotButtonRender = getScreenshotButtonRender({
onBigImageActionClick: () => {},
disableBtn: [],
}),
onCropChange,
defautlNormalizationRect: defaultNormalizationRect,
} = props;
const videoType = useMemo(() => (flvUrl && flvUrl.startsWith('http') ? 'mp4' : 'flv'), [flvUrl]);
// ========================== 播放 =========================
//实例参数
const containerRef = useRef(null); //容器ref
const videoRef = useRef(null); //video 标签dom
const videoInsRef: any = useRef(null); //flv 实例
const [playSeq, setPlaySeq] = useState(0); // 通过重置playid使FLV组件重新渲染
const videoRemoveListener = useRef(noop); //移除dom监听的中间函数
const loadingTimeRef = useRef<number | null>(0); //最后一次加载时间
const delayLoadingTimer = useRef(null); //算loading的定时器
//状态参数
const [isReady, setIsReady] = useState(false); //
const [isPlay, setIsPlay] = useState(false); //当前是否播放
const [isEnd, setIsEnd] = useState(false); //是否播放结束
const [isError, setIsError] = useState(false); //播放出错
const [isVideoLoadFinished, setIsVideoLoadFinish] = useState(false); //是否缓存加载完成
const [playTime, setPlayTime] = useState(0); //当前播放时间
const [isLoadingVideo, setIsLoadingVideo] = useState(true); //是否加载中
const [isDelayLoading, setIsDelayLoading] = useState(false); //显示的转圈loading 延迟0.2s显示
//设置延迟转圈圈
const latestIsLoadingVideo = useLatest(isLoadingVideo);
const setIsLoadingVideoWrapper = (isLoading: boolean) => {
setIsLoadingVideo((preLoading) => {
if (!preLoading && isLoading) {
loadingTimeRef.current = new Date().getTime();
}
if (!isLoading) {
loadingTimeRef.current = null;
}
//延迟0。2s相关
if (!isLoading) {
setIsDelayLoading(false);
}
if (!delayLoadingTimer.current && preLoading) {
delayLoadingTimer.current = setTimeout(() => {
if (latestIsLoadingVideo.current) {
//0.2s后才显示
setIsDelayLoading(true);
}
delayLoadingTimer.current = null;
}, 200);
}
return isLoading;
});
};
// 初始化loading 30s 直接显示错误
// TODO :逻辑忘记了 不应该是每次init player吗
useEffect(() => {
let timer = setInterval(() => {
if (loadingTimeRef.current) {
if (new Date().getTime() - loadingTimeRef.current > 1000 * 30) {
checkIsErr()
}
}
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
//结束的时候暂停 保证不播了
useUpdateEffect(() => {
if (isEnd) {
videoInsRef?.current?.pause?.();
}
}, [isEnd]);
// 捕捉视频播放报错
const checkIsErr = () => {
setIsError(true)
try {
videoInsRef?.current?.destroy?.();
} catch (error) {
console.error(error);
}
}
// 初始化
const latestMaxDuration = useLatest(maxDuration);
const initPlayer = useCallback((ins: any, dom: any) => {
videoRef.current = dom;
videoInsRef.current = ins;
const maxDuration = latestMaxDuration.current || 0;
//监听播放事件
let video = dom;
let errorLister = (e: any) => {
checkIsErr();
console.error('视频出错了', e, video.currentTime);
};
let waitingListener = (e: any) => {
setIsLoadingVideoWrapper(true);
console.debug('视频加载等待', e, video.currentTime);
};
let playingListener = (e: any) => {
setIsLoadingVideoWrapper(false);
setIsError(false)
console.debug('视频从等待中播放', e, video.currentTime);
};
let playLister = (e: any) => {
setIsPlay(true);
setIsError(false)
console.debug('提示该视频正在播放中', e, video.currentTime);
};
let pauseListener = (e: any) => {
setIsPlay(false);
console.debug('暂停播放', e, video.currentTime);
};
let endedListner = (e: any) => {
setIsEnd(true);
setIsVideoLoadFinish(true);
console.debug('视频播放完了', e, video.currentTime);
};
let timeupdateListner = (e: any) => {
console.debug(
'视频播放时间更新',
e,
video.currentTime,
videoRef.current?.duration,
maxDuration
);
let nowTime = video.currentTime;
if (nowTime >= maxDuration) {
setIsEnd(true);
setIsVideoLoadFinish(true);
}
setPlayTime(nowTime);
};
// see https://github.com/bilibili/flv.js/issues/337
let windowErrorHandle = (errorEvent: { [x: string]: string; }) => {
try {
if (
errorEvent['message'] ==
"Uncaught TypeError: Cannot read property 'flushStashedSamples' of null"
) {
checkIsErr();
console.error('视频出错了 window监听', errorEvent);
}
} catch (error) {
console.error(error);
}
};
video.addEventListener('error', errorLister);
video.addEventListener('waiting', waitingListener);
video.addEventListener('playing', playingListener);
video.addEventListener('play', playLister);
video.addEventListener('pause', pauseListener);
video.addEventListener('ended', endedListner);
video.addEventListener('timeupdate', timeupdateListner);
window.addEventListener('error', windowErrorHandle);
videoRemoveListener.current = () => {
video.removeEventListener('error', errorLister);
video.removeEventListener('waiting', waitingListener);
video.removeEventListener('playing', playingListener);
video.removeEventListener('play', playLister);
video.removeEventListener('pause', pauseListener);
video.removeEventListener('ended', endedListner);
video.removeEventListener('timeupdate', timeupdateListner);
window.removeEventListener('error', windowErrorHandle);
};
videoInsRef?.current.on(FLV_EVENT.ERROR, (type: any, errDetail: any, info: any) => {
checkIsErr();
console.error('videoInsRef 错误', type, errDetail, info, video.currentTime);
});
let playPromise = videoInsRef?.current.play();
//先ready 遮挡会导致播放失败
setIsReady(true);
playPromise
.then(() => {
setIsReady(true);
})
.catch((...arg: any) => {
try {
} catch (error) {}
// setIsError(true);
console.error('playPromise视频出错了', arg);
});
}, []);
useUnmount(() => {
try {
videoRemoveListener.current();
} catch (e) {
console.error(e);
}
});
const reload = async () => {
if (videoInsRef.current) {
let oldTime = videoInsRef.current.currentTime;
videoInsRef.current.currentTime = 0;
//如果修改时间不成功,则走重新加载的逻辑
if (oldTime === videoInsRef.current.currentTime) {
//重置状态
setIsReady(false);
setIsPlay(false);
setIsLoadingVideoWrapper(false);
setIsReady(false);
setIsEnd(false);
setIsVideoLoadFinish(false);
setPlayTime(0);
//清楚dom事件监听
try {
videoRemoveListener.current();
} catch (error) {
console.error(error);
}
setPlaySeq((pre) => pre + 1);
return;
}
videoInsRef.current.play();
}
setPlayTime(0);
setIsEnd(false);
};
const seek = (v: string) => {
if (videoInsRef.current && isVideoLoadFinished) {
setPlayTime(parseFloat(v));
videoInsRef.current.currentTime = parseFloat(v);
} else {
message.warning('待视频加载完,才可操作进度条')
}
};
// ========================== 视频opt bar =========================
const [isFullscreen, { toggleFullscreen }] = useFullscreen(containerRef, {
pageFullscreen: true,
});
const showMaxDuration = !!maxDuration
? maxDuration
: toRealNumber(get(videoRef, 'current.duration', 0));
const showSlider = videoInsRef.current && isVideoLoadFinished;
const showStatus = getShowStatus(isDelayLoading, isEnd, isError);
// ========================== 截图 =========================
const corpContainerRef = useRef();
const cropInsRef = useRef(null);
const [showCrop, setShowCrop] = useState<boolean>(false);
//回显默认框选
const isFirstFlagRef = useRef<boolean>(true);
useEffect(() => {
const isFirst = isFirstFlagRef.current;
if (!isLoadingVideo && isReady && isFirst && defaultNormalizationRect && !showStatus) {
nextTick(() => {
setShowCrop(true);
});
}
}, [isLoadingVideo, showStatus]);
//定位按钮相关参数
const alginContainerRef = useRef(null);
const alignRef = useRef(null);
const [cropRect, setCropRect] = useState<Rect| null>(null);
useEffect(() => {
showCrop ? videoInsRef?.current?.pause() : videoInsRef?.current?.play();
}, [showCrop]);
useEffect(() => {
let handlerCropStart: { remove: () => void; };
let handlerCropEnd: { remove: () => void; };
setCropRect(null);
if (!isReady) return;
if (showCrop) {
handlerCropStart = addEventListenerWrapper(corpContainerRef.current, EVENT_CROP_START, (event) => {
setCropRect(null);
});
handlerCropEnd = addEventListenerWrapper(corpContainerRef.current, EVENT_CROP_END, (event) => {
const data = event.detail;
setCropRect({
x: data.left,
y: data.top,
w: data.width,
h: data.height,
});
alignRef?.current?.forceAlign?.();
});
let video: any = videoRef.current;
//计算limitcroppbox
let scale = Math.min(
video.offsetWidth / video.videoWidth,
video.offsetHeight / video.videoHeight
);
let finalVideoWidth = video.videoWidth * scale;
let finalVideoHeight = video.videoHeight * scale;
let cropBoxLimited = {
width: finalVideoWidth,
height: finalVideoHeight,
top: (video.offsetHeight - finalVideoHeight) / 2,
left: (video.offsetWidth - finalVideoWidth) / 2,
};
//获取视频图片
let canvas = document.createElement('canvas');
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
canvas.style.display = 'none';
document.body.appendChild(canvas);
let ctx = canvas.getContext('2d');
ctx.drawImage(
video,
(video.offsetWidth - finalVideoWidth) / 2,
(video.offsetHeight - finalVideoHeight) / 2,
finalVideoWidth,
finalVideoHeight
);
let imageData = canvas.toDataURL('image/png');
canvas.parentNode?.removeChild(canvas);
//回显编辑框
const isFirst = isFirstFlagRef.current;
let initialCropBoxData = null;
if (isFirst && defaultNormalizationRect) {
initialCropBoxData = {
left: defaultNormalizationRect.x * finalVideoWidth + cropBoxLimited.left,
top: defaultNormalizationRect.y * finalVideoHeight + cropBoxLimited.top,
width: defaultNormalizationRect.w * finalVideoWidth,
height: defaultNormalizationRect.h * finalVideoHeight,
};
}
isFirstFlagRef.current = false;
cropInsRef.current = new Cropper(corpContainerRef.current, {
showMask: true,
cropBoxLimited,
img: imageData,
initialCropBoxData,
});
}
return () => {
handlerCropStart?.remove();
handlerCropEnd?.remove();
cropInsRef?.current?.destroy?.();
cropInsRef.current = null;
};
}, [showCrop, isReady]);
const latestCropRect = useLatest(cropRect);
const getCropInfo = async () => {
const cropRect = latestCropRect.current as any;
let video: any = videoRef.current;
if (!video) return
let rectList = [];
let extendRectList = [];
let selectIndex = 0;
//获取视频图片的url
let scale = Math.min(
video.offsetWidth / video.videoWidth,
video.offsetHeight / video.videoHeight
);
let finalVideoWidth = video.videoWidth * scale;
let finalVideoHeight = video.videoHeight * scale;
let canvas = document.createElement('canvas');
canvas.width = finalVideoWidth;
canvas.height = finalVideoHeight;
canvas.style.display = 'none';
document.body.appendChild(canvas);
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx.drawImage(
video,
0,
0,
finalVideoWidth,
finalVideoHeight
);
let base64 = canvas.toDataURL('image/jpeg');
const blobData = dataURLToBlob(base64);
canvas.parentNode?.removeChild(canvas);
const file = new window.File([blobData], `${new Date().getTime()}`);
let newRect = {
w: cropRect.w / finalVideoWidth,
h: cropRect.h / finalVideoHeight,
x: (cropRect.x - (video.offsetWidth - finalVideoWidth) / 2) / finalVideoWidth,
y: (cropRect.y - (video.offsetHeight - finalVideoHeight) / 2) / finalVideoHeight
};
rectList.push(newRect);
extendRectList.push(newRect);
//扩展框获取imgkey
extendRectList.forEach(async (rect, index) => {
extendRectList[index] = {
...rect,
};
})
return {
rectList,
extendRectList,
selectIndex,
file
};
};
//回调
useEffect(() => {
//计算归一化crop rect
let normalizationRect = null;
if (showCrop && cropRect) {
let video: any = videoRef.current;
let scale = Math.min(
video.offsetWidth / video.videoWidth,
video.offsetHeight / video.videoHeight
);
let finalVideoWidth = video.videoWidth * scale;
let finalVideoHeight = video.videoHeight * scale;
let cropBoxLimited = {
width: finalVideoWidth,
height: finalVideoHeight,
top: (video.offsetHeight - finalVideoHeight) / 2,
left: (video.offsetWidth - finalVideoWidth) / 2,
};
normalizationRect = {
x: (cropRect.x - cropBoxLimited.left) / cropBoxLimited.width,
y: (cropRect.y - cropBoxLimited.top) / cropBoxLimited.height,
w: cropRect.w / cropBoxLimited.width,
h: cropRect.h / cropBoxLimited.height,
};
}
onCropChange?.(showCrop, normalizationRect);
}, [showCrop, cropRect]);
// ========================== 截帧 =========================
const downloadVideoframe = useCallback(async () => {
try {
videoInsRef?.current?.pause?.();
let video: any = videoRef.current;
var canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')
let base64;
//当视频处于还未加载出来时,截屏为黑色图片
if (video.readyState === 0) {
ctx?.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
// @ts-ignore
ctx.fillStyle = 'black';
ctx?.fillRect(0, 0, canvas.width, canvas.height);
base64 = canvas.toDataURL();
} else {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
base64 = canvas.toDataURL('image/png');
}
download(base64);
} catch (error) {
console.error(error);
}
}, []);
// ============================== 暴露出去的方法 ===============================
const latestIsReady = useLatest(isReady);
const cropAble = !showStatus && isReady;
useImperativeHandle(ref, () => ({
cropAble,
setShowCrop: (dispatch) => {
const isReady = latestIsReady.current;
if (!isReady) return;
setShowCrop(dispatch);
},
downloadVideoframe,
}));
return (
<div className={classNames(`${componentName}`)} ref={containerRef}>
{flvUrl && (
<>
<FlvPlayer
playId={playSeq}
autoPlay={true}
className={classNames(`${componentName}-flv`)}
type={videoType}
url={flvUrl}
config={{
enableStashBuffer: true,
stashInitialSize: 1024 * 700,
isLive: true,
hasAudio: false,
hasVideo: true,
}}
onCreat={initPlayer}
/>
{/* //截图 */}
<div
className={classNames(`${componentName}-crop-container`)}
ref={corpContainerRef}
style={{
display: isFullscreen ? 'none' : 'block',
}}
>
{/* <div ref={corpRef}></div> */}
</div>
{showCrop && cropRect && screenshotButtonRender && (
<>
<div
ref={alginContainerRef}
className={classNames(`${componentName}-align`)}
style={Object.assign(
{
width: cropRect.w,
height: cropRect.h,
},
getTransforms({
translateX: cropRect.x,
translateY: cropRect.y,
})
)}
></div>
<Align
ref={alignRef}
monitorWindowResize
align={screenshotButtonAlign}
target={function () {
return alginContainerRef.current;
}}
>
{screenshotButtonRender({
model: 'IMAGE',
getCropInfo,
setShowCrop,
cropType: CROP_TYPE['CUSTOM'],
})}
</Align>
</>
)}
{/* 视频进度条 */}
{!showCrop && (
<div className={`${componentName}-opt`}>
<div>
<Button
type="text"
onClick={(e) => {
if (!isPlay) {
//播放中暂停
videoInsRef?.current?.play();
setShowCrop(false);
} else {
videoInsRef?.current?.pause();
}
}}
>
<Icon
styles={{
color: '#fff',
display: 'flex',
}}
color="#1890ff"
size={18}
icon={!isPlay ? 'icon-shipinbofang' : 'icon-shipinzanting'}
/>
</Button>
</div>
<div
className={`${componentName}-opt-range`}
onClick={(e) => {
e.stopPropagation();
}}
>
<Range
value={playTime}
min={0}
max={showMaxDuration}
hasTip={false}
showSlider={showSlider}
onChange={seek}
/>
<div>
{/* TODO: 删除扩展方法format */}
{formatDurationTime(playTime)}/{formatDurationTime(showMaxDuration)}
</div>
</div>
<div>
<Button
type="text"
onClick={(e) => {
e.stopPropagation();
toggleFullscreen();
}}
>
<Icon
styles={{
color: '#fff',
display: 'flex',
}}
size={18}
icon={isFullscreen ? 'icon-cancle_fullscreen' : 'icon-fullscreen'}
/>
</Button>
</div>
</div>
)}
{/* mask */}
{!!showStatus && (
<Loading status={showStatus} reload={() => reload()} />
)}
</>
)}
</div>
);
});
export default VideoPlayer;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import './iconfont.css'
interface IconFontProps {
styles?: React.CSSProperties;

View File

@ -1,3 +1,5 @@
export { default as doubleClick } from './doubleClick';
export { default as Icon } from './iconfont';
export { default as ImagePreview } from './ImagePreview'
export { default as BigImagePreview } from './BigImagePreview'
export { default as VideoPlayer } from './VideoPlayer'

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'father';
export default defineConfig({
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
esm: { output: 'es' },
cjs: { output: 'lib' },
});

View File

@ -0,0 +1,19 @@
# @zhst/request
## 0.2.4
### Patch Changes
- feat: 新增 meta 包
- Updated dependencies
- @zhst/func@0.2.4
- @zhst/hooks@0.2.4
- @zhst/meta@0.2.4
## 0.2.3
### Patch Changes
- fix: 调试包 link
- Updated dependencies
- @zhst/hooks@0.2.3

View File

@ -0,0 +1,14 @@
## 介绍
zhst 请求库
## 安装
> pnpm install @zhst/request
## 使用
```js
import React from 'react';
import request from '@zhst/request'
```

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
{
"name": "@zhst/request",
"version": "0.2.4",
"description": "请求库",
"keywords": [
"request",
"umi-request"
],
"license": "ISC",
"author": "dev",
"sideEffects": [
"dist/*",
"es/**/style/*",
"lib/**/style/*",
"*.less"
],
"main": "lib/index.ts",
"module": "es/index.js",
"typings": "es/index.d.ts",
"exports": {
"./package.json": "./package.json"
},
"files": [
"es",
"lib"
],
"scripts": {
"build": "father build"
},
"publishConfig": {
"access": "public",
"registry": "http://10.0.0.77:4874"
},
"dependencies": {
"antd": "^5.12.5",
"base-64": "^1.0.0",
"lodash-es": "^4.17.21",
"umi-request": "^1.4.0"
},
"devDependencies": {
"@types/base-64": "^1.0.2",
"@types/lodash-es": "^4.17.12",
"@zhst/types": "workspace:^"
}
}

View File

@ -0,0 +1,8 @@
---
nav:
title: 请求库
order: 1
title: 快速上手
---
<embed src="../README.md" ></embed>

View File

@ -0,0 +1,149 @@
import { extend } from 'umi-request';
import type { RequestOptionsInit } from 'umi-request';
import { omit, get } from 'lodash-es';
import { message } from 'antd';
import { User } from '@zhst/types/user';
import base64 from 'base-64';
export class ResponseError<D> extends Error {
name;
data;
response;
request;
type;
constructor(
response: Response,
text: string,
data: D,
request: {
url: string;
options: RequestOptionsInit;
},
type = 'ResponseError'
) {
super(text || response.statusText);
this.name = 'ResponseError';
this.data = data;
this.response = response;
this.request = request;
this.type = type;
}
}
export const req = extend({
getResponse: true,
// timeout: 1000,
parseResponse: false,
});
//错误处理中间件
req.use(async (ctx, next) => {
const { req } = ctx;
const { toast = true } = req?.options || {};
try {
await next();
const { res } = ctx;
const d = await res.text();
if (res.status === 401) {
localStorage.removeItem(User.TOKEN_KEY);
localStorage.removeItem(User.USER_KEY);
message.warning('登录过期,请重新登录!');
return;
}
const isEmptyRes = d === ''; //有些后端接口成功会返回空 做下兼容
if (!res) return
const body = !isEmptyRes ? JSON.parse(d) : d;
if (res.status >= 200 && res.status < 300) {
ctx.res = body;
} else {
// 先判断Grpc-Metadata-Errorx-Message
let errMsg = res.headers.get('Grpc-Metadata-Errorx-Message');
if (errMsg) {
errMsg = window?.utf8?.decode(base64.decode(errMsg));
// 后判断 body中的message
} else if (!errMsg && get(body, 'message')) {
errMsg = `${get(body, 'message')}`;
} else {
// 最后看状态码
errMsg = '您的网络发生异常,无法连接服务器';
}
toast && message.error(errMsg);
throw new ResponseError(res, errMsg, d, req, 'CustomError');
}
} catch (error) {
if (get(error, 'type') !== 'CustomError') {
toast && message.error('您的网络发生异常,无法连接服务器');
}
throw error;
}
});
export interface OPTION extends RequestOptionsInit {
toast?: boolean;
}
interface CGI {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
url: string;
baseUrl?: string;
data?: { [key: string]: any };
useBaseUrl?: boolean;
originUrl?: boolean;
refererSuffix?: string;
}
export const doRequest = <T>(cgi: CGI, option?: OPTION): Promise<T> => {
const {
method,
url,
baseUrl,
data = {},
useBaseUrl = true,
originUrl = false,
refererSuffix = '',
} = cgi;
const token = localStorage.getItem(User.TOKEN_KEY);
const userInfo = localStorage.getItem(User.USER_KEY)
? JSON.parse(localStorage.getItem(User.USER_KEY)!)
: null;
let newUrl = '';
if (useBaseUrl) {
newUrl = `${baseUrl}${url}`;
} else {
// 本地Mock -- http://127.0.0.1:4523/m1/2822485-0-default
// 7环境gateway -- http://10.0.0.7:32223
newUrl = `http://10.0.0.7:32223${url}`; // 7环境进行调试
}
if (originUrl) {
newUrl = url;
}
// 对于 /:id 类的 url 进行参数填充
const regex = /\/:(\w+)/g; // 替换 url 参数
const params = [];
let matches;
while ((matches = regex.exec(newUrl)) != null) {
if (matches[1]) {
params.push(matches[1]);
}
}
params.forEach(function (name) {
let d = data?.[name];
if (d == null) {
d = '';
}
newUrl = newUrl.replace(`:${name}`, d);
});
//通过method 判断解析成data / params
const newData = omit(data, params);
const paramObj = method.toLowerCase() === 'get' ? { params: newData } : { data: newData };
return req(newUrl, {
method: method,
...paramObj,
...option,
headers: {
authorization: token!,
...(refererSuffix ? { zhst_referer: `${baseUrl}${refererSuffix}` } : {}),
},
}) as Promise<T>;
};
export default doRequest;

28
packages/types/BigImageModal.d.ts vendored Normal file
View File

@ -0,0 +1,28 @@
export enum IBigImageOpt {
ADD_CONDITION, //添加目标
ADD_TRACK_POINT, //添加到轨迹
ADD_HISTORY, //全息检索
PLAY_VIDEO, //视频播放按钮
ADD_CONDITION_WITH_CROP,
ADD_HISTORY_WITH_CROP,
SWITCH_DIALOG_MODE,
NO_IMAGE_DOWNLOAD_BTN,
NO_VIDEO_DOWNLOAD_BTN,
ADD_HISTORY_WITH_CROP_BODY, //以人搜人,搜形体
ADD_HISTORY_WITH_CROP_VEHICLE, //搜非机动车
ADD_STORAGE_RACK, //加入暂存架
ADD_HISTORY_WITH_CROP_ARCHIVE, //添加到档案检索
ADD_HISTORY_ARCHIVE, //添加到档案检索
ADD_ARCHIVE, //添加到档案库
CREATE_MONITOR, //创建布控
ADD_HISTORY_WITH_CROP_FACE,
ADD_HISTORY_WITH_CROP_CAR,
BOX_SELECTION,
DELETE_TRACK, //删除轨迹
ADD_SMART_TRACK_IMAGE, //跳去智能追踪
TECHNICAL_WARFARE_APPLICATION, //技战应用
PEER_ANALYSIS, //同行人分析
FLAG_BY_BACK_SEARCH, //背影搜脸
ADD_ARCHIVE_WITH_HK, //添加到档案库
ADD_CURRENT_SRARCH, //添加到当前检索(目标检索)
}

View File

@ -4,12 +4,12 @@
## 安装
> pnpm install @zhst/constants
> pnpm install @zhst/types
## 使用
```js
import React from 'react';
import { TYPE } from '@zhst/constants'
import type { User } from '@zhst/types'
```

View File

@ -0,0 +1,278 @@
/// <reference path="BigImageModal.d.ts" />
export * from './BigImageModal'
export type CamerasStatusList = [string[], string[], string[]];
export type Rect = { x: number; y: number; w: number; h: number };
export type StatusList = {
taskOverview: any;
taskIndex: {
deviceId: string;
solutionId: string;
};
}[];
export enum OperationType {
OPERATION_TYPE_UNKNOW, // 未知状态,传该值会报错
OPERATION_TYPE_START,
OPERATION_TYPE_STOP
}
export enum AlgorithmVersion {
VERSION_MGN_BNN, // MGN+BNN算法
VERSION_BNN_PRO, // BNNPRO算法
VERSION_BNN_PRO_ATTR, // BNNPROATTR算法
VERSION_BNN_PRO_ATTR_SCORE, // BNNPROATTR算法
VERSION_FACE, // 人脸算法
VERSION_HEAD, // 头肩算法
VERSION_NON_MOTOR_VEHICLE, // 非机动车的算法
VERSION_REID_HEAD_ATTR, // 形体头肩属性三种特征融合的算法
VERSION_MOTOR_VEHICLE, // 机动车的算法
}
export enum AlgorithmVersionStr {
VERSION_MGN_BNN = 'VERSION_MGN_BNN', // MGN+BNN算法
VERSION_BNN_PRO = 'VERSION_BNN_PRO', // BNNPRO算法
VERSION_BNN_PRO_ATTR = 'VERSION_BNN_PRO_ATTR', // BNNPROATTR算法
VERSION_BNN_PRO_ATTR_SCORE = 'VERSION_BNN_PRO_ATTR_SCORE', // BNNPROATTR算法
VERSION_FACE = 'VERSION_FACE', // 人脸算法
VERSION_HEAD = 'VERSION_HEAD', // 头肩算法
VERSION_NON_MOTOR_VEHICLE = 'VERSION_NON_MOTOR_VEHICLE', // 非机动车的算法
VERSION_REID_HEAD_ATTR = 'VERSION_REID_HEAD_ATTR', // 形体头肩属性三种特征融合的算法
VERSION_MOTOR_VEHICLE = 'VERSION_MOTOR_VEHICLE', // 机动车的算法
}
export enum ObjectType {
OBJECT_TYPE_NULL,
OBJECT_TYPE_PEDESTRAIN,
OBJECT_TYPE_BICYCLE,
OBJECT_TYPE_CAR,
OBJECT_TYPE_MOTORBIKE,
OBJECT_TYPE_AEROPLANE,
OBJECT_TYPE_BUS,
OBJECT_TYPE_TRAIN,
OBJECT_TYPE_TRUCK,
OBJECT_TYPE_MOTOR_RIDER,
OBJECT_TYPE_BIKE_RIDER,
OBJECT_TYPE_MAX,
OBJECT_TYPE_FACE = 101,
}
// 性别
export enum Gender {
GENDER_NONE = 'GENDER_NONE',
GENDER_MAN = 'GENDER_MAN',
GENDER_WOMAN = 'GENDER_WOMAN',
}
// 年龄
export enum Age {
AGE_ALL = 'AGE_ALL',
AGE_ZERO = 'AGE_ZERO',
AGE_OVERENGHTEEN = 'AGE_OVERENGHTEEN',
AGE_OVERSIXTY = 'AGE_OVERSIXTY',
}
// 戴帽子状态
export enum Hat {
HAT_ALL = 'HAT_ALL',
HAT_NONE = 'HAT_NONE',
HAT_OWNED = 'HAT_OWNED',
}
// 颜色
export enum Color {
COLOR_ALL = 'COLOR_ALL',
COLOR_BLACK = 'COLOR_BLACK',
COLOR_WHITE = 'COLOR_WHITE',
COLOR_GRAY = 'COLOR_GRAY',
COLOR_BROWN = 'COLOR_BROWN',
COLOR_PINK = 'COLOR_PINK',
COLOR_REDANDORANGE = 'COLOR_REDANDORANGE',
COLOR_YELLOW = 'COLOR_YELLOW',
COLOR_GREEN = 'COLOR_GREEN',
COLOR_BLUE = 'COLOR_BLUE',
COLOR_PURPLE = 'COLOR_PURPLE',
}
// 背包
export enum Package {
PACKAGE_ALL = 'PACKAGE_ALL',
PACKAGE_HANDBAG = 'PACKAGE_HANDBAG',
PACKAGE_BACKPACK = 'PACKAGE_BACKPACK',
PACKAGE_SHOULDERBAG = 'PACKAGE_SHOULDERBAG',
PACKAGE_OTHER = 'PACKAGE_OTHER',
PACKAGE_NONE = 'PACKAGE_NONE',
}
// 行走模式
export enum WalkPattern {
WALKPATTERN_ALL = 'WALKPATTERN_ALL',
WALKPATTERN_WALK = 'WALKPATTERN_WALK',
WALKPATTERN_RIDING = 'WALKPATTERN_RIDING',
}
// 人类属性
export interface HumanProperty {
age: Age;
downColor: Color;
gender: Gender;
hat: Hat;
package: Package;
upColor: Color;
walkPattern: WalkPattern;
}
// 人类属性枚举
export interface EnumHumanProperty {
Gender: typeof Gender;
Age: typeof Age;
Hat: typeof Hat;
Color: typeof Color;
Package: typeof Package;
WalkPattern: typeof WalkPattern;
}
export interface IScreenshotButtonProp {
model: 'VIDEO' | 'IMAGE';
getCropInfo: () => Promise<RESP>;
setShowCrop: React.Dispatch<React.SetStateAction<boolean>>;
cropType: typeof cropType[number];
selectAlgorithmVersion: number | null;
}
export interface AlignType {
/**
* move point of source node to align with point of target node.
* Such as ['tr','cc'], align top right point of source node with center point of target node.
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
points?: AlignPoint[];
/**
* offset source node by offset[0] in x and offset[1] in y.
* If offset contains percentage string value, it is relative to sourceNode region.
*/
offset?: number[];
/**
* offset target node by offset[0] in x and offset[1] in y.
* If targetOffset contains percentage string value, it is relative to targetNode region.
*/
targetOffset?: number[];
/**
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
*/
overflow?: {
adjustX?: boolean | number;
adjustY?: boolean | number;
};
/**
* Whether use css right instead of left to position
*/
useCssRight?: boolean;
/**
* Whether use css bottom instead of top to position
*/
useCssBottom?: boolean;
/**
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
* Defaults to false.
*/
useCssTransform?: boolean;
ignoreShake?: boolean;
}
export type ODRECT = {
topleft: {
x: number;
y: number;
};
width: number;
height: number;
};
export interface ViewOption {
/* 图片url */
image?: string | HTMLImageElement;
/* 缩放灵敏度(0,1],default: 0.1 */
wheelZoomRatio?: number;
/*
*
* @default: true
*/
scaleAble?: boolean;
/*
*
* @default: true
*/
dragAble?: boolean;
/*
* fit scale
* @default: false
*/
fitScaleAsMinScale?: boolean;
}
export type IOdRectOrigin {
objectIndex: {
objectId: string
solutionId: string
deviceId: string
fragmentId: string
}
objectType: ObjectType,
sourceObjectId: string,
level: string
confidence: string | number,
pathInfo: any,
frameInfo: {
frameId: string
frameTimestamp: string | number
width: number
height: number
originWidth: number
originHeight: number
offsetTime: number
skipNumber: number
},
deviceInfo: any
infoOnSource:{
bboxInSource:any
bboxInFrame: {
bbox: any,
bboxRatio: Rect
extendBbox: any
extendBoxRatio: number
},
countInSource: any
indexInSource: any
},
qualityScore: number
frameImage: any
objectImage: any
objectExtImage: any
feature: {
name: string
type: ObjectType,
featureId: number
featureByte: string
featureBool:any[]
featureUint8: any[]
featureUint16: any[]
featureUint32: any[]
featureUint64: any[]
featureInt8: any[]
featureInt16: any[]
featureInt32: any[]
featureInt64: any[]
featureFloat32: any[]
featureString: any[]
}
subObjects: any[]
}

View File

@ -11,13 +11,15 @@
"license": "ISC",
"author": "dev",
"main": "",
"typings": "es/index.d.ts",
"typings": "index.d.ts",
"exports": {
".": {
"types": {
"default": "./index.d.ts"
}
},
"./user": "./user.d.ts",
"./BigImageModal": "./BigImageModal.d.ts",
"./package.json": "./package.json"
},
"scripts": {

View File

@ -1,7 +1,7 @@
---
nav:
title: types
order: 1
title: 类型定义
order: 99
title: 快速上手
---

4
packages/types/user.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export enum User {
TOKEN_KEY = 'USER-TOKEN',
USER_KEY = 'USER'
}

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