import { Row, showToast, showConfirmDialog } from "vant"; import { defineComponent, onMounted, onUnmounted, reactive, nextTick, ref } from "vue"; import state, { IPlatform } from "/src/state"; import request from "/src/utils/request"; import { getQuery } from "/src/utils/queryString"; import styles from "./index.module.less"; import { Button, ButtonGroup, Icon, Switch, Tooltip } from "@varlet/ui"; import "@varlet/ui/es/tooltip/style"; import "@varlet/ui/es/button-group/style"; import "@varlet/ui/es/switch/style"; import { storeData } from "/src/store"; import rightHideIcon from './image/right_hide_icon.png'; import editIcon from './image/edit.png'; import editCloseIcon from './image/edit_close.png'; import editSaveIcon from './image/edit_save.png'; import editPreIcon from './image/edit_pre.png'; import editDeleteIcon from './image/edit_delete.png'; import editResetIcon from './image/edit_reset.png'; import editReduceIcon from './image/edit_reduce.png'; import editAddIcon from './image/edit_add.png'; let extStyleConfigJson: any = {}; const clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; const showToolBox = ref(true); export const moveData = reactive({ /** 开启移动 */ open: false, zoom: 1, partIndex: "0", hasExtJson: false, isWeb: false, modelList: [] as any[], activeIndex: -1, sw: 0, tool: { isAddAndSub: false, }, noteCoords: [] as any[], }); // 所以可点击音符的dom坐标集合 const initNoteCoord = () => { const allNoteDot: any = Array.from(document.querySelectorAll('.node-dot')); moveData.noteCoords = allNoteDot.map((note: any) => { const note_bbox = note?.getBoundingClientRect?.() || { x: 0, y: 0 }; return { x: note_bbox.x, y: note_bbox.y } }) } // 找出离目标元素最近的音符 const computedDistance = (x: number, y: number) => { let minDistance = -1, minidx = 0; let a, b, c; moveData.noteCoords.forEach((note: any, idx: any) => { //a,b为直角三角形的两个直角边 a = Math.abs(note.x - x) b = Math.abs(note.y - y) //c为直角三角形的斜边 c = Math.sqrt(a * a + b * b) as 0 c = Number(c.toFixed(0)) as 0 if (c !== 0 && (minDistance === - 1 || c < minDistance)) { //min为元素中离目标元素最近元素的距离 minDistance = c minidx = idx } }) return minidx } function initSvgId() { const svg = document.querySelector("#osmdSvgPage1"); if (!svg) return; const vfstavetempo: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-stavetempo")); const vftext: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-text")); const vfstaveSection: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-StaveSection")); const vfRepetition: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-Repetition")); const vflineGroup: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-lineGroup")); // console.log('速度标记',vfstavetempo) let tempIndex = 1; [...vfstavetempo].forEach((ele) => { setEleId(ele, "temp" + tempIndex); tempIndex++; }); let textIndex = 1; [...vftext].forEach((ele) => { // console.log(ele.textContent,textIndex) setEleId(ele, "text" + textIndex); textIndex++; }); let sectionIndex = 1; [...vfstaveSection].forEach((ele) => { setEleId(ele, "section" + sectionIndex); sectionIndex++; }); let repIndex = 1; [...vfRepetition].forEach((ele) => { setEleId(ele, "repet" + repIndex); repIndex++; }); let lineIndex = 1; [...vflineGroup].forEach((ele) => { setEleId(ele, "line" + lineIndex); lineIndex++; }); // if (moveData.isWeb) { // readerModelBox(); // } // if (moveData.hasExtJson) { // setTimeout(reloadReader, 2); // } } /**赋值id */ function setEleId(ele: HTMLElement, eleId: string) { if (!ele || !eleId) return; const id = ele.getAttribute("id"); if (!id) { ele.setAttribute("id", eleId); } createModelBox(ele as any, eleId as any); } function createModelBox(ele: SVGAElement, eleId?: any) { const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || { x: 0, y: 0 }; const parentLeft = musicContainer.x || 0; const parentTop = musicContainer.y || 0; const noteBbox = ele.getBoundingClientRect(); const bbox = { left: noteBbox.x - parentLeft + "px", top: noteBbox.y - parentTop + "px", width: noteBbox.width + "px", height: noteBbox.height + "px", }; const type = ele.getAttribute("class"); moveData.modelList.push({ id: eleId || ele.getAttribute("id"), bbox, type, isMove: false, left: noteBbox.left, top: noteBbox.top, width: noteBbox.width, height: noteBbox.height, x: 0, y: 0, zoom: state.zoom, isDelete: false, d2: getLineGroupPathDx(ele as any), dx: 0, }); } function getBox(ele: SVGAElement) { if (!ele) return {}; const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || { x: 0, y: 0 }; const parentLeft = musicContainer.x || 0; const parentTop = musicContainer.y || 0; const box = ele.getBoundingClientRect(); return { left: box.x - parentLeft, top: box.y - parentTop, width: box.width, height: box.height, }; } // 切换开关 const switchMoveState = () => { // 如果编辑过,没有保存,点击取消状态,需要提醒用户是否取消 if (moveData.open && undoData.undoList.length) { showConfirmDialog({ className: "noSaveModal", title: "温馨提示", message: "您有新的修改还未保存,取消后本次编辑的内容将不会保存", }).then(() => { moveData.open = false }); } else { moveData.open = !moveData.open } } // 过滤数据 export const filterMoveData = async () => { const examSongId = state.examSongId; if (examSongId) { const fontSize = (window as any).fontSize const list = moveData.modelList .filter((n) => n.isMove) .map((n) => { /** * 找到移动后,此时与此元素距离最近的音符,并记录音符的索引和此元素与音符的x轴,y轴的间距 */ // 元素的位置 const elementX = n.left + n.x, elementY = n.top + n.y; // 找出距离元素最近的音符 const noteIdx = computedDistance(elementX, elementY); // 此元素距离最近音符的x轴,y轴距离 const noteRelativeX = elementX - moveData.noteCoords[noteIdx]?.x, noteRelativeY = elementY - moveData.noteCoords[noteIdx]?.y; let item: any = { id: n.id, isMove: n.isMove, isDelete: n.isDelete, x: n.x, y: n.y, xRem: Math.abs(n.x / fontSize), yRem: Math.abs(n.y / fontSize), zoom: n.zoom, w: moveData.sw, type: n.type, noteIdx, noteRelativeX, noteRelativeY }; if (n.type === "vf-lineGroup") { item.dx = n.dx; // 需要获取当前渐强渐弱线所对应的小节的宽度,算出相对当前宽度的比例,用于不同设备回显用 const measureNum = document.getElementById(n.id)?.getAttribute('data-mnum'); const measureWidth = measureNum ? document.querySelector(`g[data-num='${measureNum}']`)?.getBoundingClientRect()?.width : 0; if (measureWidth) { item.dxRate = n.dx / measureWidth; } } if (n.id.includes('text')) { // let copyDom = document.querySelector("#" + n.id)!.cloneNode(true) as SVGSVGElement const textContent = document.querySelector("#" + n.id)?.querySelector("text")?.innerHTML || '' item.textContent = textContent } return item; }); // if (!list.length) { // showToast("请移动元素后再保存"); // return // } extStyleConfigJson[moveData.partIndex] = list; console.log("🚀 ~ extStyleConfigJson", extStyleConfigJson) const dataParams = state.musicRenderType === 'staff' ? { id: examSongId, extStyleConfigJson: JSON.stringify(extStyleConfigJson), } : { id: examSongId, extJianStyleConfigJson: JSON.stringify(extStyleConfigJson), } const res = await request.post("/musicSheet/img", { requestType: "json", data: dataParams }); if (res && res.code == 200) { showToast("保存成功"); undoData.undoList = []; undoData.activeItem = null; if (state.musicRenderType === 'staff') { state.extStyleConfigJson = JSON.stringify(extStyleConfigJson) } else { state.extJianStyleConfigJson = JSON.stringify(extStyleConfigJson) } } clearActiveModel(); } }; // 移动 const dragData = { open: false, startX: 0, startY: 0, x: 0, y: 0, repeatEdit: false, }; // 记录 export const undoData = reactive({ undoList: [] as Array, // 撤销列表 redoList: [] as Array, // 回退列表 activeItem: null, }); function onDown(e: MouseEvent) { const el: any = e.target; const index = moveData.modelList.findIndex((n) => n.id === el.dataset.id); if (index > -1) { const item = moveData.modelList[index]; moveData.activeIndex = index; dragData.startX = e.clientX; dragData.startY = e.clientY; dragData.x = item.x; dragData.y = item.y; dragData.repeatEdit = item.noteIdx >= 0 ? true : false; // console.log("🚀 ~ 按下", index, el, item.x, item.y); document.onmousemove = onMove; document.onmouseup = onUp; dragData.open = true; if (item.type === "vf-lineGroup") { moveData.tool.isAddAndSub = true; } else { moveData.tool.isAddAndSub = false; } undoData.activeItem = { ...item }; return; } moveData.activeIndex = -1; } function onMove(e: MouseEvent) { if (dragData.open) { const _x = e.clientX - dragData.startX + dragData.x; const _y = e.clientY - dragData.startY + dragData.y; setModelPostion(moveData.modelList[moveData.activeIndex], _x, _y, dragData.repeatEdit); } } function onUp(e: MouseEvent) { // console.log("🚀 ~ 抬起"); document.onmousemove = null; document.onmouseup = null; dragData.open = false; const _x = e.clientX - dragData.startX + dragData.x; const _y = e.clientY - dragData.startY + dragData.y; if (_x || _y) { moveData.modelList[moveData.activeIndex].isMove = true; moveData.modelList[moveData.activeIndex].x = _x; moveData.modelList[moveData.activeIndex].y = _y; if (undoData.activeItem) { undoData.undoList.push({ ...(undoData.activeItem as any) }); } } undoData.activeItem = null; } /** 渲染svg元素的属性 */ const renderSvgItem = (item: any) => { setModelPostion(item, item.x, item.y); if (item.isDelete) { const g = document.querySelector("#" + item.id)!; g && ((g as any).style.display = "none"); } else { const g = document.querySelector("#" + item.id)!; g && ((g as any).style.display = ""); } }; /** 设置元素位置 */ async function setModelPostion(item: any, x: number, y: number, repeatEdit?: boolean) { // console.log(item) // console.log('位置',x,y) if (item) { const g = document.querySelector("#" + item.id)!; // svg元素 const el: HTMLElement = document.querySelector(`[data-id=${item.id}]`)!; // svg元素的背景div let scaleZoom: number = item.zoom ? item.zoom : moveData.zoom; // 预览页时0.65倍的谱面,需要特殊处理下 if (state.isPreView && state.zoom == 0.65) { scaleZoom = 0.65 } if (x === 0 && y === 0) { g && g.removeAttribute("transform"); el && (el.style.transform = ""); } else { /** 如果是app内嵌打开,需要通过rem转换 */ let tsX = x, tsY = y; // if (storeData.isApp && (item.xRem || item.yRem)) { // tsX = item.xRem * clientWidth/10 // tsY = item.yRem * clientWidth/10 // } if (item.noteIdx >= 0 && !repeatEdit) { if (!moveData.noteCoords.length) { await initNoteCoord() } const targetX = moveData.noteCoords[item.noteIdx].x + item.noteRelativeX*(state.zoom/0.8), targetY = moveData.noteCoords[item.noteIdx].y + item.noteRelativeY*(state.zoom/0.8); const original = document.getElementById(item.id)?.getBoundingClientRect() || { x: 0, y: 0 }; tsX = targetX - original.x; tsY = targetY - original.y; // console.log('距离',tsX,tsY,x,y,moveData.zoom) if (state.platform === IPlatform.PC) { // tsX = tsX / 1.5 // tsY = tsY / 1.8 } g && g.setAttribute("transform", `translate(${tsX / scaleZoom}, ${tsY / scaleZoom})`); el && (el.style.transform = `translate(${tsX}px, ${tsY}px)`); } else { g && g.setAttribute("transform", `translate(${tsX / scaleZoom}, ${tsY / scaleZoom})`); el && (el.style.transform = `translate(${tsX}px, ${tsY}px)`); } } } } /** 删除元素 */ const handleDeleteMoveNote = () => { const item = moveData.modelList[moveData.activeIndex]; if (item) { moveData.modelList[moveData.activeIndex].isMove = true; undoData.undoList.push({ ...moveData.modelList[moveData.activeIndex] }); moveData.modelList[moveData.activeIndex].isDelete = !item.isDelete; const g = document.querySelector("#" + item.id)!; g && ((g as any).style.display = moveData.modelList[moveData.activeIndex].isDelete ? "none" : ''); } else { showToast("选中需要删除的元素"); } }; /** 重置数据 */ const resetMoveNote = () => { for (let i = 0; i < moveData.modelList.length; i++) { moveData.modelList[i].x = 0; moveData.modelList[i].y = 0; moveData.modelList[i].isMove = false; moveData.modelList[i].isDelete = false; moveData.modelList[i].dx = 0; moveData.modelList[i].dxRate = 0; renderSvgItem(moveData.modelList[i]); if (moveData.modelList[i].type === "vf-lineGroup") { renderLineGroup(moveData.modelList[i]); } } clearActiveModel(); }; function clearActiveModel() { for (let i = 0; i < moveData.modelList.length; i++) { const item: HTMLElement = document.querySelector(`[data-id=${moveData.modelList[i].id}]`)!; if (item?.classList?.contains("activeModel")) { item.classList.remove("activeModel"); } } moveData.activeIndex = -1; moveData.tool.isAddAndSub = false; } // 增加或减少, 渐强和渐弱的线长 const handleAddAndSub = (type: 'add' | 'sub') => { if (!["add", "sub"].includes(type)) return; const lineGroup = moveData.modelList[moveData.activeIndex]; if (!lineGroup || lineGroup.type !== "vf-lineGroup") return; lineGroup.isMove = true; const step = type === "add" ? 10 : -10; undoData.undoList.push({ ...moveData.modelList[moveData.activeIndex] }); moveData.modelList[moveData.activeIndex].dx = lineGroup.dx + step; renderLineGroup(moveData.modelList[moveData.activeIndex]); }; // 获取line的dx function getLineGroupPathDx(lineGroup: HTMLElement) { if (!lineGroup) return 0; const lines = lineGroup.querySelectorAll("path"); if (lines?.length) { for (let i = 0; i < lines.length; i++) { const path = lines[i]; let d = path.getAttribute("d"); if (d) { let dx1: any = d.split("M")?.[1]?.split(" ") || []; let dx2: any = d.split("L")?.[1]?.split(" ") || []; dx1 = dx1[0] && !isNaN(Number(dx1[0])) ? Number(dx1[0]) : 0; dx2 = dx2[0] && !isNaN(Number(dx2[0])) ? Number(dx2[0]) : 0; if (dx1 && dx2) { if (dx1 < dx2) { return dx2; } else { return dx1; } } } } } return 0; } function renderLineGroup(lineGroup: any) { const group = document.querySelector("#" + lineGroup.id); if (!group) return; const lines = group.querySelectorAll("path"); if (lines?.length) { for (let i = 0; i < lines.length; i++) { const path = lines[i]; let d = path.getAttribute("d"); if (d) { let dx1: any = d.split("M")?.[1]?.split(" ") || []; let dx2: any = d.split("L")?.[1]?.split(" ") || []; dx1 = dx1[0] && !isNaN(Number(dx1[0])) ? Number(dx1[0]) : 0; dx2 = dx2[0] && !isNaN(Number(dx2[0])) ? Number(dx2[0]) : 0; if (dx1 && dx2) { // 根据dxRate比例转换dx let targetDx = lineGroup.dx; if (lineGroup.dxRate) { // 需要获取当前渐强渐弱线所对应的小节的宽度,算出相对当前宽度的比例,用于不同设备回显用 const measureNum = document.getElementById(lineGroup.id)?.getAttribute('data-mnum'); const measureWidth = measureNum ? document.querySelector(`g[data-num='${measureNum}']`)?.getBoundingClientRect()?.width : 0; targetDx = measureWidth ? measureWidth * lineGroup.dxRate : lineGroup.dx; } // targetDx = targetDx * state.zoom; if (storeData.isApp) { targetDx = targetDx * state.zoom; } if (dx1 < dx2) { d = d.replace(dx2, lineGroup.d2 + targetDx + ""); } else { d = d.replace(dx1, lineGroup.d2 + targetDx + ""); } path.setAttribute("d", d); } } } const { width } = getBox(group as any); const div: HTMLElement = document.querySelector(`[data-id=${lineGroup.id}]`)!; div && (div.style.width = width + "px"); } } /** 撤销 */ const handleUndo = () => { const preItem = undoData.undoList.pop(); // console.log("🚀 ~ preItem", preItem) if (preItem) { const itemIndex = moveData.modelList.findIndex((n: any) => n.id === preItem.id); if (itemIndex > -1) { moveData.modelList[itemIndex] = preItem; renderSvgItem(moveData.modelList[itemIndex]) if (preItem.type === "vf-lineGroup") { renderLineGroup(preItem); } } } }; /** 根据移动数据渲染 */ export const renderForMoveData = () => { // 一行谱模式暂时不支持谱面编辑和回显 if (state.isSingleLine) return; if (state.extStyleConfigJson || state.extJianStyleConfigJson) { try { extStyleConfigJson = state.musicRenderType === 'staff' ? JSON.parse(state.extStyleConfigJson) : JSON.parse(state.extJianStyleConfigJson); } catch (error) { extStyleConfigJson = {}; } } if (!extStyleConfigJson || !extStyleConfigJson?.[moveData.partIndex]){ initSvgId(); return } else { initSvgId(); } const list = extStyleConfigJson?.[moveData.partIndex]; if (list && Array.isArray(list)) { nextTick(() => { console.log("🚀 ~ list", list); list.forEach((item: any) => { let index = moveData.modelList.findIndex((n: any) => n.id === item.id); if (item.type === 'vf-text' && item.textContent) { let textValue = document.querySelector("#" + moveData.modelList[index]?.id)?.querySelector("text")?.innerHTML || '' let targetIndex = index, preEnd = false, done = false, preIndex = index, nextIndex = index; // while (textValue !== item.textContent) { // if (preEnd) { // targetIndex = targetIndex + 1 // } else { // targetIndex = targetIndex > 0 ? targetIndex - 1 : targetIndex // } // if (targetIndex == 0) preEnd = true // textValue = document.querySelector("#" + moveData.modelList[targetIndex].id)?.querySelector("text")?.innerHTML || '' // } if (textValue !== item.textContent) { while (!done) { let text1 = moveData.modelList[preIndex] ? document.querySelector("#" + moveData.modelList[preIndex].id)?.querySelector("text")?.innerHTML || '' : '' let text2 = moveData.modelList[nextIndex] ? document.querySelector("#" + moveData.modelList[nextIndex].id)?.querySelector("text")?.innerHTML || '' : '' if (text1 === item.textContent || text2 === item.textContent) { done = true targetIndex = text1 === item.textContent ? preIndex : nextIndex } else { // 有可能后台编辑的元素在部分屏幕尺寸下没有该元素,比如小节索引数,可能后台显示的是1,3,5,部分屏幕尺寸显示的1,3,6 if (!text1 && !text2) { done = true targetIndex = -1 } preIndex = preIndex - 1 nextIndex = nextIndex + 1 } } } index = targetIndex item.id = moveData.modelList[targetIndex]?.id } // console.log(66666666,index) if (index > -1) { moveData.modelList[index] = { ...moveData.modelList[index], ...item }; renderSvgItem(moveData.modelList[index]); if (item.type === "vf-lineGroup") { renderLineGroup(moveData.modelList[index]); } } }); }); } }; export default defineComponent({ name: "move-music-score", setup() { moveData.zoom = state.zoom; const query = getQuery(); const isOpen = query.isMove === "1" ? true : false; console.log("🚀 ~ isOpen:", isOpen); onMounted(() => { // if (isOpen) { // initSvgId(); // } // renderForMoveData(); moveData.modelList = [] nextTick(() => initNoteCoord()) // const hasToolDom = Array.from(document.body.children)?.some((item: any) => item?.id === 'toolBox') // if (!hasToolDom) { // const toolBox = document.getElementById("toolBox"); // toolBox && document.body.appendChild(toolBox); // } const toolBox = document.getElementById("toolBox"); toolBox && document.body.appendChild(toolBox); }); onUnmounted(() => { moveData.modelList = [] const toolBox = document.getElementById("toolBox"); toolBox && document.body.removeChild(toolBox); }) return () => (
{/*
{moveData.open && ( <> {moveData.tool.isAddAndSub && ( )} )}
{ !showToolBox.value && showToolBox.value = true } /> } */}
{ !state.isSingleLine && <>
{moveData.open ? '取消' : '编辑'}
保存
撤回
{moveData.modelList[moveData.activeIndex]?.isDelete ? '回显' : '删除'}
重置
{ moveData.tool.isAddAndSub &&
handleAddAndSub('sub')} /> handleAddAndSub('add')} />
} }
{moveData.modelList.map((item: any, index: number) => { return (
); })}
); }, });