ソースを参照

酷乐秀统计+意见反馈

liushengqiang 1 年間 前
コミット
1e79a43911
30 ファイル変更886 行追加26 行削除
  1. 9 0
      src/helpers/communication.ts
  2. 5 0
      src/page-colexiu/api.ts
  3. 12 0
      src/page-colexiu/custom-plugins/helper-model/icons/close2.svg
  4. BIN
      src/page-colexiu/custom-plugins/helper-model/icons/icon-right-back.png
  5. BIN
      src/page-colexiu/custom-plugins/helper-model/icons/icon-right.png
  6. 19 0
      src/page-colexiu/custom-plugins/helper-model/index.module.less
  7. 48 0
      src/page-colexiu/custom-plugins/helper-model/index.tsx
  8. 83 0
      src/page-colexiu/custom-plugins/helper-model/recommendation/index.module.less
  9. 79 0
      src/page-colexiu/custom-plugins/helper-model/recommendation/index.tsx
  10. 61 0
      src/page-colexiu/custom-plugins/helper-model/screen-model/index.module.less
  11. 26 0
      src/page-colexiu/custom-plugins/helper-model/screen-model/index.tsx
  12. 12 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/icons/close2.svg
  13. BIN
      src/page-colexiu/custom-plugins/recording-time/helper-model/icons/icon-right-back.png
  14. BIN
      src/page-colexiu/custom-plugins/recording-time/helper-model/icons/icon-right.png
  15. 19 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/index.module.less
  16. 48 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/index.tsx
  17. 83 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/recommendation/index.module.less
  18. 79 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/recommendation/index.tsx
  19. 61 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/screen-model/index.module.less
  20. 29 0
      src/page-colexiu/custom-plugins/recording-time/helper-model/screen-model/index.tsx
  21. 2 1
      src/page-colexiu/detail/index.tsx
  22. 3 4
      src/page-colexiu/header-top/index.tsx
  23. 32 4
      src/page-colexiu/header-top/settting/index.tsx
  24. 2 2
      src/page-orchestra/App.tsx
  25. 3 9
      src/page-orchestra/api.ts
  26. 51 0
      src/page-orchestra/custom-plugins/recording-time/index.tsx
  27. 113 0
      src/page-orchestra/custom-plugins/unitTest/index.tsx
  28. 0 4
      src/page-orchestra/detail/index.module.less
  29. 5 2
      src/page-orchestra/detail/index.tsx
  30. 2 0
      src/page-orchestra/header-top/index.tsx

+ 9 - 0
src/helpers/communication.ts

@@ -150,3 +150,12 @@ export const api_cloudDestroy = () =>{
 		api: 'cloudDestroy'
 	})
 }
+/** 事件埋点统计: 酷乐秀用,其它没有用 */
+export const api_setEventTracking = () =>{
+	postMessage({
+		api: 'setEventTracking',
+		content: {
+			type: 'klx_xiaokuAI'
+		}
+	})
+}

+ 5 - 0
src/page-colexiu/api.ts

@@ -22,3 +22,8 @@ export const sysMusicScoreAccompanimentQueryPage = (sysMusicScoreId: string) =>
 export const sysMusicRecordAdd = (data: any) => {
 	return request.post("/sysMusicRecord/add", { data });
 };
+
+/** 提交意见反馈 */
+export const sysSuggestionAdd = (data: any) => {
+	return request.post("/sysSuggestion/add", { data });
+};

+ 12 - 0
src/page-colexiu/custom-plugins/helper-model/icons/close2.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-493.000000, -57.000000)" fill="#01C1B5" fill-rule="nonzero">
+            <g transform="translate(143.000000, 40.000000)">
+                <g transform="translate(350.000000, 17.000000)">
+                    <path d="M1.05548433,1.07092551 L0.973890205,1.16134969 C0.666012474,1.54828454 0.694001358,2.11481906 1.05785686,2.46909941 L6.537,7.949 L1.05548433,13.4315873 C0.672191496,13.8148801 0.672191496,14.4441272 1.05548433,14.8274201 L1.08636669,14.8583024 L1.17896103,14.9399506 C1.56501142,15.2393273 2.12839072,15.2121112 2.48219949,14.8583024 L7.964,9.376 L13.4470285,14.8583024 C13.8303213,15.2415953 14.4595684,15.2415953 14.8428613,14.8583024 L14.8737436,14.8274201 L14.9553918,14.7348257 C15.2547684,14.3487753 15.2275524,13.785396 14.8737436,13.4315873 L9.391,7.949 L14.8737436,2.46675831 C15.2570364,2.08346548 15.2570364,1.45421835 14.8737436,1.07092551 L14.8428613,1.04004316 L14.7502669,0.958394979 C14.3642165,0.659018325 13.8008372,0.686234384 13.4470285,1.04004316 L7.964,6.53 L2.48231995,1.0401637 C2.09890665,0.65675032 1.46965952,0.65675032 1.08636669,1.04004316 L1.05548433,1.07092551 Z" id="路径"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/page-colexiu/custom-plugins/helper-model/icons/icon-right-back.png


