feat: 动效初始化

dev-deviceSetting
donghao 1 year ago
parent 1c26b8d4ad
commit 426e7f789b

File diff suppressed because one or more lines are too long

@ -0,0 +1,3 @@
import AnimateDevice from "./src/AnimateDevice.vue";
export { AnimateDevice };

@ -0,0 +1,38 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-01-16 13:49:37
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-01-16 13:53:47
* @FilePath: \General-AI-Platform-Web-Client\src\components\Animate\src\animateDevice.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<script setup lang="ts">
import { ref, onMounted } from "vue"; //api
import lottie from "lottie-web"; //
import json01 from "@/assets/animate/device/01.json"; //json
defineOptions({
name: "AnimateDevice"
});
const animation1 = ref(null); //dom
function initAnimate() {
console.log(lottie, "lottie");
lottie.loadAnimation({
container: animation1.value, //dom
renderer: "svg", //
loop: true, //
autoplay: true, //i,
animationData: json01 //json
});
}
onMounted(() => {
initAnimate();
});
</script>
<template>
<div style="width: 100px">
<div ref="animation1" />
</div>
</template>

@ -0,0 +1,472 @@
<template>
<div class="courseCat_wrap courseLession_wrap" v-if="pageMode == 'courseCat'">
<van-sticky>
<div class="bg-white title_box">
<p class=" fs18 fw5 text_color title_centent flex-rb flex-ac">
{{ detailInfo.name }}
</p>
<p class="fs12 text-gray progress_box">
已学习<span class="theme_color">{{ totalLesson.learnedCount }}</span
>节/共{{ totalLesson.totalCount }}节
</p>
</div>
<HTab
ref="HTabComp"
class="bg-white"
:options="csTabOptions"
:tabList="tabOptions"
@change="menuChange"
>
</HTab>
</van-sticky>
<div class="catalogTree fs14" ref="CSCatTreeRefs">
<ul class="bg-white root_cat_box pdx15">
<li v-for="(v, index) in state.courseTreeData" :key="v.id">
<div
class="chapter_box flex-ac flex-rb"
v-if="v.type == 0"
@click="handleExpend(v)"
>
<span class="chapter_label fw5 single-line">
{{ v.name }}
</span>
<div class="collapseIcon_box flex-cc">
<div class="arrow" :class="v.expended ? '' : 'activeUp'"></div>
</div>
</div>
<div class="courseware_box" v-if="v.type == 2">
<!---->
<CSLessonsBox
:lessonDownProgress="lessonDownProgress"
:info="v"
:courseDetail="detailInfo"
:lessonStudyProgress="lessonStudyProgress"
/>
</div>
<div class="section_box flex-ac flex-rb" v-if="v.type == 1">
<span>{{ v.name }}</span>
</div>
<transition name="mybox">
<div class="box" v-show="v.expended">
<ul v-if="isArray(v.childrenList) && v.childrenList.length">
<li v-for="(item, index) in [...v.childrenList]" :key="item.id">
<CSLessonsBox
:info="item"
:courseDetail="detailInfo"
:lessonDownProgress="lessonDownProgress"
:lessonStudyProgress="lessonStudyProgress"
/>
<hr
class="mdy20"
v-if="index !== [...v.childrenList].length - 1"
/>
</li>
</ul>
<CSLessonPracticeBox
:info="v.coursePaperVOS"
:courseInfo="v"
:courseDetail="detailInfo"
v-if="
v.type == 0 &&
isArray(v.coursePaperVOS) &&
v.coursePaperVOS.length &&
v.coursePaperVOS != ['']
"
/>
</div>
</transition>
</li>
<li v-for="v in practiceList" :key="v.id" class="bg-white exam_box">
<p
class="fs14 pdy12"
v-if="v.type == 0 && detailInfo.joinStatus >= 1"
>
课后练习
</p>
<div
class="flex pdy12"
v-if="v.type == 0 && detailInfo.joinStatus >= 1"
>
<span class="flex-cc exam_tag fs10">
<span class="exam_tag_icon"></span>
<span class="fss10">练习</span>
</span>
<div class="exam_title">
<p class="paperName_label fw5 single-line">
{{ v.paperName }}
</p>
<div class="flex-rb exam_status fs12 theme_color">
<span class=" single-line" v-if="v.status == 0">
未开始
</span>
<span class=" single-line paper_name" v-if="v.status == 1">
<span v-if="v.pass">已通过</span>
<span v-else>未通过</span>
</span>
<span
:class="[`${fetchDetailCatBtn(v).statusType}_tags`]"
@click="doTabCoursePractice(v, {...detailInfo})"
>
{{ fetchDetailCatBtn(v).practiceLabel }}
</span>
</div>
</div>
</div>
</li>
</ul>
<HEmpty v-if="!state.courseTreeData.length && !isLoading" />
<div ref="CSDetailCommentRefs">
<CSDetailComment
ref="detailCommentRefs"
:info="detailInfo"
:courseConf="courseConf"
@tenantComment="doTenantComment"
/>
</div>
</div>
</div>
<!-- {{ pageMode }} -->
<div v-if="pageMode == 'courseDot'">
<CSLessonPoint
ref="CSLessonPointRefs"
:stepData="pointData"
@closeDot="openCat"
@dotTab="doDotTab"
/>
</div>
<!-- 评论 -->
<CComment
ref="commentRefs"
:ocInfo="courseConf"
:isNeedClose="true"
@successComment="successComment"
/>
</template>
<script setup>
import {
reactive,
nextTick,
computed,
ref,
onMounted,
onActivated,
getCurrentInstance,
} from "vue";
import { useRouter, useRoute } from "vue-router";
//
// 组件区
import {
CSLessonsBox,
CSLessonPracticeBox,
CSDetailComment,
CSLessonPoint,
} from "../components";
import { HTab, HEmpty } from "@/components/HVant/index";
import { CComment } from "@/components/common";
// hooks
import { clientChangeVideo, clientChangeVideoDot } from "@/hooks/web/useClientFc"
import { useWebFunc } from "@/hooks/web/useWebFunc";
import { useCourseCat } from "../hooks/useCourseCat";
import { useCourseDetail } from "../hooks/useCourseDetail";
import { useConf, useReqApi, useCourseMain } from "../hooks/useMain";
import {
fetchNextCoursewareNode,
fetchCurrentCourseNode,
} from "../hooks/format";
// 其它
import { isArray } from "@/utils/is/isString";
//数据
import { testFlag } from "../hooks/test";
import { testData } from "@/testData/courseDot";
const { appContext } = getCurrentInstance();
const route = useRoute();
const router = useRouter();
const {
getCourseCatalogApi,
getCourseInfoApi,
updateLastLearnCatalogIdCourseApi,
getCourseProgressApi,
} = useReqApi;
const { courseVideoDetailTabConf } = useConf;
const {
doTabExam,
clientCourseCatPracticeAlertView,
doLoadCoursePractice,
practiceList,
doTabCoursePractice
} = useCourseCat();
const { setWebFunc } = useWebFunc();
const { fetchDetailCatBtn } = useCourseDetail();
// 初始值
const lessonDownProgress = ref({}); // 课件下载进度
const courseRecordInfo = ref({
totalProgress: 0,
}); // 课程端数据汇总
const lessonStudyProgress = ref({}); // 课件学习进度
const totalLesson = ref({
learnedCount: 0,
totalCount: 0,
});
const state = reactive({
currMenuIndex: 0,
activeLesson: [],
courseListOptions: {
// isCustom: true
},
courseTreeData: [], //courseCatData
});
const detailInfo = ref({});
const pageMode = ref("courseCat");
const isLoading = ref(false);
const courseConf = reactive({
operationType: 0,
});
const csTabOptions = reactive({
defaultValue: 0,
});
const tabOptions = computed(() => {
return courseVideoDetailTabConf.filter((item) => {
if (item.id === "1") {
item.name = `评论(${detailInfo.value.commentCount || 0})`;
}
return item;
});
});
const pointData = ref([]); // 知识点
const currentCatData = ref({}); // 当前学习目录项
// 实例
const CSCatTreeRefs = ref("");
const CSDetailCommentRefs = ref("");
const commentRefs = ref("");
const detailCommentRefs = ref("");
const CSLessonPointRefs = ref("");
/**step1 目录、评论模块切换 */
function menuChange(record) {
state.currMenuIndex = record;
nextTick(() => {
switch (record) {
case "0":
window.scrollTo(
0,
0
);
break;
case "1":
// CSDetailCommentRefs.value.scrollIntoView({
// behavior: "smooth",
// });
window.scrollTo(
0,
CSDetailCommentRefs.value.offsetTop - CSCatTreeRefs.value.offsetTop
);
break;
}
});
}
/**step2 目录详情数据 评论数据 推荐课程数据*/
// 目录数据
async function loadCatData(nextCat, nextCatId) {
isLoading.value = true;
const res = await getCourseCatalogApi({ id: route.query.id }).finally(() => {
isLoading.value = false;
});
// console.log(res, "loadCatData");
if (isArray(res)) {
state.courseTreeData = res.filter((item) => {
item.expended = true;
return item;
});
if (nextCatId) {
// 下一课已经解锁 广播下一节点id
let params = {
...nextCat,
courseTrainId: route.query.id,
};
appContext.config.globalProperties.Bus.emit(
"changeCurrentCatId",
nextCatId
);
clientChangeVideo(params, (_) => {});
}
} else {
state.courseTreeData = [];
}
}
/**step3 目录交互完善 展开收起 对接端 */
//收起/展开
function handleExpend(record) {
record.expended = !record.expended;
}
/**step4 other */
// 关闭知识点-打开目录
function openKnowledge() {
if (testFlag.isOpenDot) {
pointData.value = testData;
openDot();
} else {
setWebFunc("webOpenKnowledge", (res) => {
pointData.value = JSON.parse(res);
console.info("pointData", pointData.value);
openDot();
});
}
}
// 从端获取下载进度
function fetchDownloadProgress() {
setWebFunc("changeDownloadProgress", (res) => {
lessonDownProgress.value = res;
});
}
// 从端获取学习进度
function fetchStudyProgress() {
setWebFunc("changeStudyProgress", (res) => {
// console.log(res, "changeStudyProgress");
lessonStudyProgress.value = res.studyCourseware;
courseRecordInfo.value = res;
// // 播报课程总进度
lessonStudyProgress.value.learningProgress>=1 && loadCourseProgress()
});
}
// 从接口获取学习进度 获取总进度
async function loadCourseProgress() {
const res = await getCourseProgressApi({ id: route.query.id });
// console.log(res, "loadCourseProgress_res");
totalLesson.value = res;
}
// 完成本节播放,后续处理 (练习提醒、下一节自动切换) completeStatus 练习完成状态
function webFnPlayNextVideo() {
setWebFunc("playNextVideo", (res) => {
const currentCat = fetchCurrentCourseNode(
state.courseTreeData,
res.catalogId
);
// 播报课程总进度
loadCourseProgress();
console.log("currentCat_coursePaperVOS", currentCat);
// 确认是练习提醒还是进入下一节
if (
currentCat.coursePaperVOS &&
(![1, 2].includes(currentCat.coursePaperVOS?.status) ||
![1].includes(currentCat?.completeStatus))
) {
// 课节练习提醒 练习提醒 课程状态处理 completeStatus 0 未完成 2 音视频完成 1 全部完成含练习
currentCatData.value = {
...currentCat,
coursePracticeType: "coursewarePractice",
};
clientCourseCatPracticeAlertView({}, () => {
const { coursePaperVOS } = currentCatData.value;
doTabExam(coursePaperVOS[0]);
});
} else {
// 直接跳下一节课
fetchNextCourse(res);
}
});
}
// 判断当前课是否有练习
function fetchNextCourse(res) {
// 弹下一课提醒
const nextCat = fetchNextCoursewareNode(state.courseTreeData, res.catalogId);
const nextCatId = nextCat.id;
//有下一课, 自动播放下一课,没有不处理
if (nextCatId != res.catalogId) {
updateChangeLessonApi(nextCatId);
loadCatData(nextCat, nextCatId);
} else {
// 广播下一节点id
// appContext.config.globalProperties.Bus.emit("changeCurrentCatId", nextCatId);
}
}
// 汇报上次学到的课
function updateChangeLessonApi(nextCatId) {
updateLastLearnCatalogIdCourseApi({
courseCatalogId: nextCatId,
resourceId: route.query.id,
});
}
// 打开目录
function openCat() {
pageMode.value = "courseCat";
}
// 打开知识点
function openDot() {
pageMode.value = "courseDot";
}
// 切换知识点
function doDotTab(params) {
clientChangeVideoDot(params, (res) => {
if (res == 1) {
CSLessonPointRefs.value.initChangePoint();
}
});
}
function loadDetail() {
nextTick(() => {
detailCommentRefs.value.initData();
});
if (isLoading.value) {
return;
}
doLoadData();
loadCatData();
doLoadCoursePractice();
loadCourseProgress();
}
// 详情数据
async function doLoadData() {
const res = await getCourseInfoApi({
id: Number(route.query.id),
});
detailInfo.value = res;
}
function loadWebFunc() {
openKnowledge();
fetchDownloadProgress();
fetchStudyProgress();
webFnPlayNextVideo();
}
// 打开评论
function doTenantComment() {
commentRefs.value.initPopup();
}
// 评论成功
function successComment() {
loadDetail();
}
onMounted(() => {
loadWebFunc();
loadDetail();
});
onActivated(() => {
loadDetail();
});
</script>
<style lang="less">
@import url("../style/courseCat.less");
@import url("../style/courseDetail.less");
</style>

