Compare commits

..

No commits in common. 'develop' and 'master' have entirely different histories.

@ -4,4 +4,4 @@ VITE_PORT = 8848
# 是否隐藏首页 隐藏 true 不隐藏 false 勿删除VITE_HIDE_HOME只需在.env文件配置 # 是否隐藏首页 隐藏 true 不隐藏 false 勿删除VITE_HIDE_HOME只需在.env文件配置
VITE_HIDE_HOME = false VITE_HIDE_HOME = false
# 开发环境后端地址 # 开发环境后端地址
VITE_APP_BASE_URL = 'http://192.168.10.70:8080' VITE_APP_BASE_URL = 'http://192.168.10.21:8000'

@ -7,4 +7,4 @@ VITE_PUBLIC_PATH = ./
# 开发环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数" # 开发环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash" VITE_ROUTER_HISTORY = "hash"
# 开发环境后端地址 # 开发环境后端地址
VITE_APP_BASE_URL = 'http://192.168.10.70:8080' VITE_APP_BASE_URL = 'http://192.168.10.21:8000'

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2024-01-12 14:35:28 * @Date: 2024-01-12 14:35:28
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-26 14:40:41 * @LastEditTime: 2024-03-25 11:16:58
* @FilePath: \General-AI-Platform-Web-Client\index.html * @FilePath: \General-AI-Platform-Web-Client\index.html
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
--> -->
@ -21,7 +21,7 @@
<!-- update 2024-08-14 11:05 --> <!-- update 2024-08-14 11:05 -->
<link <link
rel="stylesheet" rel="stylesheet"
href="//at.alicdn.com/t/c/font_4412653_jsay2r4m3wm.css" href="//at.alicdn.com/t/c/font_4412653_5ql8iuyd77a.css"
/> />
<script src="https://threejs.org/build/three.js"></script> <script src="https://threejs.org/build/three.js"></script>

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-02 10:40:49 * @Date: 2024-08-02 10:40:49
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-28 16:58:13 * @LastEditTime: 2024-08-14 11:10:51
* @FilePath: \General-AI-Platform-Web-Client\src\api\workshops.ts * @FilePath: \General-AI-Platform-Web-Client\src\api\workshops.ts
* @Description: * @Description:
*/ */
@ -30,36 +30,7 @@ export const getWorkshopsApi = (params?: object) => {
/** 新增布点 */ /** 新增布点 */
export const addWorkshopsApi = (data?: object) => { export const addWorkshopsApi = (data?: object) => {
return http.request<Result>( return http.request<Result>("post", baseUrlApi("workshops/"), { data });
"post",
baseUrlApi("workshops/"),
{ data },
{
headers: { "Content-Type": "multipart/form-data" }
}
);
};
/** 编辑布点 data有id */
export const editWorkshopsApi = (data?: object) => {
return http.request<Result>(
"post",
baseUrlApi("workshops/"),
{ data },
{
headers: { "Content-Type": "multipart/form-data" }
}
);
};
/** 删除布点 status: 1 */
export const deleteWorkshopsApi = (data?: object) => {
return http.request<Result>(
"post",
baseUrlApi("workshops/"),
{ data: { ...data, status: 1 } },
{
headers: { "Content-Type": "multipart/form-data" }
}
);
}; };
/** 获取布点设备 */ /** 获取布点设备 */
@ -75,17 +46,3 @@ export const addWorkshopDevicesApi = (data?: object) => {
data data
}); });
}; };
/** 编辑布点设备 data有id */
export const editWorkshopDevicesApi = (data?: object) => {
return http.request<Result>("post", baseUrlApi("workshop_devices/"), {
data
});
};
/** 删除布点设备 */
export const deleteWorkshopDevicesListApi = (data?: Array) => {
return http.request<Result>("delete", baseUrlApi("workshop_devices/"), {
data: { ids: data }
});
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 MiB

After

Width:  |  Height:  |  Size: 30 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

@ -1,10 +0,0 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-14 17:19:37
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-20 15:32:48
* @FilePath: \General-AI-Platform-Web-Client\src\components\Action\index.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import isAction from "./src/isAction";
export const IsAction = isAction;

@ -1,16 +0,0 @@
.action_model_wrap {
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 40px 24px 14px;
}
.model_content_box {
.icon_box {
img {
width: 24px;
height: 24px;
}
}
}
}

@ -1,84 +0,0 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-20 15:31:30
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-23 16:59:59
* @FilePath: \General-AI-Platform-Web-Client\src\components\TableActionCard\isAction.tsx
* @Description:
*/
import { defineComponent, PropType } from "vue";
import { ElDialog, ElButton } from "element-plus";
import warnIcon from "@/assets/modelSetting/warn_icon.png";
import "./isAction.scss";
export default defineComponent({
name: "IsDelete",
props: {
title: {
type: String as PropType<string>,
default: "提示"
},
message: {
type: String as PropType<string>,
default: "确定要操作吗?"
},
visible: {
type: Boolean as PropType<boolean>
// required: true
}
},
emits: ["update:visible", "confirm"],
setup(props, { emit }) {
const { visible } = toRefs(props);
const localVisible = ref<boolean>(visible.value);
const handleConfirm = () => {
emit("confirm");
// visible.value = false;
// emit("update:visible", false);
};
const handleCancel = () => {
// localVisible.value = false;
emit("update:visible", false);
};
const handleClose = () => {
// localVisible.value = false;
emit("update:visible", false);
// done();
};
watch(visible, newVal => {
localVisible.value = newVal;
});
return () => (
<ElDialog
className="action_model_wrap el-dialog"
visible={localVisible.value}
width="560px"
before-close={handleClose}
show-close={false}
v-slots={{
footer: () => (
<>
<ElButton onClick={handleCancel}></ElButton>
<ElButton type="primary" onClick={handleConfirm}>
</ElButton>
</>
)
}}
>
<div className="model_content_box">
<div className="flex items-center pr-[12px] icon_box">
<img src={warnIcon} />
<span className="hf-1 pl-[12px]">{props.title}</span>
</div>
<div className="pl-[36px] pt-[14px]">
<span className="pf-1" v-html={props.message}></span>
</div>
</div>
</ElDialog>
);
}
});

@ -1,11 +1,3 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-02-22 13:38:04
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-09-24 09:38:20
* @FilePath: \general-work-web\General-AI-Platform-Web-Client\src\components\CustomTree\src\collapseTree.tsx
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { defineComponent, Transition } from "vue"; import { defineComponent, Transition } from "vue";
import CollapseTreeItem from "./collapseTreeItem"; import CollapseTreeItem from "./collapseTreeItem";
import "./collapseTreeStyle.scss"; import "./collapseTreeStyle.scss";
@ -24,6 +16,7 @@ export default defineComponent({
// const count = ref<number>(1); // const count = ref<number>(1);
// const treeData = ref(props.value); // const treeData = ref(props.value);
// console.log(treeData.value); // console.log(treeData.value);
return () => { return () => {
return ( return (
<div class="collapseTree_wrap"> <div class="collapseTree_wrap">

@ -1,3 +0,0 @@
import isDelete from "./src/isDelete";
export const IsDelete = isDelete;

@ -1,69 +0,0 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-02-22 13:38:04
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-14 15:56:12
* @FilePath: \General-AI-Platform-Web-Client\src\components\TableActionCard\isDelete.tsx
* @Description:
*/
import { defineComponent, ref, PropType } from "vue";
import { ElDialog, ElButton } from "element-plus";
export default defineComponent({
name: "IsDelete",
props: {
title: {
type: String as PropType<string>,
default: "提示"
},
message: {
type: String as PropType<string>,
default: "确定要删除吗?"
},
modelValue: {
type: Boolean as PropType<boolean>,
required: true
}
},
emits: ["update:modelValue", "confirm"],
setup(props, { emit }) {
const visible = ref(props.modelValue);
const handleConfirm = () => {
emit("confirm");
visible.value = false;
emit("update:modelValue", false);
};
const handleCancel = () => {
visible.value = false;
emit("update:modelValue", false);
};
const handleClose = (done: () => void) => {
visible.value = false;
done();
};
return () => (
<ElDialog
title={props.title}
modelValue={visible.value}
width="30%"
before-close={handleClose}
v-slots={{
footer: () => (
<>
<ElButton onClick={handleCancel}></ElButton>
<ElButton type="danger" onClick={handleConfirm}>
</ElButton>
</>
)
}}
>
<span>{props.message}</span>
</ElDialog>
);
}
});

@ -1 +0,0 @@
export const workspaceIDConf = "workspace";

@ -2,7 +2,7 @@
* @Author: zhoux zhouxia@supervision.ltd * @Author: zhoux zhouxia@supervision.ltd
* @Date: 2023-11-29 09:31:35 * @Date: 2023-11-29 09:31:35
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-26 11:50:25 * @LastEditTime: 2024-08-06 17:12:48
* @FilePath: \vue-fabric-editor\src\core\ServersPlugin.ts * @FilePath: \vue-fabric-editor\src\core\ServersPlugin.ts
* @Description: * @Description:
*/ */
@ -71,7 +71,7 @@ class ServersPlugin {
}); });
} }
insertSvgFile(jsonFile, callback) { insertSvgFile(jsonFile) {
console.log(jsonFile, "insertSvgFile"); console.log(jsonFile, "insertSvgFile");
// 加载前钩子 // 加载前钩子
this.editor.hooksEntity.hookImportBefore.callAsync(jsonFile, () => { this.editor.hooksEntity.hookImportBefore.callAsync(jsonFile, () => {
@ -80,8 +80,6 @@ class ServersPlugin {
// 加载后钩子 // 加载后钩子
this.editor.hooksEntity.hookImportAfter.callAsync(jsonFile, () => { this.editor.hooksEntity.hookImportAfter.callAsync(jsonFile, () => {
this.canvas.renderAll(); this.canvas.renderAll();
callback && callback();
console.log(this.canvas, "afterInsertSvgFile");
}); });
}); });
}); });

@ -2,7 +2,7 @@
* @Author: * @Author:
* @Date: 2023-06-13 23:00:43 * @Date: 2023-06-13 23:00:43
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-15 16:28:40 * @LastEditTime: 2024-08-07 17:17:54
* @Description: * @Description:
*/ */
@ -174,15 +174,9 @@ function deleteControl(canvas: fabric.Canvas) {
if (target.action === "rotate") return true; if (target.action === "rotate") return true;
const activeObject = canvas.getActiveObjects(); const activeObject = canvas.getActiveObjects();
if (activeObject) { if (activeObject) {
// 发送删除事件 activeObject.map(item => canvas.remove(item));
canvas.fire("object:deleteObject", { canvas.requestRenderAll();
...activeObject, canvas.discardActiveObject();
deleteCallback: () => {
activeObject.map(item => canvas.remove(item));
canvas.requestRenderAll();
canvas.discardActiveObject();
}
});
} }
return true; return true;
} }

@ -2,13 +2,12 @@
* @Author: * @Author:
* @Date: 2023-06-20 12:57:35 * @Date: 2023-06-20 12:57:35
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-15 15:51:16 * @LastEditTime: 2024-08-06 17:14:38
* @Description: * @Description:
*/ */
import { fabric } from "fabric"; import { fabric } from "fabric";
import Editor from "../core"; import Editor from "../core";
type IEditor = Editor; type IEditor = Editor;
// import { v4 as uuid } from 'uuid'; // import { v4 as uuid } from 'uuid';

