index.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. import { Row, showToast } from "vant";
  2. import { defineComponent, onMounted, reactive } from "vue";
  3. import state from "/src/state";
  4. import request from "/src/utils/request";
  5. import { getQuery } from "/src/utils/queryString";
  6. import styles from "./index.module.less";
  7. import { Button, ButtonGroup, Icon, Switch, Tooltip } from "@varlet/ui";
  8. import "@varlet/ui/es/tooltip/style";
  9. import "@varlet/ui/es/button-group/style";
  10. import "@varlet/ui/es/switch/style";
  11. let extStyleConfigJson: any = {};
  12. export const moveData = reactive({
  13. /** 开启移动 */
  14. open: false,
  15. zoom: state.zoom,
  16. partIndex: "0",
  17. hasExtJson: false,
  18. isWeb: false,
  19. modelList: [] as any[],
  20. activeIndex: -1,
  21. sw: 0,
  22. tool: {
  23. isAddAndSub: false,
  24. },
  25. });
  26. function initSvgId() {
  27. const svg = document.querySelector("#osmdSvgPage1");
  28. if (!svg) return;
  29. const vfstavetempo: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-stavetempo"));
  30. const vftext: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-text"));
  31. const vfstaveSection: HTMLElement[] = []; //Array.from(svg.querySelectorAll(".vf-StaveSection"));
  32. const vfRepetition: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-Repetition"));
  33. const vflineGroup: HTMLElement[] = Array.from(svg.querySelectorAll(".vf-lineGroup"));
  34. let tempIndex = 1;
  35. [...vfstavetempo].forEach((ele) => {
  36. setEleId(ele, "temp" + tempIndex);
  37. tempIndex++;
  38. });
  39. let textIndex = 1;
  40. [...vftext].forEach((ele) => {
  41. setEleId(ele, "text" + textIndex);
  42. textIndex++;
  43. });
  44. let sectionIndex = 1;
  45. [...vfstaveSection].forEach((ele) => {
  46. setEleId(ele, "section" + sectionIndex);
  47. sectionIndex++;
  48. });
  49. let repIndex = 1;
  50. [...vfRepetition].forEach((ele) => {
  51. setEleId(ele, "repet" + repIndex);
  52. repIndex++;
  53. });
  54. let lineIndex = 1;
  55. [...vflineGroup].forEach((ele) => {
  56. setEleId(ele, "line" + lineIndex);
  57. lineIndex++;
  58. });
  59. // if (moveData.isWeb) {
  60. // readerModelBox();
  61. // }
  62. // if (moveData.hasExtJson) {
  63. // setTimeout(reloadReader, 2);
  64. // }
  65. }
  66. /**赋值id */
  67. function setEleId(ele: HTMLElement, eleId: string) {
  68. if (!ele || !eleId) return;
  69. const id = ele.getAttribute("id");
  70. if (!id) {
  71. ele.setAttribute("id", eleId);
  72. }
  73. createModelBox(ele as any);
  74. }
  75. function createModelBox(ele: SVGAElement) {
  76. const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || { x: 0, y: 0 };
  77. const parentLeft = musicContainer.x || 0;
  78. const parentTop = musicContainer.y || 0;
  79. const noteBbox = ele.getBoundingClientRect();
  80. const bbox = {
  81. left: noteBbox.x - parentLeft + "px",
  82. top: noteBbox.y - parentTop + "px",
  83. width: noteBbox.width + "px",
  84. height: noteBbox.height + "px",
  85. };
  86. const type = ele.getAttribute("class");
  87. moveData.modelList.push({
  88. id: ele.getAttribute("id"),
  89. bbox,
  90. type,
  91. isMove: false,
  92. left: noteBbox.left,
  93. top: noteBbox.top,
  94. width: noteBbox.width,
  95. height: noteBbox.height,
  96. x: 0,
  97. y: 0,
  98. zoom: state.zoom,
  99. isDelete: false,
  100. d2: getLineGroupPathDx(ele as any),
  101. dx: 0,
  102. });
  103. }
  104. function getBox(ele: SVGAElement) {
  105. if (!ele) return {};
  106. const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || { x: 0, y: 0 };
  107. const parentLeft = musicContainer.x || 0;
  108. const parentTop = musicContainer.y || 0;
  109. const box = ele.getBoundingClientRect();
  110. return {
  111. left: box.x - parentLeft,
  112. top: box.y - parentTop,
  113. width: box.width,
  114. height: box.height,
  115. };
  116. }
  117. // 过滤数据
  118. export const filterMoveData = async () => {
  119. const examSongId = state.examSongId;
  120. if (examSongId) {
  121. const list = moveData.modelList
  122. .filter((n) => n.isMove)
  123. .map((n) => {
  124. const item: any = {
  125. id: n.id,
  126. isMove: n.isMove,
  127. isDelete: n.isDelete,
  128. x: n.x,
  129. y: n.y,
  130. zoom: n.zoom,
  131. w: moveData.sw,
  132. type: n.type,
  133. };
  134. if (n.type === "vf-lineGroup") {
  135. item.dx = n.dx;
  136. }
  137. return item;
  138. });
  139. if (!list.length) {
  140. showToast("请移动元素后再保存");
  141. return
  142. }
  143. extStyleConfigJson[moveData.partIndex] = list;
  144. console.log("🚀 ~ extStyleConfigJson", extStyleConfigJson)
  145. const res = await request.post("/musicSheet/img", {
  146. requestType: "json",
  147. data: {
  148. id: examSongId,
  149. extStyleConfigJson: JSON.stringify(extStyleConfigJson),
  150. }
  151. });
  152. if (res && res.code == 200) {
  153. showToast("保存成功");
  154. }
  155. clearActiveModel();
  156. }
  157. };
  158. // 移动
  159. const dragData = {
  160. open: false,
  161. startX: 0,
  162. startY: 0,
  163. x: 0,
  164. y: 0,
  165. };
  166. // 记录
  167. export const undoData = reactive({
  168. undoList: [] as Array<any>, // 撤销列表
  169. redoList: [] as Array<any>, // 回退列表
  170. activeItem: null,
  171. });
  172. function onDown(e: MouseEvent) {
  173. const el: any = e.target;
  174. const index = moveData.modelList.findIndex((n) => n.id === el.dataset.id);
  175. if (index > -1) {
  176. const item = moveData.modelList[index];
  177. moveData.activeIndex = index;
  178. dragData.startX = e.clientX;
  179. dragData.startY = e.clientY;
  180. dragData.x = item.x;
  181. dragData.y = item.y;
  182. // console.log("🚀 ~ 按下", index, el, item.x, item.y);
  183. document.onmousemove = onMove;
  184. document.onmouseup = onUp;
  185. dragData.open = true;
  186. if (item.type === "vf-lineGroup") {
  187. moveData.tool.isAddAndSub = true;
  188. } else {
  189. moveData.tool.isAddAndSub = false;
  190. }
  191. undoData.activeItem = { ...item };
  192. return;
  193. }
  194. moveData.activeIndex = -1;
  195. }
  196. function onMove(e: MouseEvent) {
  197. if (dragData.open) {
  198. const _x = e.clientX - dragData.startX + dragData.x;
  199. const _y = e.clientY - dragData.startY + dragData.y;
  200. setModelPostion(moveData.modelList[moveData.activeIndex], _x, _y);
  201. }
  202. }
  203. function onUp(e: MouseEvent) {
  204. // console.log("🚀 ~ 抬起");
  205. document.onmousemove = null;
  206. document.onmouseup = null;
  207. dragData.open = false;
  208. const _x = e.clientX - dragData.startX + dragData.x;
  209. const _y = e.clientY - dragData.startY + dragData.y;
  210. if (_x || _y) {
  211. moveData.modelList[moveData.activeIndex].isMove = true;
  212. moveData.modelList[moveData.activeIndex].x = _x;
  213. moveData.modelList[moveData.activeIndex].y = _y;
  214. if (undoData.activeItem) {
  215. undoData.undoList.push({ ...(undoData.activeItem as any) });
  216. }
  217. }
  218. undoData.activeItem = null;
  219. }
  220. /** 渲染svg元素的属性 */
  221. const renderSvgItem = (item: any) => {
  222. setModelPostion(item, item.x, item.y);
  223. if (item.isDelete) {
  224. const g = document.querySelector("#" + item.id)!;
  225. g && ((g as any).style.display = "none");
  226. } else {
  227. const g = document.querySelector("#" + item.id)!;
  228. g && ((g as any).style.display = "");
  229. }
  230. };
  231. /** 设置元素位置 */
  232. function setModelPostion(item: any, x: number, y: number) {
  233. if (item) {
  234. const g = document.querySelector("#" + item.id)!;
  235. const el: HTMLElement = document.querySelector(`[data-id=${item.id}]`)!;
  236. if (x === 0 && y === 0) {
  237. g && g.removeAttribute("transform");
  238. el && (el.style.transform = "");
  239. } else {
  240. g && g.setAttribute("transform", `translate(${x / moveData.zoom}, ${y / moveData.zoom})`);
  241. el && (el.style.transform = `translate(${x}px, ${y}px)`);
  242. }
  243. }
  244. }
  245. /** 删除元素 */
  246. const handleDeleteMoveNote = () => {
  247. const item = moveData.modelList[moveData.activeIndex];
  248. if (item) {
  249. moveData.modelList[moveData.activeIndex].isMove = true;
  250. undoData.undoList.push({ ...moveData.modelList[moveData.activeIndex] });
  251. moveData.modelList[moveData.activeIndex].isDelete = !item.isDelete;
  252. const g = document.querySelector("#" + item.id)!;
  253. g && ((g as any).style.display = moveData.modelList[moveData.activeIndex].isDelete ? "none" : '');
  254. } else {
  255. showToast("选中需要删除的元素");
  256. }
  257. };
  258. /** 重置数据 */
  259. const resetMoveNote = () => {
  260. for (let i = 0; i < moveData.modelList.length; i++) {
  261. moveData.modelList[i].x = 0;
  262. moveData.modelList[i].y = 0;
  263. moveData.modelList[i].isMove = false;
  264. moveData.modelList[i].isDelete = false;
  265. moveData.modelList[i].dx = 0;
  266. renderSvgItem(moveData.modelList[i]);
  267. if (moveData.modelList[i].type === "vf-lineGroup") {
  268. renderLineGroup(moveData.modelList[i]);
  269. }
  270. }
  271. clearActiveModel();
  272. };
  273. function clearActiveModel() {
  274. for (let i = 0; i < moveData.modelList.length; i++) {
  275. const item: HTMLElement = document.querySelector(`[data-id=${moveData.modelList[i].id}]`)!;
  276. if (item?.classList?.contains("activeModel")) {
  277. item.classList.remove("activeModel");
  278. }
  279. }
  280. moveData.activeIndex = -1;
  281. moveData.tool.isAddAndSub = false;
  282. }
  283. // 增加或减少, 渐强和渐弱的线长
  284. const handleAddAndSub = (type: 'add' | 'sub') => {
  285. if (!["add", "sub"].includes(type)) return;
  286. const lineGroup = moveData.modelList[moveData.activeIndex];
  287. if (!lineGroup || lineGroup.type !== "vf-lineGroup") return;
  288. lineGroup.isMove = true;
  289. const step = type === "add" ? 10 : -10;
  290. undoData.undoList.push({ ...moveData.modelList[moveData.activeIndex] });
  291. moveData.modelList[moveData.activeIndex].dx = lineGroup.dx + step;
  292. renderLineGroup(moveData.modelList[moveData.activeIndex]);
  293. };
  294. // 获取line的dx
  295. function getLineGroupPathDx(lineGroup: HTMLElement) {
  296. if (!lineGroup) return 0;
  297. const lines = lineGroup.querySelectorAll("path");
  298. if (lines?.length) {
  299. for (let i = 0; i < lines.length; i++) {
  300. const path = lines[i];
  301. let d = path.getAttribute("d");
  302. if (d) {
  303. let dx1: any = d.split("M")?.[1]?.split(" ") || [];
  304. let dx2: any = d.split("L")?.[1]?.split(" ") || [];
  305. dx1 = dx1[0] && !isNaN(Number(dx1[0])) ? Number(dx1[0]) : 0;
  306. dx2 = dx2[0] && !isNaN(Number(dx2[0])) ? Number(dx2[0]) : 0;
  307. if (dx1 && dx2) {
  308. if (dx1 < dx2) {
  309. return dx2;
  310. } else {
  311. return dx1;
  312. }
  313. }
  314. }
  315. }
  316. }
  317. return 0;
  318. }
  319. function renderLineGroup(lineGroup: any) {
  320. const group = document.querySelector("#" + lineGroup.id);
  321. if (!group) return;
  322. const lines = group.querySelectorAll("path");
  323. if (lines?.length) {
  324. for (let i = 0; i < lines.length; i++) {
  325. const path = lines[i];
  326. let d = path.getAttribute("d");
  327. if (d) {
  328. let dx1: any = d.split("M")?.[1]?.split(" ") || [];
  329. let dx2: any = d.split("L")?.[1]?.split(" ") || [];
  330. dx1 = dx1[0] && !isNaN(Number(dx1[0])) ? Number(dx1[0]) : 0;
  331. dx2 = dx2[0] && !isNaN(Number(dx2[0])) ? Number(dx2[0]) : 0;
  332. if (dx1 && dx2) {
  333. if (dx1 < dx2) {
  334. d = d.replace(dx2, lineGroup.d2 + lineGroup.dx + "");
  335. } else {
  336. d = d.replace(dx1, lineGroup.d2 + lineGroup.dx + "");
  337. }
  338. path.setAttribute("d", d);
  339. }
  340. }
  341. }
  342. const { width } = getBox(group as any);
  343. const div: HTMLElement = document.querySelector(`[data-id=${lineGroup.id}]`)!;
  344. div && (div.style.width = width + "px");
  345. }
  346. }
  347. /** 撤销 */
  348. const handleUndo = () => {
  349. const preItem = undoData.undoList.pop();
  350. // console.log("🚀 ~ preItem", preItem)
  351. if (preItem) {
  352. const itemIndex = moveData.modelList.findIndex((n: any) => n.id === preItem.id);
  353. if (itemIndex > -1) {
  354. moveData.modelList[itemIndex] = preItem;
  355. renderSvgItem(moveData.modelList[itemIndex])
  356. if (preItem.type === "vf-lineGroup") {
  357. renderLineGroup(preItem);
  358. }
  359. }
  360. }
  361. };
  362. /** 根据移动数据渲染 */
  363. export const renderForMoveData = () => {
  364. if (state.extStyleConfigJson) {
  365. try {
  366. extStyleConfigJson = JSON.parse(state.extStyleConfigJson);
  367. } catch (error) {
  368. extStyleConfigJson = {};
  369. }
  370. }
  371. if (!extStyleConfigJson || !extStyleConfigJson?.[moveData.partIndex]) return;
  372. initSvgId();
  373. const list = extStyleConfigJson?.[moveData.partIndex];
  374. if (list && Array.isArray(list)) {
  375. console.log("🚀 ~ list", list);
  376. list.forEach((item: any) => {
  377. const index = moveData.modelList.findIndex((n: any) => n.id === item.id);
  378. if (index > -1) {
  379. moveData.modelList[index] = {
  380. ...moveData.modelList[index],
  381. ...item
  382. };
  383. renderSvgItem(moveData.modelList[index]);
  384. if (item.type === "vf-lineGroup") {
  385. renderLineGroup(moveData.modelList[index]);
  386. }
  387. }
  388. });
  389. }
  390. };
  391. export default defineComponent({
  392. name: "move-music-score",
  393. setup() {
  394. const query = getQuery();
  395. const isOpen = query.isMove === "1" ? true : false;
  396. console.log("🚀 ~ isOpen:", isOpen);
  397. onMounted(() => {
  398. // if (isOpen) {
  399. // initSvgId();
  400. // }
  401. // renderForMoveData();
  402. const toolBox = document.getElementById("toolBox");
  403. toolBox && document.body.appendChild(toolBox);
  404. });
  405. return () => (
  406. <div class={[moveData.open ? "" : styles.moveDisabled]}>
  407. <div class={styles.toolBox} id="toolBox">
  408. <Switch v-model={moveData.open} />
  409. {moveData.open && (
  410. <>
  411. {moveData.tool.isAddAndSub && (
  412. <ButtonGroup size="small" elevation={false}>
  413. <Button onClick={() => handleAddAndSub('add')}>加</Button>
  414. <Button onClick={() => handleAddAndSub('sub')}>减</Button>
  415. </ButtonGroup>
  416. )}
  417. {/* <ButtonGroup size="small">
  418. <Button>
  419. <Icon name="arrow-down" style={{ transform: "rotate(-90deg)" }} />
  420. </Button>
  421. </ButtonGroup> */}
  422. <Button size="small" onClick={handleUndo} disabled={undoData.undoList.length ? false : true}>
  423. <Icon name="arrow-down" style={{ transform: "rotate(90deg)" }} />
  424. </Button>
  425. <Button size="small" onClick={handleDeleteMoveNote} disabled={moveData.activeIndex > -1 ? false : true}>
  426. {moveData.modelList[moveData.activeIndex]?.isDelete ? '显示元素' : '删除元素'}
  427. </Button>
  428. <Button size="small" onClick={resetMoveNote}>
  429. 重置数据
  430. </Button>
  431. <Button size="small" type="primary" onClick={filterMoveData}>
  432. 保存数据
  433. </Button>
  434. </>
  435. )}
  436. </div>
  437. {moveData.modelList.map((item: any, index: number) => {
  438. return (
  439. <div class={[styles.noteMove, moveData.activeIndex === index && styles.activeModel]} style={item.bbox} data-id={item.id} onMousedown={onDown}></div>
  440. );
  441. })}
  442. </div>
  443. );
  444. },
  445. });