BIN
src/page-colexiu/custom-plugins/helper-model/icons/icon-right.png


+ 19 - 0
src/page-colexiu/custom-plugins/helper-model/index.module.less

@@ -0,0 +1,19 @@
+.helperModel{
+    position: fixed;
+    right: 0;
+    top: 50%;
+    width: 26px;
+    height: 50px;
+    margin-top: -25px;
+    & > img {
+        display: block;
+        width: 100%;
+        height: 100%;
+        &:active{
+            opacity: .8;
+        }
+    }
+}
+.screen{
+    overflow: initial;
+}

+ 48 - 0
src/page-colexiu/custom-plugins/helper-model/index.tsx

@@ -0,0 +1,48 @@
+import { defineComponent, reactive } from "vue";
+import styles from "./index.module.less";
+import iconRight from "./icons/icon-right.png";
+import { Popup } from "vant";
+import ScreenModel from "./screen-model";
+import Recommendation from "./recommendation";
+
+export default defineComponent({
+	name: "helper-model",
+	setup() {
+		const helperData = reactive({
+			show: false,
+			recommendationShow: false, // 建议
+		});
+		return () => (
+			<>
+				<div class={styles.helperModel} onClick={() => (helperData.show = true)}>
+					<img id="tips-step-0" src={iconRight} />
+				</div>
+				<Popup
+					class={["popup-custom", styles.screen]}
+					v-model:show={helperData.show}
+					onClose={() => {
+						helperData.show = false;
+					}}
+					position="right"
+				>
+					<ScreenModel
+						onClose={(open: Boolean) => {
+							if (open) {
+								helperData.recommendationShow = true;
+							} else {
+								helperData.show = false;
+							}
+						}}
+					/>
+				</Popup>
+				<Popup v-model:show={helperData.recommendationShow} class="popup-custom van-scale" transition="van-scale">
+					<Recommendation
+						onClose={() => {
+							helperData.recommendationShow = false;
+						}}
+					/>
+				</Popup>
+			</>
+		);
+	},
+});

+ 83 - 0
src/page-colexiu/custom-plugins/helper-model/recommendation/index.module.less

@@ -0,0 +1,83 @@
+
+.closeBtn {
+    position: absolute;
+    right: -34px;
+    top: -6px;
+    width: 24px;
+    height: 24px;
+    border-radius: 50%;
+    background-color: #fff;
+    overflow: hidden;
+    padding: 6px;
+
+    img {
+        width: 100%;
+        height: 100%;
+        display: block;
+    }
+
+    &:active {
+        opacity: .8;
+    }
+}
+
+.content {
+    position: relative;
+    border-radius: 8px;
+    width: 300px;
+    // height: 90vh;
+    max-height: 370px;
+    background-color: #fff;
+    --van-tabs-line-height: 42px;
+    overflow: hidden;
+    :global {
+        .van-tabs__wrap{
+            border-bottom: 1Px solid #F0F0F0;
+        }
+        .van-tabs__content {
+            max-height: calc(90vh - var(--van-tabs-line-height));
+            overflow-y: auto;
+
+            &::-webkit-scrollbar {
+                width: 0;
+                display: none;
+            }
+        }
+        .van-field{
+            font-size: 12Px;
+            line-height: 16Px;
+        }
+    }
+}
+
+.tags {
+    display: flex;
+    justify-content: space-between;
+    text-align: center;
+    flex-wrap: wrap;
+    padding: 0 var(--van-cell-horizontal-padding) var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
+    >span {
+        margin: 3px 0;
+        border-radius: 3PX;
+        display: block;
+        width: 30%;
+        font-size: 12PX;
+        padding: 6PX 0;
+        background-color: #F8F8F8;
+        color: #999999;
+        border: 1PX solid #F8F8F8;
+
+        &.active {
+            color: var(--van-primary-color);
+            border-color: var(--van-primary-color);
+            background-color: #E2FFF9;
+            pointer-events: none;
+        }
+    }
+}
+.btn{
+    width: 80%;
+    height: 30px;
+    font-size: 13px;
+    margin: 0 auto;
+}

+ 79 - 0
src/page-colexiu/custom-plugins/helper-model/recommendation/index.tsx

