Browse Source

评测记录

liushengqiang 1 year ago
parent
commit
449d86d227

+ 2 - 0
src/helpers/formateMusic.ts

@@ -30,6 +30,8 @@ export const getFixTime = (speed: number) => {
 	if (state.repeatedBeats) {
 		// 音频制作问题仅2拍不重复
 		numerator = numerator === 2 ? 4 : numerator;
+	} else if (numerator === 2 && denominator === 4) {
+		numerator = 4
 	}
 	// console.log('diff', speed, duration, formatBeatUnit(beatUnit), denominator, numerator, (numerator / denominator))
 	return state.isOpenMetronome ? (60 / speed) * formatBeatUnit(beatUnit) * (numerator / denominator) : 0;

+ 11 - 4
src/page-instrument/api.ts

@@ -10,9 +10,9 @@ export const sysMusicScoreAccompanimentQueryPage = (sysMusicScoreId: string) =>
 	return request.get("/musicSheet/detail/" + sysMusicScoreId);
 };
 
-/** 记录训练时长 */
+/** 新增练习记录(包含评测) */
 export const api_musicPracticeRecordSave = (data: any) => {
-	return request.post("/musicPracticeRecord/save", {requestType: 'json', data });
+	return request.post("/musicPracticeRecord/save", { requestType: "json", data });
 };
 /** 添加作业记录 */
 export const api_lessonTrainingSubmitTraining = (data: any) => {
@@ -22,6 +22,13 @@ export const api_lessonTrainingSubmitTraining = (data: any) => {
 export const api_lessonTrainingTrainingStudentDetail = (id: any) => {
 	return request.get(`/lessonTraining/trainingContentStudentDetail?id=${id}`);
 };
+/** 上传评测视频 */
+export const api_musicPracticeRecordVideoUpload = (data: any) => {
+	return request.post(`/musicPracticeRecord/videoUpload`, {
+		data,
+		requestType: "json",
+	});
+};
 
 /** 提交意见反馈 */
 export const sysSuggestionAdd = (data: any) => {
@@ -29,6 +36,6 @@ export const sysSuggestionAdd = (data: any) => {
 };
 
 /** 获取评测报告 */
-export const musicPracticeRecordGetLastEvaluationMusicalNotesPlayStats = (recordId: string) => {
-	return request.get("/sysMusicRecord/getLastEvaluationMusicalNotesPlayStats", { params: { recordId } });
+export const api_musicPracticeRecordDetail = (recordId: string) => {
+	return request.get("/musicPracticeRecord/detail/" + recordId);
 };

+ 2 - 0
src/page-instrument/custom-plugins/recording-time/index.tsx

@@ -3,6 +3,7 @@ import state from "/src/state";
 import { api_musicPracticeRecordSave } from "../../api";
 import { browser, getBehaviorId, getCampId } from "/src/utils";
 import { getQuery } from "/src/utils/queryString";
+import { storeData } from "/src/store";
 
 const recordData = reactive({
 	starTime: 0,
@@ -16,6 +17,7 @@ const handleRecord = () => {
 	const totalTime = total / 1000;
 
 	const body = {
+		clientType: storeData.user.clientType,
 		musicSheetId: state.examSongId,
 		sysMusicScoreId: state.examSongId,
 		feature: "PRACTICE",

+ 5 - 0
src/page-instrument/evaluat-model/evaluat-result/index.module.less

@@ -223,4 +223,9 @@
             font-size: 28px;
         }
     }
+}
+
+.disablued{
+    pointer-events: none;
+    opacity: .5;
 }

+ 93 - 19
src/page-instrument/evaluat-model/evaluat-result/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent } from "vue";
+import { defineComponent, onMounted, reactive, watch } from "vue";
 import styles from "./index.module.less";
 import state from "/src/state";
 import icon1 from "../icons/1.png";
@@ -7,18 +7,64 @@ import { evaluatingData } from "/src/view/evaluating";
 import icons from "../icons/index.json";
 import imgs from "./index.json";
 import iconBadge from "./img/icon-badge.svg";
-import icon_expression0 from './img/icon_expression0.svg'
-import icon_expression1 from './img/icon_expression1.svg'
-import icon_expression2 from './img/icon_expression2.svg'
-import icon_expression3 from './img/icon_expression3.svg'
-import icon_expression4 from './img/icon_expression4.svg'
+import icon_expression0 from "./img/icon_expression0.svg";
+import icon_expression1 from "./img/icon_expression1.svg";
+import icon_expression2 from "./img/icon_expression2.svg";
+import icon_expression3 from "./img/icon_expression3.svg";
+import icon_expression4 from "./img/icon_expression4.svg";
 import { getQuery } from "/src/utils/queryString";
+import { browser, getBehaviorId } from "/src/utils";
+import { api_musicPracticeRecordSave } from "../../api";
+import { getAudioDuration } from "/src/view/audio-list";
 
 export default defineComponent({
 	name: "evaluatResult",
 	emits: ["close"],
 	setup(props, { emit }) {
 		const query = getQuery();
+		const data = reactive({
+			saveLoading: true,
+		});
+
+		/** 添加评测记录 */
+		const handleAddRecord = async () => {
+			// console.log("结束", evaluatingData.resultData);
+			const body = {
+				deviceType: browser().android ? "ANDROID" : "IOS", // 设备类型
+				intonation: evaluatingData.resultData.intonation, // 音准
+				cadence: evaluatingData.resultData.cadence, // 节奏
+				integrity: evaluatingData.resultData.integrity, // 完成度
+				scoreData: JSON.stringify(evaluatingData.resultData.scoreData), // 评测数据
+				behaviorId: getBehaviorId(), // 行为id
+				sourceTime: getAudioDuration(), // 音频时长
+				partIndex: state.partIndex, // 音轨
+				speed: state.speed, // 速度
+				practiceSource: query.workRecord ? "LESSON_TRAINING" : "EVALUATION", // 练习来源
+				score: evaluatingData.resultData.score, // 分数
+				clientType: storeData.user.clientType, // 客户端类型
+				musicSheetId: state.examSongId, // 乐谱id
+				feature: "EVALUATION", // 特征
+				playTime: evaluatingData.resultData.playTime, // 播放时长
+				heardLevel: state.setting.evaluationDifficulty, // 听力等级
+				recordFilePath: evaluatingData.resultData.url, // 录音文件路径
+			};
+			data.saveLoading = true;
+			const res = await api_musicPracticeRecordSave(body);
+			if (res?.code === 200){
+				evaluatingData.resultData.recordId = res.data
+			}
+			data.saveLoading = false;
+		};
+
+		onMounted(() => {
+			handleAddRecord();
+		});
+
+		watch(() => evaluatingData.resulstMode, (val) => {
+			if (val) {
+				handleAddRecord();
+			}
+		})
 		return () => (
 			<div class={styles.evaluatResult}>
 				<div class={styles.closeBtn} onClick={() => emit("close")}>
@@ -26,29 +72,48 @@ export default defineComponent({
 				</div>
 
 				<div class={styles.headerButton}>
-					<div class={styles.headBtn} onClick={() => emit("close", "update")}>
+					<div class={[styles.headBtn, evaluatingData.resultData.recordId ? '' : styles.disabled]} onClick={() => emit("close", "update")}>
 						保存演奏
 					</div>
-					{/* <div class={styles.headBtn} style={{ display: storeData.platformType === "STUDENT" ? "block" : "", opacity: 0, pointerEvents: 'none' }} onClick={() => emit("close", "share")}>
-						分享
-					</div> */}
 				</div>
 
 				<div class={styles.fraction}>
 					<img class={styles.bg} src={imgs.bg} />
 					<div class={styles.top}>
-						{evaluatingData.resultData.score > 79 && <img style={{}} class={styles.badge} src={iconBadge} />}
+						{evaluatingData.resultData.score > 79 && (
+							<img style={{}} class={styles.badge} src={iconBadge} />
+						)}
 						<div class={styles.text}>
 							<div>
 								<span class={styles.num}>{evaluatingData.resultData.score}</span>分
 							</div>
-							<div style={{marginLeft: '6px'}}>{evaluatingData.resultData.clxmome}</div>
+							<div style={{ marginLeft: "6px" }}>{evaluatingData.resultData.clxmome}</div>
 						</div>
-						<img style={{display: evaluatingData.resultData.leve === 0 ? '' : 'none'}} class={styles.rightBadge} src={icon_expression0} />
-						<img style={{display: evaluatingData.resultData.leve === 1 ? '' : 'none'}} class={styles.rightBadge} src={icon_expression1} />
-						<img style={{display: evaluatingData.resultData.leve === 2 ? '' : 'none'}} class={styles.rightBadge} src={icon_expression2} />
-						<img style={{display: evaluatingData.resultData.leve === 3 ? '' : 'none'}} class={styles.rightBadge} src={icon_expression3} />
-						<img style={{display: evaluatingData.resultData.leve === 4 ? '' : 'none'}} class={styles.rightBadge} src={icon_expression4} />
+						<img
+							style={{ display: evaluatingData.resultData.leve === 0 ? "" : "none" }}
+							class={styles.rightBadge}
+							src={icon_expression0}
+						/>
+						<img
+							style={{ display: evaluatingData.resultData.leve === 1 ? "" : "none" }}
+							class={styles.rightBadge}
+							src={icon_expression1}
+						/>
+						<img
+							style={{ display: evaluatingData.resultData.leve === 2 ? "" : "none" }}
+							class={styles.rightBadge}
+							src={icon_expression2}
+						/>
+						<img
+							style={{ display: evaluatingData.resultData.leve === 3 ? "" : "none" }}
+							class={styles.rightBadge}
+							src={icon_expression3}
+						/>
+						<img
+							style={{ display: evaluatingData.resultData.leve === 4 ? "" : "none" }}
+							class={styles.rightBadge}
+							src={icon_expression4}
+						/>
 					</div>
 					{state.isPercussion ? (
 						<div class={styles.percussion}>
@@ -77,9 +142,18 @@ export default defineComponent({
 
 					<div class={styles.tips}>{evaluatingData.resultData.clxtip}</div>
 					<div class={styles.ctrls}>
-						<img style={{display: query.workRecord ? 'none' : ''}} src={imgs.btn1} class={styles.ctrlsBtn} onClick={() => emit("close", "practise")} />
+						<img
+							style={{ display: query.workRecord ? "none" : "" }}
+							src={imgs.btn1}
+							class={styles.ctrlsBtn}
+							onClick={() => emit("close", "practise")}
+						/>
 						<img src={imgs.btn2} class={styles.ctrlsBtn} onClick={() => emit("close", "tryagain")} />
-						<img src={imgs.btn3} class={styles.ctrlsBtn} onClick={() => emit("close", "look")} />
+						<img
+							src={imgs.btn3}
+							class={[styles.ctrlsBtn, data.saveLoading ? styles.disablued : ""]}
+							onClick={() => emit("close", "look")}
+						/>
 					</div>
 				</div>
 			</div>

+ 7 - 15
src/page-instrument/evaluat-model/index.tsx

@@ -26,6 +26,7 @@ import startData from "./data/start.json";
 import startingData from "./data/starting.json";
 import iconTastBg from "./icons/task-bg.svg";
 import iconEvaluat from "./icons/evaluating.json";
+import { api_musicPracticeRecordVideoUpload } from "../api";
 
 // frequency 频率, amplitude 振幅, decibels 分贝
 type TCriteria = "frequency" | "amplitude" | "decibels";
@@ -163,7 +164,7 @@ export default defineComponent({
 				return;
 			} else if (type === "look") {
 				// 跳转
-				handleViewReport("recordIdStr", "instrument");
+				handleViewReport("recordId", "instrument");
 				return;
 			} else if (type === "practise") {
 				// 去练习
@@ -187,8 +188,8 @@ export default defineComponent({
 					if (res) {
 						if (res?.content?.type === "success") {
 							handleSaveResult({
-								recordId: evaluatingData.resultData?.recordId,
-								filePath: res?.content?.filePath,
+								id: evaluatingData.resultData?.recordId,
+								videoFilePath: res?.content?.filePath,
 							});
 						} else if (res?.content?.type === "error") {
 							showToast({
@@ -200,19 +201,10 @@ export default defineComponent({
 				return;
 			}
 			evaluatModel.evaluatUpdateAudio = false;
-			handleSaveResult({
-				recordId: evaluatingData.resultData?.recordId,
-			});
+			showToast("上传成功");
 		};
-		const handleSaveResult = (_body: any) => {
-			api_proxyServiceMessage({
-				header: {
-					commond: "videoUpload",
-					status: 200,
-					type: "SOUND_COMPARE",
-				},
-				body: _body,
-			});
+		const handleSaveResult = async (_body: any) => {
+			await api_musicPracticeRecordVideoUpload(_body);
 			showToast("上传成功");
 		};
 

+ 2 - 1
src/page-instrument/view-detail/index.tsx

@@ -116,7 +116,8 @@ export default defineComponent({
 				}
 			}
 			state.isOpenMetronome = data.mp3Type === "MP3_METRONOME" ? true : false;
-			state.needTick = true; // data.isOpenMetronome;
+			// 曲子包含节拍器,就不开启节拍器
+			state.needTick = data.mp3Type === "MP3_METRONOME" ? false : true;
 			state.isShowFingering = data.showFingering ? true : false;
 			state.music = data.music;
 			state.accompany = data.accompany;

+ 20 - 19
src/page-instrument/view-evaluat-report/index.tsx

@@ -1,12 +1,5 @@
 import { Skeleton } from "vant";
-import {
-	defineComponent,
-	onBeforeMount,
-	onBeforeUnmount,
-	onMounted,
-	reactive,
-	Transition,
-} from "vue";
+import { defineComponent, onBeforeMount, onBeforeUnmount, onMounted, reactive, Transition } from "vue";
 import { formateTimes } from "../../helpers/formateMusic";
 import state, { isRhythmicExercises } from "../../state";
 import { setGlobalData } from "../../utils";
@@ -19,10 +12,7 @@ import {
 } from "/src/helpers/communication";
 import { getQuery } from "/src/utils/queryString";
 import { mappingVoicePart, subjectFingering } from "/src/view/fingering/fingering-config";
-import {
-	musicPracticeRecordGetLastEvaluationMusicalNotesPlayStats,
-	sysMusicScoreAccompanimentQueryPage,
-} from "../api";
+import { api_musicPracticeRecordDetail, sysMusicScoreAccompanimentQueryPage } from "../api";
 import ShareTop from "./component/share-top";
 import { addMeasureScore } from "/src/view/evaluating";
 
@@ -39,7 +29,7 @@ export default defineComponent({
 	name: "music-list",
 	setup() {
 		const query: any = getQuery();
-		const scoreData: any = reactive({
+		const scoreData = reactive({
 			videoFilePath: "", // 回放视频路径
 			cadence: 0,
 			integrity: 0,
@@ -139,15 +129,26 @@ export default defineComponent({
 		};
 
 		onMounted(async () => {
-			const res = await musicPracticeRecordGetLastEvaluationMusicalNotesPlayStats(query.id);
+			const res = await api_musicPracticeRecordDetail(query.id);
 			state.partIndex = Number(res?.data?.partIndex);
-			detailData.musicalNotesPlayStats = res?.data?.musicalNotesPlayStats?.notesData || [];
-			detailData.userMeasureScore = res?.data?.userMeasureScore || {};
-			for (let key in scoreData) {
-				scoreData[key] = res?.data?.[key];
+			let resultData = {} as any;
+			try {
+				resultData = JSON.parse(res?.data?.scoreData);
+			} catch (error) {
+				console.error("解析评测结果:", error);
 			}
+			// console.log("🚀 ~ resultData:", resultData);
+			detailData.musicalNotesPlayStats = resultData.musicalNotesPlayStats?.notesData || [];
+			detailData.userMeasureScore = resultData.userMeasureScore || {};
+
+			scoreData.heardLevel = res.data?.heardLevel;
+			scoreData.cadence = res.data?.cadence;
+			scoreData.integrity = res.data?.integrity;
+			scoreData.intonation = res.data?.intonation;
+			scoreData.score = res.data?.score;
+			scoreData.videoFilePath = res.data?.videoFilePath || res.data?.recordFilePath;
 			Promise.all([
-				sysMusicScoreAccompanimentQueryPage(res?.data?.musicalNotesPlayStats?.examSongId),
+				sysMusicScoreAccompanimentQueryPage(resultData.musicalNotesPlayStats?.examSongId),
 			]).then((values) => {
 				getMusicInfo(values[0]);
 			});

+ 16 - 15
src/store.ts

@@ -1,20 +1,21 @@
 import { reactive } from "vue";
 
 type IUser = {
-	username?: string
-	nickname?: string
+	username?: string;
+	nickname?: string;
 	/** 真实姓名 */
-	realName?: string
-    /** 会员结束时间 */
-    membershipEndTime?: string
-	tenantId?: number
+	realName?: string;
+	/** 会员结束时间 */
+	membershipEndTime?: string;
+	tenantId?: number;
 	/** 声部名称 */
-	subjectNames?: string
-	subjectName?: string
+	subjectNames?: string;
+	subjectName?: string;
 	/** 头像 */
-	avatar?: string
-	memberRankSettingId?: number
-	id?: string | number
+	avatar?: string;
+	memberRankSettingId?: number;
+	id?: string | number;
+	clientType?: "BACKEND" | "SCHOOL" | "TEACHER" | "STUDENT";
 };
 type IStatus = "init" | "login" | "logout" | "error";
 type IPlatformType = "STUDENT" | "TEACHER" | "WEB" | "";
@@ -22,10 +23,10 @@ type IPlatformApi = "/api-student" | "/api-teacher" | "/api-web" | "/api-backend
 type IProxy = "" | "/gym" | "/colexiu" | "/orchestra" | "/instrument";
 
 export interface IStoreData {
-	platformType: IPlatformType
-	platformApi: IPlatformApi
-	proxy: IProxy
-	isApp: boolean
+	platformType: IPlatformType;
+	platformApi: IPlatformApi;
+	proxy: IProxy;
+	isApp: boolean;
 }
 
 export const storeData = reactive({

+ 1 - 0
src/view/evaluating/index.tsx

@@ -102,6 +102,7 @@ export const sendEvaluatingOffsetTime = async (currentTime: number) => {
 		},
 		body: {
 			offsetTime: delayTime < 0 ? 0 : delayTime,
+			micDelay: 0,
 		},
 	});
 };

+ 2 - 2
vite.config.ts

@@ -73,8 +73,8 @@ export default defineConfig({
 				rewrite: (path) => path.replace(/^\/orchestra/, ""),
 			},
 			"^/instrument/.*": {
-				// target: "https://dev.kt.colexiu.com",
-				target: "https://test.lexiaoya.cn",
+				target: "https://dev.kt.colexiu.com",
+				// target: "https://test.lexiaoya.cn",
 				changeOrigin: true,
 				rewrite: (path) => path.replace(/^\/instrument/, ""),
 			},