@ -2,7 +2,7 @@
* @Author: * @Author:
* @Date: 2023-06-27 12:26:41 * @Date: 2023-06-27 12:26:41
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-21 15:59:11 * @LastEditTime: 2024-08-06 17:30:13
* @Description: * @Description:
*/ */
@ -201,12 +201,12 @@ class WorkspacePlugin {
// 自动缩放 // 自动缩放
auto() { auto() {
const scale = this._getScale(); const scale = this._getScale();
this.setZoomAuto(scale); this.setZoomAuto(scale - 0.08);
} }
// 1:1 放大 // 1:1 放大
one() { one() {
this.setZoomAuto(0.8); this.setZoomAuto(0.8 - 0.08);
this.canvas.requestRenderAll(); this.canvas.requestRenderAll();
} }

@ -48,7 +48,7 @@
/* 自定义 tooltip 的类名 */ /* 自定义 tooltip 的类名 */
.pure-tooltip { .pure-tooltip {
/* 右侧操作面板right-panel类名的z-index为40000tooltip需要大于它才能显示 */ // right-panelz-index40000tooltip
z-index: 41000 !important; z-index: 41000 !important;
} }
@ -193,6 +193,8 @@
} }
/* table */ /* table */
//
.table_action_box { .table_action_box {
.el-button { .el-button {
padding: 0; padding: 0;
@ -205,22 +207,3 @@
.el-table__inner-wrapper::before { .el-table__inner-wrapper::before {
background-color: transparent !important; background-color: transparent !important;
} }
/* 气泡框 */
.el-popover.el-popper {
padding: 16px !important;
}
/* dialog 弹框 */
.el-dialog {
background-color: transparent !important;
background-image: url("@/assets/dialogBg.png") !important;
background-size: 100% 100% !important;
background-repeat: none-repetet !important;
}
/* button */
.el-button {
font-weight: 400 !important;
}

@ -1,19 +0,0 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-14 14:42:09
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-14 14:43:30
* @FilePath: \General-AI-Platform-Web-Client\src\utils\forApi.ts
* @Description:
*/
/**
* @
* @param result
* @returns boolean
*/
export function isSuccessApi(result): boolean {
if ([200].includes(result.status)) {
return true;
}
return false;
}

@ -189,15 +189,6 @@ class PureHttp {
): Promise<P> { ): Promise<P> {
return this.request<P>("get", url, params, config); return this.request<P>("get", url, params, config);
} }
/** 单独抽离的上传工具函数 */
// public uploadFile<T, P>(
// url: string,
// params?: AxiosRequestConfig<T>,
// config?: PureHttpRequestConfig
// ): Promise<P> {
// return this.request<P>("post", url, params, config);
// }
} }
export const http = new PureHttp(); export const http = new PureHttp();

@ -214,7 +214,7 @@ onMounted(() => {
:sm="24" :sm="24"
:md="12" :md="12"
:lg="12" :lg="12"
:xl="8" :xl="12"
> >
<DeviceCard :device="device" /> <DeviceCard :device="device" />
</el-col> </el-col>

@ -12,22 +12,3 @@
text-transform: none; text-transform: none;
} }
} }
.point_dialog_wrap {
.el-dialog__header {
padding: 16px 24px 0;
}
.el-dialog__body {
padding: 24px 24px 0;
}
.el-icon--upload {
font-size: 40px;
line-height: 40px;
}
.el-upload-dragger {
padding: 20px;
}
.el-upload__text {
line-height: 18px;
color: #333;
}
}