@@ -0,0 +1,79 @@
+import { defineComponent, reactive, ref } from "vue";
+import styles from "./index.module.less";
+import iconClose from "../icons/close2.svg";
+import { Button, Cell, Field, Tab, Tabs, showToast } from "vant";
+import { sysSuggestionAdd } from "/src/page-colexiu/api";
+
+export default defineComponent({
+	name: "recommendation",
+	emits: ["close"],
+	setup(props, { emit }) {
+		const tags = ["识别不准", "无法评测", "不出评测结果", "曲谱不一致", "指法错误", "其他"];
+		const recommenData = reactive({
+			loading: false,
+			active: "识别不准",
+			message: "",
+		});
+
+		/** 提交意见反馈 */
+		const handleSubmit = async () => {
+			if (!recommenData.message) {
+				showToast({
+					message: "请先填写意见反馈",
+					position: "top",
+				});
+				return;
+			}
+			recommenData.loading = true;
+			try {
+				await sysSuggestionAdd({
+					content: recommenData.message + "#" + recommenData.active,
+					type: "SMART_PRACTICE",
+				});
+				showToast({
+					message: "意见反馈已提交",
+					position: "top",
+				});
+                emit('close')
+			} catch (error) {}
+			recommenData.loading = false;
+		};
+
+		return () => (
+			<>
+				<div class={styles.closeBtn} onClick={() => emit("close")}>
+					<img src={iconClose} />
+				</div>
+				<div class={styles.content}>
+					<Tabs lineHeight={0} color="#1A1A1A">
+						<Tab title="意见反馈">
+							<Cell border={false} title="请选择问题类型" />
+							<div class={styles.tags}>
+								{tags.map((text) => (
+									<span class={[styles.tag, recommenData.active === text && styles.active]} onClick={() => (recommenData.active = text)}>
+										{text}
+									</span>
+								))}
+							</div>
+							<Field
+								v-model={recommenData.message}
+								rows="3"
+								autosize={{ maxHeight: 128 }}
+								border={false}
+								type="textarea"
+								maxlength={200}
+								placeholder="请详细描述您遇到的问题,以便我们尽快为您解决!"
+								show-word-limit
+							/>
+							<Cell>
+								<Button loading={recommenData.loading} class={styles.btn} block round type="primary" onClick={handleSubmit}>
+									提交反馈
+								</Button>
+							</Cell>
+						</Tab>
+					</Tabs>
+				</div>
+			</>
+		);
+	},
+});

+ 61 - 0
src/page-colexiu/custom-plugins/helper-model/screen-model/index.module.less

@@ -0,0 +1,61 @@
+.container {
+    position: relative;
+    width: 40vw;
+    height: 100vh;
+    max-width: 295px;
+    background: #fff;
+    border-radius: 16px 0 0 16px;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar {
+        width: 0;
+        display: none;
+    }
+
+    :global {
+        .van-tabs__content {
+            height: calc(100vh - var(--van-tabs-line-height));
+        }
+
+        .van-tab__panel {
+            display: flex;
+            flex-direction: column;
+            width: 100%;
+            height: 100%;
+        }
+
+        iframe {
+            flex: 1;
+            width: 100%;
+            border: none;
+            margin: 0;
+
+            &::-webkit-scrollbar {
+                width: 0;
+                display: none;
+            }
+        }
+    }
+}
+
+.closeBtn {
+    position: absolute;
+    left: -30px;
+    top: 50%;
+    margin-top: -20px;
+    width: 30px;
+    height: 40px;
+}
+.jianyi{
+    flex-shrink: 0;
+    height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: var(--van-primary-color);
+    border-top: 1px solid #F0F0F0;
+    font-size: 13px;
+    &:active{
+        opacity: .8;
+    }
+}

+ 26 - 0
src/page-colexiu/custom-plugins/helper-model/screen-model/index.tsx

@@ -0,0 +1,26 @@
+import { defineComponent, ref } from "vue";
+import styles from "./index.module.less";
+import iconBack from "../icons/icon-right-back.png";
+import { Icon, ImagePreview, Tab, Tabs } from "vant";
+
+export default defineComponent({
+	name: "screenModel",
+	emits: ["close"],
+	setup(props, { emit }) {
+		return () => (
+			<>
+				<img class={styles.closeBtn} src={iconBack} onClick={() => emit("close")} />
+				<div class={styles.container}>
+					<Tabs swipeable animated>
+						<Tab name="投屏" title="投屏">
+							<iframe src="https://mteaonline.dayaedu.com/#/guide" />
+						</Tab>
+						<Tab name="帮助" title="帮助">
+							<iframe src="https://mstuonline.dayaedu.com/#/KeepRepaire?mode=accompany" />
+						</Tab>
+					</Tabs>
+				</div>
+			</>
+		);
+	},
+});

+ 12 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/icons/close2.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-493.000000, -57.000000)" fill="#01C1B5" fill-rule="nonzero">
+            <g transform="translate(143.000000, 40.000000)">
+                <g transform="translate(350.000000, 17.000000)">
+                    <path d="M1.05548433,1.07092551 L0.973890205,1.16134969 C0.666012474,1.54828454 0.694001358,2.11481906 1.05785686,2.46909941 L6.537,7.949 L1.05548433,13.4315873 C0.672191496,13.8148801 0.672191496,14.4441272 1.05548433,14.8274201 L1.08636669,14.8583024 L1.17896103,14.9399506 C1.56501142,15.2393273 2.12839072,15.2121112 2.48219949,14.8583024 L7.964,9.376 L13.4470285,14.8583024 C13.8303213,15.2415953 14.4595684,15.2415953 14.8428613,14.8583024 L14.8737436,14.8274201 L14.9553918,14.7348257 C15.2547684,14.3487753 15.2275524,13.785396 14.8737436,13.4315873 L9.391,7.949 L14.8737436,2.46675831 C15.2570364,2.08346548 15.2570364,1.45421835 14.8737436,1.07092551 L14.8428613,1.04004316 L14.7502669,0.958394979 C14.3642165,0.659018325 13.8008372,0.686234384 13.4470285,1.04004316 L7.964,6.53 L2.48231995,1.0401637 C2.09890665,0.65675032 1.46965952,0.65675032 1.08636669,1.04004316 L1.05548433,1.07092551 Z" id="路径"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/page-colexiu/custom-plugins/recording-time/helper-model/icons/icon-right-back.png


