Compare commits

...

13 Commits

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

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

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-01-12 14:35:28
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-03-25 11:16:58
* @LastEditTime: 2024-08-26 14:40:41
* @FilePath: \General-AI-Platform-Web-Client\index.html
* @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 -->
<link
rel="stylesheet"
href="//at.alicdn.com/t/c/font_4412653_5ql8iuyd77a.css"
href="//at.alicdn.com/t/c/font_4412653_jsay2r4m3wm.css"
/>
<script src="https://threejs.org/build/three.js"></script>

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-02 10:40:49
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-14 11:10:51
* @LastEditTime: 2024-08-28 16:58:13
* @FilePath: \General-AI-Platform-Web-Client\src\api\workshops.ts
* @Description:
*/
@ -30,7 +30,36 @@ export const getWorkshopsApi = (params?: object) => {
/** 新增布点 */
export const addWorkshopsApi = (data?: object) => {
return http.request<Result>("post", baseUrlApi("workshops/"), { data });
return http.request<Result>(
"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" }
}
);
};
/** 获取布点设备 */
@ -46,3 +75,17 @@ export const addWorkshopDevicesApi = (data?: object) => {
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.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 MiB

After

Width:  |  Height:  |  Size: 58 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,10 @@
/*
* @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;

@ -0,0 +1,16 @@
.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;
}
}
}
}

@ -0,0 +1,84 @@
/*
* @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,3 +1,11 @@
/*
* @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 CollapseTreeItem from "./collapseTreeItem";
import "./collapseTreeStyle.scss";
@ -16,7 +24,6 @@ export default defineComponent({
// const count = ref<number>(1);
// const treeData = ref(props.value);
// console.log(treeData.value);
return () => {
return (
<div class="collapseTree_wrap">

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

@ -0,0 +1,69 @@
/*
* @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>
);
}
});

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

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

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

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

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

@ -48,7 +48,7 @@
/* 自定义 tooltip 的类名 */
.pure-tooltip {
// right-panelz-index40000tooltip
/* 右侧操作面板right-panel类名的z-index为40000tooltip需要大于它才能显示 */
z-index: 41000 !important;
}
@ -193,8 +193,6 @@
}
/* table */
//
.table_action_box {
.el-button {
padding: 0;
@ -207,3 +205,22 @@
.el-table__inner-wrapper::before {
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;
}

@ -0,0 +1,19 @@
/*
* @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,6 +189,15 @@ class PureHttp {
): Promise<P> {
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();

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

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

@ -2,61 +2,59 @@
import useSelect from "@/hooks/select";
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_2 from "@/assets/modelSetting/video_type_2.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 update = getCurrentInstance();
const { mixinState, canvasEditor } = useSelect();
const { fetchViewsBoundaries, isMoveDevice, fetchMoveDeviceObjectOrdinate } =
useDeviceObject();
/**业务属性 */
const formData = ref({
iconType: 1
name: "",
icon: 1
});
const rules = {
iconType: [{ required: true, message: "请选择图标类型", trigger: "change" }]
icon: [{ required: false, message: "请选择图标类型", trigger: "change" }]
};
const iconOptions = ref([
{
value: 1,
value: "1",
label: "图标1",
type: "1",
url: video_type_1
},
{
value: 2,
value: "2",
label: "图标2",
type: "2",
url: video_type_2
},
{
value: 3,
value: "3",
label: "图标3",
type: "3",
url: video_type_3
}
]);
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("");
}
});
};
/**拓展属性 */
//
@ -105,37 +103,24 @@ const fontAttr = reactive({
overline: false
});
//
const getCanvasBoundaries = () => {
const canvasObj = canvasEditor.canvas.getObjects()[1];
return {
width: canvasObj.width,
height: canvasObj.height
};
};
const constrainObjectWithinCanvas = obj => {
const { width, height } = getCanvasBoundaries();
//
const objLeft = obj.left;
const objTop = obj.top;
const objWidth = obj.width * obj.scaleX;
const objHeight = obj.height * obj.scaleY;
const { startLeft, startTop, endLeft, endTop } = fetchViewsBoundaries(
canvasEditor.canvas,
obj
);
//
if (objLeft < 0) {
obj.left = 0;
if (obj.left < startLeft) {
obj.left = startLeft;
}
if (objTop < 0) {
obj.top = 0;
if (obj.top < startTop) {
obj.top = startTop;
}
if (objLeft + objWidth > width) {
obj.left = width - objWidth;
if (obj.left > endLeft) {
obj.left = endLeft;
}
if (objTop + objHeight > height) {
obj.top = height - objHeight;
if (obj.top > endTop) {
obj.top = endTop;
}
//
obj.setCoords();
};
@ -181,9 +166,40 @@ const getObjectAttr = e => {
const animateObject = activeObject.get("animation");
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 = () => {
baseAttr.fill = "";
update?.proxy?.$forceUpdate();
@ -193,10 +209,20 @@ const init = () => {
//
event.on("selectCancel", selectCancel);
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);
// object:moving
canvasEditor.canvas.on("object:moving", e => {
const activeObject = e.target;
// console.log(activeObject, "activeObject_moving");
if (activeObject) {
constrainObjectWithinCanvas(activeObject);
canvasEditor.canvas.renderAll();
@ -238,53 +264,28 @@ const changeCommon = (key, value) => {
canvasEditor.canvas.renderAll();
return;
}
if (key === "iconType") {
if (key === "icon") {
// group fabric.Image
const imageObject = activeObject._objects[1];
if (imageObject) {
const targetIcon = toRaw(iconOptions.value).find(
item => item.value === value
);
console.log(targetIcon, "activeObject_iconType", activeObject);
if (targetIcon) {
//
imageObject.setSrc(
targetIcon.url,
() => {
//
console.log("Image loaded successfully activeObject_iconType");
activeObject.addWithUpdate(); //
canvasEditor.canvas.renderAll();
},
{ 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" //
// }
// );
startEditDevice(() => {
imageObject.setSrc(
targetIcon.url,
() => {
//
console.log("Image loaded successfully activeObject_iconType");
activeObject.addWithUpdate(); //
canvasEditor.canvas.renderAll();
},
{ crossOrigin: "anonymous" }
);
});
}
return;
}
@ -302,30 +303,18 @@ onMounted(init);
onBeforeUnmount(() => {
event.off("selectCancel", selectCancel);
event.off("selectOne", getObjectAttr);
canvasEditor.canvas.off("object:deleteObject");
canvasEditor.canvas.off("mouse:up");
canvasEditor.canvas.off("object:modified", getObjectAttr);
canvasEditor.canvas.off("object:moving");
});
</script>
<template>
<div class="box" v-if="mixinState.mSelectMode === 'one'">
<!-- 字体属性 -->
<div v-show="textTypeConf.includes(mixinState.mSelectOneType)">
<!-- 字体属性 -->
<div class="deviceAtrr_toolbar" v-if="mixinState.mSelectMode === 'one'">
<div class="hf-1 text-[#000]">
<!-- 设备待选列表是name, 新增后返回是device_name -->
{{ formData?.device_name || formData?.name }}
</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
ref="formRef"
:rules="rules"
@ -334,62 +323,25 @@ onBeforeUnmount(() => {
class="demo-form-inline"
label-position="top"
>
<el-form-item label="选择图标" class="w-full" prop="iconType">
<!-- // TODO UI -->
<el-form-item label="选择图标" class="w-full pt-[12px]" prop="icon">
<el-radio-group
v-model="formData.iconType"
@change="changeCommon('iconType', formData.iconType)"
v-model="formData.icon"
@change="changeCommon('icon', formData.icon)"
>
<el-radio
v-for="option in iconOptions"
:key="option.value"
:label="option.value"
>
<i>
<!-- 图标 -->
</i>
{{ option.label }}
<el-image
:src="option?.url || ''"
:fit="'contain'"
class="w-[32px]"
/>
</el-radio>
</el-radio-group>
</el-form-item>
</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>
</template>

@ -1,55 +1,34 @@
<!--
* @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">
// import { Modal } from "view-ui-plus";
import useSelect from "@/hooks/select";
// import { cloneDeep } from "lodash-es";
import { v4 as uuid } from "uuid";
// import { useI18n } from "vue-i18n";
import { useDeviceObject } from "../hooks/useDeviceObject";
// import watchOnlineSelected from "../../../assets/modelSetting/watchOnlineSelected.svg";
// const testSrc =
// "https://img.cgmodel.com/image/2020/1010/big/1537169-1390622992.jpg";
import { IsAction } from "@/components/Action";
const props = defineProps({
deviceList: {
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 { initDeviceGroupObjects } = useDeviceObject();
const {
initDeviceGroupObjects,
isInViewBoundaries,
getDragDeviceObjectOrdinate,
fetchOrdinateByView
} = useDeviceObject();
// const { t } = useI18n();
const defaultPosition = {
left: 100,
@ -71,11 +50,7 @@ interface materialItemI {
src: string;
}
// const allType: materialTypeI = {
// value: "",
// label: ""
// };
const allDeviceList = ref([]);
const state = reactive({
search: "",
// placeholder: <undefined | string>"",
@ -84,232 +59,295 @@ const state = reactive({
materialTypelist: [], //
materialist: [] //
});
const detailInfo = ref({});
//
canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => {
state.materialTypelist = [...list];
state.materialist = list;
});
//
// const handleChange = (e, item) => {
// //
const currDetailInfo = ref({});
const isUpdateBindVisible = ref<boolean>(false);
const bindDeviceData = ref<Record<string, any>>({});
const updateMessage = computed(() => {
return `<span class="font-bold">“${currDetailInfo.value?.name}”</span>已绑定至<span class="font-bold">“${currDetailInfo.value?.workshop_name}”</span>,确定解除绑定并重新配置吗?`;
});
//
canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => {
state.materialTypelist = [...list];
state.materialist = list;
});
function fetchDetail(record) {
detailInfo.value = record;
console.log(detailInfo.value, "fetchDetail");
}
/**
* 校验是否允许添加设备
* @param record 选中的素材
* 处理添加设备&重复添加设备同点位不同点位
*/
function isValidAdd(record) {
console.log("isValidAdd_record", record);
return false;
function deviceSelected(callback, options) {
const { targetDeviceItem } = options;
console.log(targetDeviceItem, "deviceSelected", props.currDeviceList);
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) => {
if (isValidAdd(deviceItem)) {
return;
}
console.log(event, deviceItem, "dragItem");
fabric.util.enlivenObjects(
[
{
...defaultPosition,
shadow: "",
fontFamily: "arial",
id: uuid(),
name: "svg元素",
...initDeviceGroupObjects(deviceItem)
}
],
function (objects) {
// objects JSONGroup
const item = objects[0];
// if (!option) {
// groupText.center();
// }
// GroupCanvas
// var canvas = new fabric.Canvas('canvas-id');
// canvas.add(group);
canvasEditor.dragAddItem(event, item);
fetchDetail(deviceItem);
// Canvas
// canvas.renderAll();
}
);
};
//
const addItem = deviceItem => {
if (isValidAdd(deviceItem)) {
return;
}
fabric.util.enlivenObjects(
[
{
...defaultPosition,
id: uuid(),
// name: "svg",
...initDeviceGroupObjects(deviceItem)
const { x_ordinate, y_ordinate } = getDragDeviceObjectOrdinate(
event,
canvasEditor.canvas,
item
);
//
if (
!isInViewBoundaries(canvasEditor.canvas, {
...item,
x_ordinate,
y_ordinate
})
) {
return;
}
],
function (objects) {
// objects JSONGroup
const item = objects[0];
// if (!option) {
// groupText.center();
// }
const targetDeviceItem = {
...deviceItem,
x_ordinate,
y_ordinate
};
// GroupCanvas
// var canvas = new fabric.Canvas('canvas-id');
// canvas.add(group);
canvasEditor.canvas.add(item);
canvasEditor.canvas.setActiveObject(item);
canvasEditor.canvas.requestRenderAll();
fetchDetail(deviceItem);
// Canvas
// canvas.renderAll();
// canvasEditor.dragAddItem(event, item);
deviceSelected(
_ => {
//
//
// item.set({
// name: resData?.device_name,
// deviceInfo: resData
// });
// canvasEditor.dragAddItem(event, item);
},
{
targetDeviceItem,
event,
item
}
);
}
);
// 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 addItem = deviceItem => {
// if (isValidAdd(deviceItem)) {
// return;
// }
// ]);
const DefaultSize = {
width: 947,
height: 642
};
onMounted(() => {
canvasEditor.setSize(DefaultSize.width, DefaultSize.height);
canvasEditor.on("sizeChange", (width, height) => {
width.value = width;
height.value = height;
});
// canvas.editor.editorWorkspace.setSize(width.value, height.value);
// canvas.editor.editorWorkspace = new EditorWorkspace(canvas.c, {
// width: width.value,
// height: height.value,
// });
});
// const setSizeBy = (w, h) => {
// modalData.width = w;
// modalData.height = h;
// };
// const setSize = () => {
// canvasEditor.setSize(width.value, height.value);
// // canvas.editor.editorWorkspace.setSize(width.value, height.value);
// console.log(deviceItem, "addItem_deviceItem");
// fabric.util.enlivenObjects(
// [
// {
// ...defaultPosition,
// // name: "svg",
// ...initDeviceGroupObjects(deviceItem)
// }
// ],
// function (objects) {
// const item = objects[0];
// deviceSelected(() => {
// canvasEditor.canvas.add(item);
// canvasEditor.canvas.setActiveObject(item);
// canvasEditor.canvas.requestRenderAll();
// });
// }
// );
// };
// const handleClose = () => {
// showModal.value = false;
// };
// const handleConfirm = () => {
// width.value = modalData.width;
// height.value = modalData.height;
// setSize();
// handleClose();
// };
//
function renderDeviceToCanvas(record) {
//
canvasEditor.canvas.getObjects().forEach(item => {
if (item.name === "device") {
canvasEditor.canvas.remove(item);
}
});
const startArr = record;
const finalArr = [];
startArr.map(deviceItem => {
const fullDeviceItem = {
...defaultPosition,
...deviceItem,
...fetchOrdinateByView(canvasEditor.canvas, deviceItem),
name: deviceItem.device_name
};
finalArr.push({
...fullDeviceItem,
...initDeviceGroupObjects(fullDeviceItem)
});
});
fabric.util.enlivenObjects([...finalArr], function (enlivenedObjects) {
enlivenedObjects.forEach(groupObject => {
if (groupObject.type === "group") {
// group canvas
canvasEditor.canvas.add(groupObject);
}
});
canvasEditor.canvas.requestRenderAll();
});
}
watch(
() => props.deviceList,
() => {
console.log(props.deviceList, "watch_deviceList");
allDeviceList.value = toRaw(props.deviceList);
console.log(allDeviceList.value, "allDeviceList", props.deviceList);
},
{
immediate: true,
deep: true
}
);
defineExpose({ renderDeviceToCanvas });
</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>
<style scoped lang="scss">
.search-box {
padding-top: 10px;
display: flex;
.input {
margin-left: 10px;
}
}
.tmpl-img {
display: inline-block;
width: 53px;
margin-left: 2px;
margin-bottom: 2px;
background: #f5f5f5;
padding: 6px;
cursor: pointer;
// width: 135px;
// cursor: pointer;
// margin-right: 5px;
}
.form-wrap {
display: flex;
justify-content: space-around;
align-content: center;
margin-bottom: 10px;
}
</style>
../hooks/useWatchModels../hooks/useDeviceObject
<span>详情</span>
</div>
</template>
<div class="device_info_box">
<div class="pf-1 pb-[8px] font-bold text-[16px]">设备信息</div>
<ul class="text-[16px] text-web-font1 device_info_items">
<li>
<span>设备名称</span>
<span>{{ info?.name }}</span>
</li>
<li>
<span>设备ID</span>
<span>{{ info?.entity_suid }}</span>
</li>
<li>
<span>设备类型</span>
<span>{{ info?.classification }}</span>
</li>
<li>
<span>设备型号</span>
<span>{{ info?.device_model }}</span>
</li>
</ul>
</div>
</el-popover>
</li>
</ul>
</div>
</div>
</div>
<IsAction
v-model="isUpdateBindVisible"
@update:visible="val => (isUpdateBindVisible = val)"
title="确定重新绑定吗?"
:message="updateMessage"
@confirm="updateConfirmBind"
/>
</template>

@ -0,0 +1,244 @@
<!--
* @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>

@ -0,0 +1,181 @@
<!--
* @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>

@ -1,118 +0,0 @@
/*
* @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,31 +4,272 @@
*/
// https://t7.baidu.com/it/u=2757924858,1404466263&fm=193
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 = () => {
const initDeviceGroupObjects: Record<string, any> = (record: {
id: string;
value: string;
}) => {
// 拖拽新增获取设备对象在画布上的坐标
function getDragDeviceObjectOrdinate(event, viewObj, deviceObj) {
// 移动到画布上的设备对象的相对坐标,使用相对图片的坐标系,而不是画布的坐标系
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");
// 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;
// }
let iconObjectSrc = video_type_1;
switch (record?.icon) {
case "3":
iconObjectSrc = video_type_3;
break;
case "2":
iconObjectSrc = video_type_2;
break;
case "1":
default:
iconObjectSrc = video_type_1;
break;
}
let bgDeviceImage;
const fontStyle = { fill: "#333333" };
// 不在可视区域内的设备
if (record?.notInVisable) {
bgDeviceImage = device_video_bg_gray;
fontStyle.fill = "gray";
} else {
bgDeviceImage = device_video_bg;
}
return {
name: "device",
id: uuid(),
deviceInfo: record, // 设备信息
left: record?.left ? Number(record?.left) : 0,
top: record?.top ? Number(record?.top) : 0,
selectable: true,
hasControls: true,
lockUniScaling: true, // 当设置为trueObject将无法被锁定比例进行缩放。默认值为false。
@ -39,10 +280,8 @@ export const useDeviceObject = () => {
version: "5.3.0",
originX: "left",
originY: "top",
left: 87.4695,
top: 85.8002,
width: 113,
height: 66,
width: 132,
height: 62,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
@ -66,17 +305,16 @@ export const useDeviceObject = () => {
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
id: "520579bf-b8b3-451b-b3aa-5b8a9d6cd1a8",
objects: [
{
type: "image",
version: "5.3.0",
originX: "left",
originY: "top",
left: -56.5,
left: -66,
top: -33,
width: 113,
height: 66,
width: 264,
height: 132,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
@ -86,8 +324,8 @@ export const useDeviceObject = () => {
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
scaleX: 0.5,
scaleY: 0.5,
angle: 0,
flipX: false,
flipY: false,
@ -104,7 +342,7 @@ export const useDeviceObject = () => {
cropY: 0,
selectable: false,
hasControls: false,
src: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTEzIiBoZWlnaHQ9IjY2IiB2aWV3Qm94PSIwIDAgMTEzIDY2IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kXzIyMDNfMTAzMTgpIj4KPG1hc2sgaWQ9InBhdGgtMS1pbnNpZGUtMV8yMjAzXzEwMzE4IiBmaWxsPSJ3aGl0ZSI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMkM3Ljc5MDg2IDIgNiAzLjc5MDg2IDYgNlY0NEM2IDQ2LjIwOTEgNy43OTA4NiA0OCAxMCA0OEg0OS44TDU3IDU2TDY0LjIgNDhIMTAzQzEwNS4yMDkgNDggMTA3IDQ2LjIwOTEgMTA3IDQ0VjZDMTA3IDMuNzkwODYgMTA1LjIwOSAyIDEwMyAySDEwWiIvPgo8L21hc2s+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAgMkM3Ljc5MDg2IDIgNiAzLjc5MDg2IDYgNlY0NEM2IDQ2LjIwOTEgNy43OTA4NiA0OCAxMCA0OEg0OS44TDU3IDU2TDY0LjIgNDhIMTAzQzEwNS4yMDkgNDggMTA3IDQ2LjIwOTEgMTA3IDQ0VjZDMTA3IDMuNzkwODYgMTA1LjIwOSAyIDEwMyAySDEwWiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC44IiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz4KPHBhdGggZD0iTTQ5LjggNDhMNTAuNTQzMyA0Ny4zMzFMNTAuMjQ1NCA0N0g0OS44VjQ4Wk01NyA1Nkw1Ni4yNTY3IDU2LjY2OUw1NyA1Ny40OTQ4TDU3Ljc0MzMgNTYuNjY5TDU3IDU2Wk02NC4yIDQ4VjQ3SDYzLjc1NDZMNjMuNDU2NyA0Ny4zMzFMNjQuMiA0OFpNNyA2QzcgNC4zNDMxNSA4LjM0MzE1IDMgMTAgM1YxQzcuMjM4NTggMSA1IDMuMjM4NTcgNSA2SDdaTTcgNDRWNkg1VjQ0SDdaTTEwIDQ3QzguMzQzMTUgNDcgNyA0NS42NTY5IDcgNDRINUM1IDQ2Ljc2MTQgNy4yMzg1NyA0OSAxMCA0OVY0N1pNNDkuOCA0N0gxMFY0OUg0OS44VjQ3Wk00OS4wNTY3IDQ4LjY2OUw1Ni4yNTY3IDU2LjY2OUw1Ny43NDMzIDU1LjMzMUw1MC41NDMzIDQ3LjMzMUw0OS4wNTY3IDQ4LjY2OVpNNTcuNzQzMyA1Ni42NjlMNjQuOTQzMyA0OC42NjlMNjMuNDU2NyA0Ny4zMzFMNTYuMjU2NyA1NS4zMzFMNTcuNzQzMyA1Ni42NjlaTTEwMyA0N0g2NC4yVjQ5SDEwM1Y0N1pNMTA2IDQ0QzEwNiA0NS42NTY5IDEwNC42NTcgNDcgMTAzIDQ3VjQ5QzEwNS43NjEgNDkgMTA4IDQ2Ljc2MTQgMTA4IDQ0SDEwNlpNMTA2IDZWNDRIMTA4VjZIMTA2Wk0xMDMgM0MxMDQuNjU3IDMgMTA2IDQuMzQzMTUgMTA2IDZIMTA4QzEwOCAzLjIzODU4IDEwNS43NjEgMSAxMDMgMVYzWk0xMCAzSDEwM1YxSDEwVjNaIiBmaWxsPSJ3aGl0ZSIgbWFzaz0idXJsKCNwYXRoLTEtaW5zaWRlLTFfMjIwM18xMDMxOCkiLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9kXzIyMDNfMTAzMTgiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMTMiIGhlaWdodD0iNjYiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iNCIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4xIDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfMjIwM18xMDMxOCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd18yMjAzXzEwMzE4IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=",
src: bgDeviceImage,
crossOrigin: null,
filters: []
},
@ -113,7 +351,7 @@ export const useDeviceObject = () => {
version: "5.3.0",
originX: "left",
originY: "top",
left: -40.6281,
left: -50.6281,
top: -22.9893,
width: 48,
height: 48,
@ -144,7 +382,7 @@ export const useDeviceObject = () => {
cropY: 0,
selectable: false,
hasControls: false,
src: video_type_1,
src: iconObjectSrc,
crossOrigin: null,
filters: []
},
@ -153,11 +391,11 @@ export const useDeviceObject = () => {
version: "5.3.0",
originX: "left",
originY: "top",
left: -7.3377,
left: -14.3377,
top: -15.4904,
width: 320,
height: 90.4,
fill: "#333333",
fill: fontStyle.fill,
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
@ -200,11 +438,59 @@ export const useDeviceObject = () => {
pathAlign: "baseline",
selectable: 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 {
initDeviceGroupObjects
initDeviceGroupObjects,
isInViewBoundaries,
getDragDeviceObjectOrdinate,
isMoveDevice,
fetchViewsBoundaries,
fetchOrdinateByView,
fetchMoveDeviceObjectOrdinate,
isInCurrViewBoundaries
};
};

@ -1,581 +0,0 @@
/**
* @
* 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,5 +1,14 @@
/*
* @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 = () => {
function getPointObject(record) {
function getPointObject(record, objectOptions) {
const { width, height } = objectOptions;
const { picture } = record;
// TODO: 获取点位对象
return JSON.stringify({
@ -12,9 +21,9 @@ export const usePointObject = () => {
originY: "top",
left: 0,
top: 0,
width: 947,
height: 610,
fill: "rgba(255,35,255,1)",
width: width,
height: height,
fill: "", // rgba(21, 77, 221, 0.1)
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
@ -41,15 +50,16 @@ export const usePointObject = () => {
ry: 0,
id: "workspace",
selectable: false,
hasControls: false
hasControls: false,
selection: false
},
{
type: "image",
version: "5.3.0",
originX: "left",
originY: "top",
width: 947,
height: 610,
// originX: "left",
// originY: "top",
// left: 0,
// top: 0,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
@ -78,6 +88,7 @@ export const usePointObject = () => {
id: "a3ab29c6-7008-49fe-abf3-edc9a47cd460",
selectable: false,
hasControls: false,
selection: false,
evented: false,
crossOrigin: null,
src: picture,

@ -60,13 +60,13 @@
padding-top: 40px;
.bg_preview {
height: 412px;
background-color: red;
border: 1px dashed #ddd;
}
}
.point_detail_wrap {
.deviceOfPoint_wrap {
background-color: goldenrod;
height: calc(100vh - 290px);
border: 1px dashed #ddd;
height: calc(100vh - 300px);
}
.footer_btns {
padding: 16px 0;
@ -77,9 +77,12 @@
/* TODO 待使用 */
.right-bar {
margin-left: 16px;
width: 241px;
height: 100%;
overflow-y: auto;
width: 304.07px;
height: calc(100vh - 300px);
overflow-y: scroll;
background: #fafbff;
border-radius: 4px;
border: 1px solid rgba(21, 77, 221, 0.1);
}
#workspace {
flex: 1;
@ -90,6 +93,7 @@
}
/* 选择设备栏位 */
.deviceSelect_toolbar {
font-size: 14px;
.deviceSelect_list {
li {
border-radius: 2px;
@ -99,4 +103,38 @@
}
}
}
.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
* @Date: 2024-08-02 10:52:32
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-14 11:22:32
* @LastEditTime: 2024-08-30 13:37:45
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\index.vue
* @Description: 设备点位管理设置
@ 交互说明
@ -12,15 +12,27 @@
4. 设备关联属性设置
5. 效果预览
-->
<script name="Home" setup>
<script setup lang="ts">
//
import DeviceAttr from "./components/deviceAttr.vue";
import DeviceSettingAdd from "./components/add.vue";
import DeviceSettingEdit from "./components/edit.vue";
import DeviceSelect from "./components/deviceSelect.vue";
import { IsAction } from "@/components/Action";
import { usePointObject } from "./hooks/usePointObject";
import { getWorkshopsApi } from "@/api/workshops";
import {
getWorkshopsApi,
deleteWorkshopsApi,
addWorkshopDevicesApi,
editWorkshopDevicesApi,
getWorkshopDevicesApi
} from "@/api/workshops";
import { getLinkDevicesApi } from "@/api/device";
// TODO
import { isSuccessApi } from "@/utils/forApi";
// TODO
//
import { CanvasEventEmitter } from "@/utils/event/notifier";
// import { downFile } from '@/utils/utils';
@ -65,30 +77,74 @@ const state = reactive({
* @设备点位
*/
const { getPointObject } = usePointObject();
const pointList = ref([]);
const deviceSettingAddRef = ref("");
const deviceSettingEditRef = ref("");
const deviceSelectRef = ref("");
const activePointId = ref("");
const activePoint = ref({
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 initFile = () => {
// console.log("canvasEditor");
canvasEditor.insertSvgFile(getPointObject(toRaw(activePoint.value)));
//
const refreshCanvas = (isLoadDevice = true) => {
const currWorkSpace = document.getElementById("workspace");
console.log(
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&
const initFabric = () => {
const canvas = new fabric.Canvas("canvas", {
fireRightClick: true, // button3
fireRightClick: false, // button3
stopContextMenu: true, //
controlsAboveOverlay: true // clipPath
controlsAboveOverlay: true, // clipPath
selectable: false,
hasControls: false,
selection: false
});
canvas.loadFromJSON(
getPointObject(toRaw(activePoint.value)),
canvas.renderAll.bind(canvas)
);
canvas.setZoom(1);
//
canvasEditor.init(canvas);
canvasEditor.use(DringPlugin);
@ -112,24 +168,11 @@ const initFabric = () => {
canvasEditor.use(MaterialPlugin);
event.init(canvas);
state.showFabric = true;
nextTick(() => {
initFile();
});
};
/**
* @设备点位
*/
//
function addPoint() {
deviceSettingAddRef.value?.openDialog();
}
//
function editPoint() {
deviceSettingAddRef.value?.openDialog();
}
//
function deletePoint() {}
//
async function fetchPointList() {
const { data } = await getWorkshopsApi();
@ -161,13 +204,18 @@ async function fetchPointList() {
// ];
activePointId.value = pointList.value[0].id;
activePoint.value = pointList.value[0];
nextTick(() => {
refreshCanvas();
});
}
//
async function fetchDeviceList() {
const { data } = await getLinkDevicesApi();
// TODO 使loading
deviceList.value = data;
console.log(deviceList.value, "fetchDeviceList_data", data);
const { data, ...resp } = await getLinkDevicesApi();
if (isSuccessApi(resp)) {
// TODO 使loading
deviceList.value = data;
console.log(deviceList.value, "fetchDeviceList_data", data);
}
}
//
function tabPoint(tab) {
@ -175,39 +223,187 @@ function tabPoint(tab) {
activePoint.value = selectedTab;
console.log(tab.props.name, "tabPoint", activePoint.value);
nextTick(() => {
initFile();
refreshCanvas();
});
}
//
watch(
() => pointList.value,
() => {
console.log("pointList", pointList);
if (pointList.value?.length) {
nextTick(() => {
initFabric();
});
//
function addPoint() {
deviceSettingAddRef.value?.openDialog();
}
//
function afterFinishAdd(record) {
//
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();
}
//
}
// watch(
// () => activePoint.value,
// () => {
// console.log("activePoint", activePoint);
// if (activePoint.value?.id) {
// nextTick(() => {
// initFile();
// });
// }
// }
// );
//
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);
}
}
//
function afterDeleteDevice(record) {
const deleteDeviceIds = JSON.parse(record);
currDeviceList.value = toRaw(currDeviceList.value).filter(
item => !deleteDeviceIds.includes(item?.id)
);
deviceList.value = toRaw(deviceList.value).filter(item => {
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(() => {
initFabric();
fetchPointList();
fetchDeviceList();
});
//
provide("fabric", fabric);
provide("event", event);
@ -255,21 +451,27 @@ provide("canvasEditor", canvasEditor);
<div class="flex w-full h-full">
<!-- 左侧画布区域 -->
<div id="workspace" class="h-full deviceOfPoint_wrap">
<div class="canvas-box">
<!-- <div class="inside-shadow"></div> -->
<canvas
id="canvas"
:class="state.ruler ? 'design-stage-grid' : ''"
/>
<!-- <dragMode v-if="state.showFabric"></dragMode>
<!-- <div class="inside-shadow"></div> -->
<canvas id="canvas" />
<!-- <dragMode v-if="state.showFabric"></dragMode>
<zoom></zoom> -->
<!-- <mouseMenu></mouseMenu> -->
</div>
<!-- <mouseMenu></mouseMenu> -->
</div>
<!-- 右侧属性区域-->
<div class="right-bar" v-if="state.showFabric">
<DeviceSelect :deviceList="deviceList" />
<DeviceAttr />
<DeviceSelect
ref="deviceSelectRef"
:deviceList="deviceList"
:currDeviceList="currDeviceList"
@addDevice="addDevice"
@editDevice="editDevice"
:pointInfo="activePoint"
/>
<DeviceAttr
@afterDelete="afterDeleteDevice"
@editDevice="editDevice"
:pointInfo="activePoint"
/>
</div>
</div>
<div class="footer_btns">
@ -279,14 +481,26 @@ provide("canvasEditor", canvasEditor);
<el-button
type="danger"
plain
@click="deletePoint"
@click="beforeDeletePoint"
v-if="pointList.length"
>删除位置</el-button
>
<IsAction
v-model="isDeleteVisible"
@update:visible="val => (isDeleteVisible = val)"
title="确定删除吗?"
:message="deleteMessage"
@confirm="deletePoint"
/>
</div>
</div>
</div>
<DeviceSettingAdd ref="deviceSettingAddRef" />
<DeviceSettingAdd ref="deviceSettingAddRef" @finishAdd="afterFinishAdd" />
<DeviceSettingEdit
ref="deviceSettingEditRef"
@finishEdit="afterFinishEdit"
:currDeviceList="currDeviceList"
/>
</div>
</template>

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

Loading…
Cancel
Save