import { MessageAction, penUpdatePoints, useDrawRunning, useInteractiveDrawShapeAPI, usePointBeforeHandler, } from "@/core/hook/use-draw"; import { components, ComponentSnapInfo, SnapPoint } from ".."; import { useHistory, useHistoryAttach } from "@/core/hook/use-history"; import { useStore } from "@/core/store"; import { useViewerTransform } from "@/core/hook/use-viewer"; import { useOperMode } from "@/core/hook/use-status"; import { installGlobalVar, useCursor } from "@/core/hook/use-global-vars"; import { useInteractiveDots } from "@/core/hook/use-interactive"; import { computed, reactive, ref, watch } from "vue"; import { copy, mergeFuns } from "@/utils/shared"; import { eqPoint, Pos } from "@/utils/math"; import { getSnapInfos, type LineData } from "./"; import { useCustomSnapInfos } from "@/core/hook/use-snap"; type PayData = Pos; export let initData: LineData | undefined; export const useInitData = installGlobalVar(() => ref()); // 单例钢笔添加 export const useDraw = () => { const type = "line"; const { quitDrawShape } = useInteractiveDrawShapeAPI(); const isRuning = useDrawRunning(type); const obj = components[type]; const beforeHandler = usePointBeforeHandler(true, true); const history = useHistory(); const store = useStore(); const viewTransform = useViewerTransform(); const operMode = useOperMode(); const hInitData = useInitData(); const customSnapInfos = useCustomSnapInfos(); // 可能历史空间会撤销 重做更改到正在绘制的组件 const currentCursor = ref("./icons/m_add.png"); const cursor = useCursor(); let cursorPop: ReturnType | null = null; let stopWatch: (() => void) | null = null; let prev: SnapPoint; let currentIsDel = false; let isTempDraw = false; let snapInfos: ComponentSnapInfo[] | null = null; let drawSnapInfos: ComponentSnapInfo[] | null = null; const ia = useInteractiveDots({ shapeType: type, isRuning, enter() { cursorPop = cursor.push(currentCursor.value); watch(currentCursor, () => { cursorPop?.set(currentCursor.value); }); }, quit: () => { quitDrawShape(); beforeHandler.clear(); cursorPop && cursorPop(); stopWatch && stopWatch(); if (!drawItems[0]) return; if (isTempDraw) { console.log(drawItems[0].points) drawItems[0].lines.pop(); drawItems[0].points.pop(); drawItems[0].polygon.pop(); } const lastP = drawItems[0].points[drawItems[0].points.length - 1] if (lastP) { console.log(lastP) const ctx = getInitCtx() ctx.add.points[lastP.id] = lastP normalLineData(drawItems[0], ctx) } snapInfos?.forEach(customSnapInfos.remove); drawSnapInfos?.forEach(customSnapInfos.remove); initData = hInitData.value = void 0; }, beforeHandler: (p) => { beforeHandler.clear(); const pa = beforeHandler.transform(p, prev && [prev, p]); currentIsDel && beforeHandler.clear(); return pa; }, }); const shapeId = computed( () => ia.isRunning.value && store.getTypeItems(type)[0]?.id ); const drawItems = reactive([]) as LineData[]; watch( shapeId, () => { const data = shapeId.value ? (store.getItemById(shapeId.value) as LineData) : undefined; if (data) { initData = hInitData.value = { ...data, points: [...data.points], lines: [...data.lines], polygon: [...data.polygon], }; drawItems[0] = { ...data, createTime: initData.createTime + 1, points: [], lines: [], polygon: [], }; snapInfos = getSnapInfos(initData); snapInfos.forEach(customSnapInfos.add); } else { drawItems.pop(); initData = hInitData.value = undefined; } }, { immediate: true } ); const messages = useHistoryAttach( `${type}-pen`, isRuning, () => [], true ); const getAddMessage = (cur: Pos) => { let consumed = messages.value; currentCursor.value = "./icons/m_add.png"; let pen: null | ReturnType = null; return { pen, consumed, cur, action: MessageAction.add, } as any; }; const setMessage = (cur: Pos) => { const { pen, ...msg } = getAddMessage(cur); if ((currentIsDel = pen?.oper === "del")) { currentCursor.value = "./icons/m_reduce.png"; beforeHandler.clear(); } return msg; }; const pushMessages = (cur: Pos) => { const { pen } = getAddMessage(cur); if (pen) { if (!pen.unchanged) { messages.value = pen.points; cur = pen.cur; messages.value.push(cur); } } else { messages.value.push(cur); } return !pen?.unchanged; }; const addItem = (cur: PayData) => { if (!drawItems[0]) { const data = obj.interactiveToData({ preset: ia.preset as any, info: setMessage(cur), viewTransform: viewTransform.value, history, store, }); if (!data) { drawItems.pop(); return; } if (initData?.id) { data.id = initData?.id; } drawItems[0] = reactive(data); } let prevItemIds: string[] = []; const storeAddItem = (cItem: LineData) => { drawSnapInfos?.forEach(customSnapInfos.remove); drawSnapInfos = getSnapInfos(cItem); const ctx = getInitCtx(); cItem.points.forEach((p) => { if (!prevItemIds.includes(p.id)) { prevItemIds.push(p.id); ctx.add.points[p.id] = p; } }); cItem.lines.forEach((l) => { if (!prevItemIds.includes(l.id)) { prevItemIds.push(l.id); ctx.add.lines[l.id] = l; } }); if (initData) { cItem = { ...cItem, points: [...initData.points, ...cItem.points], lines: [...initData.lines, ...cItem.lines], polygon: [...initData.polygon, ...cItem.polygon], }; } else { cItem = { ...cItem, points: [...cItem.points], lines: [...cItem.lines], polygon: [...cItem.polygon], }; } cItem = normalLineData(cItem, ctx); drawSnapInfos.forEach(customSnapInfos.add); if (drawItems[0] && store.getItemById(drawItems[0].id)) { store.setItem(type, { id: cItem.id, value: cItem }); } else { store.addItem(type, cItem); } }; if (ia.singleDone.value) { storeAddItem(drawItems[0]); return; } const update = () => { const msg = setMessage(cur); drawItems[0] = obj.interactiveFixData({ data: drawItems[0]!, info: msg, viewTransform: viewTransform.value, history, store, }); isTempDraw = true; }; stopWatch = mergeFuns( watch(() => operMode.value.freeDraw, update), watch(cur, update, { immediate: true, deep: true }), watch( messages, () => { if (!messages.value) return; if (messages.value.length === 0) { quitDrawShape(); } else { update(); } }, { deep: true } ), // 监听是否消费完毕 watch(ia.singleDone, () => { prev = { ...cur, view: true }; const isChange = pushMessages(cur); if (isChange) { storeAddItem(copy(drawItems[0])); } beforeHandler.clear(); stopWatch && stopWatch(); stopWatch = null; isTempDraw = false; }) ); }; // 每次拽结束都加组件 watch( () => ia.messages, (datas: any) => { datas.forEach(addItem); ia.consume(datas); }, { immediate: true } ); return drawItems; }; export type NLineDataCtx = { del: { points: Record; lines: Record; }; add: { points: Record; lines: Record; }; update: { points: Record; lines: Record; }; }; export const getInitCtx = (): NLineDataCtx => ({ del: { points: {}, lines: {}, }, add: { points: {}, lines: {}, }, update: { points: {}, lines: {}, }, }); export const repPointRef = (data: LineData, delId: string, repId: string) => { for (let i = 0; i < data.lines.length; i++) { const line = data.lines[i]; if (line.a === delId) { data.lines[i] = { ...line, a: repId }; } if (line.b === delId) { data.lines[i] = { ...line, b: repId }; } } return data }; export const deduplicateLines = (data: LineData) => { const seen = new Map(); let isChange = false for (const line of data.lines) { if (line.a === line.b) continue; // 生成标准化键:确保 (a,b) 和 (b,a) 被视为相同,并且 a === b 时也去重 const key1 = `${line.a},${line.b}`; const key2 = `${line.b},${line.a}`; // 检查是否已存在相同键 const existingKey = seen.has(key1) ? key1 : seen.has(key2) ? key2 : null; if (existingKey) { // 如果存在重复键,覆盖旧值(保留尾部元素) seen.delete(existingKey); seen.set(key1, line); // 统一存储为 key1 格式 isChange = true } else { // 新记录,直接存储 seen.set(key1, line); } } if (isChange) { data.lines = Array.from(seen.values()) } return data }; export const normalLineData = (data: LineData, ctx: NLineDataCtx) => { const changePoints = [ ...Object.values(ctx.add.points), ...Object.values(ctx.update.points), ]; for (const p2 of changePoints) { const ndx = data.points.findIndex((item) => item.id === p2.id); if (!~ndx) continue; for (let i = 0; i < data.points.length; i++) { const p1 = data.points[i]; if (p1.id !== p2.id && eqPoint(p1, p2)) { repPointRef(data, p1.id, p2.id); data.points.splice(i, 1); i--; } } } const pointIds = Object.values(ctx.del.lines).flatMap(item => [item.a, item.b]) pointIds.push(...Object.keys(ctx.add.points)) const linePointIds = data.lines.flatMap(item => [item.a, item.b]) for (let id of pointIds) { if (!linePointIds.includes(id)) { const ndx = data.points.findIndex(p => p.id === id) ~ndx && data.points.splice(ndx, 1) } } return deduplicateLines(data); };