BIN
src/page-colexiu/custom-plugins/recording-time/helper-model/icons/icon-right.png


+ 19 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/index.module.less

@@ -0,0 +1,19 @@
+.helperModel{
+    position: fixed;
+    right: 0;
+    top: 50%;
+    width: 26px;
+    height: 50px;
+    margin-top: -25px;
+    & > img {
+        display: block;
+        width: 100%;
+        height: 100%;
+        &:active{
+            opacity: .8;
+        }
+    }
+}
+.screen{
+    overflow: initial;
+}

+ 48 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/index.tsx

@@ -0,0 +1,48 @@
+import { defineComponent, reactive } from "vue";
+import styles from "./index.module.less";
+import iconRight from "./icons/icon-right.png";
+import { Popup } from "vant";
+import ScreenModel from "./screen-model";
+import Recommendation from "./recommendation";
+
+export default defineComponent({
+	name: "helper-model",
+	setup() {
+		const helperData = reactive({
+			show: false,
+			recommendationShow: false, // 建议
+		});
+		return () => (
+			<>
+				<div class={styles.helperModel} onClick={() => (helperData.show = true)}>
+					<img id="tips-step-0" src={iconRight} />
+				</div>
+				<Popup
+					class={["popup-custom", styles.screen]}
+					v-model:show={helperData.show}
+					onClose={() => {
+						helperData.show = false;
+					}}
+					position="right"
+				>
+					<ScreenModel
+						onClose={(open: Boolean) => {
+							if (open) {
+								helperData.recommendationShow = true;
+							} else {
+								helperData.show = false;
+							}
+						}}
+					/>
+				</Popup>
+				<Popup v-model:show={helperData.recommendationShow} class="popup-custom van-scale" transition="van-scale">
+					<Recommendation
+						onClose={() => {
+							helperData.recommendationShow = false;
+						}}
+					/>
+				</Popup>
+			</>
+		);
+	},
+});

+ 83 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/recommendation/index.module.less

@@ -0,0 +1,83 @@
+
+.closeBtn {
+    position: absolute;
+    right: -34px;
+    top: -6px;
+    width: 24px;
+    height: 24px;
+    border-radius: 50%;
+    background-color: #fff;
+    overflow: hidden;
+    padding: 6px;
+
+    img {
+        width: 100%;
+        height: 100%;
+        display: block;
+    }
+
+    &:active {
+        opacity: .8;
+    }
+}
+
+.content {
+    position: relative;
+    border-radius: 8px;
+    width: 300px;
+    // height: 90vh;
+    max-height: 370px;
+    background-color: #fff;
+    --van-tabs-line-height: 42px;
+    overflow: hidden;
+    :global {
+        .van-tabs__wrap{
+            border-bottom: 1Px solid #F0F0F0;
+        }
+        .van-tabs__content {
+            max-height: calc(90vh - var(--van-tabs-line-height));
+            overflow-y: auto;
+
+            &::-webkit-scrollbar {
+                width: 0;
+                display: none;
+            }
+        }
+        .van-field{
+            font-size: 12Px;
+            line-height: 16Px;
+        }
+    }
+}
+
+.tags {
+    display: flex;
+    justify-content: space-between;
+    text-align: center;
+    flex-wrap: wrap;
+    padding: 0 var(--van-cell-horizontal-padding) var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
+    >span {
+        margin: 3px 0;
+        border-radius: 3PX;
+        display: block;
+        width: 30%;
+        font-size: 12PX;
+        padding: 6PX 0;
+        background-color: #F8F8F8;
+        color: #999999;
+        border: 1PX solid #F8F8F8;
+
+        &.active {
+            color: var(--van-primary-color);
+            border-color: var(--van-primary-color);
+            background-color: #E2FFF9;
+            pointer-events: none;
+        }
+    }
+}
+.btn{
+    width: 80%;
+    height: 30px;
+    font-size: 13px;
+    margin: 0 auto;
+}

+ 79 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/recommendation/index.tsx