@ -2,92 +2,65 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-07 14:47:44 * @Date: 2024-08-07 14:47:44
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-26 13:59:21 * @LastEditTime: 2024-08-12 15:34:22
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\components\add.vue * @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\components\add.vue
* @Description: 新建位置 * @Description: 新建位置
--> -->
<script setup lang="ts"> <script setup lang="ts">
// import type { FormInstance } from "element-plus"; // import type { FormInstance } from "element-plus";
import historyAlarm from "@/assets/history_alarm.png";
import { UploadFilled } from "@element-plus/icons-vue";
import { message } from "@/utils/message"; import { message } from "@/utils/message";
import { addWorkshopsApi } from "@/api/workshops";
import { isSuccessApi } from "@/utils/forApi";
defineOptions({ defineOptions({
name: "DeviceSettingAdd" name: "DeviceSettingAdd"
}); });
const emit = defineEmits(["finishAdd"]);
const formData = ref({ const formData = ref({
name: "", name: "",
picture: null file: null as File | null
}); });
const formRef = ref(null); const formRef = ref(null);
const uploadRef = ref(null);
const imagePreview = ref<string | null>(null); // URL
const dialogVisible = ref<boolean>(false); const dialogVisible = ref<boolean>(false);
const fileList = ref([]); const fileList = ref([]);
const rules = { const rules = {
name: [{ required: true, message: "请输入位置名称", trigger: "blur" }], name: [{ required: true, message: "请输入位置名称", trigger: "blur" }],
picture: [{ required: true, message: "请上传图片文件", trigger: "change" }] file: [{ required: true, message: "请上传图片文件", trigger: "change" }]
}; };
const uploadAction = ""; //
const openDialog = () => { const openDialog = () => {
dialogVisible.value = true; dialogVisible.value = true;
}; };
const handleFileChange = (file: any) => { const handleFileChange = (file: any) => {
// 使 FileReader console.log(file, "handleFileChange");
const reader = new FileReader();
reader.onload = e => {
imagePreview.value = e.target?.result as string;
};
reader.readAsDataURL(file.raw);
console.log(file, "handleFileChange", fileList.value);
formData.value = {
...formData.value,
picture: file.raw
};
};
//
const removeFile = () => {
imagePreview.value = null;
formData.value = { formData.value = {
...formData.value, ...formData.value,
picture: null file: "https://img.cgmodel.com/image/2020/1010/big/1537169-1390622992.jpg"
}; };
}; };
const beforeUpload = (file: File) => { const beforeUpload = (file: File) => {
console.log("beforeUpload_file", file);
const isJPG = file.type === "image/jpeg" || file.type === "image/png"; const isJPG = file.type === "image/jpeg" || file.type === "image/png";
const isLt500K = file.size / 1024 / 1024 < 20; const isLt500K = file.size / 1024 / 1024 < 0.5;
if (!isJPG) { if (!isJPG) {
ElMessage.error("上传图片只能是 JPG/PNG 格式!"); ElMessage.error("上传图片只能是 JPG/PNG 格式!");
} }
if (!isLt500K) { if (!isLt500K) {
ElMessage.error("上传图片大小不能超过 20MB!"); ElMessage.error("上传图片大小不能超过 500KB!");
} }
return isJPG && isLt500K; return isJPG && isLt500K;
}; };
async function fetchAddPoint() {
const formParams = new FormData();
formParams.append("name", formData.value.name);
formParams.append("picture", formData.value.picture);
const resp = await addWorkshopsApi(formParams);
console.log(resp, "fetchAddPoint_resp");
if (isSuccessApi(resp)) {
message(resp.msg || "添加成功", { type: "success" });
emit("finishAdd", resp.data);
dialogVisible.value = false;
} else {
message(resp.msg || "添加失败", { type: "error" });
}
}
const submitForm = () => { const submitForm = () => {
formRef.value.validate((valid: boolean) => { formRef.validate((valid: boolean) => {
if (valid) { if (valid) {
fetchAddPoint(); message("提交成功", { type: "success" });
dialogVisible.value = false;
} else { } else {
message("验证失败", { type: "error" }); message("验证失败", { type: "error" });
return false; return false;
@ -95,6 +68,11 @@ const submitForm = () => {
}); });
}; };
// const resetForm = (formEl: FormInstance | undefined) => {
// if (!formEl) return;
// formEl.resetFields();
// };
defineExpose({ defineExpose({
openDialog openDialog
}); });
@ -103,19 +81,18 @@ defineExpose({
<template> <template>
<div> <div>
<el-dialog <el-dialog
class="point_dialog_wrap"
top="5vh" top="5vh"
v-model="dialogVisible" v-model="dialogVisible"
title="新建布点位置" title="新建布点位置"
append-to-body append-to-body
width="640px" width="44.445vw"
:style="{ :style="{
borderRadius: '6px' borderRadius: '6px'
}" }"
> >
<template #header="{ titleId, titleClass }"> <template #header="{ titleId, titleClass }">
<div class="flex items-center my-header"> <div class="flex items-center my-header">
<img src="@/assets/addIcon.png" class="w-[26px] h-[26px]" /> <img :src="historyAlarm" class="w-[26px] h-[26px]" />
<h4 :id="titleId" class="pl-[10px]" :class="titleClass"> <h4 :id="titleId" class="pl-[10px]" :class="titleClass">
新建布点位置 新建布点位置
</h4> </h4>
@ -136,32 +113,28 @@ defineExpose({
<el-form-item <el-form-item
label="上传图片(请上传png或jpeg格式图片文件尺寸不超过4096*3112px容量不超过20M)" label="上传图片(请上传png或jpeg格式图片文件尺寸不超过4096*3112px容量不超过20M)"
class="w-full" class="w-full"
prop="picture" prop="file"
> >
<el-upload <el-upload
ref="uploadRef"
class="w-full" class="w-full"
drag drag
:action="uploadAction"
:on-change="handleFileChange" :on-change="handleFileChange"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:on-remove="removeFile"
:auto-upload="false"
:file-list="fileList" :file-list="fileList"
:show-file-list="false" :limit="1"
accept="image/*" accept="image/*"
> >
<div> <div>
<div v-if="!imagePreview"> <div v-if="!formData?.file">
<el-icon class="el-icon--upload text-[40px]"> <el-icon class="el-icon--upload"><upload-filled /></el-icon>
<img src="@/assets/uploadImageIcon.png" />
</el-icon>
<div class="el-upload__text"> <div class="el-upload__text">
拖拽图片到这里<em>点此添加</em> 拖拽图片到这里或点此添加 <em>点此添加</em>
</div> </div>
</div> </div>
<el-image <el-image
v-if="imagePreview" v-else
:src="imagePreview || ''" :src="formData?.file || ''"
:fit="'contain'" :fit="'contain'"
class="w-full" class="w-full"
/> />

@ -2,59 +2,61 @@
import useSelect from "@/hooks/select"; import useSelect from "@/hooks/select";
import { fetchArrayByAttrObject, setAttrObjectByArray } from "@/utils/utils"; import { fetchArrayByAttrObject, setAttrObjectByArray } from "@/utils/utils";
// //
import { textTypeConf } from "@/config/attribute/baseType";
import video_type_1 from "@/assets/modelSetting/video_type_1.png"; import video_type_1 from "@/assets/modelSetting/video_type_1.png";
import video_type_2 from "@/assets/modelSetting/video_type_2.png"; import video_type_2 from "@/assets/modelSetting/video_type_2.png";
import video_type_3 from "@/assets/modelSetting/video_type_3.png"; import video_type_3 from "@/assets/modelSetting/video_type_3.png";
import {
deleteWorkshopDevicesListApi
// editWorkshopDevicesApi
} from "@/api/workshops";
import { isSuccessApi } from "@/utils/forApi";
import { useDeviceObject } from "../hooks/useDeviceObject";
const props = defineProps({
pointInfo: {
type: Object as Record<string, any>
}
});
const emit = defineEmits(["afterDelete", "editDevice"]);
const event = inject("event"); const event = inject("event");
const update = getCurrentInstance(); const update = getCurrentInstance();
const { mixinState, canvasEditor } = useSelect(); const { mixinState, canvasEditor } = useSelect();
const { fetchViewsBoundaries, isMoveDevice, fetchMoveDeviceObjectOrdinate } =
useDeviceObject();
/**业务属性 */ /**业务属性 */
const formData = ref({ const formData = ref({
name: "", iconType: 1
icon: 1
}); });
const rules = { const rules = {
icon: [{ required: false, message: "请选择图标类型", trigger: "change" }] iconType: [{ required: true, message: "请选择图标类型", trigger: "change" }]
}; };
const iconOptions = ref([ const iconOptions = ref([
{ {
value: "1", value: 1,
label: "图标1", label: "图标1",
type: "1", type: "1",
url: video_type_1 url: video_type_1
}, },
{ {
value: "2", value: 2,
label: "图标2", label: "图标2",
type: "2", type: "2",
url: video_type_2 url: video_type_2
}, },
{ {
value: "3", value: 3,
label: "图标3", label: "图标3",
type: "3", type: "3",
url: video_type_3 url: video_type_3
} }
]); ]);
const formRef = ref(null); const formRef = ref(null);
const submitForm = () => {
formRef.value?.validate((valid: boolean) => {
if (valid) {
//
// axios
// .post("/api/submit-icon", form.value)
// .then(() => {
// ElMessage.success("");
// })
// .catch(() => {
// ElMessage.error("");
// });
} else {
// ElMessage.error("");
}
});
};
/**拓展属性 */ /**拓展属性 */
// //
@ -103,24 +105,37 @@ const fontAttr = reactive({
overline: false overline: false
}); });
//
const getCanvasBoundaries = () => {
const canvasObj = canvasEditor.canvas.getObjects()[1];
return {
width: canvasObj.width,
height: canvasObj.height
};
};
const constrainObjectWithinCanvas = obj => { const constrainObjectWithinCanvas = obj => {
const { startLeft, startTop, endLeft, endTop } = fetchViewsBoundaries( const { width, height } = getCanvasBoundaries();
canvasEditor.canvas,
obj //
); const objLeft = obj.left;
const objTop = obj.top;
const objWidth = obj.width * obj.scaleX;
const objHeight = obj.height * obj.scaleY;
// //
if (obj.left < startLeft) { if (objLeft < 0) {
obj.left = startLeft; obj.left = 0;
} }
if (obj.top < startTop) { if (objTop < 0) {
obj.top = startTop; obj.top = 0;
} }
if (obj.left > endLeft) { if (objLeft + objWidth > width) {
obj.left = endLeft; obj.left = width - objWidth;
} }
if (obj.top > endTop) { if (objTop + objHeight > height) {
obj.top = endTop; obj.top = height - objHeight;
} }
// //
obj.setCoords(); obj.setCoords();
}; };
@ -166,40 +181,9 @@ const getObjectAttr = e => {
const animateObject = activeObject.get("animation"); const animateObject = activeObject.get("animation");
animationAttr.type = animateObject.type; animationAttr.type = animateObject.type;
} }
//
if (activeObject.get("deviceInfo")) {
formData.value = activeObject.get("deviceInfo");
}
} }
}; };
//
async function deleteDeviceObject(deleteObject) {
const currDeviceInfo = deleteObject[0].get("deviceInfo");
console.log(currDeviceInfo, "deleteDeviceObject", deleteObject);
const resp = await deleteWorkshopDevicesListApi([currDeviceInfo.id]);
if (isSuccessApi(resp)) {
deleteObject.deleteCallback();
emit("afterDelete", JSON.stringify([currDeviceInfo.id]));
}
}
function startEditDevice(callback) {
const activeObject = canvasEditor.canvas.getActiveObject();
const currDeviceInfo = activeObject.get("deviceInfo");
const { x_ordinate, y_ordinate } = fetchMoveDeviceObjectOrdinate(
canvasEditor.canvas,
activeObject
);
const editParams = {
id: currDeviceInfo.id,
device_id: currDeviceInfo.device_id,
workshop_id: props.pointInfo?.id,
x_ordinate,
y_ordinate,
icon: formData.value?.icon
};
emit("editDevice", { editParams, callback });
}
const selectCancel = () => { const selectCancel = () => {
baseAttr.fill = ""; baseAttr.fill = "";
update?.proxy?.$forceUpdate(); update?.proxy?.$forceUpdate();
@ -209,20 +193,10 @@ const init = () => {
// //
event.on("selectCancel", selectCancel); event.on("selectCancel", selectCancel);
event.on("selectOne", getObjectAttr); event.on("selectOne", getObjectAttr);
canvasEditor.canvas.on("object:deleteObject", deleteDeviceObject);
canvasEditor.canvas.on("mouse:up", () => {
const activeObject = canvasEditor.canvas.getActiveObject();
//
activeObject &&
activeObject.get("deviceInfo") &&
isMoveDevice(canvasEditor.canvas, activeObject) &&
startEditDevice();
});
canvasEditor.canvas.on("object:modified", getObjectAttr); canvasEditor.canvas.on("object:modified", getObjectAttr);
// object:moving // object:moving
canvasEditor.canvas.on("object:moving", e => { canvasEditor.canvas.on("object:moving", e => {
const activeObject = e.target; const activeObject = e.target;
// console.log(activeObject, "activeObject_moving");
if (activeObject) { if (activeObject) {
constrainObjectWithinCanvas(activeObject); constrainObjectWithinCanvas(activeObject);
canvasEditor.canvas.renderAll(); canvasEditor.canvas.renderAll();
@ -264,28 +238,53 @@ const changeCommon = (key, value) => {
canvasEditor.canvas.renderAll(); canvasEditor.canvas.renderAll();
return; return;
} }
if (key === "icon") { if (key === "iconType") {
// group fabric.Image // group fabric.Image
const imageObject = activeObject._objects[1]; const imageObject = activeObject._objects[1];
if (imageObject) { if (imageObject) {
const targetIcon = toRaw(iconOptions.value).find( const targetIcon = toRaw(iconOptions.value).find(
item => item.value === value item => item.value === value
); );
console.log(targetIcon, "activeObject_iconType", activeObject); console.log(targetIcon, "activeObject_iconType", activeObject);
if (targetIcon) { if (targetIcon) {
// //
startEditDevice(() => { imageObject.setSrc(
imageObject.setSrc( targetIcon.url,
targetIcon.url, () => {
() => { //
// console.log("Image loaded successfully activeObject_iconType");
console.log("Image loaded successfully activeObject_iconType"); activeObject.addWithUpdate(); //
activeObject.addWithUpdate(); // canvasEditor.canvas.renderAll();
canvasEditor.canvas.renderAll(); },
}, { crossOrigin: "anonymous" }
{ crossOrigin: "anonymous" } );
);
}); //
// fabric.Image.fromURL(
// targetIcon.url,
// img => {
// // 使 oldImage.get()
// // img.set(imageObject.get());
// // group
// console.log(img, "old_img", imageObject);
// const index = activeObject._objects.indexOf(imageObject);
// if (index !== -1) {
// activeObject._objects[index] = {
// ...imageObject.get(),
// src: img.src
// }; //
// activeObject.addWithUpdate(); // group
// canvasEditor.canvas.renderAll(); //
// } else {
// console.error("Image object not found in the group.");
// }
// },
// {
// crossOrigin: "anonymous" //
// }
// );
} }
return; return;
} }
@ -303,18 +302,30 @@ onMounted(init);
onBeforeUnmount(() => { onBeforeUnmount(() => {
event.off("selectCancel", selectCancel); event.off("selectCancel", selectCancel);
event.off("selectOne", getObjectAttr); event.off("selectOne", getObjectAttr);
canvasEditor.canvas.off("object:deleteObject");
canvasEditor.canvas.off("mouse:up");
canvasEditor.canvas.off("object:modified", getObjectAttr); canvasEditor.canvas.off("object:modified", getObjectAttr);
canvasEditor.canvas.off("object:moving"); canvasEditor.canvas.off("object:moving");
}); });
</script> </script>
<template> <template>
<div class="deviceAtrr_toolbar" v-if="mixinState.mSelectMode === 'one'"> <div class="box" v-if="mixinState.mSelectMode === 'one'">
<div class="hf-1 text-[#000]"> <!-- 字体属性 -->
<!-- 设备待选列表是name, 新增后返回是device_name --> <div v-show="textTypeConf.includes(mixinState.mSelectOneType)">
{{ formData?.device_name || formData?.name }} <!-- 字体属性 -->
</div> </div>
<!-- ID属性 -->
<!-- <div>
<div class="flex-view">
<div class="flex-item">
<span class="label">{{ $t("attributes.id") }}</span>
<div class="content slider-box">
<input
v-model="baseAttr.id"
@change="changeCommon('id', baseAttr.id)"
/>
</div>
</div>
</div>
</div> -->
<el-form <el-form
ref="formRef" ref="formRef"
:rules="rules" :rules="rules"
@ -323,25 +334,62 @@ onBeforeUnmount(() => {
class="demo-form-inline" class="demo-form-inline"
label-position="top" label-position="top"
> >
<!-- // TODO UI --> <el-form-item label="选择图标" class="w-full" prop="iconType">
<el-form-item label="选择图标" class="w-full pt-[12px]" prop="icon">
<el-radio-group <el-radio-group
v-model="formData.icon" v-model="formData.iconType"
@change="changeCommon('icon', formData.icon)" @change="changeCommon('iconType', formData.iconType)"
> >
<el-radio <el-radio
v-for="option in iconOptions" v-for="option in iconOptions"
:key="option.value" :key="option.value"
:label="option.value" :label="option.value"
> >
<el-image <i>
:src="option?.url || ''" <!-- 图标 -->
:fit="'contain'" </i>
class="w-[32px]" {{ option.label }}
/>
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 动态属性 -->
<!-- <div>
<ul>
<li class="flex-view" v-for="(v, k) in baseAttr.userProperty" :key="k">
<div class="flex-item">
<span class="content slider-box">
<input
v-model="baseAttr.userProperty[k].key"
@change="
changeCommon('userProperty_key', baseAttr.userProperty)
"
/>
</span>
<div class="content slider-box">
<input
v-model="baseAttr.userProperty[k].value"
@change="
changeCommon('userProperty_value', baseAttr.userProperty)
"
/>
</div>
</div>
</li>
<li
class="flex-view"
style="justify-content: center; color: dodgerblue; text-align: center"
@click="
() => {
baseAttr.userProperty.push({
key: 'key' + baseAttr.userProperty.length,
value: 'value'
});
}
"
>
<span>新增一项</span>
</li>
</ul>
</div> -->
</div> </div>
</template> </template>

@ -1,34 +1,55 @@
<!-- <!--
* @Description: 设备选择 * @Description: 设备选择
--> -->
<template>
<div v-if="!mixinState.mSelectMode">
<div class="px-[12px] deviceSelect_toolbar">
<div>
<h4 class="hf-1 py-[12px]">设备列表</h4>
<p class="pf-2">
可直接点击拖拽设备名称至图中目标位置鼠标悬停可查看设备详情
</p>
<ul class="mt-[8px] deviceSelect_list">
<li
class="flex items-center px-[16px] mb-[12px]"
:class="detailInfo?.id === info.id ? 'active' : ''"
v-for="(info, i) in props.deviceList"
:key="`${i}-logo1-button`"
:draggable="true"
@click="addItem(info)"
@dragend="event => dragItem(event, info)"
>
<span>
{{ info.name }}
</span>
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup name="CanvasSize" lang="ts"> <script setup name="CanvasSize" lang="ts">
// import { Modal } from "view-ui-plus";
import useSelect from "@/hooks/select"; import useSelect from "@/hooks/select";
// import { cloneDeep } from "lodash-es"; // import { cloneDeep } from "lodash-es";
import { v4 as uuid } from "uuid";
// import { useI18n } from "vue-i18n"; // import { useI18n } from "vue-i18n";
import { useDeviceObject } from "../hooks/useDeviceObject"; import { useDeviceObject } from "../hooks/useDeviceObject";
import { IsAction } from "@/components/Action";
// import watchOnlineSelected from "../../../assets/modelSetting/watchOnlineSelected.svg";
// const testSrc =
// "https://img.cgmodel.com/image/2020/1010/big/1537169-1390622992.jpg";
const props = defineProps({ const props = defineProps({
deviceList: { deviceList: {
type: Array as Record<string, any>[] type: Array as Record<string, any>[]
},
currDeviceList: {
type: Array as Record<string, any>[]
},
pointInfo: {
type: Object as Record<string, any>
} }
}); });
const emit = defineEmits(["addDevice", "editDevice"]);
const { fabric, mixinState, canvasEditor } = useSelect(); const { fabric, mixinState, canvasEditor } = useSelect();
const { const { initDeviceGroupObjects } = useDeviceObject();
initDeviceGroupObjects,
isInViewBoundaries,
getDragDeviceObjectOrdinate,
fetchOrdinateByView
} = useDeviceObject();
// const { t } = useI18n(); // const { t } = useI18n();
const defaultPosition = { const defaultPosition = {
left: 100, left: 100,
@ -50,7 +71,11 @@ interface materialItemI {
src: string; src: string;
} }
const allDeviceList = ref([]); // const allType: materialTypeI = {
// value: "",
// label: ""
// };
const state = reactive({ const state = reactive({
search: "", search: "",
// placeholder: <undefined | string>"", // placeholder: <undefined | string>"",
@ -59,295 +84,232 @@ const state = reactive({
materialTypelist: [], // materialTypelist: [], //
materialist: [] // materialist: [] //
}); });
const currDetailInfo = ref({}); const detailInfo = ref({});
const isUpdateBindVisible = ref<boolean>(false); //
const bindDeviceData = ref<Record<string, any>>({}); canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => {
state.materialTypelist = [...list];
const updateMessage = computed(() => { state.materialist = list;
return `<span class="font-bold">“${currDetailInfo.value?.name}”</span>已绑定至<span class="font-bold">“${currDetailInfo.value?.workshop_name}”</span>,确定解除绑定并重新配置吗?`;
}); });
//
// const handleChange = (e, item) => {
// //
// //
canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => { canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => {
state.materialTypelist = [...list]; state.materialTypelist = [...list];
state.materialist = list; state.materialist = list;
}); });
function fetchDetail(record) {
detailInfo.value = record;
console.log(detailInfo.value, "fetchDetail");
}
/** /**
* 处理添加设备&重复添加设备同点位不同点位 * 校验是否允许添加设备
* @param record 选中的素材
*/ */
function deviceSelected(callback, options) { function isValidAdd(record) {
const { targetDeviceItem } = options; console.log("isValidAdd_record", record);
console.log(targetDeviceItem, "deviceSelected", props.currDeviceList); return false;
currDetailInfo.value = targetDeviceItem;
if (targetDeviceItem.is_binding) {
let isSeemPoint = false;
toRaw(props.currDeviceList).forEach(item => {
if (item.device_id === targetDeviceItem.id) {
isSeemPoint = true;
}
});
if (!isSeemPoint) {
//
isUpdateBindVisible.value = true;
bindDeviceData.value = options;
} else {
//
emit("editDevice", {
editParams: {
// id: currDeviceInfo.id,
// device_id: currDeviceInfo.device_id,
id: targetDeviceItem.wd_id,
device_id: targetDeviceItem.id,
workshop_id: props.pointInfo?.id,
x_ordinate: targetDeviceItem.x_ordinate,
y_ordinate: targetDeviceItem.y_ordinate
},
callback: () => {
//
console.log(
canvasEditor.canvas.getObjects(),
"canvasEditor_callback"
);
canvasEditor.canvas.getObjects().forEach(v => {
if (v?.deviceInfo?.device_id === targetDeviceItem.id) {
const { left, top } = fetchOrdinateByView(
canvasEditor.canvas,
targetDeviceItem
);
v.set({
left,
top
});
canvasEditor.canvas.renderAll();
}
});
}
});
}
return;
}
//
emit("addDevice", { record: targetDeviceItem, callback });
}
//
function updateConfirmBind() {
const { event, item, targetDeviceItem } = toRaw(bindDeviceData.value);
console.log(event, item, targetDeviceItem, "updateConfirmBind");
emit("addDevice", {
record: targetDeviceItem,
callback: () => {
// canvasEditor.dragAddItem(event, item); //
bindDeviceData.value = {}; //
isUpdateBindVisible.value = false;
}
});
} }
const dragItem = (event, deviceItem) => { const dragItem = (event, deviceItem) => {
if (isValidAdd(deviceItem)) {
return;
}
console.log(event, deviceItem, "dragItem");
fabric.util.enlivenObjects( fabric.util.enlivenObjects(
[ [
{ {
...defaultPosition, ...defaultPosition,
shadow: "", shadow: "",
fontFamily: "arial", fontFamily: "arial",
id: uuid(),
name: "svg元素", name: "svg元素",
...initDeviceGroupObjects(deviceItem) ...initDeviceGroupObjects(deviceItem)
} }
], ],
function (objects) { function (objects) {
// objects JSONGroup
const item = objects[0]; const item = objects[0];
const { x_ordinate, y_ordinate } = getDragDeviceObjectOrdinate( // if (!option) {
event, // groupText.center();
canvasEditor.canvas, // }
item
);
//
if (
!isInViewBoundaries(canvasEditor.canvas, {
...item,
x_ordinate,
y_ordinate
})
) {
return;
}
const targetDeviceItem = {
...deviceItem,
x_ordinate,
y_ordinate
};
// canvasEditor.dragAddItem(event, item); // GroupCanvas
deviceSelected( // var canvas = new fabric.Canvas('canvas-id');
_ => { // canvas.add(group);
// canvasEditor.dragAddItem(event, item);
// fetchDetail(deviceItem);
// item.set({ // Canvas
// name: resData?.device_name, // canvas.renderAll();
// deviceInfo: resData
// });
// canvasEditor.dragAddItem(event, item);
},
{
targetDeviceItem,
event,
item
}
);
} }
); );
}; };
// //
// const addItem = deviceItem => { const addItem = deviceItem => {
// if (isValidAdd(deviceItem)) { if (isValidAdd(deviceItem)) {
// return; return;
// } }
// console.log(deviceItem, "addItem_deviceItem"); fabric.util.enlivenObjects(
// fabric.util.enlivenObjects( [
// [ {
// { ...defaultPosition,
// ...defaultPosition, id: uuid(),
// // name: "svg", // name: "svg",
// ...initDeviceGroupObjects(deviceItem) ...initDeviceGroupObjects(deviceItem)
// } }
// ], ],
// function (objects) {
// const item = objects[0]; function (objects) {
// deviceSelected(() => { // objects JSONGroup
// canvasEditor.canvas.add(item); const item = objects[0];
// canvasEditor.canvas.setActiveObject(item); // if (!option) {
// canvasEditor.canvas.requestRenderAll(); // groupText.center();
// }); // }
// }
// );
// };
// // GroupCanvas
function renderDeviceToCanvas(record) { // var canvas = new fabric.Canvas('canvas-id');
// // canvas.add(group);
canvasEditor.canvas.getObjects().forEach(item => { canvasEditor.canvas.add(item);
if (item.name === "device") { canvasEditor.canvas.setActiveObject(item);
canvasEditor.canvas.remove(item); canvasEditor.canvas.requestRenderAll();
fetchDetail(deviceItem);
// Canvas
// canvas.renderAll();
} }
);
// console.log(e, "addItem_e");
// const url = watchOnlineSelected;
// fabric.loadSVGFromURL(url, (objects, options) => {
// const item = fabric.util.groupSVGElements(objects, {
// ...options,
// ...defaultPosition,
// id: uuid(),
// name: "svg"
// });
// });
};
// const DefaultSize = {
// width: 1200,
// height: 900
// };
// const modalData = reactive({
// width: DefaultSize.width,
// height: DefaultSize.height
// });
// let width = ref(DefaultSize.width);
// let height = ref(DefaultSize.height);
// let presetSize = reactive([
// {
// label: t("red_book_vertical"),
// width: 900,
// height: 1200
// },
// {
// label: t("red_book_horizontal"),
// width: 1200,
// height: 900
// },
// {
// label: t("phone_wallpaper"),
// width: 1080,
// height: 1920
// },
// {
// label: "kindle",
// width: 1200,
// height: 860
// },
// {
// label: "kindle-resize",
// width: 860,
// height: 1200
// }
// ]);
const DefaultSize = {
width: 947,
height: 642
};
onMounted(() => {
canvasEditor.setSize(DefaultSize.width, DefaultSize.height);
canvasEditor.on("sizeChange", (width, height) => {
width.value = width;
height.value = height;
}); });
const startArr = record;
const finalArr = []; // canvas.editor.editorWorkspace.setSize(width.value, height.value);
startArr.map(deviceItem => { // canvas.editor.editorWorkspace = new EditorWorkspace(canvas.c, {
const fullDeviceItem = { // width: width.value,
...defaultPosition, // height: height.value,
...deviceItem, // });
...fetchOrdinateByView(canvasEditor.canvas, deviceItem), });
name: deviceItem.device_name
}; // const setSizeBy = (w, h) => {
finalArr.push({ // modalData.width = w;
...fullDeviceItem, // modalData.height = h;
...initDeviceGroupObjects(fullDeviceItem) // };
}); // const setSize = () => {
}); // canvasEditor.setSize(width.value, height.value);
fabric.util.enlivenObjects([...finalArr], function (enlivenedObjects) { // // canvas.editor.editorWorkspace.setSize(width.value, height.value);
enlivenedObjects.forEach(groupObject => { // };
if (groupObject.type === "group") {
// group canvas // const handleClose = () => {
canvasEditor.canvas.add(groupObject); // showModal.value = false;
} // };
});
canvasEditor.canvas.requestRenderAll(); // const handleConfirm = () => {
}); // width.value = modalData.width;
} // height.value = modalData.height;
// setSize();
// handleClose();
// };
watch( watch(
() => props.deviceList, () => props.deviceList,
() => { () => {
allDeviceList.value = toRaw(props.deviceList); console.log(props.deviceList, "watch_deviceList");
console.log(allDeviceList.value, "allDeviceList", props.deviceList);
}, },
{ {
immediate: true,
deep: true deep: true
} }
); );
defineExpose({ renderDeviceToCanvas });
</script> </script>
<template>
<div v-if="!mixinState.mSelectMode">
<div class="px-[12px] deviceSelect_toolbar">
<div>
<h4 class="hf-1 py-[12px]">设备列表</h4>
<p class="pf-2">
<span>可直接点击</span>
<span class="text-[#154DDD]">拖拽设备名称</span>
<span>至图中目标位置鼠标悬停可查看设备详情</span>
</p>
<ul class="mt-[8px] deviceSelect_list cursor-move">
<li
class="flex items-center px-[12px] mb-[12px] justify-between"
:class="currDetailInfo?.id === info.id ? 'active' : ''"
v-for="(info, i) in allDeviceList"
:key="`${i}-logo1-button`"
:draggable="true"
@dragend="event => dragItem(event, info)"
>
<div class="flex items-center">
<span
class="truncate overflow-hidden whitespace-nowrap max-w-[120px]"
>
{{ info.name }}
</span>
<span
class="flex justify-center bind_tag ml-[8px]"
v-if="info?.is_binding"
>
已绑定
</span>
</div>
<el-popover placement="left" :width="230" trigger="click">
<template #reference>
<div class="flex items-center cursor-pointer device_info_btn">
<el-icon
style="
width: 14px;
height: 14px;
color: #154ddd;
margin-right: 4px;
"
>
<i class="iconfont icon-xiangqing" />
</el-icon>
<span>详情</span> <style scoped lang="scss">
</div> .search-box {
</template> padding-top: 10px;
<div class="device_info_box"> display: flex;
<div class="pf-1 pb-[8px] font-bold text-[16px]">设备信息</div> .input {
<ul class="text-[16px] text-web-font1 device_info_items"> margin-left: 10px;
<li> }
<span>设备名称</span> }
<span>{{ info?.name }}</span> .tmpl-img {
</li> display: inline-block;
<li> width: 53px;
<span>设备ID</span> margin-left: 2px;
<span>{{ info?.entity_suid }}</span> margin-bottom: 2px;
</li> background: #f5f5f5;
<li> padding: 6px;
<span>设备类型</span> cursor: pointer;
<span>{{ info?.classification }}</span> // width: 135px;
</li> // cursor: pointer;
<li> // margin-right: 5px;
<span>设备型号</span> }
<span>{{ info?.device_model }}</span>
</li> .form-wrap {
</ul> display: flex;
</div> justify-content: space-around;
</el-popover> align-content: center;
</li> margin-bottom: 10px;
</ul> }
</div> </style>
</div> ../hooks/useWatchModels../hooks/useDeviceObject
</div>
<IsAction
v-model="isUpdateBindVisible"
@update:visible="val => (isUpdateBindVisible = val)"
title="确定重新绑定吗?"
:message="updateMessage"
@confirm="updateConfirmBind"
/>
</template>

@ -1,244 +0,0 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-16 14:28:49
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-28 17:36:11
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\components\edit.vue
* @Description: 新建位置
-->
<script setup lang="ts">
// import type { FormInstance } from "element-plus";
import { message } from "@/utils/message";
import {
editWorkshopsApi,
deleteWorkshopDevicesListApi
} from "@/api/workshops";
import { isSuccessApi } from "@/utils/forApi";
// import useSelect from "@/hooks/select";
import previewCurrent from "./previewCurrent.vue";
defineOptions({
name: "DeviceSettingEdit"
});
const emit = defineEmits(["finishEdit"]);
const props = defineProps({
currDeviceList: {
type: Array as Record<string, any>[]
}
});
const formData = ref({
id: "",
name: "",
picture: null
});
const formRef = ref(null);
const uploadRef = ref(null);
const previewCurrentRef = ref(null);
const imagePreview = ref<string | null>(null); // URL
const dialogVisible = ref<boolean>(false);
const isChangePic = ref<boolean>(false);
const fileList = ref([]);
const rules = {
name: [{ required: true, message: "请输入位置名称", trigger: "blur" }],
picture: [{ required: true, message: "请上传图片文件", trigger: "change" }]
};
const openDialog = record => {
formData.value = {
id: record.id,
name: record.name,
picture: record.picture
};
imagePreview.value = record.picture; // 使
dialogVisible.value = true;
nextTick(() => {
previewCurrentRef.value.renderPreview();
});
};
const handleFileChange = (file: any) => {
// 使 FileReader
const reader = new FileReader();
reader.onload = e => {
imagePreview.value = e.target?.result as string;
nextTick(() => {
previewCurrentRef.value.refreshPic();
});
};
reader.readAsDataURL(file.raw);
console.log(file, "handleFileChange", fileList.value);
formData.value = {
...formData.value,
picture: file.raw
};
isChangePic.value = true;
};
//
const removeFile = () => {
imagePreview.value = null;
formData.value = {
...formData.value,
picture: null
};
};
const beforeUpload = (file: File) => {
console.log("beforeUpload_file", file);
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
const isLt500K = file.size / 1024 / 1024 < 20;
if (!isJPG) {
ElMessage.error("上传图片只能是 JPG/PNG 格式!");
}
if (!isLt500K) {
ElMessage.error("上传图片大小不能超过 20MB!");
}
return isJPG && isLt500K;
};
function resetFormData() {
imagePreview.value = null;
isChangePic.value = false;
}
async function fetchEditPoint() {
const formParams = new FormData();
formParams.append("id", formData.value.id);
formParams.append("name", formData.value.name);
if (isChangePic.value) {
// 使
formParams.append("picture", formData.value.picture);
}
const resp = await editWorkshopsApi(formParams);
console.log(resp, "fetchAddPoint_resp");
if (isSuccessApi(resp)) {
message(resp.msg || "编辑成功", { type: "success" });
resetFormData();
emit("finishEdit", resp.data);
dialogVisible.value = false;
} else {
message(resp.msg || "编辑失败", { type: "error" });
}
}
async function deleteNotInVisableList() {
console.log("isNotInVisableList", previewCurrentRef.value.isNotInVisableList);
const resp = await deleteWorkshopDevicesListApi(
toRaw(previewCurrentRef.value.isNotInVisableList)
);
console.log(resp, "fetchAddPoint_resp");
if (isSuccessApi(resp)) {
fetchEditPoint();
}
}
const submitForm = () => {
formRef.value.validate((valid: boolean) => {
if (valid) {
const currList = toRaw(previewCurrentRef.value.isNotInVisableList);
if (currList.length) {
//
deleteNotInVisableList();
} else {
fetchEditPoint();
}
} else {
message("验证失败", { type: "error" });
return false;
}
});
};
defineExpose({
openDialog
});
</script>
<template>
<div>
<el-dialog
class="point_dialog_wrap"
top="5vh"
v-model="dialogVisible"
title="编辑布点位置"
append-to-body
width="640px"
:style="{
borderRadius: '6px'
}"
>
<template #header="{ titleId, titleClass }">
<div class="flex items-center my-header">
<img src="@/assets/editIcon.png" class="w-[26px] h-[26px]" />
<h4 :id="titleId" class="pl-[10px]" :class="titleClass">
编辑布点位置
</h4>
</div>
</template>
<div class="deviceSettingAdd_modal_box">
<el-form
ref="formRef"
:rules="rules"
:inline="true"
:model="formData"
class="demo-form-inline"
label-position="top"
>
<el-form-item label="位置名称" class="w-full" prop="name">
<el-input v-model="formData.name" placeholder="请输入" clearable />
</el-form-item>
<el-form-item
label="上传图片(请上传png或jpeg格式图片文件尺寸不超过4096*3112px容量不超过20M)"
class="w-full"
prop="picture"
>
<el-upload
ref="uploadRef"
class="w-full"
drag
:on-change="handleFileChange"
:before-upload="beforeUpload"
:on-remove="removeFile"
:auto-upload="false"
:file-list="fileList"
:show-file-list="false"
accept="image/*"
>
<div>
<div v-if="!imagePreview">
<el-icon class="el-icon--upload text-[40px]">
<img src="@/assets/uploadImageIcon.png" />
</el-icon>
<div class="el-upload__text">
拖拽图片到这里<em>点此添加</em>
</div>
</div>
<div v-else class="w-full h-[311px]">
<previewCurrent
:picture="imagePreview"
:currDeviceList="props.currDeviceList"
ref="previewCurrentRef"
/>
</div>
<!-- <el-image
v-if="imagePreview"
:src="imagePreview || ''"
:fit="'contain'"
class="w-full"
/> -->
</div>
</el-upload>
</el-form-item>
</el-form>
</div>
<template v-slot:footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm"></el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss">
@import url("./add.scss");
</style>

@ -1,181 +0,0 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-26 11:32:07
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-30 14:16:10
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\components\previewCurrent.vue
* @Description: 预览组件
-->
<script lang="ts" setup>
import { fabric } from "fabric";
import { useDeviceObject } from "../hooks/useDeviceObject";
import device_video_bg_gray from "@/assets/modelSetting/device_video_bg_gray.png";
import device_video_bg from "@/assets/modelSetting/device_video_bg.png";
const { initDeviceGroupObjects, isInCurrViewBoundaries } = useDeviceObject();
const props = defineProps({
picture: {
type: String
},
currDeviceList: {
type: Array as Record<string, any>[]
}
});
const viewData = ref<{
width: number;
height: number;
}>({
width: 551, //
height: 311 //
});
const isNotInVisableList = ref([]);
const viewPicOptions = ref({
scale: 1
});
const canvasRef = ref<any>(null);
const cvs = ref<any>(null);
//
//
function refreshDevicePosition() {
// const { scale } = toRaw(viewPicOptions.value);
const canvasObject = cvs.value;
const notInVisableIds = [];
canvasObject.getObjects().forEach(deviceItem => {
const targetBgImage = deviceItem.getObjects()[0]; //
const targetTextBox = deviceItem.getObjects()[2]; //
const targetDelIcon = deviceItem.getObjects()[3]; //
console.log(targetBgImage, "notInVisableIds", deviceItem);
let notInVisable = false; //
if (isInCurrViewBoundaries(canvasObject, deviceItem)) {
//
notInVisable = true;
notInVisableIds.push(Number(deviceItem?.deviceInfo?.id));
}
targetTextBox.set("fill", notInVisable ? "red" : "#333333"); //
targetBgImage.setSrc(
notInVisable ? device_video_bg_gray : device_video_bg,
() => {
console.log("Image loaded successfully activeObject_iconType");
deviceItem.addWithUpdate(); //
canvasObject.renderAll(); //
cvs.value = canvasObject;
},
{ crossOrigin: "anonymous" }
);
targetDelIcon.set("visible", notInVisable);
canvasObject.renderAll();
cvs.value = canvasObject;
});
isNotInVisableList.value = notInVisableIds;
}
//
function renderDeviceToCanvas() {
const { scale } = toRaw(viewPicOptions.value);
console.log("renderDeviceToCanvas", props.currDeviceList, scale);
const canvasObject = cvs.value;
const startArr = props.currDeviceList;
const finalArr = [];
// const { startLeft, startTop } = getBoundaryPoints();
startArr.map(deviceItem => {
const fullDeviceItem = {
// left: 100,
// top: 100,
shadow: "",
fontFamily: "1-1",
...deviceItem,
left: Number(deviceItem.x_ordinate) * scale + viewData.value.width / 2,
top: viewData.value.height / 2 - Number(deviceItem.y_ordinate) * scale,
name: deviceItem.device_name
};
finalArr.push({
...fullDeviceItem,
...initDeviceGroupObjects(fullDeviceItem, {
selectable: false,
hasControls: false,
scaleX: scale,
scaleY: scale
})
});
});
fabric.util.enlivenObjects([...finalArr], function (enlivenedObjects) {
enlivenedObjects.forEach(groupObject => {
if (groupObject.type === "group") {
// group canvas
canvasObject.add(groupObject);
}
});
canvasObject.renderAll();
});
}
function refreshPic(loadDevice) {
const canvasObject = cvs.value;
//
fabric.Image.fromURL(props.picture, function (img) {
//
const canvasWidth = viewData.value.width;
const canvasHeight = viewData.value.height;
//
const imgWidth = img.width;
const imgHeight = img.height;
// 使
const scaleX = canvasWidth / imgWidth;
const scaleY = canvasHeight / imgHeight;
const scale = Math.min(scaleX, scaleY); //
//
img.scale(scale);
//
img.set({
originX: "center",
originY: "center",
left: canvasWidth / 2,
top: canvasHeight / 2
});
viewPicOptions.value = {
scale
};
//
canvasObject.setBackgroundImage(
img,
canvasObject.renderAll.bind(canvasObject)
);
cvs.value = canvasObject;
if (!loadDevice) {
refreshDevicePosition();
} else {
renderDeviceToCanvas();
}
});
// canvasObject.loadFromJSON(JSON.stringify(currJson), () => {
// canvasObject.renderAll();
// });
console.log(canvasObject);
}
function renderPreview() {
const currWorkSpace = document.getElementById("previewPic");
viewData.value = {
width: currWorkSpace.clientWidth,
height: currWorkSpace.clientHeight
};
const canvasObject = new fabric.Canvas(canvasRef.value);
cvs.value = canvasObject;
nextTick(() => {
refreshPic(true);
});
}
defineExpose({ renderPreview, refreshPic, isNotInVisableList });
</script>
<template>
<div class="w-full h-full" id="previewPic">
<canvas ref="canvasRef" :width="viewData.width" :height="viewData.height" />
</div>
</template>

@ -0,0 +1,118 @@
/*
* @Author: zhoux zhouxia@supervision.ltd
* @Date: 2023-12-13 14:29:17
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-07 11:31:52
* @FilePath: \vue-fabric-editor\src\hooks\useAddModels.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
// import {watch00} from '@/assets/modelSettingJsons/watch01'
import watchError from "@/assets/modelSetting/watchError.svg";
import watchOnline from "@/assets/modelSetting/watchOnline.svg";
import watchOutline from "@/assets/modelSetting/watchOutline.svg";
import watchWarn from "@/assets/modelSetting/watchWarn.svg";
import watchErrorSelected from "@/assets/modelSetting/watchErrorSelected.svg";
import watchOnlineSelected from "@/assets/modelSetting/watchOnlineSelected.svg";
import watchOutlineSelected from "@/assets/modelSetting/watchOutlineSelected.svg";
import watchWarnSelected from "@/assets/modelSetting/watchWarnSelected.svg";
export interface materialItemI {
value: string;
name: string;
tempUrl: string;
src: string;
color?: string;
}
const customObjectJson = {
type: "group",
objects: [
{
type: "rect",
left: 100,
top: 100,
width: 100,
height: 100,
fill: "red"
},
{
type: "circle",
left: 200,
top: 100,
radius: 50,
fill: "blue"
},
{
type: "text",
left: 150,
top: 200,
text: "Hello",
fontSize: 24,
fill: "white"
}
]
};
export const useWatchModels = () => {
const locaWatchList: materialItemI[] = [
{
value: "watchError",
name: "摄像头1",
tempUrl: "",
groupObject: customObjectJson,
src: watchError,
color: "#E80D0D"
},
{
value: "watchOnline",
name: "摄像头",
tempUrl: "",
src: watchOnline,
color: "#52C41A"
},
{
value: "watchOutline",
name: "摄像头",
tempUrl: "",
src: watchOutline,
color: "#CCCCCC"
},
{
value: "watchWarn",
name: "摄像头",
tempUrl: "",
src: watchWarn,
color: "#FAAD14"
},
{
value: "watchErrorSelected",
name: "摄像头",
tempUrl: "",
src: watchErrorSelected,
color: "#E80D0D"
},
{
value: "watchOnlineSelected",
name: "摄像头",
tempUrl: "",
src: watchOnlineSelected,
color: "#52C41A"
},
{
value: "watchOutlineSelected",
name: "摄像头",
tempUrl: "",
src: watchOutlineSelected,
color: "#CCCCCC"
},
{
value: "watchWarnSelected",
name: "摄像头",
tempUrl: "",
src: watchWarnSelected,
color: "#FAAD14"
}
];
return {
locaWatchList
};
};

@ -4,272 +4,31 @@
*/ */
// https://t7.baidu.com/it/u=2757924858,1404466263&fm=193 // https://t7.baidu.com/it/u=2757924858,1404466263&fm=193
import video_type_1 from "@/assets/modelSetting/video_type_1.png"; import video_type_1 from "@/assets/modelSetting/video_type_1.png";
import video_type_2 from "@/assets/modelSetting/video_type_2.png";
import video_type_3 from "@/assets/modelSetting/video_type_3.png";
import device_video_bg from "@/assets/modelSetting/device_video_bg.png";
import device_video_bg_gray from "@/assets/modelSetting/device_video_bg_gray.png";
import notInVisableIcon from "@/assets/modelSetting/notInVisableIcon.png";
import { workspaceIDConf } from "@/config/attribute/viewType";
import { v4 as uuid } from "uuid";
const deviceInfoKey = "deviceInfo";
// TODO 设计实现点位绑定设备实体的位置偏移问题
/**
* @
* 1. (0,0)
*/
export const useDeviceObject = () => { export const useDeviceObject = () => {
// 拖拽新增获取设备对象在画布上的坐标 const initDeviceGroupObjects: Record<string, any> = (record: {
function getDragDeviceObjectOrdinate(event, viewObj, deviceObj) { id: string;
// 移动到画布上的设备对象的相对坐标,使用相对图片的坐标系,而不是画布的坐标系 value: string;
const currWorkSpaceData = document }) => {
.getElementById(workspaceIDConf)
.getBoundingClientRect();
const cvsObjects = viewObj.getObjects()[0]; // 获取画布对象
const imgObjects = viewObj.getObjects()[1]; // 获取图片对象
const { left, top, width, height } = currWorkSpaceData;
// .getSelectionElement()
// .getBoundingClientRect();
// 该设备基于当前画布的坐标信息
const currPointView = {
x: event.clientX - left,
y: event.clientY - top
};
// const pointerVpt = viewObj.restorePointerVpt(currPointView);
const x_ordinate =
currPointView.x - width / 2 - (deviceObj.width * deviceObj.scaleX) / 2;
const y_ordinate =
height / 2 - currPointView.y + (deviceObj.height * deviceObj.scaleY) / 2;
console.log(
"currWorkSpace:",
currWorkSpaceData,
"cvsObjects:",
cvsObjects,
"event:",
event,
"getDragDeviceObjectOrdinate",
"imgObjects:",
imgObjects.getCenterPoint(),
x_ordinate,
y_ordinate
);
return {
x_ordinate,
y_ordinate
};
}
// 在画布上移动后设备对象坐标
function fetchMoveDeviceObjectOrdinate(viewObj, activeObject) {
const cvsObjects = viewObj.getObjects()[0]; // 获取画布对象
const x_ordinate = activeObject.get("left") - cvsObjects.width / 2;
const y_ordinate = cvsObjects.height / 2 - activeObject.get("top");
return {
x_ordinate,
y_ordinate
};
}
// 使用相对坐标换实际位置的方法
function fetchOrdinateByView(viewObj, deviceItem) {
const cvsObjects = viewObj.getObjects()[0]; // 获取画布对象
const { x_ordinate, y_ordinate } = deviceItem;
const left = cvsObjects.width / 2 + parseFloat(x_ordinate);
const top = cvsObjects.height / 2 - parseFloat(y_ordinate);
console.log(cvsObjects, "fetchOrdinateByView", left, top);
return {
left,
top
};
}
// 可操作区域的各点位相对坐标【限定可操作区域边界】
function fetchViewsBoundaries(viewObj, activeObj) {
// 图片原点为中心图标
const cvsObjects = viewObj.getObjects()[0]; // 获取画布对象
const imgObjects = viewObj.getObjects()[1]; // 获取图片对象
const y_mistake = -8 * activeObj?.scaleY; // 上下边界误差
const currIconWidth = activeObj?.width * activeObj?.scaleX;
const currIconHeight = activeObj?.height * activeObj?.scaleY;
const startLeft =
(cvsObjects.width * cvsObjects.scaleX -
imgObjects.width * imgObjects.scaleX) /
2 -
currIconWidth / 2;
const startTop =
(cvsObjects.height * cvsObjects.scaleY -
imgObjects.height * imgObjects.scaleY) /
2 -
currIconHeight -
y_mistake;
const endLeft = startLeft + imgObjects.width * imgObjects.scaleX;
const endTop = startTop + imgObjects.height * imgObjects.scaleY;
const currImgWidth = imgObjects.width * imgObjects.scaleX;
const currImgHeight = imgObjects.height * imgObjects.scaleY;
return {
x_min: -currImgWidth / 2 - currIconWidth / 2,
y_min: -currImgHeight / 2 - currIconHeight,
x_max: currImgWidth / 2 + currIconWidth / 2,
y_max: currImgHeight / 2,
startLeft,
startTop,
endLeft,
endTop
};
}
// 操作区判断设备是否在可视区域内
function isInViewBoundaries(viewObj, deviceObj): boolean {
const { x_min, x_max, y_min, y_max } = fetchViewsBoundaries(
viewObj,
deviceObj
);
const { x_ordinate, y_ordinate } = deviceObj;
// 限制对象在画布内移动
if (x_ordinate < x_min) {
return false;
}
if (y_ordinate < y_min) {
return false;
}
if (x_ordinate > x_max) {
return false;
}
if (y_ordinate > y_max) {
return false;
}
return true;
}
// 预览判断当前的图片图层内
function isInCurrViewBoundaries(viewObj, deviceObj): boolean {
const objLeft = deviceObj.left;
const objTop = deviceObj.top;
const canvasObject = viewObj;
const { backgroundImage } = canvasObject; // backgroundImage 背景图片
const currIconWidth = deviceObj?.width * deviceObj?.scaleX;
const currIconHeight = deviceObj?.height * deviceObj?.scaleY;
const y_mistake = -8 * deviceObj?.scaleY; // 上下边界误差
const x_mistake = 1; // 左右边界误差
const startLeft =
(canvasObject.width - backgroundImage.width * backgroundImage.scaleX) /
2 -
currIconWidth / 2;
const startTop =
(canvasObject.height - backgroundImage.height * backgroundImage.scaleY) /
2 -
currIconHeight -
y_mistake;
const endLeft = startLeft + backgroundImage.width * backgroundImage.scaleX;
const endTop = startTop + backgroundImage.height * backgroundImage.scaleY;
console.log(
backgroundImage.getBoundingClientRect(),
canvasObject.width,
canvasObject.scaleX,
backgroundImage.width,
backgroundImage.scaleX,
currIconWidth,
"isInCurrViewBoundaries",
backgroundImage.scaleX,
deviceObj,
"startLeft",
startLeft,
"startTop",
startTop,
"endLeft",
endLeft,
"endTop",
endTop
);
if (
objLeft < startLeft - x_mistake ||
objTop < startTop ||
objLeft > endLeft + x_mistake ||
objTop > endTop
) {
return true;
}
return false;
}
// function isInView() {
// // TODO 用三角函数判断
// return;
// }
// 设备对象是否移动
function isMoveDevice(activeObject, viewObj) {
const x_ordinate_history = activeObject.get(deviceInfoKey)?.x_ordinate;
const y_ordinate_history = activeObject.get(deviceInfoKey)?.y_ordinate;
const { x_ordinate, y_ordinate } = fetchMoveDeviceObjectOrdinate(
viewObj,
activeObject
);
// 误差设置为 0.1
const errorValue = [1, 1];
let x_move = true;
let y_move = true;
const x_move_value = Math.abs(
Number(x_ordinate_history) - Number(x_ordinate)
);
const y_move_value = Math.abs(
Number(y_ordinate_history) - Number(y_ordinate)
);
// 重新绑定车间的设备没有坐标信息,暂时不判断设备是否移动【默认没有移动】
// if (Number.isNaN(x_move_value) || Number.isNaN(y_move_value)) {
// return false;
// }
if (x_move_value < errorValue[0]) {
x_move = false;
}
if (y_move_value < errorValue[1]) {
y_move = false;
}
console.log(
"isMoveDevice:" + (x_move || y_move),
"x_move_value" + x_move_value,
"y_move_value" + y_move_value
);
return x_move || y_move;
}
// 初始化设备对象
const initDeviceGroupObjects: Record<string, any> = (
record,
restDeviceItem = {}
) => {
console.log(record, "initDeviceGroupObjects"); console.log(record, "initDeviceGroupObjects");
// const { value } = record; // const { value } = record;
let iconObjectSrc = video_type_1; // let watchIconObject = watchIcon2;
switch (record?.icon) { // switch (value) {
case "3": // case "watchError":
iconObjectSrc = video_type_3; // watchIconObject = watchIcon1;
break; // break;
case "2": // case "watchOnline":
iconObjectSrc = video_type_2; // watchIconObject = watchIcon2;
break; // break;
case "1": // case "watchOutline":
default: // watchIconObject = watchIcon3;
iconObjectSrc = video_type_1; // break;
break; // case "watchWarn":
} // default:
let bgDeviceImage; // watchIconObject = watchIcon4;
const fontStyle = { fill: "#333333" }; // break;
// 不在可视区域内的设备 // }
if (record?.notInVisable) {
bgDeviceImage = device_video_bg_gray;
fontStyle.fill = "gray";
} else {
bgDeviceImage = device_video_bg;
}
return { return {
name: "device",
id: uuid(),
deviceInfo: record, // 设备信息
left: record?.left ? Number(record?.left) : 0,
top: record?.top ? Number(record?.top) : 0,
selectable: true, selectable: true,
hasControls: true, hasControls: true,
lockUniScaling: true, // 当设置为trueObject将无法被锁定比例进行缩放。默认值为false。 lockUniScaling: true, // 当设置为trueObject将无法被锁定比例进行缩放。默认值为false。
@ -280,8 +39,10 @@ export const useDeviceObject = () => {
version: "5.3.0", version: "5.3.0",
originX: "left", originX: "left",
originY: "top", originY: "top",
width: 132, left: 87.4695,
height: 62, top: 85.8002,
width: 113,
height: 66,
fill: "rgb(0,0,0)", fill: "rgb(0,0,0)",
stroke: null, stroke: null,
strokeWidth: 0, strokeWidth: 0,
@ -305,16 +66,17 @@ export const useDeviceObject = () => {
globalCompositeOperation: "source-over", globalCompositeOperation: "source-over",
skewX: 0, skewX: 0,
skewY: 0, skewY: 0,
id: "520579bf-b8b3-451b-b3aa-5b8a9d6cd1a8",
objects: [ objects: [
{ {
type: "image", type: "image",
version: "5.3.0", version: "5.3.0",
originX: "left", originX: "left",
originY: "top", originY: "top",
left: -66, left: -56.5,
top: -33, top: -33,
width: 264, width: 113,
height: 132, height: 66,
fill: "rgb(0,0,0)", fill: "rgb(0,0,0)",
stroke: null, stroke: null,
strokeWidth: 0, strokeWidth: 0,
@ -324,8 +86,8 @@ export const useDeviceObject = () => {
strokeLineJoin: "miter", strokeLineJoin: "miter",
strokeUniform: false, strokeUniform: false,
strokeMiterLimit: 4, strokeMiterLimit: 4,
scaleX: 0.5, scaleX: 1,
scaleY: 0.5, scaleY: 1,
angle: 0, angle: 0,
flipX: false, flipX: false,
flipY: false, flipY: false,
@ -342,7 +104,7 @@ export const useDeviceObject = () => {
cropY: 0, cropY: 0,
selectable: false, selectable: false,
hasControls: false, hasControls: false,
src: bgDeviceImage, src: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEzIiBoZWlnaHQ9IjY2IiB2aWV3Qm94PSIwIDAgMTEzIDY2IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kXzIyMDNfMTAzMTgpIj4KPG1hc2sgaWQ9InBhdGgtMS1pbnNpZGUtMV8yMjAzXzEwMzE4IiBmaWxsPSJ3aGl0ZSI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMkM3Ljc5MDg2IDIgNiAzLjc5MDg2IDYgNlY0NEM2IDQ2LjIwOTEgNy43OTA4NiA0OCAxMCA0OEg0OS44TDU3IDU2TDY0LjIgNDhIMTAzQzEwNS4yMDkgNDggMTA3IDQ2LjIwOTEgMTA3IDQ0VjZDMTA3IDMuNzkwODYgMTA1LjIwOSAyIDEwMyAySDEwWiIvPgo8L21hc2s+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMkM3Ljc5MDg2IDIgNiAzLjc5MDg2IDYgNlY0NEM2IDQ2LjIwOTEgNy43OTA4NiA0OCAxMCA0OEg0OS44TDU3IDU2TDY0LjIgNDhIMTAzQzEwNS4yMDkgNDggMTA3IDQ2LjIwOTEgMTA3IDQ0VjZDMTA3IDMuNzkwODYgMTA1LjIwOSAyIDEwMyAySDEwWiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC44IiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz4KPHBhdGggZD0iTTQ5LjggNDhMNTAuNTQzMyA0Ny4zMzFMNTAuMjQ1NCA0N0g0OS44VjQ4Wk01NyA1Nkw1Ni4yNTY3IDU2LjY2OUw1NyA1Ny40OTQ4TDU3Ljc0MzMgNTYuNjY5TDU3IDU2Wk02NC4yIDQ4VjQ3SDYzLjc1NDZMNjMuNDU2NyA0Ny4zMzFMNjQuMiA0OFpNNyA2QzcgNC4zNDMxNSA4LjM0MzE1IDMgMTAgM1YxQzcuMjM4NTggMSA1IDMuMjM4NTcgNSA2SDdaTTcgNDRWNkg1VjQ0SDdaTTEwIDQ3QzguMzQzMTUgNDcgNyA0NS42NTY5IDcgNDRINUM1IDQ2Ljc2MTQgNy4yMzg1NyA0OSAxMCA0OVY0N1pNNDkuOCA0N0gxMFY0OUg0OS44VjQ3Wk00OS4wNTY3IDQ4LjY2OUw1Ni4yNTY3IDU2LjY2OUw1Ny43NDMzIDU1LjMzMUw1MC41NDMzIDQ3LjMzMUw0OS4wNTY3IDQ4LjY2OVpNNTcuNzQzMyA1Ni42NjlMNjQuOTQzMyA0OC42NjlMNjMuNDU2NyA0Ny4zMzFMNTYuMjU2NyA1NS4zMzFMNTcuNzQzMyA1Ni42NjlaTTEwMyA0N0g2NC4yVjQ5SDEwM1Y0N1pNMTA2IDQ0QzEwNiA0NS42NTY5IDEwNC42NTcgNDcgMTAzIDQ3VjQ5QzEwNS43NjEgNDkgMTA4IDQ2Ljc2MTQgMTA4IDQ0SDEwNlpNMTA2IDZWNDRIMTA4VjZIMTA2Wk0xMDMgM0MxMDQuNjU3IDMgMTA2IDQuMzQzMTUgMTA2IDZIMTA4QzEwOCAzLjIzODU4IDEwNS43NjEgMSAxMDMgMVYzWk0xMCAzSDEwM1YxSDEwVjNaIiBmaWxsPSJ3aGl0ZSIgbWFzaz0idXJsKCNwYXRoLTEtaW5zaWRlLTFfMjIwM18xMDMxOCkiLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9kXzIyMDNfMTAzMTgiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMTMiIGhlaWdodD0iNjYiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iNCIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4xIDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfMjIwM18xMDMxOCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd18yMjAzXzEwMzE4IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=",
crossOrigin: null, crossOrigin: null,
filters: [] filters: []
}, },
@ -351,7 +113,7 @@ export const useDeviceObject = () => {
version: "5.3.0", version: "5.3.0",
originX: "left", originX: "left",
originY: "top", originY: "top",
left: -50.6281, left: -40.6281,
top: -22.9893, top: -22.9893,
width: 48, width: 48,
height: 48, height: 48,
@ -382,7 +144,7 @@ export const useDeviceObject = () => {
cropY: 0, cropY: 0,
selectable: false, selectable: false,
hasControls: false, hasControls: false,
src: iconObjectSrc, src: video_type_1,
crossOrigin: null, crossOrigin: null,
filters: [] filters: []
}, },
@ -391,11 +153,11 @@ export const useDeviceObject = () => {
version: "5.3.0", version: "5.3.0",
originX: "left", originX: "left",
originY: "top", originY: "top",
left: -14.3377, left: -7.3377,
top: -15.4904, top: -15.4904,
width: 320, width: 320,
height: 90.4, height: 90.4,
fill: fontStyle.fill, fill: "#333333",
stroke: null, stroke: null,
strokeWidth: 1, strokeWidth: 1,
strokeDashArray: null, strokeDashArray: null,
@ -438,59 +200,11 @@ export const useDeviceObject = () => {
pathAlign: "baseline", pathAlign: "baseline",
selectable: false, selectable: false,
hasControls: false hasControls: false
},
{
type: "image",
version: "5.3.0",
originX: "left",
originY: "top",
left: -47.6281,
top: -19.9893,
width: 40,
height: 40,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 0.6174,
scaleY: 0.6174,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: false,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
cropX: 0,
cropY: 0,
selectable: false,
hasControls: false,
src: notInVisableIcon,
crossOrigin: null,
filters: []
} }
], ]
...restDeviceItem
}; };
}; };
return { return {
initDeviceGroupObjects, initDeviceGroupObjects
isInViewBoundaries,
getDragDeviceObjectOrdinate,
isMoveDevice,
fetchViewsBoundaries,
fetchOrdinateByView,
fetchMoveDeviceObjectOrdinate,
isInCurrViewBoundaries
}; };
}; };

@ -0,0 +1,581 @@
/**
* @
* 1. 使
*/
export const useDeviceObject = () => {
const initDeviceGroupObjects: Record<string, any> = (record: {
id: string;
value: string;
}) => {
console.log(record, "initDeviceGroupObjects");
// const { value } = record;
// let watchIconObject = watchIcon2;
// switch (value) {
// case "watchError":
// watchIconObject = watchIcon1;
// break;
// case "watchOnline":
// watchIconObject = watchIcon2;
// break;
// case "watchOutline":
// watchIconObject = watchIcon3;
// break;
// case "watchWarn":
// default:
// watchIconObject = watchIcon4;
// break;
// }
return {
selectable: true,
hasControls: true,
lockUniScaling: true, // 当设置为trueObject将无法被锁定比例进行缩放。默认值为false。
lockScalingX: true, // 当设置为trueObject水平方向将无法被缩放。默认值为false。
lockScalingY: true, // 当设置为trueObject垂直方向将无法被缩放。默认值为false。
lockRotation: true, // 当设置为trueObject的旋转将被锁定。默认值为false。
type: "group",
version: "5.3.0",
originX: "left",
originY: "top",
left: 20.9866,
top: 16.4716,
width: 131,
height: 66,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
objects: [
{
type: "group",
version: "5.3.0",
originX: "left",
originY: "top",
left: -65.5,
top: -33,
width: 131,
height: 66,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: "",
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
objects: [
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -60,
top: -31.5,
width: 119,
height: 54,
fill: "white",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "evenodd",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 10, 2],
["C", 7.79086, 2, 6, 3.79086, 6, 6],
["L", 6, 44],
["C", 6, 46.2091, 7.79086, 48, 10, 48],
["L", 58.8, 48],
["L", 66, 56],
["L", 73.2, 48],
["L", 121, 48],
["C", 123.209, 48, 125, 46.2091, 125, 44],
["L", 125, 6],
["C", 125, 3.79086, 123.209, 2, 121, 2],
["L", 10, 2],
["Z"]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -61,
top: -32.5,
width: 121,
height: 56.4948,
fill: "rgba(21,77,221,0.2)",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 58.8, 48],
["L", 59.5433, 47.331],
["L", 59.2454, 47],
["L", 58.8, 47],
["L", 58.8, 48],
["Z"],
["M", 66, 56],
["L", 65.2567, 56.669],
["L", 66, 57.4948],
["L", 66.7433, 56.669],
["L", 66, 56],
["Z"],
["M", 73.2, 48],
["L", 73.2, 47],
["L", 72.7546, 47],
["L", 72.4567, 47.331],
["L", 73.2, 48],
["Z"],
["M", 7, 6],
["C", 7, 4.34315, 8.34315, 3, 10, 3],
["L", 10, 1],
["C", 7.23858, 1, 5, 3.23857, 5, 6],
["L", 7, 6],
["Z"],
["M", 7, 44],
["L", 7, 6],
["L", 5, 6],
["L", 5, 44],
["L", 7, 44],
["Z"],
["M", 10, 47],
["C", 8.34315, 47, 7, 45.6569, 7, 44],
["L", 5, 44],
["C", 5, 46.7614, 7.23858, 49, 10, 49],
["L", 10, 47],
["Z"],
["M", 58.8, 47],
["L", 10, 47],
["L", 10, 49],
["L", 58.8, 49],
["L", 58.8, 47],
["Z"],
["M", 58.0567, 48.669],
["L", 65.2567, 56.669],
["L", 66.7433, 55.331],
["L", 59.5433, 47.331],
["L", 58.0567, 48.669],
["Z"],
["M", 66.7433, 56.669],
["L", 73.9433, 48.669],
["L", 72.4567, 47.331],
["L", 65.2567, 55.331],
["L", 66.7433, 56.669],
["Z"],
["M", 121, 47],
["L", 73.2, 47],
["L", 73.2, 49],
["L", 121, 49],
["L", 121, 47],
["Z"],
["M", 124, 44],
["C", 124, 45.6569, 122.657, 47, 121, 47],
["L", 121, 49],
["C", 123.761, 49, 126, 46.7614, 126, 44],
["L", 124, 44],
["Z"],
["M", 124, 6],
["L", 124, 44],
["L", 126, 44],
["L", 126, 6],
["L", 124, 6],
["Z"],
["M", 121, 3],
["C", 122.657, 3, 124, 4.34315, 124, 6],
["L", 126, 6],
["C", 126, 3.23858, 123.761, 1, 121, 1],
["L", 121, 3],
["Z"],
["M", 10, 3],
["L", 121, 3],
["L", 121, 1],
["L", 10, 1],
["L", 10, 3],
["Z"]
]
},
{
type: "rect",
version: "5.3.0",
originX: "left",
originY: "top",
left: -52,
top: -24.5,
width: 32,
height: 32,
fill: "#52C41A",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
rx: 2,
ry: 2,
selectable: true,
hasControls: true
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -45.9945,
top: -17.6,
width: 19.8963,
height: 0,
fill: "",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 40.0018, 16],
["L", 29.5301, 16],
["L", 20.1055, 16]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -36.5727,
top: -17.6,
width: 0,
height: 8.8419,
fill: "",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 29.5273, 24.8419],
["L", 29.5273, 16]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -46.1,
top: -10.5375,
width: 18.5869,
height: 10.9372,
fill: "white",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 21.8972, 23.0625],
["L", 38.5869, 27.5048],
["L", 37.6746, 28.8773],
["L", 35.579, 32.6272],
["L", 34.6667, 33.9997],
["L", 20, 30.0959],
["L", 21.8972, 23.0625],
["Z"]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -30.5219,
top: -4.7211,
width: 4.1186,
height: 4.5576,
fill: "",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 37.6737, 28.8789],
["L", 39.6967, 29.4174],
["L", 38.6126, 33.4365],
["L", 35.5781, 32.6288]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -43.3008,
top: -7.6387,
width: 4.2123,
height: 4.181,
fill: "",
stroke: "#52C41A",
strokeWidth: 0.5,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 26.6615, 27.7018],
["C", 26.6615, 28.8548, 25.7201, 29.7923, 24.5554, 29.7923],
["C", 23.3906, 29.7923, 22.4492, 28.8548, 22.4492, 27.7018],
["C", 22.4492, 26.5488, 23.3906, 25.6113, 24.5554, 25.6113],
["C", 25.7201, 25.6113, 26.6615, 26.5488, 26.6615, 27.7018],
["Z"]
]
}
]
},
{
type: "textbox",
version: "5.3.0",
originX: "left",
originY: "top",
left: -15.709,
top: -15.0177,
width: 67.6873,
height: 13.56,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: "",
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
fontFamily: "arial",
fontWeight: "normal",
fontSize: 12,
text: record?.name || "设备",
underline: false,
overline: false,
linethrough: false,
textAlign: "left",
fontStyle: "normal",
lineHeight: 1.16,
textBackgroundColor: "",
charSpacing: 0,
styles: [],
direction: "ltr",
path: null,
pathStartOffset: 0,
pathSide: "left",
pathAlign: "baseline",
minWidth: 20,
splitByGrapheme: true,
selectable: true,
hasControls: true
}
]
};
};
return {
initDeviceGroupObjects
};
};

