feat(all): 修复html复制报错

This commit is contained in:
plum 2026-04-22 17:47:28 +08:00
parent 37e1e58883
commit ecd24acf19
4 changed files with 209 additions and 193 deletions

View File

@ -18,7 +18,7 @@ import {onMounted, ref} from "vue";
import {Copy} from "@vicons/carbon"; import {Copy} from "@vicons/carbon";
import {NIcon, NTooltip} from "naive-ui"; import {NIcon, NTooltip} from "naive-ui";
import {t} from "@/language"; import {t} from "@/language";
import {App,Hooks,AddObjectCommand} from "@astral3d/engine"; import {App,Hooks,Loader,Utils,AddObjectCommand} from "@astral3d/engine";
const disabled = ref(true); const disabled = ref(true);
@ -36,6 +36,15 @@ function handleClone() {
// //
if (object === null || object.parent === null) return; if (object === null || object.parent === null) return;
if (Utils.isHtmlPanelObject(object)) {
const _json = object.toJSON() as any;
Loader.objectLoader.parseAsync(_json).then(newObject3D => {
App.execute(new AddObjectCommand(newObject3D));
}).catch((e: Error) => window.$message?.error(e.message));
return;
}
object = object.clone(); object = object.clone();
App.execute(new AddObjectCommand(object)); App.execute(new AddObjectCommand(object));

View File

@ -19,7 +19,7 @@ import {
ChoroplethMap ChoroplethMap
} from '@vicons/carbon'; } from '@vicons/carbon';
import {t} from "@/language"; import {t} from "@/language";
import {App,Hooks, MoveObjectCommand, RemoveObjectCommand, AddObjectCommand} from "@astral3d/engine"; import {App,Hooks,Loader,Utils, MoveObjectCommand, RemoveObjectCommand, AddObjectCommand} from "@astral3d/engine";
import {escapeHTML, findSiblingsAndIndex} from "@/utils/common/utils"; import {escapeHTML, findSiblingsAndIndex} from "@/utils/common/utils";
import {getMaterialName} from "@/utils/common/scenes"; import {getMaterialName} from "@/utils/common/scenes";
import EsContextmenu from "@/components/es/EsContextmenu.vue"; import EsContextmenu from "@/components/es/EsContextmenu.vue";
@ -367,6 +367,15 @@ function handleContextmenuSelect(key: string) {
if (parent !== null) App.execute(new RemoveObjectCommand(object)); if (parent !== null) App.execute(new RemoveObjectCommand(object));
break; break;
case "clone": case "clone":
if (Utils.isHtmlPanelObject(object)) {
const _json = object.toJSON() as any;
Loader.objectLoader.parseAsync(_json).then(newObject3D => {
App.execute(new AddObjectCommand(newObject3D));
}).catch((e: Error) => window.$message?.error(e.message));
break;
}
const _object = object.clone(); const _object = object.clone();
App.execute(new AddObjectCommand(_object)); App.execute(new AddObjectCommand(_object));

View File

@ -29,6 +29,22 @@ onBeforeUnmount(() => {
Hooks.useRemoveSignal("objectSelected", updateUI); Hooks.useRemoveSignal("objectSelected", updateUI);
}) })
function ensureHtmlPanelOptions(object: any) {
const options = object?.options || {};
const codes = Array.isArray(options.codes) ? options.codes : [];
const isSingleHtml = typeof options.isSingleHtml === 'boolean' ? options.isSingleHtml : true;
const isSprite = typeof options.isSprite === 'boolean' ? options.isSprite : !!object?.isHtmlSprite;
object.options = {
...options,
codes,
isSingleHtml,
isSprite
};
return object.options;
}
function updateUI() { function updateUI() {
const object = App.selected; const object = App.selected;
if(!object) return; if(!object) return;
@ -39,7 +55,8 @@ function updateUI() {
} }
data.type = object.isHtmlPanel? 'panel' :'sprite'; data.type = object.isHtmlPanel? 'panel' :'sprite';
data.codes = object.options.codes; const options = ensureHtmlPanelOptions(object);
data.codes = options.codes;
} }
function update(method: string){ function update(method: string){
@ -55,6 +72,7 @@ function update(method: string){
const _json = object.toJSON() as any; const _json = object.toJSON() as any;
_json.object.type = data.type === 'panel' ? 'HtmlSprite' : 'HtmlPanel'; _json.object.type = data.type === 'panel' ? 'HtmlSprite' : 'HtmlPanel';
_json.object.options = _json.object.options || {};
_json.object.options.isSprite = !_json.object.options.isSprite; _json.object.options.isSprite = !_json.object.options.isSprite;
Loader.objectLoader.parseAsync(_json).then(newObject3D => { Loader.objectLoader.parseAsync(_json).then(newObject3D => {
@ -73,9 +91,11 @@ function update(method: string){
_code.content = editorCode.code; _code.content = editorCode.code;
const options = ensureHtmlPanelOptions(object);
const htmlPanelOption = { const htmlPanelOption = {
isSprite: object.options.isSprite, isSprite: options.isSprite,
isSingleHtml:object.options.isSingleHtml, isSingleHtml: options.isSingleHtml,
codes: data.codes codes: data.codes
} }
@ -135,10 +155,12 @@ function updateFileList(fList: UploadFileInfo[]) {
isSprite: data.type ==='sprite', isSprite: data.type ==='sprite',
fileName: file.name fileName: file.name
}).then(htmlPlaneObj => { }).then(htmlPlaneObj => {
object.options.codes = htmlPlaneObj.options.codes; const options = ensureHtmlPanelOptions(object);
object.options.isSingleHtml = htmlPlaneObj.options.isSingleHtml;
data.codes = object.options.codes; options.codes = htmlPlaneObj.options.codes;
options.isSingleHtml = htmlPlaneObj.options.isSingleHtml;
data.codes = options.codes;
// Keep object identity (id/uuid) so scene tree and context menu references stay valid. // Keep object identity (id/uuid) so scene tree and context menu references stay valid.
while (object.element.firstChild) { while (object.element.firstChild) {

View File

@ -1,5 +1,5 @@
<template> <template>
<!-- <n-split direction="horizontal" :default-size="0.2" :max="0.8" :min="0.2" style="height: 100%" class="h-full"> <!-- <n-split direction="horizontal" :default-size="0.2" :max="0.8" :min="0.2" style="height: 100%" class="h-full">
<template #1> <template #1>
<DataSetGroup /> <DataSetGroup />
</template> </template>
@ -20,204 +20,180 @@
</template> </template>
</n-split> --> </n-split> -->
<n-flex class="h-full w-full"> <n-flex class="h-full w-full">
<DataSetGroup class="w-50 min-w-300px max-w-500px" @select="handleGroupSelect" /> <DataSetGroup class="w-50 min-w-300px max-w-500px" @select="handleGroupSelect" />
<n-divider vertical class="!h-full" /> <n-divider vertical class="!h-full" />
<div class="flex-1"> <div class="flex-1">
<div class="flex justify-between mb-3"> <div class="flex justify-between mb-3">
<n-input <n-input :placeholder="t('prompt.Please enter a name for the dataset')" class="max-w-64" show-count
:placeholder="t('prompt.Please enter a name for the dataset')" :maxlength="25" @update:value="handleSearch">
class="max-w-64" <template #prefix>
show-count <n-icon :component="Search" />
:maxlength="25" </template>
@update:value="handleSearch" </n-input>
>
<template #prefix>
<n-icon :component="Search" />
</template>
</n-input>
<n-button type="primary" @click="handleAddDataSet">{{ t("home.Add data set") }}</n-button> <n-button type="primary" @click="handleAddDataSet">{{ t('home.Add data set') }}</n-button>
</div> </div>
<n-data-table :columns="dataSetColumns" :data="dataSets" :pagination="pagination" :loading="tableLoading" remote /> <n-data-table :columns="dataSetColumns" :data="dataSets" :pagination="pagination" :loading="tableLoading" />
</div> </div>
</n-flex> </n-flex>
<!-- 数据集模态框 --> <!-- 数据集模态框 -->
<DataSetModal v-model:show="showDataSetModal" :model="currentDataSet" @refresh="getList" /> <DataSetModal v-model:show="showDataSetModal" :model="currentDataSet" @refresh="getList" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, h, onMounted } from "vue"; import { ref, reactive, h, onMounted } from 'vue';
import { NButton, NPopconfirm } from "naive-ui"; import { NButton, NPopconfirm } from 'naive-ui';
import { Search } from "@vicons/carbon"; import { Search } from '@vicons/carbon';
import { t } from "@/language"; import { t } from "@/language";
import { fetchDataSetDetail, fetchDataSetPage, fetchDeleteDataSet } from "@/http/api/dataSet"; import { fetchDataSetDetail, fetchDataSetPage, fetchDeleteDataSet } from "@/http/api/dataSet";
import DataSetModal from "./DataSetModal.vue"; import DataSetModal from "./DataSetModal.vue";
import DataSetGroup from "./DataSetGroup.vue"; import DataSetGroup from "./DataSetGroup.vue";
const dataSets = ref<IDataSet.Item[]>([]); const dataSets = ref<IDataSet.Item[]>([])
const tableLoading = ref(false); const tableLoading = ref(false);
const showDataSetModal = ref(false); const showDataSetModal = ref(false)
const searchName = ref(""); const searchName = ref("");
const selectedGroupId = ref<IDataSet.IGroup["id"] | null>(null); const selectedGroupId = ref<IDataSet.IGroup["id"] | null>(null);
const defaultDataSet: IDataSet.Item = { const defaultDataSet: IDataSet.Item = {
id: "", id: '',
groupId: "", groupId: '',
name: "", name: '',
type: "API", type: 'API',
method: "GET", method: 'GET',
api: "", api: '',
dataSourceId: "", dataSourceId: '',
sql: "", sql: '',
json: "", json: ''
}; };
const currentDataSet = reactive<IDataSet.Item>({ const currentDataSet = reactive<IDataSet.Item>({
...defaultDataSet, ...defaultDataSet
}); })
const dataSetColumns = [ const dataSetColumns = [
{ {
title: t("home.Data set name"), title: t("home.Data set name"),
key: "name", key: 'name',
defaultSortOrder: "ascend", defaultSortOrder: 'ascend',
sorter: "default", sorter: 'default',
resizable: true, resizable: true,
minWidth: 120, minWidth: 120,
maxWidth: 400, maxWidth: 400,
ellipsis: { ellipsis: {
tooltip: true, tooltip: true
}, },
filter(value, row) { filter(value, row) {
return ~row.name.indexOf(value); return ~row.name.indexOf(value);
}, }
}, },
{ {
title: t("home.Data set type"), title: t("home.Data set type"),
key: "type", key: 'type',
defaultSortOrder: "ascend", defaultSortOrder: 'ascend',
sorter: "default", sorter: 'default',
resizable: true, resizable: true,
width: 180, width: 180,
minWidth: 120, minWidth: 120,
maxWidth: 260, maxWidth: 260
}, },
{ {
title: t("other.Action"), title: t("other.Action"),
key: "actions", key: 'actions',
resizable: true, resizable: true,
minWidth: 120, minWidth: 120,
maxWidth: 300, maxWidth: 300,
render: row => render: (row) => h('div', [
h("div", [ h(NButton, {
h( size: 'small',
NButton, onClick: () => editDataSet(row),
{ style: { marginRight: '0.4rem' }
size: "small", }, t("layout.sider.script.Edit")),
onClick: () => editDataSet(row), h(NPopconfirm, {
style: { marginRight: "0.4rem" }, onPositiveClick: () => deleteDataSet(row)
}, }, {
t("layout.sider.script.Edit") default: () => t("prompt.Are you sure you want to delete it?"),
), trigger: () => h(NButton, {
h( size: 'small'
NPopconfirm, }, t("home.Delete"))
{ })
onPositiveClick: () => deleteDataSet(row), ])
}, }
{ ]
default: () => t("prompt.Are you sure you want to delete it?"), //
trigger: () => const pagination = reactive({
h( page: 1,
NButton, pageSize: 10,
{ pageCount: 1,
size: "small", "on-update:page": (page: number) => {
}, pagination.page = page;
t("home.Delete") getList();
), }
} })
),
]),
},
];
//
const pagination = reactive({
page: 1,
pageSize: 10,
pageCount: 1,
onChange: (page: number) => {
pagination.page = page;
getList();
},
onUpdatePageSize: (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
getList();
},
});
function resetCurrentDataSet(data?: Partial<IDataSet.Item>) { function resetCurrentDataSet(data?: Partial<IDataSet.Item>) {
Object.assign(currentDataSet, defaultDataSet, data || {}); Object.assign(currentDataSet, defaultDataSet, data || {});
} }
async function getList() { async function getList() {
tableLoading.value = true; tableLoading.value = true;
const res = await fetchDataSetPage({ const res = await fetchDataSetPage({
page: pagination.page, page: pagination.page,
pageSize: pagination.pageSize, pageSize: pagination.pageSize,
name: searchName.value || undefined, name: searchName.value || undefined,
groupId: selectedGroupId.value ?? undefined, groupId: selectedGroupId.value ?? undefined
}); });
tableLoading.value = false; tableLoading.value = false;
dataSets.value = res.data?.items || []; dataSets.value = res.data?.items || [];
pagination.pageCount = res.data?.pages || 1; pagination.pageCount = res.data?.pages || 1;
if (res.data?.current) { if (res.data?.current) {
pagination.page = res.data.current; pagination.page = res.data.current;
} }
} }
function handleSearch(searchText: string) { function handleSearch(searchText: string) {
searchName.value = searchText; searchName.value = searchText;
pagination.page = 1; pagination.page = 1;
getList(); getList();
} }
function handleGroupSelect(group: IDataSet.IGroup | null) { function handleGroupSelect(group: IDataSet.IGroup | null) {
selectedGroupId.value = group?.id ?? null; selectedGroupId.value = group?.id ?? null;
pagination.page = 1; pagination.page = 1;
getList(); getList();
} }
function handleAddDataSet() { function handleAddDataSet() {
resetCurrentDataSet({ resetCurrentDataSet({
groupId: selectedGroupId.value ?? "", groupId: selectedGroupId.value ?? ""
}); });
showDataSetModal.value = true; showDataSetModal.value = true;
} }
async function editDataSet(item) { async function editDataSet(item) {
const res = await fetchDataSetDetail(item.id); const res = await fetchDataSetDetail(item.id);
if (res.error) { if (res.error) {
return; return;
} }
const detail = (res.data || item) as any; const detail = (res.data || item) as any;
resetCurrentDataSet({ resetCurrentDataSet({
...detail, ...detail,
dataSourceId: detail.dataSourceId || detail.dataSource ? String(detail.dataSourceId || detail.dataSource) : "", dataSourceId: detail.dataSourceId || detail.dataSource ? String(detail.dataSourceId || detail.dataSource) : ""
}); });
showDataSetModal.value = true; showDataSetModal.value = true
} }
async function deleteDataSet(item) { async function deleteDataSet(item) {
const res = await fetchDeleteDataSet(item.id); const res = await fetchDeleteDataSet(item.id);
if (res.error) { if (res.error) {
return; return;
} }
window.$message?.success(t("prompt.Success to delete")); window.$message?.success(t("prompt.Success to delete"));
getList(); getList();
} }
onMounted(() => { onMounted(() => {
getList(); getList();
}); })
</script> </script>