Przeglądaj źródła

Merge branch 'feature-tianyong-newVersion' of http://git.dayaedu.com/liushengqiang/music-score into hqyDevNewVersion

黄琪勇 2 miesięcy temu
rodzic
commit
acd7a3a600

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit 6106f19f06cc5815d2f2069a1370cac1cb228d8e
+Subproject commit a9aa056f79fb3aa710a6586b2064241a6636bc1c

+ 25 - 8
src/helpers/customMusicScore.ts

@@ -320,11 +320,11 @@ export const resetFormate = () => {
 			.forEach((t) => {
 				// console.log('文本123',t.textContent,'是否在可视区域内',isElementInViewport(t))
 				// D.C循环标记不在可视区域内,需要修复移动其位置信息
-				if (t.textContent?.includes('D.C')) {
-					if (!isElementInViewport(t)) {
-						t.style.transform = `translateX(-40px)`;
-					}
-				}
+				// if (t.textContent?.includes('D.C')) {
+				// 	if (!isElementInViewport(t)) {
+				// 		t.style.transform = `translateX(-40px)`;
+				// 	}
+				// }
 				vfbeams.forEach((curve) => {
 					const result = collisionDetection(t, curve);
 					const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
@@ -595,8 +595,8 @@ export const resetFormate = () => {
 		if (!state.isCreateImg && !state.isPreView) {
 			staves.forEach((stave: any,i: number) => {
 				const list = [
-					Array.from(stave?.getElementsByTagName("text") || []),
 					Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
+					Array.from(stave?.getElementsByTagName("text") || []),
 					Array.from(stave?.querySelectorAll(".vf-Volta") || []),
 					Array.from(stave?.querySelectorAll(".vf-clef") || []),
 					Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
@@ -625,10 +625,13 @@ export const resetFormate = () => {
 				const customG = `<g>${rect}${rectBottom}</g>`
 				try {
 					if (list.length) {
-						list.forEach((_el: any) => {
+						for(const _el of list) {
+							if (_el?.parentElement?.classList?.contains('vf-StaveSection')) {
+								continue;
+							}
 							stave?.appendChild(_el)
 							_el?.style?.removeProperty("display");
-						});
+						}
 					}
 				} catch (error) {}
 				stave.innerHTML = customG + stave.innerHTML;
@@ -655,6 +658,20 @@ export const resetFormate = () => {
 			// }
 		});
 
+		// 修复D.C、D.S等渲染位置不对的问题
+		const repairWord = ["D.S.", "D.C.", "Fine"];
+		[...vfmeasures].forEach((measure: any) => {
+			const needRepairTexts = measure.querySelectorAll('text').length ? Array.from(measure.querySelectorAll('text'))?.filter((item: any) => repairWord.includes(item?.textContent)) : [];
+			if (needRepairTexts.length) {
+				// 该小节结束位置的x坐标
+				const measureCoordinate = measure?.querySelector('.vf-custom-bg')?.getBBox() || null
+				const measureEndX = measureCoordinate ? measureCoordinate?.x + measureCoordinate?.width - 30 : 0;
+				needRepairTexts.forEach((text: any) => {
+					text?.setAttribute('x', measureEndX)
+				})
+			}
+		});
+
 	}
 	if (!state.isCombineRender && state.isSingleLine) {
 		transSinglePage();

+ 11 - 2
src/helpers/formateMusic.ts

@@ -665,9 +665,18 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 	const minutes: any = xmlParse.getElementsByTagName("per-minute");
 	let speeds: any = []
 	for (const minute of minutes) {
-		if (minute.textContent && !!Number(minute.textContent)) {
-			speeds.push(Number(minute.textContent))
+		let measureSpeed = minute.textContent ? Number(minute.textContent) : 0;
+		// 速度带附点,需要转换成不带附点的速度值
+		const hasSpeedDot = Array.from(minute?.parentElement?.children || []).some((item: any) => item?.tagName === 'beat-unit-dot')
+		measureSpeed = hasSpeedDot ? measureSpeed + measureSpeed/2 : measureSpeed;
+		if (minute.textContent && measureSpeed) {
+			speeds.push(Number(measureSpeed))
 		}
+		// if (hasSpeedDot && measureSpeed) {
+		// 	minute.textContent = measureSpeed
+		// 	const dotDom = minute?.parentElement.querySelector('beat-unit-dot')
+		// 	minute?.parentElement?.removeChild(dotDom)
+		// }
 	}
 	speeds = [...new Set(speeds)]
 	const hasVaryingSpeed = speeds.length > 1 ? true : false

+ 3 - 1
src/page-instrument/header-top/index.tsx

@@ -690,7 +690,9 @@ export default defineComponent({
           {/* 返回和标题 */}
           {!(state.playState == "play" || followData.start || evaluatingData.startBegin) && (
             <div id="noticeBarRollDom" class={styles.headTopLeftBox}>
-              <img src={iconBack} class={["headTopBackBtn", styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
+              {
+                !query.isMove && <img src={iconBack} class={["headTopBackBtn", styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
+              }
               {smoothAnimationState.isShow.value || state.isCombineRender ? (
                 <div
                   style={

+ 5 - 3
src/page-instrument/header-top/settting/index.tsx

@@ -3,7 +3,7 @@ import styles from "./index.module.less"
 import { headImg } from "../image";
 import { headTopData } from "../index"
 import { Switch, showToast, Field, Popup, Slider } from "vant";
-import state, { refreshMusicSvg, IPlatform } from "/src/state"
+import state, { refreshMusicSvg, IPlatform, checkMoveNoSave } from "/src/state"
 import { api_closeCamera, api_openCamera, api_savePicture } from "/src/helpers/communication";
 import { smoothAnimationState} from "/src/page-instrument/view-detail/smoothAnimation"
 import Recommendation from "../../custom-plugins/helper-model/recommendation";
@@ -208,10 +208,11 @@ export default defineComponent({
                                 <div class={styles.radioBox}>
                                     {
                                         [{name:'单行谱',value:true},{name:'多行谱',value:false}].map(item=>{
-                                            return <div class={ state.isSingleLine===item.value && styles.active } onClick={ ()=>{ 
+                                            return <div class={ state.isSingleLine===item.value && styles.active } onClick={ async ()=>{ 
                                                 if(state.isSingleLine === item.value){
                                                     return
                                                 }
+                                                await checkMoveNoSave();
                                                 headTopData.settingMode = false
                                                 // resetRenderMusicScore(state.musicRenderType)
                                                 const _time = setTimeout(() => {
@@ -233,10 +234,11 @@ export default defineComponent({
                                 <div class={styles.radioBox}>
                                     {
                                         notationList.value.map(item=>{
-                                            return <div class={ state.musicRenderType===item.value && styles.active } onClick={ ()=>{ 
+                                            return <div class={ state.musicRenderType===item.value && styles.active } onClick={ async ()=>{ 
                                                 if(state.musicRenderType === item.value){
                                                     return
                                                 }
+                                                await checkMoveNoSave();
                                                 headTopData.settingMode = false
                                                 // resetRenderMusicScore(state.musicRenderType)
                                                 const _time = setTimeout(() => {

+ 37 - 5
src/state.ts

@@ -1,4 +1,4 @@
-import { closeToast, showToast } from "vant";
+import { closeToast, showToast, showConfirmDialog } from "vant";
 import { nextTick, reactive, watch } from "vue";
 import { OpenSheetMusicDisplay } from "../osmd-extended/src";
 import { metronomeData } from "./helpers/metronome";
@@ -21,6 +21,7 @@ import { downloadXmlStr } from "./view/music-score"
 import { musicScoreRef, headerColumnHide } from "/src/page-instrument/view-detail/index"
 import { headTopData } from "/src/page-instrument/header-top/index";
 import { api_lessonTrainingTrainingStudentDetail } from "/src/page-instrument/api"
+import { undoData, moveData } from "/src/view/plugins/move-music-score"
 
 const query: any = getQuery();
 
@@ -1284,12 +1285,13 @@ export const scrollViewNote = () => {
   const domId = "vf" + noteId;
   const cursorElement: any = document.querySelector(`[data-vf=${domId}]`)?.parentElement;
   const musicAndSelection = document.getElementById(state.scrollContainer)!;
+  const noteCenterOffsetTop = cursorElement ? cursorElement?.offsetTop + (cursorElement?.offsetHeight/2) : 0;
   // console.log('滑动',cursorElement.offsetTop,offsetTop, cursorElement, )
-  if (!cursorElement || !musicAndSelection || offsetTop === cursorElement.offsetTop || Math.abs(offsetTop - cursorElement.offsetTop) < 30) return;
-  offsetTop = cursorElement.offsetTop;
-  if (offsetTop > 50) {
+  if (!cursorElement || !noteCenterOffsetTop || !musicAndSelection || offsetTop === noteCenterOffsetTop || Math.abs(offsetTop - noteCenterOffsetTop) < 30) return;
+  offsetTop = noteCenterOffsetTop;
+  if (offsetTop > 100) {
     musicAndSelection.scrollTo({
-      top: (offsetTop - 50) * state.musicZoom,
+      top: (offsetTop - 100) * state.musicZoom,
       behavior: "smooth",
     });
   } else {
@@ -2100,8 +2102,38 @@ watch(
   }
 )
 
+// 后台编辑谱面模式,切换谱面时,如果有操作没有保存,需要给出提示
+export const checkMoveNoSave = async () => {
+  return new Promise((resolve, reject) => {
+    if (query.isMove) {
+      if (moveData.open && undoData.undoList.length) {
+        showConfirmDialog({
+          className: "noSaveModal",
+          title: "温馨提示",
+          message: "您有新的修改还未保存,切换谱面后本次编辑的内容将不会保存",
+        }).then(() => {
+          moveData.open = false
+          resolve(true)
+        }).catch(() => {
+          return;
+        });
+      } else {
+        moveData.open = false
+        undoData.undoList = []
+        resolve(true)
+      }
+    } else {
+      resolve(true)
+    }
+  });
+
+
+}
+
+
 /** 刷新谱面 */
 export const refreshMusicSvg = () => {
+  moveData.modelList = []
   clearSelection();
   resetBaseRate();
   state.activeMeasureIndex = -1;

+ 4 - 0
src/style.css

@@ -123,6 +123,10 @@ body {
   transition: all 0.3s;
 }
 
+.noSaveModal {
+  transform: scale(0.8) translateY(-50%);
+}
+
 /* 引导动画 */
 @keyframes guideKeyframes {
   0% {

+ 1 - 1
src/view/music-score/index.tsx

@@ -258,7 +258,7 @@ export default defineComponent({
 				]}
 			>
 				{slots.default?.()}
-				{props.showSelection && musicData.showSelection && !state.isPreView && !state.isEvaluatReport &&!state.isSimplePage && <Selection />}
+				{props.showSelection && musicData.showSelection && !state.isPreView && !state.isEvaluatReport &&!state.isSimplePage && state.musicRendered && <Selection />}
 			</div>
 		);
 	},

BIN
src/view/plugins/move-music-score/image/edit.png


BIN
src/view/plugins/move-music-score/image/edit_add.png


BIN
src/view/plugins/move-music-score/image/edit_close.png


BIN
src/view/plugins/move-music-score/image/edit_delete.png


BIN
src/view/plugins/move-music-score/image/edit_next.png


BIN
src/view/plugins/move-music-score/image/edit_pre.png


BIN
src/view/plugins/move-music-score/image/edit_reduce.png


BIN
src/view/plugins/move-music-score/image/edit_reset.png


BIN
src/view/plugins/move-music-score/image/edit_save.png


+ 83 - 0
src/view/plugins/move-music-score/index.module.less

@@ -54,4 +54,87 @@
     cursor: pointer;
     transition: all 0.5s;
     transform: rotate(180deg);
+}
+
+.editToolBox {
+    position: fixed;
+    left: 0;
+    top: 0;
+    width: 100%;
+    background: rgba(19, 36, 64, 0.5);
+    z-index: 999999;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    padding: 14PX 30PX;
+    pointer-events: none;
+    min-height: 58PX;
+    .editItem {
+        display: flex;
+        align-items: center;
+        padding: 5PX 12PX;
+        background: rgba(255,255,255,0.2);
+        border-radius: 20PX;
+        margin-left: 18PX;
+        cursor: pointer;
+        pointer-events: all;
+        &:active {
+            opacity: .5;
+        }
+        img {
+            width: 18PX;
+            height: 18PX;
+            margin-right: 6PX;
+        }
+        span {
+            font-size: 14PX;
+            color: #fff;
+        }
+    }
+    .extraItem {
+        margin-left: 18PX;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 5PX 12PX;
+        background: rgba(255,255,255,0.2);
+        border-radius: 20PX;
+        position: relative;
+        width: 76PX;
+        box-sizing: border-box;
+        img {
+            width: 18PX;
+            height: 18PX;
+            cursor: pointer;
+            &:active {
+                opacity: .5;
+            }
+        }
+        &::before {
+            content: "";
+            position: absolute;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%,-50%);
+            width: 1PX;
+            height: 20PX;
+            background: rgba(255,255,255,0.3);
+            z-index: 1;
+        }
+    }
+    .disabled {
+        opacity: .5;
+        pointer-events: none;
+    }
+}
+
+.itemDisabled {
+    .editItem {
+        opacity: .5;
+        pointer-events: none;
+    }
+    .canEdit {
+        opacity: 1;
+        pointer-events: visible;
+    }
 }

+ 84 - 15
src/view/plugins/move-music-score/index.tsx

@@ -1,5 +1,5 @@
-import { Row, showToast } from "vant";
-import { defineComponent, onMounted, reactive, nextTick, ref } from "vue";
+import { Row, showToast, showConfirmDialog } from "vant";
+import { defineComponent, onMounted, onUnmounted, reactive, nextTick, ref } from "vue";
 import state from "/src/state";
 import request from "/src/utils/request";
 import { getQuery } from "/src/utils/queryString";
@@ -10,6 +10,14 @@ 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;
@@ -70,9 +78,10 @@ function initSvgId() {
 	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 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);
@@ -80,6 +89,7 @@ function initSvgId() {
 	});
 	let textIndex = 1;
 	[...vftext].forEach((ele) => {
+		// console.log(ele.textContent,textIndex)
 		setEleId(ele, "text" + textIndex);
 		textIndex++;
 	});
@@ -112,10 +122,10 @@ function setEleId(ele: HTMLElement, eleId: string) {
 	if (!id) {
 		ele.setAttribute("id", eleId);
 	}
-	createModelBox(ele as any);
+	createModelBox(ele as any, eleId as any);
 }
 
-function createModelBox(ele: SVGAElement) {
+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;
@@ -128,7 +138,7 @@ function createModelBox(ele: SVGAElement) {
 	};
 	const type = ele.getAttribute("class");
 	moveData.modelList.push({
-		id: ele.getAttribute("id"),
+		id: eleId || ele.getAttribute("id"),
 		bbox,
 		type,
 		isMove: false,
@@ -159,6 +169,22 @@ function getBox(ele: SVGAElement) {
 	};
 }
 
+// 切换开关
+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;
@@ -216,6 +242,9 @@ export const filterMoveData = async () => {
 		});
 		if (res && res.code == 200) {
 			showToast("保存成功");
+			undoData.undoList = [];
+			undoData.activeItem = null;
+			state.extStyleConfigJson = JSON.stringify(extStyleConfigJson)
 		}
 		clearActiveModel();
 	}
@@ -462,6 +491,7 @@ const handleUndo = () => {
 
 /** 根据移动数据渲染 */
 export const renderForMoveData = () => {
+	if (state.isSingleLine || state.musicRenderType !== 'staff') return; 
 	if (state.extStyleConfigJson) {
 		try {
 			extStyleConfigJson = JSON.parse(state.extStyleConfigJson);
@@ -514,7 +544,7 @@ export const renderForMoveData = () => {
 					// index = targetIndex + 1
 					// item.id = `text${index}`
 					index = targetIndex
-					item.id = `text${targetIndex+1}`
+					item.id = `text${targetIndex}`
 				}
 				// console.log(66666666,index)
 				if (index > -1) {
@@ -544,14 +574,25 @@ export default defineComponent({
 			// 	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 () => (
 			<div class={[moveData.open ? "" : styles.moveDisabled]}>
 				<div id="toolBox">
-					<div class={[styles.toolBox, !showToolBox.value && styles.hideTool]} >
+					{/* <div class={[styles.toolBox, !showToolBox.value && styles.hideTool]} >
 						<Switch v-model={moveData.open} />
 						{moveData.open && (
 							<>
@@ -561,12 +602,6 @@ export default defineComponent({
 										<Button onClick={() => handleAddAndSub('sub')}>减</Button>
 									</ButtonGroup>
 								)}
-								{/* <ButtonGroup size="small">
-									
-									<Button>
-										<Icon name="arrow-down" style={{ transform: "rotate(-90deg)" }} />
-									</Button>
-								</ButtonGroup> */}
 								<Button size="small" onClick={handleUndo} disabled={undoData.undoList.length ? false : true}>
 									<Icon name="arrow-down" style={{ transform: "rotate(90deg)" }} />
 								</Button>
@@ -592,7 +627,41 @@ export default defineComponent({
 							class={[styles.rightHideIcon, !showToolBox.value ? styles.rightIconShow : '']} 
 							src={rightHideIcon}
 							onClick={() => showToolBox.value = true } />
-					}  
+					}   */}	
+					<div class={[styles.editToolBox, !moveData.open && styles.itemDisabled]}>		
+						{
+							state.musicRenderType === 'staff' && !state.isSingleLine && 
+							<>
+								<div class={[styles.editItem, styles.canEdit]} onClick={switchMoveState}>
+									<img src={moveData.open ? editCloseIcon : editIcon} />
+									<span>{moveData.open ? '取消' : '编辑'}</span>
+								</div>
+								<div class={styles.editItem} onClick={filterMoveData}>
+									<img src={editSaveIcon} />
+									<span>保存</span>
+								</div>
+								<div class={[styles.editItem, !undoData.undoList.length && styles.disabled]} onClick={handleUndo}>
+									<img src={editPreIcon} />
+									<span>撤回</span>
+								</div>
+								<div class={[styles.editItem, moveData.activeIndex <= -1 && styles.disabled]} onClick={handleDeleteMoveNote}>
+									<img src={editDeleteIcon} />
+									<span>{moveData.modelList[moveData.activeIndex]?.isDelete ? '回显' : '删除'}</span>
+								</div>
+								<div class={styles.editItem} onClick={resetMoveNote}>
+									<img src={editResetIcon} />
+									<span>重置</span>
+								</div>
+								{
+									moveData.tool.isAddAndSub && 
+									<div class={styles.extraItem}>
+										<img src={editReduceIcon} onClick={() => handleAddAndSub('sub')} />
+										<img src={editAddIcon} onClick={() => handleAddAndSub('add')} />
+									</div>								
+								}		
+							</>						
+						}										
+					</div>		
 				</div>
 				{moveData.modelList.map((item: any, index: number) => {
 					return (

+ 3 - 2
src/view/plugins/toggleMusicSheet/choosePartName/index.tsx

@@ -1,7 +1,7 @@
 import { PropType, computed, defineComponent, ref, toRefs, onMounted, watch, nextTick } from 'vue'
 import { Picker, Button, Icon } from 'vant'
 import styles from './index.module.less'
-import state, { IPlatform } from "/src/state";
+import state, { IPlatform, checkMoveNoSave } from "/src/state";
 import changeName from "./imgs/changeName.png"
 import { headImg } from "/src/page-instrument/header-top/image";
 import { toggleMusicSheet } from "../index"
@@ -73,7 +73,8 @@ export default defineComponent({
               visible-option-num={5}
               option-height={"1.06666rem"}
             />
-            <img src={ okBtn } class={styles.button} onClick={() => {
+            <img src={ okBtn } class={styles.button} onClick={async () => {
+                await checkMoveNoSave();
                 myPicker.value.confirm()
                 nextTick(()=>{
                   emit('close', selValues.value[0])

+ 3 - 1
src/view/plugins/toggleMusicSheet/index.tsx

@@ -37,7 +37,9 @@ export default defineComponent({
           sortId,
           canselect
         }
-      }).filter((item: any) => item.canselect).sort((a: any, b: any) => a.sortId - b.sortId)
+      }).filter((item: any) => item.canselect)
+      // 不需要自定义排序,改为按照xml声轨顺序显示
+      // .sort((a: any, b: any) => a.sortId - b.sortId)
       // 支持总谱渲染的时候 加上总谱字段
       state.isScoreRender && arr.unshift({canselect:true, sortId:999, text: "总谱", value: 999})
       return arr

+ 4 - 1
src/view/selection/index.tsx

@@ -255,7 +255,10 @@ export default defineComponent({
 			// 初始化谱面可移动的元素位置
 			try {
 			moveData.partIndex = state.partIndex + ""
-			nextTick(() => renderForMoveData())
+			// 速度标记元素和谱面并非同时渲染,初始化可移动元素的时候,需要加个延迟
+			setTimeout(() => {
+				renderForMoveData()
+			}, 0);
 			} catch (error) {}
 		});
 		return () => (