|
@@ -1,355 +0,0 @@
|
|
|
-<template>
|
|
|
- <HotItemLabel
|
|
|
- :data="data"
|
|
|
- :active="active"
|
|
|
- class="edit-item-layer"
|
|
|
- :x="x"
|
|
|
- :y="y"
|
|
|
- noUseDefault
|
|
|
- :forceHide="!active"
|
|
|
- :permanent="true"
|
|
|
- @mousedown.stop.prevent="downHandler"
|
|
|
- @click.stop="clickHandler"
|
|
|
- >
|
|
|
- <template v-slot:content>
|
|
|
- <h3 class="edit-title">
|
|
|
- {{ $t("hotspot.name") }}
|
|
|
- <ui-icon type="close" ctrl @click.stop="quit" class="edit-close" />
|
|
|
- </h3>
|
|
|
- <StylesManage
|
|
|
- :styles="allStyles"
|
|
|
- :active="data.style"
|
|
|
- @change="(style) => changeStyle(data, style)"
|
|
|
- @delete="deleteStyle"
|
|
|
- @uploadStyles="uploadStyles"
|
|
|
- />
|
|
|
-
|
|
|
- <ui-input
|
|
|
- require
|
|
|
- class="input"
|
|
|
- width="100%"
|
|
|
- :placeholder="$t('hotspot.edit.placeholder.title')"
|
|
|
- type="text"
|
|
|
- :modelValue="data.title"
|
|
|
- @update:modelValue="(value) => (data.title = value.trim())"
|
|
|
- maxlength="15"
|
|
|
- />
|
|
|
- <ui-input
|
|
|
- class="input"
|
|
|
- width="100%"
|
|
|
- height="158px"
|
|
|
- :placeholder="$t('hotspot.edit.placeholder.content')"
|
|
|
- type="richtext"
|
|
|
- v-model="data.content"
|
|
|
- :maxlength="maxContentLen"
|
|
|
- ref="contentRef"
|
|
|
- :onUpdatePos="(i) => (index = i)"
|
|
|
- >
|
|
|
- <template v-slot:icon>
|
|
|
- <div class="link">
|
|
|
- <LinkManage
|
|
|
- :show="!!(inInsertLink && maxTextLen)"
|
|
|
- :textlen="maxTextLen"
|
|
|
- @close="inInsertLink = false"
|
|
|
- @add="insertel"
|
|
|
- />
|
|
|
- <ui-icon type="link" class="icon" @click="openInsertLink" ctrl />
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </ui-input>
|
|
|
- <MetasManage :data="data" @change="changeMeta" :hotFiles="hotFiles" />
|
|
|
- <div class="submit-ctrl">
|
|
|
- <div class="radio-group">
|
|
|
- <ui-input
|
|
|
- v-for="(item, type) in custom"
|
|
|
- :key="type"
|
|
|
- class="radio"
|
|
|
- type="radio"
|
|
|
- :modelValue="data.type === type"
|
|
|
- @update:modelValue="data.type = type"
|
|
|
- :icon="item.icon"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </HotItemLabel>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup lang="ts">
|
|
|
-import MetasManage from "./metas-upload.vue";
|
|
|
-import StylesManage from "./styles-manage.vue";
|
|
|
-import HotItemLabel from "@/components/show-hot-item/index.vue";
|
|
|
-import { computed, ref, watchEffect } from "vue";
|
|
|
-import { HotAtom, TemploraryID } from "@/store";
|
|
|
-import {
|
|
|
- custom,
|
|
|
- LocalMeta,
|
|
|
- Meta,
|
|
|
- MetaAtom,
|
|
|
- LocalStyleAtom,
|
|
|
- selectStyle,
|
|
|
- Metas,
|
|
|
- ServerMeta,
|
|
|
- LocalStyleAtomIcon,
|
|
|
- StyleAtom,
|
|
|
- MAX_DIS,
|
|
|
-} from "./constant";
|
|
|
-import { styles, HotStyleAtom, list } from "@/store/hot";
|
|
|
-import LinkManage from "./link-manage.vue";
|
|
|
-import { Message } from "@kankan/components/index";
|
|
|
-import { useSDK } from "@/hook";
|
|
|
-import { calcLintDis } from "@/utils";
|
|
|
-import { ui18n } from "@/lang";
|
|
|
-
|
|
|
-const laser = useSDK();
|
|
|
-const props = defineProps<{
|
|
|
- hotFiles: WeakMap<HotAtom, Array<File>>;
|
|
|
- styleFile: WeakMap<HotStyleAtom, File>;
|
|
|
- active?: boolean;
|
|
|
- data: HotAtom;
|
|
|
- x: number;
|
|
|
- y: number;
|
|
|
- quit: () => void;
|
|
|
-}>();
|
|
|
-
|
|
|
-const clickHandler = () => {
|
|
|
- laser.carry.store.share.showHot = props.data;
|
|
|
-};
|
|
|
-
|
|
|
-const move = ref(false);
|
|
|
-const downHandler = (sev) => {
|
|
|
- const el = sev.target as HTMLElement;
|
|
|
- const mountEl = document.documentElement;
|
|
|
- const preset = {
|
|
|
- x: el.offsetWidth / 2 - sev.offsetX,
|
|
|
- y: el.offsetHeight - sev.offsetY,
|
|
|
- };
|
|
|
-
|
|
|
- const moveHandler = (ev: MouseEvent) => {
|
|
|
- move.value = true;
|
|
|
- const sdk = useSDK();
|
|
|
- const pos = sdk.scene.getPointByScreen({
|
|
|
- x: ev.pageX + preset.x,
|
|
|
- y: ev.pageY + preset.y,
|
|
|
- inDrag: true,
|
|
|
- });
|
|
|
- if (
|
|
|
- pos?.position &&
|
|
|
- calcLintDis(sdk.scene.currentCamera(), pos.position) <= MAX_DIS
|
|
|
- ) {
|
|
|
- props.data.pos.x = pos.position.x;
|
|
|
- props.data.pos.y = pos.position.y;
|
|
|
- props.data.pos.z = pos.position.z;
|
|
|
- props.data.dataset_location = pos.dataset_location;
|
|
|
- props.data.datasetId = pos.datasetId;
|
|
|
- }
|
|
|
- };
|
|
|
- const upHandler = (ev: MouseEvent) => {
|
|
|
- mountEl.removeEventListener("mousemove", moveHandler);
|
|
|
- mountEl.removeEventListener("mouseup", upHandler);
|
|
|
- move.value = false;
|
|
|
- };
|
|
|
-
|
|
|
- mountEl.addEventListener("mousemove", moveHandler);
|
|
|
- mountEl.addEventListener("mouseup", upHandler);
|
|
|
-};
|
|
|
-
|
|
|
-const contentRef = ref(null);
|
|
|
-const maxContentLen = 200;
|
|
|
-const inInsertLink = ref(false);
|
|
|
-const metas = ref<Metas>({
|
|
|
- TEXT: [],
|
|
|
- AUDIO: [],
|
|
|
- VIDEO: [],
|
|
|
- IMAGE: [],
|
|
|
- WEB: [],
|
|
|
-});
|
|
|
-props.data.type = props.data.type === "TEXT" ? "IMAGE" : props.data.type;
|
|
|
-metas.value[props.data.type] = props.data.meta || [];
|
|
|
-
|
|
|
-const normalizeMate = (item: MetaAtom): ServerMeta => {
|
|
|
- if ((item as LocalMeta).preview) {
|
|
|
- return (item as LocalMeta).preview;
|
|
|
- } else {
|
|
|
- return item as ServerMeta;
|
|
|
- }
|
|
|
-};
|
|
|
-const normalizeIcon = (item: LocalStyleAtomIcon | string): string => {
|
|
|
- if ((item as LocalStyleAtomIcon).preview) {
|
|
|
- return (item as LocalStyleAtomIcon).preview;
|
|
|
- } else {
|
|
|
- return item as string;
|
|
|
- }
|
|
|
-};
|
|
|
-watchEffect(() => {
|
|
|
- const meta = metas.value[props.data.type] || [];
|
|
|
-
|
|
|
- props.data.meta = meta.map(normalizeMate);
|
|
|
- const files = [];
|
|
|
- meta.forEach((item: MetaAtom) => {
|
|
|
- if ((item as LocalMeta).file) {
|
|
|
- files.push((item as LocalMeta).file);
|
|
|
- }
|
|
|
- });
|
|
|
- props.hotFiles.set(props.data, files);
|
|
|
-});
|
|
|
-
|
|
|
-const el = document.createElement("div");
|
|
|
-const contentLen = computed(() => {
|
|
|
- el.innerHTML = props.data.content;
|
|
|
- return el.textContent.length;
|
|
|
-});
|
|
|
-const maxTextLen = computed(() => 40);
|
|
|
-
|
|
|
-const index = ref(0);
|
|
|
-const openInsertLink = () => {
|
|
|
- if (contentLen.value > maxContentLen) {
|
|
|
- return Message.error(ui18n.t("hotspot.edit.maxContentLen"));
|
|
|
- } else {
|
|
|
- inInsertLink.value = true;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const insertel = (html: string, link, text) => {
|
|
|
- el.innerHTML = html;
|
|
|
- let htmlLen = el.textContent.length;
|
|
|
- props.data.content = props.data.content || "";
|
|
|
- el.innerHTML = props.data.content;
|
|
|
- while (true) {
|
|
|
- const overstep = el.textContent.length - (maxContentLen - htmlLen);
|
|
|
- if (overstep <= 0) break;
|
|
|
- const lastDOM = el.childNodes[el.childNodes.length - 1] as Element;
|
|
|
-
|
|
|
- if (lastDOM.textContent.length > overstep) {
|
|
|
- let content = lastDOM.textContent;
|
|
|
- content = content.substring(0, content.length - overstep);
|
|
|
- lastDOM.textContent = content;
|
|
|
- } else {
|
|
|
- el.removeChild(lastDOM);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let current = 0;
|
|
|
- if (el.childNodes.length) {
|
|
|
- for (let i = 0; i < el.childNodes.length; i++) {
|
|
|
- const node = el.childNodes[i];
|
|
|
- let length = node.textContent.length;
|
|
|
-
|
|
|
- if ((node as any).innerHTML === "<br>") {
|
|
|
- length = 1;
|
|
|
- (node as any).innerHTML = "";
|
|
|
- }
|
|
|
-
|
|
|
- if (current + length > index.value) {
|
|
|
- const start = index.value - current;
|
|
|
-
|
|
|
- if (node.nodeType === Node.ELEMENT_NODE) {
|
|
|
- let ehtml = (node as HTMLElement).innerHTML;
|
|
|
- ehtml = ehtml.substring(0, start) + html + ehtml.substring(start);
|
|
|
- (node as HTMLElement).innerHTML = ehtml;
|
|
|
- } else {
|
|
|
- let ehtml = node.textContent;
|
|
|
- const startNode = node.cloneNode();
|
|
|
- startNode.textContent = ehtml.substring(0, start);
|
|
|
- el.insertBefore(startNode, node);
|
|
|
- const dom = document.createElement("a");
|
|
|
- dom.setAttribute("href", link);
|
|
|
- dom.setAttribute("target", "_blank");
|
|
|
- dom.innerHTML = text;
|
|
|
- el.insertBefore(dom, node);
|
|
|
- const endNode = node.cloneNode();
|
|
|
- endNode.textContent = ehtml.substring(start);
|
|
|
- el.insertBefore(endNode, node);
|
|
|
- el.removeChild(node);
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
- } else {
|
|
|
- current += node.textContent.length;
|
|
|
- }
|
|
|
-
|
|
|
- if (i === el.childNodes.length - 1) {
|
|
|
- el.innerHTML += html;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- el.innerHTML = html;
|
|
|
- }
|
|
|
- props.data.content = el.innerHTML;
|
|
|
- inInsertLink.value = false;
|
|
|
-};
|
|
|
-
|
|
|
-const changeMeta = (ameta: Meta) => {
|
|
|
- const meta = metas.value[props.data.type];
|
|
|
- const nmeta: Meta = [];
|
|
|
-
|
|
|
- for (let i = 0; i < ameta.length; i++) {
|
|
|
- const index = meta.findIndex(
|
|
|
- (atom) => atom === ameta[i] || (atom as LocalMeta).preview === ameta[i]
|
|
|
- );
|
|
|
- if (~index) {
|
|
|
- nmeta.push(meta[index]);
|
|
|
- } else {
|
|
|
- nmeta.push(ameta[i]);
|
|
|
- }
|
|
|
- }
|
|
|
- metas.value[props.data.type] = nmeta;
|
|
|
-};
|
|
|
-
|
|
|
-const changeStyle = (info, style) => {
|
|
|
- info.style = style;
|
|
|
- selectStyle.value = style;
|
|
|
-};
|
|
|
-
|
|
|
-const allStyles = computed(() =>
|
|
|
- [...styles.value].sort((a, b) => {
|
|
|
- return a.default
|
|
|
- ? -1
|
|
|
- : b.default
|
|
|
- ? 1
|
|
|
- : a.id === TemploraryID
|
|
|
- ? -1
|
|
|
- : b.id === TemploraryID
|
|
|
- ? 1
|
|
|
- : 0;
|
|
|
- })
|
|
|
-);
|
|
|
-const uploadStyles = (unStyles: Array<LocalStyleAtom>) => {
|
|
|
- const addStyles = unStyles.map((item) => {
|
|
|
- const style = {
|
|
|
- ...item,
|
|
|
- icon: normalizeIcon(item.icon),
|
|
|
- };
|
|
|
- props.styleFile.set(style, item.icon.file);
|
|
|
- return style;
|
|
|
- });
|
|
|
- styles.value.push(...addStyles);
|
|
|
- changeStyle(props.data, addStyles[0]);
|
|
|
-};
|
|
|
-
|
|
|
-const deleteStyle = (delStyle: HotStyleAtom) => {
|
|
|
- const index = styles.value.indexOf(delStyle);
|
|
|
- if (~index) {
|
|
|
- styles.value.splice(index, 1);
|
|
|
- for (const item of list.value) {
|
|
|
- if (item.style === delStyle) {
|
|
|
- item.style = styles.value.find(({ default: isDefault }) => isDefault);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-</script>
|
|
|
-
|
|
|
-<style lang="sass" scoped>
|
|
|
-@import './style.scss'
|
|
|
-</style>
|
|
|
-
|
|
|
-<style>
|
|
|
-.edit-item-layer > img {
|
|
|
- cursor: move;
|
|
|
-}
|
|
|
-.radio-group .label {
|
|
|
- font-size: 16px;
|
|
|
-}
|
|
|
-</style>
|