@@ -0,0 +1,79 @@
+import { defineComponent, reactive, ref } from "vue";
+import styles from "./index.module.less";
+import iconClose from "../icons/close2.svg";
+import { Button, Cell, Field, Tab, Tabs, showToast } from "vant";
+import { suggestionAdd } from "../../api";
+
+export default defineComponent({
+	name: "recommendation",
+	emits: ["close"],
+	setup(props, { emit }) {
+		const tags = ["识别不准", "无法评测", "不出评测结果", "曲谱不一致", "指法错误", "其他"];
+		const recommenData = reactive({
+			loading: false,
+			active: "识别不准",
+			message: "",
+		});
+
+		/** 提交意见反馈 */
+		const handleSubmit = async () => {
+			if (!recommenData.message) {
+				showToast({
+					message: "请先填写意见反馈",
+					position: "top",
+				});
+				return;
+			}
+			recommenData.loading = true;
+			try {
+				await suggestionAdd({
+					content: recommenData.message + "#" + recommenData.active,
+					type: "SMART_PRACTICE",
+				});
+				showToast({
+					message: "意见反馈已提交",
+					position: "top",
+				});
+                emit('close')
+			} catch (error) {}
+			recommenData.loading = false;
+		};
+
+		return () => (
+			<>
+				<div class={styles.closeBtn} onClick={() => emit("close")}>
+					<img src={iconClose} />
+				</div>
+				<div class={styles.content}>
+					<Tabs lineHeight={0} color="#1A1A1A">
+						<Tab title="意见反馈">
+							<Cell border={false} title="请选择问题类型" />
+							<div class={styles.tags}>
+								{tags.map((text) => (
+									<span class={[styles.tag, recommenData.active === text && styles.active]} onClick={() => (recommenData.active = text)}>
+										{text}
+									</span>
+								))}
+							</div>
+							<Field
+								v-model={recommenData.message}
+								rows="3"
+								autosize={{ maxHeight: 128 }}
+								border={false}
+								type="textarea"
+								maxlength={200}
+								placeholder="请详细描述您遇到的问题,以便我们尽快为您解决!"
+								show-word-limit
+							/>
+							<Cell>
+								<Button loading={recommenData.loading} class={styles.btn} block round type="primary" onClick={handleSubmit}>
+									提交反馈
+								</Button>
+							</Cell>
+						</Tab>
+					</Tabs>
+				</div>
+			</>
+		);
+	},
+});

+ 61 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/screen-model/index.module.less

@@ -0,0 +1,61 @@
+.container {
+    position: relative;
+    width: 40vw;
+    height: 100vh;
+    max-width: 295px;
+    background: #fff;
+    border-radius: 16px 0 0 16px;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar {
+        width: 0;
+        display: none;
+    }
+
+    :global {
+        .van-tabs__content {
+            height: calc(100vh - var(--van-tabs-line-height));
+        }
+
+        .van-tab__panel {
+            display: flex;
+            flex-direction: column;
+            width: 100%;
+            height: 100%;
+        }
+
+        iframe {
+            flex: 1;
+            width: 100%;
+            border: none;
+            margin: 0;
+
+            &::-webkit-scrollbar {
+                width: 0;
+                display: none;
+            }
+        }
+    }
+}
+
+.closeBtn {
+    position: absolute;
+    left: -30px;
+    top: 50%;
+    margin-top: -20px;
+    width: 30px;
+    height: 40px;
+}
+.jianyi{
+    flex-shrink: 0;
+    height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: var(--van-primary-color);
+    border-top: 1px solid #F0F0F0;
+    font-size: 13px;
+    &:active{
+        opacity: .8;
+    }
+}

+ 29 - 0
src/page-colexiu/custom-plugins/recording-time/helper-model/screen-model/index.tsx

@@ -0,0 +1,29 @@
+import { defineComponent, ref } from "vue";
+import styles from "./index.module.less";
+import iconBack from "../icons/icon-right-back.png";
+import { Icon, ImagePreview, Tab, Tabs } from "vant";
+
+export default defineComponent({
+	name: "screenModel",
+	emits: ["close"],
+	setup(props, { emit }) {
+		return () => (
+			<>
+				<img class={styles.closeBtn} src={iconBack} onClick={() => emit("close")} />
+				<div class={styles.container}>
+					<Tabs swipeable animated>
+						<Tab name="投屏" title="投屏">
+							<iframe src="https://mteaonline.dayaedu.com/#/guide" />
+						</Tab>
+						<Tab name="帮助" title="帮助">
+							<iframe src="https://mstuonline.dayaedu.com/#/KeepRepaire?mode=accompany" />
+							<div class={styles.jianyi} onClick={() => emit('close', true)}>
+								意见反馈 <Icon name="arrow" />
+							</div>
+						</Tab>
+					</Tabs>
+				</div>
+			</>
+		);
+	},
+});

+ 2 - 1
src/page-colexiu/detail/index.tsx

@@ -12,7 +12,7 @@ import { sysMusicScoreAccompanimentQueryPage } from "../api";
 import EvaluatModel from "../evaluat-model";
 import HeaderTop from "../header-top";
 import styles from "./index.module.less";