@ -0,0 +1,347 @@
<template>
<div class="course_lessonsBox fs14 text_1">
<!---->
<div class="section_box flex-ac flex-rb" v-if="catLatestInfo.type == 1">
<span>{{ catLatestInfo.name }}</span>
</div>
<!---->
<!-- filter_gray -->
<div class="lesson_box flex-rb" :class="{'filter_gray': fetchIsNoneEffect(catLatestInfo)}" v-if="catLatestInfo.type == 2" @click="handelTab">
<div class="flex-re">
<span class="lesson_tag courseCat_tag theme_color flex-cc fs10">
<span class="icon_cat" :class="activeClass(catLatestInfo).iconClass"></span>
<span class="fss10">{{ activeClass(catLatestInfo).label }}</span>
</span>
<!-- 课件内容 -->
<div class="lesson_content_box">
<div class="flex cat_title_box">
<p class="lesson_infoName text_2 flex-wr">{{ catLatestInfo.name }}</p>
<span class="fss11" v-if="currentCatId == catLatestInfo.id">正在学</span>
<span class="fss11" v-if="!currentCatId && catLatestInfo.lastLearn"
>上次学到</span
>
</div>
<div class="course_status_box text_3">
<csLessonStatus
:isLock="currentIsLock"
:courseInfo="catLatestInfo"
:courseDetail="courseDetail"
:lessonStudyProgress="lessonStudyProgress"
/>
</div>
</div>
</div>
<div @click.stop="doDownLoadEvent()">
<!-- 课件下载 -->
<div
class="flex-cc lesson_down"
v-if="currentIsLock == 0 && [3, 4].includes(catLatestInfo.courseType)"
>
<!-- 下载进度为0 -->
<span v-if="courseDetail.joinStatus < 1"></span>
<!-- @click.stop="doDownLoad" -->
<span
class="lesson_down_icon"
v-else-if="downloadProgress == 0"
></span>
<!-- 正在下载 | 暂停 -->
<div
class="tc"
v-else-if="downloadProgress < 100 && downloadProgress > 0"
>
<!-- @click.stop="changeDownLoadMode" -->
<div>
<span
class="lesson_down_loading_icon"
:class="isDownload ? 'downloadPause' : 'downLoading'"
></span>
<p class="w-8 fss11 theme_color">下载 {{ downloadProgress }} %</p>
</div>
<!-- <div v-if="!isDownload" @click.stop="doDownLoad">
<span class="lesson_down_loading_icon"></span>
<p class="fss11 theme_color">已暂停</p>
</div> -->
</div>
<!-- 下载完成 -->
<span v-else class="theme_color">已下载</span>
</div>
<div class="flex-cc noeffect_box" v-if="fetchIsNoneEffect(info)">
<div class="icon_noEffect"></div>
</div>
</div>
</div>
<ul>
<li v-for="v in catLatestInfo.childrenList">
<courseLessonsBox
:info="v"
:courseDetail="courseDetail"
color="#21CC64"
:lessonDownProgress="lessonDownProgress"
:lessonStudyProgress="lessonStudyProgress"
/>
</li>
</ul>
<csLessonPracticeBox
:info="catLatestInfo.coursePaperVOS"
:courseInfo="info"
v-if="
catLatestInfo.type == 1 &&
isArray(catLatestInfo.coursePaperVOS) &&
catLatestInfo.coursePaperVOS.length &&
catLatestInfo.coursePaperVOS != ['']
"
/>
</div>
</template>
<script setup>
import {
defineComponent,
ref,
nextTick,
getCurrentInstance,
onMounted,
watch,
computed,
} from "vue";
import { useRouter, useRoute } from "vue-router";
// 组件
import csLessonStatus from "./csLessonStatus.vue";
import csLessonPracticeBox from "./csLessonPracticeBox.vue";
// hooks
import { clientChangeVideo, clientDownloadVideo, clientMediaDownloadProgress,
clientCancelDownloadMedia } from "@/hooks/web/useClientFc"
import { formatIsUnLock } from "@/hooks/format/comm";
import { useCourseMain, useReqApi } from "../hooks/useMain";
import { useCourseDetail } from "../hooks/useCourseDetail";
import { debounceFun } from "@/hooks/core/useLodash.js";
import {globalDebounceTime} from "@/config/settingConfig/core.js"
//方法
import { isArray } from "@/utils/is/isString";
const { appContext } = getCurrentInstance();
const {
activeClass
} = useCourseMain();
const props = defineProps({
info: {
type: Object,
default() {
return {
id: "",
name: "",
childrenList: [],
isLastLearnCatalog: false,
};
},
},
courseDetail: {
type: Object,
default() {
return {};
},
},
lessonDownProgress: {
type: Object,
default() {
return {};
},
},
lessonStudyProgress: {
type: Object,
default() {
return {};
},
},
});
defineComponent({ name: "courseLessonsBox" });
const router = useRouter();
const route = useRoute();
const currentCatId = ref(""); // 课件目录id (正在学)
const currentDownloadCatId = ref(""); // 课件目录id (下载)
const { fetchCourseTipByRecord , fetchIsNoneEffect} = useCourseDetail();
const { updateLastLearnCatalogIdCourseApi } = useReqApi;
const isDownload = ref(true); //控制下载课件的暂停
const currCatFullData = ref({}); //当前目录课节更新所有数据
const catLatestInfo = computed(() => { //实时的课程目录课件数据
return {...props.info ,...currCatFullData.value}
})
// 下载进度
const downloadProgress = ref(0);
// const currentIsLock = ref(1);
const currentIsLock = computed(()=>{
return props.info?.isLock
})
// 跳转播放视频
const handelTab =
debounceFun(function() {
console.log('debounceFun_handelTab')
if(fetchIsNoneEffect(props.info)){ // 课节已失效
return
}
if(props.info.id == currentCatId.value){ // 相同目录事件不处理
return
}
updateChangeLessonApi();
}, globalDebounceTime);
// 汇报上次学到的课 拿到当前实时的返回进度
async function updateChangeLessonApi() {
const resData = await updateLastLearnCatalogIdCourseApi({
courseCatalogId: props.info.id,
resourceId: route.query.id,
});
let params = {
...props.info,
courseTrainId: route.query.id,
};
if(resData){
params = {...params, ...resData.data}
}
if (formatIsUnLock({ ...params })) {
currCatFullData.value = params // 本课节接口数据更新
appContext.config.globalProperties.Bus.emit(
"changeCurrentCatId",
props.info.id
);
//判断是否有锁
clientChangeVideo(params, (res) => {});
} else {
// 报名、锁状态提示
fetchCourseTipByRecord(props.courseDetail, "课件");
}
console.log(params, "updateChangeLessonApi");
}
// 下载相关
function doDownLoadEvent() {
if(currentIsLock.value == 0 && [3, 4].includes(catLatestInfo.value?.courseType)){
if(props.courseDetail.joinStatus < 1){
return
}
if(downloadProgress.value == 0){
doDownLoad()
return
}
if(downloadProgress.value < 100 && downloadProgress.value > 0){
changeDownLoadMode()
return
}
}
}
// 下载课件
const doDownLoad = debounceFun(function() {
if(fetchIsNoneEffect(props.info)){
return
}
let params = {
...props.info,
courseTrainId: route.query.id,
};
currentDownloadCatId.value = props.info.id;
console.log(params, "clientDownloadVideo");
clientDownloadVideo(params, (res) => {});
}, globalDebounceTime);
//停止下载正在下载的课件
const changeDownLoadMode = debounceFun(function() {
let params = {
...props.info,
courseTrainId: route.query.id,
};
currentDownloadCatId.value = props.info.id;
console.log(params, "clientCancelDownloadMedia");
if (isDownload.value) {
doDownLoad();
} else {
clientCancelDownloadMedia(params, (res) => {});
}
changeIsDownload();
}, globalDebounceTime);
// 切换暂停 | 继续 下载模式
function changeIsDownload() {
isDownload.value = !isDownload.value;
}
function initChangeDownloadProgress() {
console.log(
props.info.id,
props.lessonDownProgress,
"initChangeDownloadProgress"
);
const { progress } = props.lessonDownProgress;
fetchDownloadProgressVal(progress);
}
function fetchDownloadProgressVal(progress) {
let currProgress = Math.floor(100 * progress);
if (currProgress <= 0) {
currProgress = 0;
}
if (currProgress >= 100) {
currProgress = 100;
isDownload.value = false;
}
downloadProgress.value = currProgress;
}
nextTick(() => {
// if (props.info) {
// currentIsLock.value = props.info?.isLock;
// }
if (props.info.isLastLearnCatalog) {
currentCatId.value = props.info.id;
}
// console.log(props.info.lessonId, "isLastLearnCatalog");
appContext.config.globalProperties.Bus.on("changeCurrentCatId", (data) => {
console.log("接收到的内容是:" + data);
currentCatId.value = data;
// if(){
// currentIsLock.value = 0; //解锁
// }
});
if (props.info.type == 2) {
// 只有课才调用获取初始加载进度
clientMediaDownloadProgress(
{ ...props.info, courseTrainId: route.query.id },
(res) => {
// initChangeDownloadProgress(res)
isDownload.value = res?.progress > 0 && res?.progress < 1;
fetchDownloadProgressVal(res?.progress);
}
);
}
});
watch(
() => props.lessonDownProgress,
() => {
if (props.lessonDownProgress.catalogId == props.info.id) {
initChangeDownloadProgress();
}
},
{ deep: true }
);
</script>
<style lang="less">
@import url("../style/courseCat.less");
</style>

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-01-15 11:06:34
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-01-15 11:07:03
* @LastEditTime: 2024-01-16 13:50:46
* @FilePath: \general-work-web\General-AI-Platform-Web-Client\src\views\demo\animateDevice.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
@ -32,7 +32,7 @@ onMounted(() => {
</script>
<template>
<div>
<div style="width: 100px">
<div ref="animation1" />
</div>
</template>

@ -3,6 +3,7 @@ import { computed, PropType } from "vue";
import control from "@/assets/svg/device/control.svg?component";
import monitor1 from "@/assets/svg/device/monitor1.svg?component";
import monitor2 from "@/assets/svg/device/monitor2.svg?component";
import { AnimateDevice } from "@/components/Animate";
defineOptions({
name: "DeviceCard"
});
@ -41,7 +42,7 @@ const isEnabledClass = computed(() => [
<template>
<div :class="cardClass">
<div class="device-header">
<div class="device--logo mr-2">
<div class="mr-2 device--logo">
<control />
<monitor1 v-if="false" />
<monitor2 v-if="false" />
@ -67,7 +68,9 @@ const isEnabledClass = computed(() => [
</div>
<div>创建时间<span>DEVICE00002</span></div>
</div>
<div class="device-icon"><img src="@/assets/control.png" /></div>
<div class="device-icon">
<AnimateDevice /><img src="@/assets/control.png" />
</div>
</div>
</div>
</template>

Loading…
Cancel
Save