|
@@ -0,0 +1,434 @@
|
|
|
+import { showToast } from "vant";
|
|
|
+import { defineComponent, onMounted, reactive } from "vue";
|
|
|
+import { useRoute } from "vue-router";
|
|
|
+import state from "/src/state";
|
|
|
+import { detailData_gym } from "../../detail";
|
|
|
+import request from "/src/utils/request";
|
|
|
+import { getQuery } from "/src/utils/queryString";
|
|
|
+
|
|
|
+let extStyleConfigJson: any = {};
|
|
|
+export const moveData = reactive({
|
|
|
+ partIndex: "0",
|
|
|
+ hasExtJson: false,
|
|
|
+ isWeb: false,
|
|
|
+ modelList: [] as any[],
|
|
|
+ activeIndex: -1,
|
|
|
+ sw: 0,
|
|
|
+ tool: {
|
|
|
+ isAddAndSub: false,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+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"));
|
|
|
+ let tempIndex = 1;
|
|
|
+ [...vfstavetempo].forEach((ele) => {
|
|
|
+ setEleId(ele, "temp" + tempIndex);
|
|
|
+ tempIndex++;
|
|
|
+ });
|
|
|
+ let textIndex = 1;
|
|
|
+ [...vftext].forEach((ele) => {
|
|
|
+ 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++;
|
|
|
+ });
|
|
|
+ readerModelBox();
|
|
|
+ // 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);
|
|
|
+}
|
|
|
+
|
|
|
+const reloadReader = () => {
|
|
|
+ const detail = detailData_gym;
|
|
|
+ if (detail.extStyleConfigJson) {
|
|
|
+ try {
|
|
|
+ extStyleConfigJson = JSON.parse(detail.extStyleConfigJson);
|
|
|
+ } catch (error) {
|
|
|
+ extStyleConfigJson = {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!extStyleConfigJson || !extStyleConfigJson?.[moveData.partIndex]) return;
|
|
|
+ const list = extStyleConfigJson?.[moveData.partIndex];
|
|
|
+ if (list && Array.isArray(list)) {
|
|
|
+ console.log("🚀 ~ list", list);
|
|
|
+ list.forEach((item: any) => {
|
|
|
+ const index = moveData.modelList.findIndex((n: any) => n.id === item.id);
|
|
|
+ if (index > -1) {
|
|
|
+ item.x = moveData.sw && item.w ? item.x * (moveData.sw / item.w) : item.x;
|
|
|
+ moveData.modelList[index] = {
|
|
|
+ ...moveData.modelList[index],
|
|
|
+ ...item,
|
|
|
+ };
|
|
|
+ setModelPostion(moveData.modelList[index]);
|
|
|
+ if (item.type === "vf-lineGroup") {
|
|
|
+ renderLineGroup(moveData.modelList[index]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+const zoom = 0.7;
|
|
|
+
|
|
|
+function formateZoom(n: number) {
|
|
|
+ return n * zoom;
|
|
|
+}
|
|
|
+
|
|
|
+function createModelBox(ele: SVGAElement) {
|
|
|
+ const { left, top, width, height } = getBox(ele);
|
|
|
+ const type = ele.getAttribute("class");
|
|
|
+ moveData.modelList.push({
|
|
|
+ id: ele.getAttribute("id"),
|
|
|
+ type,
|
|
|
+ isMove: false,
|
|
|
+ left,
|
|
|
+ top,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ zoom,
|
|
|
+ isDelete: false,
|
|
|
+ d2: getLineGroupPathDx(ele as any),
|
|
|
+ dx: 0,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function readerModelBox() {
|
|
|
+ const container = document.querySelector(".moveModelWrap");
|
|
|
+ if (!container) return;
|
|
|
+ // console.log("🚀 ~ moveData.modelList", moveData.modelList);
|
|
|
+ for (let i = 0; i < moveData.modelList.length; i++) {
|
|
|
+ const item = moveData.modelList[i];
|
|
|
+ const div = document.createElement("div");
|
|
|
+ div.classList.add("moveModel");
|
|
|
+ div.style.left = item.left + "px";
|
|
|
+ div.style.top = item.top + "px";
|
|
|
+ div.style.width = item.width + "px";
|
|
|
+ div.style.height = item.height + "px";
|
|
|
+ div.dataset.id = item.id;
|
|
|
+ dragmove(div);
|
|
|
+ container.appendChild(div);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getBox(ele: SVGAElement) {
|
|
|
+ if (!ele) return {};
|
|
|
+ const box = ele.getBBox();
|
|
|
+ return {
|
|
|
+ left: formateZoom(box.x - 5),
|
|
|
+ top: formateZoom(box.y - 5),
|
|
|
+ width: formateZoom(box.width + 10),
|
|
|
+ height: formateZoom(box.height + 10),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 过滤数据
|
|
|
+export const filterMoveData = async () => {
|
|
|
+ const examSongId = state.examSongId;
|
|
|
+ if (examSongId) {
|
|
|
+ const list = moveData.modelList
|
|
|
+ .filter((n) => n.isMove)
|
|
|
+ .map((n) => {
|
|
|
+ const item: any = {
|
|
|
+ id: n.id,
|
|
|
+ isMove: n.isMove,
|
|
|
+ isDelete: n.isDelete,
|
|
|
+ x: n.x,
|
|
|
+ y: n.y,
|
|
|
+ zoom: n.zoom,
|
|
|
+ w: moveData.sw,
|
|
|
+ type: n.type,
|
|
|
+ };
|
|
|
+ if (n.type === "vf-lineGroup") {
|
|
|
+ item.dx = n.dx;
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ });
|
|
|
+ extStyleConfigJson[moveData.partIndex] = list;
|
|
|
+ // console.log("🚀 ~ extStyleConfigJson", extStyleConfigJson)
|
|
|
+ const res = await request.post("/sysMusicScore/updateExtStyleConfigJson", {
|
|
|
+ data: {
|
|
|
+ sysMusicScoreId: examSongId,
|
|
|
+ extStyleConfigJson: JSON.stringify(extStyleConfigJson),
|
|
|
+ },
|
|
|
+ });
|
|
|
+ // console.log(res)
|
|
|
+ if (res && res.code == 200) {
|
|
|
+ showToast("保存成功");
|
|
|
+ }
|
|
|
+ clearActiveModel();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 移动
|
|
|
+const dragData = {
|
|
|
+ moving: false,
|
|
|
+ startX: 0,
|
|
|
+ startY: 0,
|
|
|
+};
|
|
|
+
|
|
|
+// 记录
|
|
|
+export const undoData = reactive({
|
|
|
+ undoList: [] as Array<any>, // 撤销列表
|
|
|
+ redoList: [] as Array<any>, // 回退列表
|
|
|
+ activeItem: null,
|
|
|
+});
|
|
|
+
|
|
|
+function dragmove(div: HTMLElement) {
|
|
|
+ div.addEventListener("mousedown", onDown);
|
|
|
+}
|
|
|
+
|
|
|
+function onDown(e: MouseEvent) {
|
|
|
+ if (dragData.moving) return;
|
|
|
+ const el: any = e.target;
|
|
|
+ const index = moveData.modelList.findIndex((n) => n.id === el.dataset.id);
|
|
|
+ if (index > -1) {
|
|
|
+ // console.log("🚀 ~ 按下", index, el);
|
|
|
+ const item = moveData.modelList[index];
|
|
|
+ moveData.activeIndex = index;
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ dragData.startX = e.clientX - item.x;
|
|
|
+ dragData.startY = e.clientY - item.y;
|
|
|
+ document.addEventListener("mousemove", onMove);
|
|
|
+ document.addEventListener("mouseup", onUp);
|
|
|
+ dragData.moving = true;
|
|
|
+ if (item.type === "vf-lineGroup") {
|
|
|
+ moveData.tool.isAddAndSub = true;
|
|
|
+ } else {
|
|
|
+ moveData.tool.isAddAndSub = false;
|
|
|
+ }
|
|
|
+ undoData.activeItem = { ...item };
|
|
|
+ }
|
|
|
+ setActiveModel(el);
|
|
|
+}
|
|
|
+function onMove(e: MouseEvent) {
|
|
|
+ if (dragData.moving) {
|
|
|
+ const _x = e.clientX - dragData.startX;
|
|
|
+ const _y = e.clientY - dragData.startY;
|
|
|
+ moveData.modelList[moveData.activeIndex].isMove = true;
|
|
|
+ moveData.modelList[moveData.activeIndex].x = _x;
|
|
|
+ moveData.modelList[moveData.activeIndex].y = _y;
|
|
|
+ const item = moveData.modelList[moveData.activeIndex];
|
|
|
+ setModelPostion(item);
|
|
|
+ }
|
|
|
+}
|
|
|
+function onUp() {
|
|
|
+ // console.log("🚀 ~ 抬起");
|
|
|
+ document.removeEventListener("mousemove", onMove);
|
|
|
+ document.removeEventListener("mouseup", onUp);
|
|
|
+ dragData.moving = false;
|
|
|
+ if (undoData.activeItem) {
|
|
|
+ undoData.undoList.push({ ...(undoData.activeItem as any) });
|
|
|
+ }
|
|
|
+ undoData.activeItem = null;
|
|
|
+}
|
|
|
+
|
|
|
+function setModelPostion(item: any) {
|
|
|
+ if (item) {
|
|
|
+ const g = document.querySelector("#" + item.id)!;
|
|
|
+ g && g.setAttribute("transform", `translate(${item.x / zoom}, ${item.y / zoom})`);
|
|
|
+ const el: HTMLElement = document.querySelector(`[data-id=${item.id}]`)!;
|
|
|
+ if (el) {
|
|
|
+ el.style.left = item.left + item.x + "px";
|
|
|
+ el.style.top = item.top + item.y + "px";
|
|
|
+ el.style.width = item.width + "px";
|
|
|
+ el.style.height = item.height + "px";
|
|
|
+ }
|
|
|
+ if (item.isDelete) {
|
|
|
+ g && ((g as any).style.display = "none");
|
|
|
+ el && ((el as any).style.display = "none");
|
|
|
+ } else {
|
|
|
+ g && ((g as any).style.display = "");
|
|
|
+ el && ((el as any).style.display = "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function setActiveModel(_div: HTMLElement) {
|
|
|
+ if (!_div) return;
|
|
|
+ moveData.modelList.forEach((n) => {
|
|
|
+ const el: HTMLElement = document.querySelector(`[data-id=${n.id}]`)!;
|
|
|
+ if (el) {
|
|
|
+ if (n.id == _div.dataset.id) {
|
|
|
+ _div.classList.add("activeModel");
|
|
|
+ } else {
|
|
|
+ el.classList.remove("activeModel");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+//删除可移动元素
|
|
|
+export const deleteMoveNote = () => {
|
|
|
+ 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")) {
|
|
|
+ moveData.modelList[i].isMove = true;
|
|
|
+ undoData.undoList.push({ ...moveData.modelList[i] });
|
|
|
+ moveData.modelList[i].isDelete = true;
|
|
|
+ setModelPostion(moveData.modelList[i]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ showToast("选中需要删除的元素");
|
|
|
+};
|
|
|
+//重置数据
|
|
|
+export 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;
|
|
|
+ setModelPostion(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;
|
|
|
+}
|
|
|
+
|
|
|
+// 增加或减少, 渐强和渐弱的线长
|
|
|
+export const addAndSub = (type: string) => {
|
|
|
+ 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) {
|
|
|
+ if (dx1 < dx2) {
|
|
|
+ d = d.replace(dx2, lineGroup.d2 + lineGroup.dx + "");
|
|
|
+ } else {
|
|
|
+ d = d.replace(dx1, lineGroup.d2 + lineGroup.dx + "");
|
|
|
+ }
|
|
|
+ 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");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 撤销
|
|
|
+export 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;
|
|
|
+ setModelPostion(preItem);
|
|
|
+ if (preItem.type === "vf-lineGroup") {
|
|
|
+ renderLineGroup(preItem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ console.log(undoData.undoList);
|
|
|
+};
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: "move-music-score",
|
|
|
+ setup() {
|
|
|
+ const query = getQuery();
|
|
|
+ const isOpen = query.isMove === "1" ? true : false;
|
|
|
+ onMounted(() => {
|
|
|
+ if (isOpen) {
|
|
|
+ initSvgId();
|
|
|
+ }
|
|
|
+ if (detailData_gym.extStyleConfigJson) {
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return () => <div style={{ display: "none" }}></div>;
|
|
|
+ },
|
|
|
+});
|