@ -1,14 +1,5 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-14 11:26:47
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-22 11:25:32
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\hooks\usePointObject.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export const usePointObject = () => { export const usePointObject = () => {
function getPointObject(record, objectOptions) { function getPointObject(record) {
const { width, height } = objectOptions;
const { picture } = record; const { picture } = record;
// TODO: 获取点位对象 // TODO: 获取点位对象
return JSON.stringify({ return JSON.stringify({
@ -21,9 +12,9 @@ export const usePointObject = () => {
originY: "top", originY: "top",
left: 0, left: 0,
top: 0, top: 0,
width: width, width: 947,
height: height, height: 610,
fill: "", // rgba(21, 77, 221, 0.1) fill: "rgba(255,35,255,1)",
stroke: null, stroke: null,
strokeWidth: 0, strokeWidth: 0,
strokeDashArray: null, strokeDashArray: null,
@ -50,16 +41,15 @@ export const usePointObject = () => {
ry: 0, ry: 0,
id: "workspace", id: "workspace",
selectable: false, selectable: false,
hasControls: false, hasControls: false
selection: false
}, },
{ {
type: "image", type: "image",
version: "5.3.0", version: "5.3.0",
// originX: "left", originX: "left",
// originY: "top", originY: "top",
// left: 0, width: 947,
// top: 0, height: 610,
fill: "rgb(0,0,0)", fill: "rgb(0,0,0)",
stroke: null, stroke: null,
strokeWidth: 0, strokeWidth: 0,
@ -88,7 +78,6 @@ export const usePointObject = () => {
id: "a3ab29c6-7008-49fe-abf3-edc9a47cd460", id: "a3ab29c6-7008-49fe-abf3-edc9a47cd460",
selectable: false, selectable: false,
hasControls: false, hasControls: false,
selection: false,
evented: false, evented: false,
crossOrigin: null, crossOrigin: null,
src: picture, src: picture,

@ -60,13 +60,13 @@
padding-top: 40px; padding-top: 40px;
.bg_preview { .bg_preview {
height: 412px; height: 412px;
border: 1px dashed #ddd; background-color: red;
} }
} }
.point_detail_wrap { .point_detail_wrap {
.deviceOfPoint_wrap { .deviceOfPoint_wrap {
border: 1px dashed #ddd; background-color: goldenrod;
height: calc(100vh - 300px); height: calc(100vh - 290px);
} }
.footer_btns { .footer_btns {
padding: 16px 0; padding: 16px 0;
@ -77,12 +77,9 @@
/* TODO 待使用 */ /* TODO 待使用 */
.right-bar { .right-bar {
margin-left: 16px; margin-left: 16px;
width: 304.07px; width: 241px;
height: calc(100vh - 300px); height: 100%;
overflow-y: scroll; overflow-y: auto;
background: #fafbff;
border-radius: 4px;
border: 1px solid rgba(21, 77, 221, 0.1);
} }
#workspace { #workspace {
flex: 1; flex: 1;
@ -93,7 +90,6 @@
} }
/* 选择设备栏位 */ /* 选择设备栏位 */
.deviceSelect_toolbar { .deviceSelect_toolbar {
font-size: 14px;
.deviceSelect_list { .deviceSelect_list {
li { li {
border-radius: 2px; border-radius: 2px;
@ -103,38 +99,4 @@
} }
} }
} }
.bind_tag {
width: 52px;
height: 24px;
background: rgba(82, 196, 26, 0.05);
border-radius: 2px;
border: 1px solid #52c41a;
font-size: 12px;
color: #52c41a;
line-height: 24px;
}
.device_info_btn {
color: #333333;
}
}
.device_info_box {
.device_info_items {
& > li {
padding-bottom: 12px;
&:last-child {
padding-bottom: 0;
}
}
}
}
/* 设备属性栏位 */
.deviceAtrr_toolbar {
padding: 12px;
.el-radio__label {
display: flex;
align-items: center;
}
.el-form-item__label {
color: #000000;
}
} }

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd * @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-02 10:52:32 * @Date: 2024-08-02 10:52:32
* @LastEditors: donghao donghao@supervision.ltd * @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-30 13:37:45 * @LastEditTime: 2024-08-14 11:22:32
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\index.vue * @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\index.vue
* @Description: 设备点位管理设置 * @Description: 设备点位管理设置
@ 交互说明 @ 交互说明
@ -12,27 +12,15 @@
4. 设备关联属性设置 4. 设备关联属性设置
5. 效果预览 5. 效果预览
--> -->
<script setup lang="ts"> <script name="Home" setup>
// //
import DeviceAttr from "./components/deviceAttr.vue"; import DeviceAttr from "./components/deviceAttr.vue";
import DeviceSettingAdd from "./components/add.vue"; import DeviceSettingAdd from "./components/add.vue";
import DeviceSettingEdit from "./components/edit.vue";
import DeviceSelect from "./components/deviceSelect.vue"; import DeviceSelect from "./components/deviceSelect.vue";
import { IsAction } from "@/components/Action";
import { usePointObject } from "./hooks/usePointObject"; import { usePointObject } from "./hooks/usePointObject";
import { getWorkshopsApi } from "@/api/workshops";
import {
getWorkshopsApi,
deleteWorkshopsApi,
addWorkshopDevicesApi,
editWorkshopDevicesApi,
getWorkshopDevicesApi
} from "@/api/workshops";
import { getLinkDevicesApi } from "@/api/device"; import { getLinkDevicesApi } from "@/api/device";
import { isSuccessApi } from "@/utils/forApi"; // TODO
// TODO
// //
import { CanvasEventEmitter } from "@/utils/event/notifier"; import { CanvasEventEmitter } from "@/utils/event/notifier";
// import { downFile } from '@/utils/utils'; // import { downFile } from '@/utils/utils';
@ -77,74 +65,30 @@ const state = reactive({
* @设备点位 * @设备点位
*/ */
const { getPointObject } = usePointObject(); const { getPointObject } = usePointObject();
const pointList = ref([]); const pointList = ref([]);
const deviceSettingAddRef = ref(""); const deviceSettingAddRef = ref("");
const deviceSettingEditRef = ref("");
const deviceSelectRef = ref("");
const activePointId = ref(""); const activePointId = ref("");
const activePoint = ref({ const activePoint = ref({
name: "" name: ""
}); });
const deviceList = ref([]); // const deviceList = ref([]);
const currDeviceList = ref([]); //
const isDeleteVisible = ref<boolean>(false);
const deleteMessage = computed(() => {
return `'确定删除<span class="font-bold">“${activePoint.value.name}”</span>吗?删除后设备所处区域的实拍图或平面图及设备点位将一并被删除,请谨慎操作。`;
});
// //
const refreshCanvas = (isLoadDevice = true) => { const initFile = () => {
const currWorkSpace = document.getElementById("workspace"); // console.log("canvasEditor");
console.log( canvasEditor.insertSvgFile(getPointObject(toRaw(activePoint.value)));
currWorkSpace.clientWidth,
currWorkSpace.clientHeight,
"currWorkSpace",
canvasEditor.canvas.viewportTransform
);
canvasEditor.insertSvgFile(
getPointObject(toRaw(activePoint.value), {
width: currWorkSpace.clientWidth,
height: currWorkSpace.clientHeight
}),
() => {
//
const img = canvasEditor.canvas.getObjects()[1] as fabric.Rect;
const cvs = canvasEditor.canvas.getObjects()[0] as fabric.Image;
// Get canvas dimensions
const canvasWidth = cvs.width;
const canvasHeight = cvs.height;
// Calculate the center position for the image
// TODO
const startScale = 1;
const left = (canvasWidth - img.width * startScale) / 2;
const top = (canvasHeight - img.height * startScale) / 2;
img.set({
left,
top,
scaleX: startScale,
scaleY: startScale
});
// Re-render the canvas to apply changes
canvasEditor.canvas.renderAll();
console.log("插入文件完成", canvasEditor.canvas.getObjects());
isLoadDevice && fetchDeviceByPoint();
}
);
}; };
// fabric& // fabric&
const initFabric = () => { const initFabric = () => {
const canvas = new fabric.Canvas("canvas", { const canvas = new fabric.Canvas("canvas", {
fireRightClick: false, // button3 fireRightClick: true, // button3
stopContextMenu: true, // stopContextMenu: true, //
controlsAboveOverlay: true, // clipPath controlsAboveOverlay: true // clipPath
selectable: false,
hasControls: false,
selection: false
}); });
canvas.setZoom(1); canvas.loadFromJSON(
getPointObject(toRaw(activePoint.value)),
canvas.renderAll.bind(canvas)
);
// //
canvasEditor.init(canvas); canvasEditor.init(canvas);
canvasEditor.use(DringPlugin); canvasEditor.use(DringPlugin);
@ -168,11 +112,24 @@ const initFabric = () => {
canvasEditor.use(MaterialPlugin); canvasEditor.use(MaterialPlugin);
event.init(canvas); event.init(canvas);
state.showFabric = true; state.showFabric = true;
nextTick(() => {
initFile();
});
}; };
/** /**
* @设备点位 * @设备点位
*/ */
//
function addPoint() {
deviceSettingAddRef.value?.openDialog();
}
//
function editPoint() {
deviceSettingAddRef.value?.openDialog();
}
//
function deletePoint() {}
// //
async function fetchPointList() { async function fetchPointList() {
const { data } = await getWorkshopsApi(); const { data } = await getWorkshopsApi();
@ -204,18 +161,13 @@ async function fetchPointList() {
// ]; // ];
activePointId.value = pointList.value[0].id; activePointId.value = pointList.value[0].id;
activePoint.value = pointList.value[0]; activePoint.value = pointList.value[0];
nextTick(() => {
refreshCanvas();
});
} }
// //
async function fetchDeviceList() { async function fetchDeviceList() {
const { data, ...resp } = await getLinkDevicesApi(); const { data } = await getLinkDevicesApi();
if (isSuccessApi(resp)) { // TODO 使loading
// TODO 使loading deviceList.value = data;
deviceList.value = data; console.log(deviceList.value, "fetchDeviceList_data", data);
console.log(deviceList.value, "fetchDeviceList_data", data);
}
} }
// //
function tabPoint(tab) { function tabPoint(tab) {
@ -223,187 +175,39 @@ function tabPoint(tab) {
activePoint.value = selectedTab; activePoint.value = selectedTab;
console.log(tab.props.name, "tabPoint", activePoint.value); console.log(tab.props.name, "tabPoint", activePoint.value);
nextTick(() => { nextTick(() => {
refreshCanvas(); initFile();
}); });
} }
// //
function addPoint() { watch(
deviceSettingAddRef.value?.openDialog(); () => pointList.value,
} () => {
console.log("pointList", pointList);
// if (pointList.value?.length) {
function afterFinishAdd(record) { nextTick(() => {
// initFabric();
pointList.value = [...toRaw(pointList.value), record]; });
activePointId.value = record.id;
activePoint.value = record;
console.log("afterFinishAdd", record);
nextTick(() => {
refreshCanvas(false); //
});
}
//
function editPoint() {
deviceSettingEditRef.value?.openDialog(toRaw(activePoint.value));
}
//
function afterFinishEdit(record) {
//
pointList.value = toRaw(pointList.value).filter(item => {
if (item.id === record.id) {
Object.assign(item, record);
activePoint.value = item;
}
return item;
});
fetchDeviceList();
console.log("afterFinishEdit", record);
nextTick(() => {
refreshCanvas(); //
});
}
//
function beforeDeletePoint() {
isDeleteVisible.value = true;
}
//
function afterFinishDelete() {
// pointList
const currPointList = toRaw(pointList.value);
const targetIndex = currPointList.findIndex(
item => item.id === activePointId.value
);
currPointList.splice(targetIndex, 1);
// false
pointList.value = currPointList;
if (!currPointList.length) {
activePoint.value = {};
activePointId.value = null;
return;
}
if (targetIndex > 0) {
activePoint.value = currPointList[targetIndex - 1];
activePointId.value = currPointList[targetIndex - 1].id;
} else {
activePoint.value = currPointList[0];
activePointId.value = currPointList[0].id;
}
nextTick(() => {
refreshCanvas();
});
}
//
async function deletePoint() {
const resp = await deleteWorkshopsApi({
id: activePointId.value
});
if (isSuccessApi(resp)) {
isDeleteVisible.value = false;
afterFinishDelete();
}
//
}
//
async function fetchDeviceByPoint() {
const resp = await getWorkshopDevicesApi({
workshop_id: activePointId.value,
is_binding: true
});
if (isSuccessApi(resp)) {
currDeviceList.value = resp.data.results;
}
console.log("fetchDeviceByPoint_resp", resp);
deviceSelectRef.value?.renderDeviceToCanvas(resp.data.results);
}
//
function afterAddDevice(record) {
fetchDeviceByPoint();
fetchDeviceList();
// currDeviceList.value = [...toRaw(currDeviceList.value), record];
// deviceList.value = toRaw(deviceList.value).filter(item => {
// if (item.id === record?.device_id) {
// Object.assign(item, {
// wd_id: record?.id,
// workshop_name: record?.workshop_name,
// is_binding: true
// });
// }
// return item;
// });
console.log("新增完成", deviceList.value, record);
}
//
async function addDevice({ record, callback }) {
const resp = await addWorkshopDevicesApi({
workshop_id: activePointId.value,
device_id: record.id,
x_ordinate: record?.x_ordinate || 0,
y_ordinate: record?.y_ordinate || 0,
icon: record?.type || 1,
wd_id: record?.wd_id
});
if (isSuccessApi(resp)) {
callback && callback(resp.data);
afterAddDevice(resp.data);
}
}
//
function afterEditDevice(record) {
currDeviceList.value = toRaw(currDeviceList.value).filter(item => {
if (item.device_id === record.device_id) {
Object.assign(item, record);
} }
return item;
});
console.log("编辑完成", currDeviceList.value, record);
}
//
async function editDevice({ editParams, callback }) {
console.log("editDevice", editParams);
const resp = await editWorkshopDevicesApi(editParams);
if (isSuccessApi(resp)) {
afterEditDevice(resp.data);
callback && callback(resp.data);
} }
} );
// // watch(
function afterDeleteDevice(record) { // () => activePoint.value,
const deleteDeviceIds = JSON.parse(record); // () => {
currDeviceList.value = toRaw(currDeviceList.value).filter( // console.log("activePoint", activePoint);
item => !deleteDeviceIds.includes(item?.id) // if (activePoint.value?.id) {
); // nextTick(() => {
deviceList.value = toRaw(deviceList.value).filter(item => { // initFile();
if (deleteDeviceIds.includes(item?.wd_id)) { // });
item.wd_id = undefined; // }
item.workshop_name = undefined; // }
item.is_binding = false; // );
}
return item;
});
console.log(
currDeviceList.value,
"删除完成",
deviceList.value,
deleteDeviceIds
);
}
onMounted(() => { onMounted(() => {
initFabric();
fetchPointList(); fetchPointList();
fetchDeviceList(); fetchDeviceList();
}); });
// //
provide("fabric", fabric); provide("fabric", fabric);
provide("event", event); provide("event", event);
@ -451,27 +255,21 @@ provide("canvasEditor", canvasEditor);
<div class="flex w-full h-full"> <div class="flex w-full h-full">
<!-- 左侧画布区域 --> <!-- 左侧画布区域 -->
<div id="workspace" class="h-full deviceOfPoint_wrap"> <div id="workspace" class="h-full deviceOfPoint_wrap">
<!-- <div class="inside-shadow"></div> --> <div class="canvas-box">
<canvas id="canvas" /> <!-- <div class="inside-shadow"></div> -->
<!-- <dragMode v-if="state.showFabric"></dragMode> <canvas
id="canvas"
:class="state.ruler ? 'design-stage-grid' : ''"
/>
<!-- <dragMode v-if="state.showFabric"></dragMode>
<zoom></zoom> --> <zoom></zoom> -->
<!-- <mouseMenu></mouseMenu> --> <!-- <mouseMenu></mouseMenu> -->
</div>
</div> </div>
<!-- 右侧属性区域--> <!-- 右侧属性区域-->
<div class="right-bar" v-if="state.showFabric"> <div class="right-bar" v-if="state.showFabric">
<DeviceSelect <DeviceSelect :deviceList="deviceList" />
ref="deviceSelectRef" <DeviceAttr />
:deviceList="deviceList"
:currDeviceList="currDeviceList"
@addDevice="addDevice"
@editDevice="editDevice"
:pointInfo="activePoint"
/>
<DeviceAttr
@afterDelete="afterDeleteDevice"
@editDevice="editDevice"
:pointInfo="activePoint"
/>
</div> </div>
</div> </div>
<div class="footer_btns"> <div class="footer_btns">
@ -481,26 +279,14 @@ provide("canvasEditor", canvasEditor);
<el-button <el-button
type="danger" type="danger"
plain plain
@click="beforeDeletePoint" @click="deletePoint"
v-if="pointList.length" v-if="pointList.length"
>删除位置</el-button >删除位置</el-button
> >
<IsAction
v-model="isDeleteVisible"
@update:visible="val => (isDeleteVisible = val)"
title="确定删除吗?"
:message="deleteMessage"
@confirm="deletePoint"
/>
</div> </div>
</div> </div>
</div> </div>
<DeviceSettingAdd ref="deviceSettingAddRef" @finishAdd="afterFinishAdd" /> <DeviceSettingAdd ref="deviceSettingAddRef" />
<DeviceSettingEdit
ref="deviceSettingEditRef"
@finishEdit="afterFinishEdit"
:currDeviceList="currDeviceList"
/>
</div> </div>
</template> </template>

@ -57,7 +57,7 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: { proxy: {
"/api/": { "/api/": {
target: "http://192.168.10.70:8080/", target: "http://192.168.10.21:8000/",
changeOrigin: true, changeOrigin: true,
secure: false secure: false
// rewrite: path => path.replace(/^\/api/, "") // rewrite: path => path.replace(/^\/api/, "")

Loading…
Cancel
Save