This commit is contained in:
joylink_fanyuhong 2024-03-11 18:05:05 +08:00
commit f134ea6b5f
7 changed files with 430 additions and 15 deletions

View File

@ -46,6 +46,7 @@
"vue-i18n": "^8.12.0", "vue-i18n": "^8.12.0",
"vue-quill-editor": "^3.0.6", "vue-quill-editor": "^3.0.6",
"vue-router": "^3.1.6", "vue-router": "^3.1.6",
"vue-video-player": "^5.0.1",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",
"vuex": "^3.1.0", "vuex": "^3.1.0",
"wangeditor": "^4.6.17", "wangeditor": "^4.6.17",

View File

@ -332,6 +332,31 @@ export function publishContextSence(data) {
}); });
} }
/**
* @param {Object} data
* @param {String} data.name 场景名称
* @param {String} data.type Video=视频
* @param {Object} data.scene 大赛场景
* @param {String} data.scene.url 地址
* @param {String} data.scene.fileName 文件名
*/
export function saveSceneVideo(data) {
return request({
url: `/api/exercise/race/scene/custom/edit`,
method: 'post',
data
});
}
/** 场景视频编辑 */
export function editSceneVideo(sceneId, data) {
return request({
url: `/api/exercise/race/scene/custom/${sceneId}/edit`,
method: 'post',
data
});
}
/** /**
* @param {String} paperId 试卷id * @param {String} paperId 试卷id
* @param {String} moduleId 模块id * @param {String} moduleId 模块id

View File

@ -28,6 +28,11 @@ import '@/directive/verticalDrag/index.js';
import '@/directive/waves/index.js'; import '@/directive/waves/index.js';
import messages from '@/i18n/index'; import messages from '@/i18n/index';
import VideoPlayer from 'vue-video-player/src';
import 'vue-video-player/src/custom-theme.css';
import 'video.js/dist/video-js.css';
Vue.use(VideoPlayer);
Vue.use(ElementUI); Vue.use(ElementUI);
Vue.use(VueI18n); Vue.use(VueI18n);
Vue.config.devtools = true; // 开发环境显示vue控制台 Vue.config.devtools = true; // 开发环境显示vue控制台

View File

@ -0,0 +1,123 @@
<template>
<el-dialog v-dialogDrag :title="sceneName" :visible.sync="dialogVisible" width="50%" :before-close="handleClose" center :close-on-click-modal="false">
<div>
<video-player
ref="videoPlayer"
:playsinline="false"
:options="playOptions"
@play="onPlayerPlay($event)"
@timeupdate="onPlayerTimeupdate($event)"
/>
</div>
</el-dialog>
</template>
<script>
import { getContextSenceDetail } from '@/api/contest';
export default {
data() {
return {
dialogVisible: false,
sceneName:'播放视频',
playedTime: '',
currentTime: 0,
maxTime: 0,
playOptions: {
height: '200px',
width: '100%',
playbackRates: [1.0],
autoplay: false,
muted: false,
loop: false,
preload: 'auto',
language: 'zh-CN',
aspectRatio: '16:9',
fluid: true,
sources: [
{
type: 'video/mp4',
src: ''
}
],
poster: '',
notSupportedMessage: '此视频暂无法播放,请稍后再试',
controlBar: {
currentTimeDisplay: true,
progressControl: true,
playbackRateMenuButton: true,
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: true,
fullscreenToggle: true
}
}
};
},
computed: {
urlPrefix() {
return this.$store.state.user.ossUrl;
}
},
methods: {
doShow(sceneId) {
getContextSenceDetail(sceneId).then((res) => {
this.dialogVisible = true;
this.playOptions.sources[0].src = `${this.urlPrefix}/${res.data.scene.url}`;
this.sceneName = res.data.name;
}).catch(error => {
this.$message.error(error.message);
});
},
handleClose() {
this.dialogVisible = false;
},
onPlayerPlay(player) {
let playTime = 0;
if (
Number(Math.floor(this.playedTime)) ===
Number(Math.floor(player.duration()))
) {
this.playedTime = 0;
playTime = 0;
} else if (
Number(Math.floor(player.currentTime())) !==
Number(Math.floor(this.playedTime))
) {
playTime = this.playedTime;
player.currentTime(playTime);
}
},
onPlayerPause(player) {
this.playedTime = player.currentTime();
},
onPlayerTimeupdate(player) {
const timeDisplay = player.currentTime();
if (timeDisplay - this.currentTime > 1) {
player.currentTime(this.currentTime > this.maxTime ? this.currentTime : this.maxTime);
}
this.currentTime = player.currentTime();
this.maxTime = this.currentTime > this.maxTime ? this.currentTime : this.maxTime;
}
}
};
</script>
<style lang="scss" scoped>
/deep/{
.el-dialog__title {
color:#fff;
}
.el-dialog__header{
background: linear-gradient(to bottom, #01468B, #00172E);
}
.el-dialog__body{
padding: 0 !important;
}
.video-js .vjs-big-play-button {
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
background-color: black;
}
}
</style>

View File

@ -44,12 +44,14 @@
</div> </div>
<div style="text-align: center;margin-top: 15px;"> <div style="text-align: center;margin-top: 15px;">
<el-button v-show="nowData.ruleId" v-loading="loading" type="primary" @click="showScoreRule">评分表</el-button> <el-button v-show="nowData.ruleId" v-loading="loading" type="primary" @click="showScoreRule">评分表</el-button>
<el-button v-show="nowData.sceneId" v-loading="loading" type="primary" @click="startTask">开始任务</el-button> <el-button v-show="nowData.sceneId&&nowData.scenetype!=='Video'" v-loading="loading" type="primary" @click="startTask">开始任务</el-button>
<el-button v-show="nowData.sceneId&&nowData.scenetype=='Video'" type="primary" @click="playSceneVideo">播放视频</el-button>
<el-button v-loading="loading" type="primary" :disabled="nowKey === taskList.length-1" @click="nextTask">下一任务</el-button> <el-button v-loading="loading" type="primary" :disabled="nowKey === taskList.length-1" @click="nextTask">下一任务</el-button>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<score-rule ref="scoreRule" /> <score-rule ref="scoreRule" />
<play-Video ref="playVideo" />
</el-row> </el-row>
</template> </template>
@ -58,10 +60,12 @@ import { getTaskTree, getContextSenceDetail} from '@/api/contest';
import { createSimulationNoFunction } from '@/api/simulation'; import { createSimulationNoFunction } from '@/api/simulation';
import { getPublishMapInfo } from '@/api/jmap/map'; import { getPublishMapInfo } from '@/api/jmap/map';
import ScoreRule from './scoreRule'; import ScoreRule from './scoreRule';
import PlayVideo from './PlayVideo';
export default { export default {
name: 'ContestDetail', name: 'ContestDetail',
components: { components: {
ScoreRule ScoreRule,
PlayVideo
}, },
data() { data() {
return { return {
@ -145,6 +149,9 @@ export default {
this.nowKey++; this.nowKey++;
this.nowData = this.taskList[this.nowKey]; this.nowData = this.taskList[this.nowKey];
}, },
playSceneVideo() {
this.$refs.playVideo.doShow(this.nowData.sceneId);
},
startTask() { startTask() {
this.loading = true; this.loading = true;
try { try {

View File

@ -1,13 +1,18 @@
<template> <template>
<div> <div>
<query-list-page ref="user" :card-padding="10" :pager-config="pagerConfig" :query-form="queryForm" :query-list="queryList" /> <query-list-page ref="user" :card-padding="10" :pager-config="pagerConfig" :query-form="queryForm" :query-list="queryList" />
<upload-Video ref="uploadVideo" @reloadTable="reloadTable" />
</div> </div>
</template> </template>
<script> <script>
import { queryContestSencePaged, deleteContestSence } from '@/api/contest'; import { queryContestSencePaged, deleteContestSence } from '@/api/contest';
import UploadVideo from './UploadVideo';
export default { export default {
name: 'ContestSeasonManage', name: 'ContestSeasonManage',
components: {
UploadVideo
},
data() { data() {
return { return {
pagerConfig: { pagerConfig: {
@ -28,7 +33,7 @@ export default {
type: 'select', type: 'select',
label: '类 型', label: '类 型',
config: { config: {
data: [{label: '二维', value: 'Local'}, {label: '三维', value: 'Link'}] data: [{label: '二维', value: 'Local'}, {label: '三维', value: 'Link'}, {label: '视频', value: 'Video'}]
} }
} }
} }
@ -50,7 +55,15 @@ export default {
title: '类 型', title: '类 型',
prop: 'type', prop: 'type',
type: 'tag', type: 'tag',
columnValue: (row) => { return row.type === 'Local' ? '二维' : '三维'; }, columnValue: (row) => {
if (row.type === 'Local') {
return '二维';
} else if (row.type === 'Link') {
return '三维';
} else {
return '视频';
}
},
tagType: (row) => { return ''; } tagType: (row) => { return ''; }
}, },
{ {
@ -72,10 +85,13 @@ export default {
title: '操 作', title: '操 作',
width: '320', width: '320',
buttons: [ buttons: [
// { {
// name: '', name: '编辑',
// handleClick: this.doEdit handleClick: this.doEdit,
// }, showControl: row => {
return row.type == 'Video';
}
},
{ {
name: '删 除', name: '删 除',
handleClick: this.doDelete, handleClick: this.doDelete,
@ -83,10 +99,10 @@ export default {
} }
] ]
} }
],
actions: [
{ text: '上 传', handler: this.doUpload }
] ]
// actions: [
// { text: ' ', handler: this.doCreate }
// ]
} }
}; };
}, },
@ -94,11 +110,11 @@ export default {
reloadTable() { reloadTable() {
this.queryList.reload(); this.queryList.reload();
}, },
doCreate() { doUpload() {
// this.$refs.addSeason.doShow(); this.$refs.uploadVideo.doShow();
}, },
doEdit(row) { doEdit(index, row) {
// this.$refs.addSeason.doShow(row); this.$refs.uploadVideo.doShow(row.id);
}, },
doDelete(index, row) { doDelete(index, row) {
this.$confirm('该操作将删除竞赛场景,是否继续?', '提 示', { this.$confirm('该操作将删除竞赛场景,是否继续?', '提 示', {

View File

@ -0,0 +1,238 @@
<template>
<el-dialog v-dialogDrag :visible="show" width="500px" center :close-on-click-modal="false" title="上传视频" :before-close="doClose" destroy-on-close>
<div v-if="formData.id==''||formData.reUpLoad" class="wrapper">
<label v-show="showUpload" for="file">
<el-card shadow="hover" class="card">
<i class="el-icon-upload" />
</el-card>
</label>
<input id="file" ref="file" style="display: none;" type="file" @change="onUploadChange">
<div v-show="!showUpload" class="preview" @mouseenter="showDelete = true" @mouseleave="showDelete = false">
<el-card shadow="hover" class="card">
<div class="file-type">{{ fileType }}</div>
<div class="file-name">{{ formData.fileName }}</div>
<div v-show="showDelete" class="delete" @click="handleDelete">
<i class="el-icon-delete-solid" />
</div>
</el-card>
</div>
</div>
<el-form ref="form" :model="formData" inline :rules="rules">
<el-form-item label="场景名字" required prop="title">
<el-input v-model="formData.title" />
</el-form-item>
<el-form-item v-if="formData.id" label="是否重新上传" prop="reUpLoad">
<el-checkbox v-model="formData.reUpLoad" />
</el-form-item>
<el-form-item v-if="formData.id==''||formData.reUpLoad" label="文件名" required prop="fileName">
<el-input v-model="formData.fileName" />
</el-form-item>
</el-form>
<div v-if="!showUpload" class="path-display">
<span>文件存储路径: </span>
<code>{{ `${urlPrefix}/${formData.directory}/${formData.fileName}` }}</code>
</div>
<footer>
<el-button v-if="formData.id==''" type="primary" size="small" @click="handleUpload">上传</el-button>
<el-button v-if="formData.id" type="primary" size="small" @click="doSave">确定</el-button>
<el-button size="small" @click="doClose">取消</el-button>
</footer>
</el-dialog>
</template>
<script>
import { getUploadUrl } from '@/api/projectConfig';
import { checkIsExist } from '@/api/management/fileManage';
import { saveSceneVideo, getContextSenceDetail, editSceneVideo } from '@/api/contest';
export default {
name: 'UploadVideo',
data() {
return {
show: false,
showUpload: true,
showDelete: false,
fileType: '',
formData: {
id:'',
reUpLoad:false,
directory: 'Video',
title: '',
fileName: ''
},
rules: {
title: [{ required: true, message: '请输入场景名字', trigger: 'blur' }],
fileName: [{ required: true, message: '请输入文件名', trigger: 'blur' }]
}
};
},
computed: {
urlPrefix() {
return this.$store.state.user.ossUrl;
}
},
methods: {
doShow(sceneId) {
this.show = true;
if (sceneId) {
getContextSenceDetail(sceneId).then((res) => {
this.formData.id = res.data.id;
this.formData.title = res.data.name;
this.formData.fileName = res.data.scene.fileName;
}).catch(error => {
this.$message.error(error.message);
});
}
},
doClose() {
this.show = false;
this.handleDelete();
},
onUploadChange(e) {
const fileList = e.target.files;
if (!fileList.length) return;
this.showUpload = false;
const fileName = fileList[0].name;
this.formData.fileName = fileName;
const devideIndex = fileName.lastIndexOf('.');
this.formData.title = fileName.slice(0, devideIndex);
this.fileType = fileList[0].type.split('/').pop();
},
doSave() {
const data = {name:this.formData.title, type:this.formData.directory,
scene:{url:`${this.formData.directory}/${this.formData.fileName}`, fileName:this.formData.fileName}};
if (!this.formData.reUpLoad) {
editSceneVideo(this.formData.id, data).then((res) => {
this.$message.success('编辑成功');
this.doClose();
this.$emit('reloadTable');
}).catch(error => {
this.$message.error(error.message);
});
} else {
this.handleUpload();
}
},
handleUpload() {
const form = this.$refs.form;
const file = this.$refs.file.files[0];
const params = {
directory: this.formData.directory,
fileName: this.formData.fileName
};
const execUpload = () => {
getUploadUrl({ ...params, method: 'PUT' }).then(res => {
const url = res.data;
const xhr = new XMLHttpRequest();
xhr.open('PUT', url);
xhr.upload.onload = () => {
const data = {name:this.formData.title, type:this.formData.directory,
scene:{url:`${this.formData.directory}/${this.formData.fileName}`, fileName:this.formData.fileName}};
if (!this.formData.id) {
saveSceneVideo(data).then(res => {
this.$message.success('上传成功');
this.doClose();
this.$emit('reloadTable');
});
} else {
editSceneVideo(this.formData.id, data).then((res) => {
this.$message.success('编辑且重新上传成功');
this.doClose();
this.$emit('reloadTable');
}).catch(error => {
this.$message.error(error.message);
});
}
};
xhr.send(file);
});
};
form.validate().then(valid => {
if (valid && file.size > 0) {
checkIsExist(params).then(resp => {
if (!resp.data) {
execUpload();
} else {
this.$confirm('该目录下有同名文件, 是否覆盖?', this.$t('global.tips'), {
confirmButtonText: this.$t('global.confirm'),
cancelButtonText: this.$t('global.cancel'),
type: 'warning'
}).then(execUpload);
}
});
}
});
},
handleDelete() {
if (this.formData.id == '' || this.formData.reUpLoad) {
this.$refs.file.value = '';
}
this.showUpload = true;
this.formData.id = '';
this.formData.reUpLoad = false;
this.formData.title = '';
this.formData.fileName = '';
this.formData.directory = '';
this.fileType = '';
}
}
};
</script>
<style lang="scss" scoped>
footer {
display: flex;
justify-content: space-evenly;
margin-top: 20px;
}
.block {
user-select: none;
padding: 5px;
color: #909399;
}
.wrapper {
display: flex;
justify-content: center;
margin-bottom: 20px;
.card {
display: flex;
justify-content: center;
align-items: center;
position: relative;
color: #909399;
cursor: pointer;
width: 150px;
height: 150px;
.el-icon-upload {
font-size: 4em;
}
.file-type {
text-align: center;
font-size: 4rem;
line-height: 6rem;
}
.file-name {
line-height: 1.2em;
text-align: center;
}
.delete {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: 0%;
width: 100%;
height: 100%;
background-color: #000000;
opacity: 0.5;
transition-duration: 1s;
transition-property: all;
font-size: 3em;
}
}
}
.path-display {
margin-top: 10px;
}
</style>