You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

456 lines
14 KiB
Vue

3 weeks ago
<template>
<div id="mt-edit" class="relative flex-auto w-1/1 h-1/1 dark">
<el-container class="h-1/1">
<!-- 头部1 -->
<el-header
height="45px"
class="dark:bg-myDarkBgColor cb-border p-0 select-none"
@mousedown="mainPanelRef?.stopListenerKeyDown()"
>
<!-- 头部2 -->
<header-panel
v-model:leftAside="aside_state.left_show"
v-model:rightAside="aside_state.right_show"
v-model:lock-state="globalStore.lock"
:selected-items-id="globalStore.selected_items_id"
:group-enabled="header_group_enabled"
:un-group-enabled="header_un_group_enabled"
:align-enabled="header_align_enabled"
:delete-enabled="header_delete_enabled"
:undo-enabled="cacheStore.historyIndex > 0"
:redo-enabled="cacheStore.historyIndex < cacheStore.history.length - 1"
:real-time-data="globalStore.real_time_data"
:use-thumbnail="mtEidtProps.useThumbnail"
@on-group-click="mainPanelRef?.createGroupItem"
@on-ungroup-click="mainPanelRef?.onUngroup"
@on-delete-click="onDeleteClick"
@on-export-click="onExportClick"
@on-tree-click="done_json_tree_visiable = true"
@on-help-click="onHelpClick"
@align-selected="onAlignSelected"
@on-redo-click="onRedoClick"
@on-undo-click="onUndoClick"
@on-import-click="onImportClick"
@on-return-click="emits('onReturnClick')"
@on-save-click="onSaveClick"
@on-preview-click="onPreviewClick"
@on-thumbnail-click="onThumbnailClick"
@on-draw-line-click="onDrawLineClick"
@on-right-drawer="onRightDrawer"
@on-image-dialog="onImageDialog"
>
</header-panel>
</el-header>
<!-- 中间1 -->
<el-container class="h-[calc(100%-45px-40px)]">
<!-- 左侧 -->
<el-aside
:width="aside_state.left_show ? '200px' : '0px'"
class="dark:bg-myDarkBgColor cr-border mt-edit-aside h-1/1 select-none"
@mousedown="mainPanelRef?.stopListenerKeyDown()"
>
<el-scrollbar height="100%">
<left-aside :leftAsideConfig="leftAsideStore.config"></left-aside>
</el-scrollbar>
</el-aside>
<!-- 中间2 渲染组件 -->
<el-main
class="dark:bg-myMainDarkBgColor"
@mousedown="mainPanelRef?.beginListenerKeyDown()"
>
<main-panel
ref="mainPanelRef"
:group-enabled="header_group_enabled"
:un-group-enabled="header_un_group_enabled"
:delete-enabled="header_delete_enabled"
:line-append-enable="line_append_enable"
></main-panel>
</el-main>
<!-- 右侧 -->
<el-aside
:width="aside_state.right_show ? '200px' : '0px'"
class="dark:bg-myDarkBgColor cl-border mt-edit-aside select-none"
@mousedown="mainPanelRef?.stopListenerKeyDown()"
>
<!-- 右侧2 渲染选项 -->
<right-aside>
<template v-if="hasDeviceBindSlot" #deviceBind="{ item }">
<slot name="deviceBind" :item="item"></slot>
</template>
</right-aside>
</el-aside>
</el-container>
<!-- 底部 -->
<!-- <el-footer height="40px" class="dark:bg-myDarkBgColor ct-border select-none">
<footer-panel></footer-panel>
</el-footer> -->
</el-container>
<!-- 导入数据 -->
<el-dialog
v-model="import_visible"
title="数据导入"
@close="mainPanelRef?.beginListenerKeyDown()"
>
<import-json ref="importJsonRef"></import-json>
<template #footer>
<el-button type="primary" @click="onImportYes"></el-button>
</template>
</el-dialog>
<!-- 导出数据 -->
<el-dialog
v-model="export_visible"
title="数据导出"
@close="mainPanelRef?.beginListenerKeyDown()"
>
<export-json
:done-json="objectDeepClone(globalStore.done_json)"
:canvas-cfg="globalStore.canvasCfg"
:grid-cfg="globalStore.gridCfg"
></export-json>
</el-dialog>
<el-drawer v-model="done_json_tree_visiable" title="图形结构树" direction="ltr" size="30%">
<done-tree
:done-json="globalStore.done_json"
:selected-items-id="globalStore.selected_items_id"
@update-selected-items-id="onTreeUpdateSelectedItemsId"
@update-selected-id-hide="onDoneTreeUpdateSelectedIdHide"
></done-tree>
</el-drawer>
<!-- 数据模型 -->
<el-drawer v-model="drawerVisible" :modal="false" title="数据模型文件121" modal-penetrable>
<!-- <button @click="testData"></button> -->
<!-- <el-button @click="resetSelection"></el-button> -->
<!-- <el-button @click="getNode"></el-button> -->
<!-- @check="handleCheck" -->
<el-tree-v2
style="max-width: 600px; height: 100px"
:data="treeData"
:props="props"
:height="450"
:show-checkbox="true"
:check-strictly="true"
:highlight-current="true"
ref="treeRef"
>
<template #default="{ node }">
<el-icon class="el-icon--left">
<Document v-if="node.isLeaf" />
<Folder v-else-if="!node.expanded" />
<FolderOpened v-else />
</el-icon>
<span class="prefix" :class="{ 'is-leaf': node.isLeaf }">{{ node.label }}</span>
<!-- <span>{{ node.label }}</span> -->
</template>
</el-tree-v2>
<template #footer>
<div class="drawer-footer">
<el-button >保存模型11</el-button>
<el-button v-loading.fullscreen.lock="fullscreenLoading"
>加载模型2</el-button
>
<!-- <el-button @click="coverModel"> </el-button> -->
<el-button > 移除模型 </el-button>
</div>
</template>
</el-drawer>
<!-- 数据保存弹框 -->
<el-dialog v-model="dialogFormVisible" title="数据模型保存" width="500">
<span>确定保存或者覆盖 {{ fileName }} 模型文件</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button
type="primary"
v-loading.fullscreen.lock="fullscreenLoading"
>
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 图片模型 -->
<imageModel
v-if="dialogImageVisible"
:dialogImageVisible="dialogImageVisible"
:isEdit="true"
:isDelete="true"
:isBinding="false"
:isUpload="true"
@update-dialog-image-visible="updateDialogImageVisible"
/>
<!-- END -->
</div>
</template>
<script setup lang="ts">
import HeaderPanel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/header-panel/index.vue';
import LeftAside from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/left-aside/index.vue';
import MainPanel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/main-panel/index.vue';
import RightAside from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/right-aside/index.vue';
import { leftAsideStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/left-aside';
import {
ElContainer,
ElHeader,
ElAside,
ElMain,
ElDialog,
ElDrawer,
ElButton,
ElMessage,
ElTreeV2
} from 'element-plus';
import { globalStore } from '@/views/teacher/teacherStatistics/components/mt-edit/store/global';
import {computed, reactive, ref, useSlots, onMounted, getCurrentInstance, onBeforeUnmount} from 'vue';
import DoneTree from '@/views/teacher/teacherStatistics/components/mt-edit/components/done-tree/index.vue';
import { cacheStore } from './store/cache';
import ExportJson from '@/views/teacher/teacherStatistics/components/mt-edit/components/export-json/index.vue';
import ImportJson from '@/views/teacher/teacherStatistics/components/mt-edit/components/import-json/index.vue';
import { objectDeepClone } from './utils';
import { genExportJson, useExportJsonToDoneJson } from './composables';
import type { IExportJson } from './components/types';
import imageModel from '@/views/teacher/teacherStatistics/components/mt-edit/components/layout/base-panel/imageModel.vue';
interface Tree {
id: string;
label: string;
disabled?: boolean;
children?: Tree[];
}
const props = {
value: 'id',
label: 'label',
children: 'children',
disabled: 'disabled'
};
let treeData: Tree[] = [];
function updateDialogImageVisible() {
dialogImageVisible.value = false;
}
// 打开图片模型弹窗
const onImageDialog = () => {
dialogImageVisible.value = true;
console.log('点击:', dialogImageVisible.value);
};
let drawerVisible = ref(false);
let dialogFormVisible = ref(false);
let dialogImageVisible = ref(false);
let fileName = ref('');
let menuType = ref('');
type MtEditProps = {
useThumbnail?: boolean;
screen?: string;
};
const mtEidtProps = withDefaults(defineProps<MtEditProps>(), {
useThumbnail: false
});
const emits = defineEmits([
'onPreviewClick',
'onReturnClick',
'onSaveClick',
'onThumbnailClick',
'onRightDrawer',
'onImageDialog',
'updateDialogImageVisible'
]);
const slots = useSlots();
const mainPanelRef = ref<InstanceType<typeof MainPanel>>();
const importJsonRef = ref<InstanceType<typeof ImportJson>>();
const aside_state = reactive({
left_show: true,
right_show: true
});
const hasDeviceBindSlot = computed(() => {
return !!slots.deviceBind;
});
const header_delete_enabled = computed(() => {
return globalStore.selected_items_id.length > 0;
});
const header_group_enabled = computed(() => {
return globalStore.selected_items_id.length > 1;
});
const header_un_group_enabled = computed(() => {
if (globalStore.selected_items_id.length === 1) {
const item = globalStore.done_json.find((f) => f.id === globalStore.selected_items_id[0]);
return item?.type === 'group';
}
return false;
});
const header_align_enabled = computed(() => {
const selected_items = globalStore.done_json.filter(
(f) => globalStore.selected_items_id.includes(f.id) && f.type !== 'sys-line'
);
return selected_items.length > 1;
});
const import_visible = ref(false);
const export_visible = ref(false);
const done_json_tree_visiable = ref(false);
const line_append_enable = ref(false);
const onDeleteClick = () => {
globalStore.deleteSelectedItems();
cacheStore.addHistory(globalStore.done_json);
};
const onImportClick = () => {
import_visible.value = true;
mainPanelRef.value?.stopListenerKeyDown();
};
const onExportClick = () => {
export_visible.value = true;
mainPanelRef.value?.stopListenerKeyDown();
};
const onTreeUpdateSelectedItemsId = (id: string) => {
globalStore.setSingleSelect(id);
};
const onDoneTreeUpdateSelectedIdHide = (id: string) => {
const item = globalStore.done_json.find((f) => f.id === id);
if (item) {
item.hide = !item.hide;
}
};
const onAlignSelected = (
type:
| 'left'
| 'horizontally'
| 'right'
| 'top'
| 'vertically'
| 'bottom'
| 'horizontal-distribution'
| 'vertical-distribution'
) => {
mainPanelRef.value?.onAlignSelected(type);
};
const onHelpClick = () => {
window.open('http://mt.yaolm.top');
};
const onRedoClick = () => {
mainPanelRef.value?.onRedo();
};
const onUndoClick = () => {
mainPanelRef.value?.onUndo();
};
// 导入
const onImportYes = async () => {
const res = await importJsonRef.value?.onImport();
if (res) {
import_visible.value = false;
cacheStore.addHistory(globalStore.done_json);
} else {
ElMessage.error('导入失败,请检查数据格式');
}
};
const onPreviewClick = () => {
// 获取导出json
const { exportJson } = genExportJson(
globalStore.canvasCfg,
globalStore.gridCfg,
globalStore.done_json
);
emits('onPreviewClick', exportJson);
};
const onSaveClick = () => {
// 获取导出json
const { exportJson } = genExportJson(
globalStore.canvasCfg,
globalStore.gridCfg,
globalStore.done_json
);
emits('onSaveClick', exportJson);
};
const onThumbnailClick = () => {
emits('onThumbnailClick');
};
const onDrawLineClick = (val: boolean) => {
line_append_enable.value = val;
};
let fileNames: Array<{ id: string; name: string }> = reactive([]);
let radio = ref('-1');
//加载数据模型
const fullscreenLoading = ref(false);
const onRightDrawer = () => {
drawerVisible.value = true;
// fileRead();
};
onMounted(() => {
// fileRead();
});
onBeforeUnmount(() => {
// 1. 强制停止 main-panel 的键盘监听(极其重要!)
if (mainPanelRef.value && mainPanelRef.value.stopListenerKeyDown) {
mainPanelRef.value.stopListenerKeyDown();
}
// 2. 清空占据大量内存的全局 Store 数据(假设您的 store 提供了清空/重置的方法)
// 注意:具体方法名请根据您 store 里的实际定义来修改
globalStore.done_json = [];
globalStore.selected_items_id = [];
cacheStore.history = [];
cacheStore.historyIndex = 0;
// 3. 关闭所有可能未关闭的弹窗
dialogFormVisible.value = false;
drawerVisible.value = false;
dialogImageVisible.value = false;
});
const setImportJson = (exportJson: IExportJson) => {
const { canvasCfg, gridCfg, importDoneJson } = useExportJsonToDoneJson(exportJson);
globalStore.canvasCfg = canvasCfg;
globalStore.gridCfg = gridCfg;
globalStore.setGlobalStoreDoneJson(importDoneJson);
cacheStore.history[0] = importDoneJson;
return true;
};
const treeRef = ref<InstanceType<typeof ElTreeV2> | null>();
defineExpose({
setImportJson
});
</script>
<style scoped>
.demo-pagination-block {
margin-top: 20px;
}
.prefix {
color: var(--el-color-primary);
margin-right: 10px;
}
.prefix.is-leaf {
color: var(--el-color-success);
}
.mt-edit-aside {
transition: width 0.3s;
}
.vertical-radio-group {
display: flex;
flex-direction: column;
gap: 5px;
align-items: flex-start;
}
</style>