-import { api_cloudLoading, api_openCamera, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
+import { api_cloudLoading, api_openCamera, api_setEventTracking, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
 import { getQuery } from "/src/utils/queryString";
 import Evaluating, { evaluatingData } from "/src/view/evaluating";
 import MeasureSpeed from "/src/view/plugins/measure-speed";
@@ -123,6 +123,7 @@ export default defineComponent({
 			Promise.all([sysMusicScoreAccompanimentQueryPage(query.id)]).then((values) => {
 				getMusicInfo(values[0]);
 			});
+			api_setEventTracking()
 		});
 
 		/** 渲染完成 */

+ 3 - 4
src/page-colexiu/header-top/index.tsx

@@ -5,11 +5,10 @@ import iconBack from "./image/icon-back.svg";
 import Title from "./title";
 import { headImg } from "./image";
 import icons from "./image/headerTop.json";
-import { Badge, Circle, Popover } from "vant";
+import { Badge, Circle, Popover, Popup } from "vant";
 import { metronomeData } from "../../helpers/metronome";
 import Speed from "./speed";
 import { evaluatingData, handleStartEvaluat } from "/src/view/evaluating";
-import { Popup } from "@varlet/ui";
 import Settting from "./settting";
 import ModeTypeMode from "./mode-type-mode";
 import state, { handleChangeSection, handleResetPlay, handleRessetState, togglePlay } from "/src/state";
@@ -207,10 +206,10 @@ export default defineComponent({
 					</div>
 				</div>
 
-				<Popup teleport="body" defaultStyle={false} v-model:show={headerData.settingMode}>
+				<Popup v-model:show={headerData.settingMode} class="popup-custom van-scale" transition="van-scale" teleport="body">
 					<Settting onClose={() => (headerData.settingMode = false)} />
 				</Popup>
-				<Popup teleport="body" position="bottom" closeOnClickOverlay={false} overlay={false} defaultStyle={false} v-model:show={headerData.modeMode}>
+				<Popup v-model:show={headerData.modeMode} teleport="body" class="popup-custom" position="bottom" closeOnClickOverlay={false} overlay={false}>
 					<ModeTypeMode onClose={(value) => handleChangeModeType(value)} />
 				</Popup>
 			</div>

+ 32 - 4
src/page-colexiu/header-top/settting/index.tsx

@@ -1,7 +1,7 @@
-import { defineComponent, onBeforeMount, watch } from "vue";
+import { defineComponent, reactive, watch } from "vue";
 import styles from "./index.module.less";
 import iconClose from "../image/close2.svg";
-import { Cell, Field, NoticeBar, Radio, RadioGroup, Slider, Switch, Tab, Tabs } from "vant";
+import { Cell, Field, NoticeBar, Popup, Radio, RadioGroup, Slider, Switch, Tab, Tabs } from "vant";
 import state from "/src/state";
 import { api_closeCamera, api_openCamera } from "/src/helpers/communication";
 import store from "store";
@@ -9,11 +9,17 @@ import iconInfo from "../image/info.svg";
 import iconDown from "../image/down.svg";
 import iconTv from "../image/tv.svg";
 import iconYijian from "../image/yijian.svg";
+import ScreenModel from "../../custom-plugins/helper-model/screen-model";
+import Recommendation from "../../custom-plugins/helper-model/recommendation";
 
 export default defineComponent({
 	name: "header-settting",
 	emits: ["close"],
 	setup(props, { emit }) {
+		const helperData = reactive({
+			show: false,
+			recommendationShow: false, // 建议
+		});
 		// 设置改变触发
 		watch(state.setting, () => {
 			store.set("musicscoresetting", state.setting);
@@ -37,11 +43,11 @@ export default defineComponent({
 									<img src={iconDown} />
 									下载曲谱
 								</div>
-								<div class={styles.btn}>
+								<div class={styles.btn} onClick={() => (helperData.show = true)}>
 									<img src={iconTv} />
 									投屏帮助
 								</div>
-								<div class={styles.btn}>
+								<div class={styles.btn} onClick={() => (helperData.recommendationShow = true)}>
 									<img src={iconYijian} />
 									意见反馈
 								</div>
@@ -129,6 +135,28 @@ export default defineComponent({
 						</Tab>
 					</Tabs>
 				</div>
+				<Popup
+					class={["popup-custom", styles.screen]}
+					v-model:show={helperData.show}
+					onClose={() => {
+						helperData.show = false;
+					}}
+					position="right"
+					teleport="body"
+				>
+					<ScreenModel
+						onClose={(open: Boolean) => {
+							helperData.show = false;
+						}}
+					/>
+				</Popup>
+				<Popup v-model:show={helperData.recommendationShow} class="popup-custom van-scale" transition="van-scale" teleport="body">
+					<Recommendation
+						onClose={() => {
+							helperData.recommendationShow = false;
+						}}
+					/>
+				</Popup>
 			</div>
 		);
 	},

+ 2 - 2
src/page-orchestra/App.tsx

@@ -3,7 +3,7 @@ import { computed, defineComponent, onBeforeMount, onMounted } from "vue";
 import { RouterView } from "vue-router";
 import TheError from "../components/The-error";
 import { setUserInfo, storeData } from "../store";
-import { getRandomKey, setToken } from "../utils";
+import { getRandomKey, setBehaviorId, setToken } from "../utils";
 import { getQuery } from "../utils/queryString";
 import Notfind from "../view/notfind";
 import { employeeQueryUserInfo, studentQueryUserInfo, teacherQueryUserInfo } from "./api";
@@ -34,7 +34,7 @@ export default defineComponent({
 				setToken(query.Authorization);
 			}
 			setUser();
-			localStorage.setItem("behaviorId", getRandomKey());
+			setBehaviorId(getRandomKey())
 		});
 		onMounted(() => {
 			document.getElementById("loading")!.className = "";

+ 3 - 9
src/page-orchestra/api.ts

@@ -18,13 +18,7 @@ export const sysMusicScoreAccompanimentQueryPage = (sysMusicScoreId: string) =>
 	return request.get("/musicSheet/detail/" + sysMusicScoreId);
 };
 
-/** 获取曲谱分类 */
-export const sysMusicScoreCategoriesQueryTree = (enable = false) => {
-	return request.get(`/sysMusicScoreCategories/queryTree`, {
-		params: {
-			parentId: 0,
-			// 后台详情忽略是否启用分类
-			enable,
-		},
-	});
+/** 记录训练时长 */
+export const musicPracticeRecordSave = (data: any) => {
+	return request.post("/musicPracticeRecord/save", { data });
 };

+ 51 - 0
src/page-orchestra/custom-plugins/recording-time/index.tsx

@@ -0,0 +1,51 @@
+import { defineComponent, onBeforeUnmount, onMounted, onUnmounted, reactive, ref, watch } from "vue";
+import state from "/src/state";
+import { musicPracticeRecordSave } from "../../api";
+import { browser, getBehaviorId } from "/src/utils";
+
+const recordData = reactive({
+	starTime: Date.now(),
+});
+const handleRecord = () => {
+	// 不是评测模式不记录
+	if (state.modeType === 'evaluating') return;
+	let total = Date.now() - recordData.starTime;
+	recordData.starTime = Date.now();
+	if (total < 0) total = 0;
+	const body = {
+		musicSheetId: state.examSongId,
+		sysMusicScoreId: state.examSongId,
+		feature: "PRACTICE",
+		practiceSource: state.modeType,
+		playTime: total / 1000,
+		deviceType: browser().android ? "ANDROID" : "IOS",
+		behaviorId: getBehaviorId()
+	};
+	musicPracticeRecordSave(body);
+};
+
+export const handleNoEndExit = () => {
+    if (state.playState === 'play') {
+        handleRecord()
+    }
+}
+
+/**
+ * 记录练习时长, 仅记录练习模式的时长
+ */
+export default defineComponent({
+	name: "recordingTime",
+	setup() {
+		const timer = ref()
+		onMounted(() => {
+			clearInterval(timer.value)
+			timer.value = setInterval(() => {
+				handleRecord()
+			}, 60 * 1000)
+		})
+		onBeforeUnmount(() => {
+			clearInterval(timer.value)
+		})
+		return () => <div></div>;
+	},
+});

+ 113 - 0
src/page-orchestra/custom-plugins/unitTest/index.tsx

@@ -0,0 +1,113 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useOriginSearch } from '../uses'
+import request from '/src/helpers/request'
+import SettingState from '/src/pages/detail/setting-state'
+import runtime, { changeSpeed } from '/src/pages/detail/runtime'
+import state from '/src/pages/detail/state'
+import { Toast } from 'vant'
+import { userInfo } from '../App'
+import { browser } from '/src/helpers/utils'
+
+interface IquestionExtendsInfo {
+  /** 评测难度 */
+  difficulty?: 'ONE' | 'TWO' | 'THREE' | '1' | '2' | '3' | ''
+  /** 开始小节 */
+  start?: string | number
+  /** 结束小节 */
+  end?: string | number
+  /** 速度 */
+  speed?: number
+}
+const difficultyData: { [_: string]: any } = {
+  ONE: 'BEGINNER',
+  TWO: 'ADVANCED',
+  THREE: 'PERFORMER',
+  '1': 'BEGINNER',
+  '2': 'ADVANCED',
+  '3': 'PERFORMER',
+}
+
+export const unitTestData = reactive({
+  /** 是否是选段模式 */
+  isSelectMeasureMode: false,
+})
+
+export default defineComponent({
+  name: 'unitTest',
+  setup() {
+    const questionExtendsInfo = ref<IquestionExtendsInfo>({difficulty: ''})
+    /**
+     * 如果是课后训练或者单元测验,获取数据
+     * @param xml 获取的xml数据
+     * @returns 格式化的xml数据
+     */
+    const getUnitData = async () => {
+      const search = useOriginSearch()
+      if (!search.questionId) return ''
+      
+      try {
+        const res: any = await request.get(`/examinationQuestion/detail?examinationQuestionId=${search.questionId}`)
+        questionExtendsInfo.value = JSON.parse(res?.data?.questionExtendsInfo) || {}
+        questionExtendsInfo.value.start = Number(questionExtendsInfo.value.start)
+        questionExtendsInfo.value.end = Number(questionExtendsInfo.value.end)
+      } catch (error) {
+        console.error('解析单元测验曲谱题目失败', error)
+      }
+      setSection()
+    }
+    
+    const getlessonTrainingData = async () => {
+      const search = useOriginSearch()
+      if (!search.lessonTrainingId) return
+      try {
+        const res: any = await request.post(`/studentLessonTraining/trainingRecord/${search.courseScheduleId}?userId=${userInfo.id}`)
+        if (Array.isArray(res?.data?.trainings)){
+          const train = res.data.trainings.find((n: any) => n.materialId == search.materialId)
+          const info = JSON.parse(train.trainingContent)
+          // console.log("🚀 ~ info", info)
+          questionExtendsInfo.value.start = Number(info.startSection)
+          questionExtendsInfo.value.end = Number(info.endSection)
+          questionExtendsInfo.value.speed = isNaN(info.speed) ? 0 : Number(info.speed)
+        }
+      } catch (error) {
+        console.error('解析课后训练曲谱题目失败', error)
+      }
+      setSection()
+    }
+
+    /**设置小节 */
+    const setSection = () => {
+      const startNotes = state.times.filter(
+        (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == questionExtendsInfo.value.start
+      )
+      const endNotes = state.times.filter(
+        (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == questionExtendsInfo.value.end
+      )
+      const startNote = startNotes[0]
+      const endNote = endNotes[endNotes.length - 1]
+      //   console.log('🚀 ~ activeNote', startNote, endNote, questionExtendsInfo.value.end)
+      if (startNote && endNote) {
+        unitTestData.isSelectMeasureMode = true
+        // 设置小节
+        state.sectionStatus = true
+        state.section = [startNote, endNote]
+        // 设置评测难度
+        if (difficultyData[questionExtendsInfo.value.difficulty!]) {
+          SettingState.eva.difficulty = difficultyData[questionExtendsInfo.value.difficulty!]
+        }
+        //设置速度
+        if (questionExtendsInfo.value.speed) {
+          changeSpeed(questionExtendsInfo.value.speed)
+        }
+      }
+    }
+    onMounted(() => {
+      const browserInfo = browser()
+      //如果不是学生端直接return
+      if (!browserInfo.isStudent) return
+      getUnitData()
+      getlessonTrainingData()
+    })
+    return () => ''
+  },
+})

+ 0 - 4
src/page-orchestra/detail/index.module.less

@@ -52,10 +52,6 @@
     }
 }
 
-.plugins {
-    display: none;
-}
-
 :global {
     #cursorImg-0 {
         min-height: 58PX;

+ 5 - 2
src/page-orchestra/detail/index.tsx

@@ -8,7 +8,7 @@ import { storeData } from "../../store";
 import { setGlobalData } from "../../utils";
 import AudioList from "../../view/audio-list";
 import MusicScore, { resetMusicScore } from "../../view/music-score";
-import { sysMusicScoreAccompanimentQueryPage, sysMusicScoreCategoriesQueryTree } from "../api";
+import { sysMusicScoreAccompanimentQueryPage } from "../api";
 import EvaluatModel from "../evaluat-model";
 import HeaderTop from "../header-top";
 import styles from "./index.module.less";
@@ -22,6 +22,7 @@ import store from "store";
 import Tick, { handleInitTick } from "/src/view/tick";
 import FollowPractice from "/src/view/follow-practice";
 import FollowModel from "../follow-model";
+import RecordingTime from "../custom-plugins/recording-time";
 
 export default defineComponent({
 	name: "music-list",
@@ -259,10 +260,12 @@ export default defineComponent({
 				)}
 
 				{/* 公用的插件 */}
-				<div class={styles.plugins}>
+				<div class="plugins-box">
 					{state.musicRendered && (
 						<>
 							<MeasureSpeed />
+							{/* 统计训练时长 */}
+							{storeData.platformType === 'STUDENT' && <RecordingTime />}
 						</>
 					)}
 				</div>

+ 2 - 0
src/page-orchestra/header-top/index.tsx

@@ -15,6 +15,7 @@ import { getAudioCurrentTime } from "/src/view/audio-list";
 import { toggleFollow } from "/src/view/follow-practice";
 import { api_back } from "/src/helpers/communication";
 import MusicType from "./music-type";
+import { handleNoEndExit } from "../custom-plugins/recording-time";
 
 export const headData = reactive({
 	speedShow: false,
@@ -66,6 +67,7 @@ export default defineComponent({
 
 		/** 返回 */
 		const handleBack = () => {
+			handleNoEndExit()
 			api_back();
 		};