Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
dpe_video_editor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
template admin
dpe_video_editor
Commits
c810faa8
Commit
c810faa8
authored
Jun 29, 2024
by
李维
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
添加视频上传组件
parent
797e5629
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
594 additions
and
440 deletions
+594
-440
package-lock.json
form_vue/package-lock.json
+29
-4
package.json
form_vue/package.json
+3
-0
App.vue
form_vue/src/App.vue
+16
-436
VideoUploader.vue
form_vue/src/components/VideoUploader.vue
+542
-0
main.js
form_vue/src/main.js
+4
-0
No files found.
form_vue/package-lock.json
View file @
c810faa8
...
@@ -9,8 +9,11 @@
...
@@ -9,8 +9,11 @@
"version"
:
"0.1.0"
,
"version"
:
"0.1.0"
,
"dependencies"
:
{
"dependencies"
:
{
"axios"
:
"^1.7.2"
,
"axios"
:
"^1.7.2"
,
"crypto-js"
:
"^4.2.0"
,
"echarts"
:
"^5.5.0"
,
"echarts"
:
"^5.5.0"
,
"element-ui"
:
"^2.15.14"
,
"element-ui"
:
"^2.15.14"
,
"spark-md5"
:
"^3.0.2"
,
"uuid"
:
"^10.0.0"
,
"vue"
:
"^2.6.14"
"vue"
:
"^2.6.14"
},
},
"devDependencies"
:
{
"devDependencies"
:
{
...
@@ -2097,6 +2100,11 @@
...
@@ -2097,6 +2100,11 @@
"semver"
:
"bin/semver"
"semver"
:
"bin/semver"
}
}
},
},
"node_modules/crypto-js"
:
{
"version"
:
"4.2.0"
,
"resolved"
:
"https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz"
,
"integrity"
:
"sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"node_modules/css-declaration-sorter"
:
{
"node_modules/css-declaration-sorter"
:
{
"version"
:
"6.4.1"
,
"version"
:
"6.4.1"
,
"resolved"
:
"https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz"
,
"resolved"
:
"https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz"
,
...
@@ -6590,6 +6598,15 @@
...
@@ -6590,6 +6598,15 @@
"websocket-driver"
:
"^0.7.4"
"websocket-driver"
:
"^0.7.4"
}
}
},
},
"node_modules/sockjs/node_modules/uuid"
:
{
"version"
:
"8.3.2"
,
"resolved"
:
"https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz"
,
"integrity"
:
"sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
,
"dev"
:
true
,
"bin"
:
{
"uuid"
:
"dist/bin/uuid"
}
},
"node_modules/source-map"
:
{
"node_modules/source-map"
:
{
"version"
:
"0.6.1"
,
"version"
:
"0.6.1"
,
"resolved"
:
"https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz"
,
"resolved"
:
"https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz"
,
...
@@ -6616,6 +6633,11 @@
...
@@ -6616,6 +6633,11 @@
"source-map"
:
"^0.6.0"
"source-map"
:
"^0.6.0"
}
}
},
},
"node_modules/spark-md5"
:
{
"version"
:
"3.0.2"
,
"resolved"
:
"https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz"
,
"integrity"
:
"sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
},
"node_modules/spdx-correct"
:
{
"node_modules/spdx-correct"
:
{
"version"
:
"3.2.0"
,
"version"
:
"3.2.0"
,
"resolved"
:
"https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz"
,
"resolved"
:
"https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz"
,
...
@@ -7135,10 +7157,13 @@
...
@@ -7135,10 +7157,13 @@
}
}
},
},
"node_modules/uuid"
:
{
"node_modules/uuid"
:
{
"version"
:
"8.3.2"
,
"version"
:
"10.0.0"
,
"resolved"
:
"https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz"
,
"resolved"
:
"https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz"
,
"integrity"
:
"sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
,
"integrity"
:
"sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="
,
"dev"
:
true
,
"funding"
:
[
"https://github.com/sponsors/broofa"
,
"https://github.com/sponsors/ctavan"
],
"bin"
:
{
"bin"
:
{
"uuid"
:
"dist/bin/uuid"
"uuid"
:
"dist/bin/uuid"
}
}
...
...
form_vue/package.json
View file @
c810faa8
...
@@ -8,8 +8,11 @@
...
@@ -8,8 +8,11 @@
},
},
"dependencies"
:
{
"dependencies"
:
{
"axios"
:
"^1.7.2"
,
"axios"
:
"^1.7.2"
,
"crypto-js"
:
"^4.2.0"
,
"echarts"
:
"^5.5.0"
,
"echarts"
:
"^5.5.0"
,
"element-ui"
:
"^2.15.14"
,
"element-ui"
:
"^2.15.14"
,
"spark-md5"
:
"^3.0.2"
,
"uuid"
:
"^10.0.0"
,
"vue"
:
"^2.6.14"
"vue"
:
"^2.6.14"
},
},
"devDependencies"
:
{
"devDependencies"
:
{
...
...
form_vue/src/App.vue
View file @
c810faa8
<
template
>
<
template
>
<div
id=
"app"
>
<div
id=
"app"
>
<div
class=
"main-header"
>
<VideoUploader
v-model=
"itemData.url"
@
change=
"save"
/>
<div
style=
"float: left;"
>
<span>
当前编辑视频:
</span>
<el-button
type=
"text"
@
click=
"handleClickSelectSyllabus"
>
在库中选择视频
</el-button>
</div>
<div
style=
"float: right;"
>
<el-button
type=
"primary"
@
click=
"handleClickSave"
>
保存
</el-button>
</div>
</div>
<div
class=
"main-body"
>
<div
ref=
"refDubbingScrollList"
class=
"dubbing-items"
>
<el-card
v-for=
"item, index in dubbingData.dubbingItems"
:key=
"index"
class=
"box-card"
>
<div
style=
"position: relative;"
>
<span
style=
"position: absolute;"
class=
"item-index"
>
{{
index
+
1
}}
<span
style=
"font-size: medium; line-height: normal;"
>
{{
currentSelectDubbingIndex
==
index
?
'
正在编辑
'
:
''
}}
</span></span>
<div
style=
"text-align: right;"
>
<el-button
size=
"mini"
type=
"danger"
@
click=
"handleClickDelete(index)"
>
删除
</el-button>
<el-button
size=
"mini"
type=
"primary"
@
click=
"handleClickSetCurrent(index)"
>
编辑
</el-button>
</div>
</div>
<el-divider></el-divider>
<el-form
label-width=
"50px"
>
<el-form-item
label=
"原文"
>
<el-input
type=
"text"
v-model=
"item.orgText"
/>
</el-form-item>
<!--
<el-form-item
label=
"译文"
>
<el-input
type=
"text"
v-model=
"item.transText"
/>
</el-form-item>
-->
<el-form-item
label=
"开始"
>
<el-input
type=
"text"
v-model=
"item.timelineIn"
>
<template
slot=
"append"
>
(秒)
</
template
>
</el-input>
</el-form-item>
<el-form-item
label=
"结束"
>
<el-input
type=
"text"
v-model=
"item.timelineOut"
>
<
template
slot=
"append"
>
(秒)
</
template
>
</el-input>
</el-form-item>
</el-form>
</el-card>
<el-card
class=
"box-card"
>
<el-button
type=
"primary"
@
click=
"handelClickAdd"
style=
"width: 100%;"
>
新建
</el-button>
</el-card>
</div>
<div
class=
"video-panel"
>
<div
class=
"center-container"
>
<video
v-if=
"!updateVideoSource"
ref=
"refVideo"
controls
width=
"768px"
>
<source
:src=
"videoUrl"
type=
"video/mp4"
>
Your browser does not support the video tag.
</video>
<TimeRangeSelector
v-model=
"timeRange"
@
change=
"handleRangeChange"
></TimeRangeSelector>
</div>
</div>
<el-collapse-transition>
<div
v-show=
"currentSelectDubbingIndex >= 0"
class=
"button-group"
>
<el-button
type=
""
size=
"medium"
icon=
"el-icon-refresh"
@
click=
"handleClickPlayRange"
>
播放区间
</el-button>
<el-button
v-show=
"!isPlaying"
type=
""
size=
"medium"
icon=
"el-icon-video-play"
@
click=
"handleClickPlay"
>
播放
</el-button>
<el-button
v-show=
"isPlaying"
type=
""
size=
"medium"
icon=
"el-icon-video-pause"
@
click=
"handleClickPause"
>
暂停
</el-button>
<el-button
type=
""
size=
"medium"
@
click=
"handelClickRre10s"
>
向前1秒
</el-button>
<el-button
type=
""
size=
"medium"
@
click=
"handleClickSetStart"
>
设置为起点
</el-button>
<el-button
type=
""
size=
"medium"
@
click=
"handleClickSetEnd"
>
设置为结束点
</el-button>
</div>
</el-collapse-transition>
</div>
<el-dialog
title=
"选择内容"
:visible
.
sync=
"dialogSelectShow"
width=
"500px"
>
<div
class=
"el-main"
style=
"height: 300px;"
>
<el-tree
:data=
"treeListData"
:props=
"treeProps"
@
node-click=
"handleClickTreeNode"
:lazy=
"true"
:load=
"handleLazyLoadTreeData"
></el-tree>
</div>
<div
slot=
"footer"
class=
"dialog-footer"
>
<span
class=
"current-selected-text"
>
当前选择:{{ currentSelectTreeNode ? currentSelectTreeNode.name : "" }} -
{{ currentSelectTreeNode ? currentSelectTreeNode.id : "" }}
</span>
<el-button
@
click=
"handleClickCancelSelect"
>
取 消
</el-button>
<el-button
type=
"primary"
@
click=
"handleClickConfirmSelect"
>
确 定
</el-button>
</div>
</el-dialog>
</div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
TimeRangeSelector
from
'
./components/TimeRangeSelector.vue
'
import
TimeRangeSelector
from
'
./components/TimeRangeSelector.vue
'
import
{
getSyllabusListById
,
getFirstCoursewareBySyllId
,
getPidPathName
}
from
'
@/api/syllabus
'
import
{
getSyllabusListById
,
getFirstCoursewareBySyllId
,
getPidPathName
}
from
'
@/api/syllabus
'
import
VideoUploader
from
'
./components/VideoUploader.vue
'
export
default
{
export
default
{
name
:
'
App
'
,
name
:
'
App
'
,
components
:
{
components
:
{
TimeRangeSelector
TimeRangeSelector
,
VideoUploader
},
},
data
()
{
data
()
{
return
{
return
{
// 是否正在播放
itemData
:
{
isPlaying
:
false
,
url
:
""
,
}
// 视频地址
videoUrl
:
''
,
// 时间区间
timeRange
:
{
start
:
0
,
end
:
1
},
// 配音数据
dubbingData
:
{
backgroundAudio
:
{
url
:
""
,
duration
:
0
,
size
:
0
},
dubbingItems
:
[]
},
// 选择内容对话框显示隐藏控制
dialogSelectShow
:
false
,
// 树形列表数据
treeListData
:
[],
// 当前选择的节点信息
currentSelectTreeNode
:
null
,
// 树形列表配置
treeProps
:
{
children
:
'
children
'
,
label
:
'
name
'
,
isLeaf
:
function
(
data
,
node
)
{
return
data
.
has_courseware
==
"
1
"
;
}
},
// 是否更新视频资源
updateVideoSource
:
false
,
// 当前选择的配音秀索引
currentSelectDubbingIndex
:
-
1
};
};
},
},
async
mounted
()
{
async
mounted
()
{
this
.
getData
();
this
.
getData
();
// this.$setData([
// {
// "orgText": "Apple.",
// "transText": "",
// "timelineIn": "0.352728",
// "timelineOut": "2.349307"
// },
// {
// "orgText": "Green apple.",
// "transText": "",
// "timelineIn": "4.938305",
// "timelineOut": "7.2651"
// },
// {
// "orgText": "Red apple.",
// "transText": "",
// "timelineIn": "10.687003",
// "timelineOut": "13.054597"
// },
// {
// "orgText": "Juicy apple.",
// "transText": "",
// "timelineIn": "16.128933",
// "timelineOut": "20.358868"
// },
// {
// "orgText": "Gulp,gulp,hic!",
// "transText": "",
// "timelineIn": "20.674126",
// "timelineOut": "23.316044"
// },
// {
// "orgText": "BOTH:apples!",
// "transText": "",
// "timelineIn": "26.360674",
// "timelineOut": "29.569075"
// },
// {
// "orgText": "Shiny apple.",
// "transText": "",
// "timelineIn": "34.97507",
// "timelineOut": "37.929512"
// },
// {
// "orgText": "Crunchy apple.",
// "transText": "",
// "timelineIn": "41.66807",
// "timelineOut": "44.267387"
// },
// {
// "orgText": "Bobbing apple.",
// "transText": "",
// "timelineIn": "48.00274",
// "timelineOut": "50.683136"
// },
// {
// "orgText": "Way-hay!Apples!",
// "transText": "",
// "timelineIn": "53.787037",
// "timelineOut": "57.842"
// }
// ])
},
},
methods
:
{
methods
:
{
async
getData
()
{
async
getData
()
{
const
dpeData
=
await
this
.
$getDPEData
();
console
.
log
(
"
====dpeData====
"
,
dpeData
)
const
res
=
await
this
.
$getData
();
const
res
=
await
this
.
$getData
();
console
.
log
(
"
====getData====
"
,
res
)
const
videoData
=
dpeData
.
find
(
item
=>
item
.
name
===
'
视频
'
);
// console.log("videoData", videoData)
if
(
!
videoData
)
{
this
.
videoUrl
=
''
;
}
else
{
try
{
const
obj
=
JSON
.
parse
(
videoData
.
data
);
this
.
videoUrl
=
obj
.
url
;
// 重新加载视频组件
this
.
updateVideoSource
=
true
;
this
.
$nextTick
(()
=>
{
this
.
updateVideoSource
=
false
;
})
// console.log("videoUrl", this.videoUrl)
}
catch
(
error
)
{
}
}
if
(
!
res
)
{
if
(
!
res
)
{
// 默认数据
// 默认数据
this
.
dubbingData
=
{
this
.
itemData
=
{
backgroundAudio
:
{
url
:
""
,
url
:
""
,
duration
:
0
,
size
:
0
},
dubbingItems
:
[]
}
}
}
else
{
}
else
{
this
.
dubbingData
=
res
this
.
itemData
=
res
}
},
handleRangeChange
(
event
)
{
this
.
$refs
.
refVideo
.
currentTime
=
event
.
start
*
this
.
$refs
.
refVideo
.
duration
;
const
sTime
=
this
.
timeRange
.
start
*
this
.
$refs
.
refVideo
.
duration
;
const
eTime
=
this
.
timeRange
.
end
*
this
.
$refs
.
refVideo
.
duration
;
this
.
dubbingData
.
dubbingItems
[
this
.
currentSelectDubbingIndex
].
timelineIn
=
sTime
;
this
.
dubbingData
.
dubbingItems
[
this
.
currentSelectDubbingIndex
].
timelineOut
=
eTime
;
},
// 点击区间播放
handleClickPlayRange
()
{
const
sTime
=
this
.
timeRange
.
start
*
this
.
$refs
.
refVideo
.
duration
;
const
eTime
=
this
.
timeRange
.
end
*
this
.
$refs
.
refVideo
.
duration
;
this
.
$refs
.
refVideo
.
currentTime
=
sTime
;
this
.
$refs
.
refVideo
.
play
().
then
(()
=>
{
this
.
isPlaying
=
true
;
const
checkTime
=
()
=>
{
if
(
this
.
$refs
.
refVideo
.
currentTime
>=
eTime
)
{
this
.
$refs
.
refVideo
.
pause
();
this
.
isPlaying
=
false
;
this
.
$refs
.
refVideo
.
removeEventListener
(
'
timeupdate
'
,
checkTime
);
}
}
this
.
$refs
.
refVideo
.
addEventListener
(
'
timeupdate
'
,
checkTime
,
false
);
})
.
catch
(
error
=>
{
console
.
log
(
error
);
});
},
handleClickPlay
()
{
this
.
$refs
.
refVideo
.
play
();
this
.
isPlaying
=
true
;
},
handleClickPause
()
{
this
.
$refs
.
refVideo
.
pause
();
this
.
isPlaying
=
false
;
},
handelClickRre10s
()
{
this
.
$refs
.
refVideo
.
currentTime
-=
1
;
},
handleClickSetStart
()
{
this
.
timeRange
.
start
=
this
.
$refs
.
refVideo
.
currentTime
/
this
.
$refs
.
refVideo
.
duration
;
this
.
dubbingData
.
dubbingItems
[
this
.
currentSelectDubbingIndex
].
timelineIn
=
this
.
$refs
.
refVideo
.
currentTime
;
},
handleClickSetEnd
()
{
this
.
timeRange
.
end
=
this
.
$refs
.
refVideo
.
currentTime
/
this
.
$refs
.
refVideo
.
duration
;
this
.
dubbingData
.
dubbingItems
[
this
.
currentSelectDubbingIndex
].
timelineOut
=
this
.
$refs
.
refVideo
.
currentTime
;
},
handleClickSetCurrent
(
index
,
play
=
false
)
{
const
timelineIn
=
this
.
dubbingData
.
dubbingItems
[
index
].
timelineIn
/
this
.
$refs
.
refVideo
.
duration
;
const
timelineOut
=
this
.
dubbingData
.
dubbingItems
[
index
].
timelineOut
/
this
.
$refs
.
refVideo
.
duration
;
this
.
currentSelectDubbingIndex
=
index
;
this
.
timeRange
=
{
start
:
timelineIn
,
end
:
timelineOut
}
}
play
&&
this
.
handleClickPlayRange
()
},
// 删除配音秀
handleClickDelete
(
index
)
{
this
.
$confirm
(
"
确定要删除吗?
"
).
then
(
res
=>
{
this
.
dubbingData
.
dubbingItems
.
splice
(
index
,
1
)
}).
catch
(
_
=>
""
)
},
// 添加配音
handelClickAdd
()
{
this
.
dubbingData
.
dubbingItems
.
push
({
orgText
:
''
,
transText
:
''
,
timelineIn
:
0
,
timelineOut
:
1
})
this
.
$nextTick
(()
=>
{
this
.
$refs
.
refDubbingScrollList
.
scrollTop
=
this
.
$refs
.
refDubbingScrollList
.
scrollHeight
;
})
},
// 点击切换配音资源
async
handleClickSelectSyllabus
()
{
this
.
dialogSelectShow
=
true
;
const
resData
=
await
getSyllabusListById
();
this
.
treeListData
=
resData
.
rows
;
},
// 点击树形列表节点
handleClickTreeNode
(
node
)
{
if
(
node
.
has_courseware
==
"
1
"
)
{
this
.
currentSelectTreeNode
=
node
;
}
},
// 懒加载树形列表数据
handleLazyLoadTreeData
(
node
,
resolve
)
{
getSyllabusListById
(
node
.
data
.
id
).
then
(
data
=>
{
resolve
(
data
.
rows
)
})
},
},
// 取消选择
handleClickCancelSelect
()
{
// 保存数据
this
.
dialogSelectShow
=
false
;
save
()
{
this
.
currentSelectTreeNode
=
null
;
console
.
log
(
this
.
itemData
)
this
.
treeListData
=
[];
this
.
$setData
(
this
.
itemData
)
},
},
// 确定选择节点
handleUploading
(
progress
)
{
async
handleClickConfirmSelect
()
{
// 获取视频课件信息
const
resData
=
await
getFirstCoursewareBySyllId
(
this
.
currentSelectTreeNode
.
id
);
let
jsonData
=
null
;
try
{
jsonData
=
JSON
.
parse
(
resData
.
data
);
}
catch
(
error
)
{
}
// 获取视频地址
this
.
videoUrl
=
jsonData
?
jsonData
.
url
:
''
;
// 重新加载视频组件
this
.
updateVideoSource
=
true
;
this
.
$nextTick
(()
=>
{
this
.
updateVideoSource
=
false
;
})
// 关闭对话框
this
.
handleClickCancelSelect
();
},
// 保存数据
async
handleClickSave
()
{
this
.
$setData
(
this
.
dubbingData
)
}
}
}
}
}
}
...
@@ -411,76 +63,4 @@ html {
...
@@ -411,76 +63,4 @@ html {
}
}
#app
{}
#app
{}
.cascader-item
{
width
:
150px
;
}
.button-item
{
margin-left
:
10px
;
}
.current-selected-text
{
float
:
left
;
font-size
:
small
;
}
.video-panel
{
margin-left
:
320px
;
}
.center-container
{
width
:
768px
;
margin-left
:
320px
;
padding
:
20px
10px
;
margin
:
0
auto
;
}
.button-group
{
margin-left
:
320px
;
text-align
:
center
;
}
.dubbing-items
{
width
:
300px
;
height
:
calc
(
100%
-
90px
);
background-color
:
#f0f0f0
;
position
:
absolute
;
left
:
0
;
overflow
:
auto
;
padding
:
10px
;
}
.box-card
{
margin-bottom
:
10px
;
}
::v-deep
.el-card__body
{
padding
:
0
!important
;
}
.item-index
{
font-size
:
20px
;
font-weight
:
bold
;
margin-right
:
10px
;
}
.main-header
{
height
:
70px
;
line-height
:
70px
;
position
:
fixed
;
z-index
:
99
;
top
:
0
;
background-color
:
#FFFFFF
;
width
:
calc
(
100%
-
40px
);
border-bottom
:
1px
solid
#dcdfe6
;
padding
:
0
20px
;
box-shadow
:
0
0
20px
rgba
(
0
,
0
,
0
,
0.2
);
}
.main-body
{
margin-top
:
70px
;
height
:
calc
(
100%
-
70px
);
overflow
:
auto
;
}
</
style
>
</
style
>
form_vue/src/components/VideoUploader.vue
0 → 100644
View file @
c810faa8
<!-- 视频上传组件 -->
<
template
>
<div
style=
"position: relative; width: 360px; height: 180px;"
>
<el-upload
:data=
"uploadData"
:disabled=
"uploading || transcoding"
:action=
"uploadUrl"
drag
:before-upload=
"customReq"
accept=
".mp4"
:show-file-list=
"false"
>
<!-- 上传提示 -->
<i
v-show=
"!transcoding && !videoDone && !uploading"
class=
"el-icon-upload"
></i>
<div
v-show=
"!transcoding && !videoDone && !uploading"
class=
"el-upload__text"
>
将文件拖到此处,或
<em>
点击上传
</em></div>
<div
v-if=
"!transcoding && !videoDone && !uploading"
style=
"text-align: center;"
class=
"el-upload__tip"
slot=
"tip"
>
只能上传mp4文件
</div>
<div
v-if=
"transcoding"
style=
"text-align: center;"
class=
"el-upload__tip"
slot=
"tip"
>
视频后台转码中,请稍后...
</div>
<!-- 进度条 -->
<div
v-show=
"!transcoding && !videoDone && uploading"
style=
"position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);"
>
<el-progress
type=
"circle"
:percentage=
"uploadPercentage"
:status=
"uploadStatus"
></el-progress>
<div>
{{
uploadSpeed
}}
</div>
</div>
<!-- 预览视频 -->
<div
v-show=
"!transcoding && videoDone"
>
<video
ref=
"refVideo"
width=
"360px"
height=
"180px"
controls
crossOrigin=
"anonymous"
>
<source
:src=
"`$
{videoUrl}`" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<!-- 转换动画 -->
<div
v-if=
"transcoding"
>
<div
class=
"mesh-loader"
>
<div
class=
"set-one"
>
<div
class=
"circle"
></div>
<div
class=
"circle"
></div>
</div>
<div
class=
"set-two"
>
<div
class=
"circle"
></div>
<div
class=
"circle"
></div>
</div>
</div>
</div>
</el-upload>
<!-- 截图 下载 删除按钮 -->
<div
v-if=
"videoDone"
class=
"buttons"
>
<el-button
type=
"primary"
size=
"mini"
icon=
"el-icon-camera"
@
click=
"captureVideo"
></el-button>
<el-button
type=
"primary"
size=
"mini"
icon=
"el-icon-download"
@
click=
"download"
></el-button>
<el-button
type=
"danger"
size=
"mini"
icon=
"el-icon-delete"
@
click=
"deleteVideo"
></el-button>
</div>
</div>
</
template
>
<
script
>
import
cryptoJs
from
'
crypto-js
'
;
import
axios
from
'
axios
'
;
import
SparkMD5
from
'
spark-md5
'
;
export
default
{
name
:
'
VideoUploader
'
,
props
:
{
value
:
{
type
:
String
,
default
:
''
},
},
watch
:
{
value
:
function
(
val
,
oldVal
)
{
// 监测父组件的更新
if
(
val
)
{
// 查看是否资源已经存在
this
.
httpHeadCall
(
val
,
async
(
exist
)
=>
{
if
(
exist
)
{
// 存在,直接标记视频完成
this
.
videoDone
=
true
}
else
{
// 如果不存在则默认是正在后台转码状态
this
.
transcoding
=
true
;
await
this
.
checkResourceAvaliable
();
this
.
$message
({
type
:
'
success
'
,
message
:
'
转换完成
'
});
this
.
videoDone
=
true
;
this
.
transcoding
=
false
;
// 刷新视频源
this
.
$refs
.
refVideo
.
load
()
}
})
}
this
.
videoUrl
=
val
},
videoUrl
:
function
()
{
this
.
$refs
.
refVideo
.
load
()
}
},
data
()
{
return
{
// 上传地址
uploadUrl
:
""
,
// 上传附带的DATA
uploadData
:
""
,
// 上传中标记位
uploading
:
false
,
// 转码中
transcoding
:
false
,
// 视频是否上传完成
videoDone
:
false
,
// 上传进度
uploadPercentage
:
0
,
// 上传进度条状态 null success
uploadStatus
:
null
,
// 上传速度
uploadSpeed
:
"
0KB/s
"
,
// 上传开始时间
startTime
:
null
,
// 上传结束时间
endTime
:
null
,
// 已经上传的文件大小
loaded
:
0
,
// 视频地址
videoUrl
:
""
,
// 定时器ID
setIntervalIds
:
[],
setTimeoutIds
:
[],
}
},
created
()
{
// 获取上传地址
this
.
uploadUrl
=
window
.
courseware
.
uploadVideoUrl
();
this
.
uploadData
=
window
.
courseware
.
uploadData
();
window
[
'
air
'
].
getUploadVideoCallback
=
(
url
,
data
)
=>
{
this
.
uploadUrl
=
url
;
this
.
uploadData
=
data
;
};
},
mounted
()
{
// 添加点击事件监听,防止点击视频画面自动播放。(点击视频画面可以重新上传视频)
this
.
$refs
.
refVideo
.
addEventListener
(
'
click
'
,
()
=>
{
const
stop
=
()
=>
{
this
.
$refs
.
refVideo
.
pause
();
this
.
$refs
.
refVideo
.
removeEventListener
(
"
play
"
,
stop
)
}
this
.
$refs
.
refVideo
.
addEventListener
(
"
play
"
,
stop
)
});
},
methods
:
{
// 解析文件名
parseFileName
(
fileName
)
{
const
lastDotIndex
=
fileName
.
lastIndexOf
(
'
.
'
);
const
name
=
fileName
.
substring
(
0
,
lastDotIndex
);
const
ext
=
fileName
.
substring
(
lastDotIndex
);
return
{
name
,
ext
};
},
// 重置上传变量
resetUploadVars
()
{
this
.
uploadPercentage
=
0
;
this
.
uploadStatus
=
null
;
this
.
uploadSpeed
=
"
0KB/s
"
;
this
.
startTime
=
null
;
this
.
endTime
=
null
;
this
.
loaded
=
0
;
},
// 发送HEAD请求 测试资源是否存在
httpHeadCall
(
requsetUrl
,
callback
)
{
let
xhr
=
new
XMLHttpRequest
();
try
{
xhr
.
onreadystatechange
=
()
=>
{
try
{
if
(
xhr
.
readyState
==
4
)
{
if
((
xhr
.
status
>=
200
&&
xhr
.
status
<
400
))
{
callback
(
true
);
}
else
{
callback
(
false
);
}
}
}
catch
(
e
)
{
callback
(
false
);
}
};
xhr
.
open
(
"
HEAD
"
,
requsetUrl
,
true
);
xhr
.
send
();
xhr
.
timeout
=
15000
;
xhr
.
onerror
=
(
e
)
=>
{
callback
(
false
);
};
xhr
.
ontimeout
=
(
e
)
=>
{
callback
(
false
);
};
}
catch
(
e
)
{
callback
(
false
);
}
},
// 上传文件
async
customReq
(
item
)
{
const
{
name
,
ext
}
=
this
.
parseFileName
(
item
.
name
)
if
(
ext
!==
'
.mp4
'
)
{
this
.
$message
({
type
:
'
error
'
,
message
:
'
只能上传mp4文件
'
});
return
false
}
// 获取文件MD5
const
fileName
=
await
this
.
getFileMD5
(
item
);
const
protocolStr
=
document
.
location
.
protocol
;
const
baseCDN
=
`
${
protocolStr
}
//teach.cdn.ireadabc.com`
;
const
baseAPI
=
`
${
protocolStr
}
//openapi.iteachabc.com`
;
// 如果当前视频和新上传的视频是同一个,则不变。
if
(
this
.
videoUrl
==
`
${
baseCDN
}
/
${
fileName
}
_h.mp4`
)
{
this
.
$refs
.
refVideo
.
pause
();
this
.
$refs
.
refVideo
.
currentTime
=
0
;
this
.
$message
({
type
:
'
success
'
,
message
:
'
极速上传完成
'
})
return
}
// 重置上传变量,并设置状态为上传中
this
.
resetUploadVars
();
this
.
videoUrl
=
""
;
this
.
uploading
=
true
;
this
.
transcoding
=
false
;
this
.
videoDone
=
false
;
// 测试文件是否在服务上存在
this
.
httpHeadCall
(
`
${
baseCDN
}
/
${
fileName
}
_h.mp4`
,
(
exit
)
=>
{
if
(
exit
)
{
// 文件存在,极速上传完成。并标记为完成状态。
this
.
uploadStatus
=
"
success
"
;
this
.
videoUrl
=
`
${
baseCDN
}
/
${
fileName
}
_h.mp4`
;
this
.
$emit
(
"
input
"
,
this
.
videoUrl
);
this
.
$emit
(
'
change
'
,
{
url
:
this
.
videoUrl
,
file
:
item
});
this
.
$emit
(
'
success
'
,
{
url
:
this
.
videoUrl
,
file
:
item
});
this
.
uploading
=
false
;
this
.
$message
({
type
:
'
success
'
,
message
:
'
极速上传完成
'
})
return
}
// 开始上传到OSS
axios
.
get
(
`
${
baseAPI
}
/oss/timestamp`
,
{}).
then
(
tmps
=>
{
const
sign
=
cryptoJs
.
MD5
(
tmps
+
"
iplayABC2019
"
).
toString
();
axios
.
get
(
`
${
baseAPI
}
/oss/signature`
,
{
sign
,
tmps
}).
then
((
res
)
=>
{
const
serve
=
res
.
data
.
data
;
const
formData
=
new
FormData
();
formData
.
append
(
'
key
'
,
fileName
+
`.mp4`
);
formData
.
append
(
'
bucket
'
,
`iplayabc-worker-queue-videolmh`
);
formData
.
append
(
'
OSSAccessKeyId
'
,
serve
.
OSSAccessKeyId
);
formData
.
append
(
'
policy
'
,
serve
.
policy
);
formData
.
append
(
'
Signature
'
,
serve
.
signature
);
formData
.
append
(
'
success_action_status
'
,
'
200
'
);
formData
.
append
(
'
file
'
,
item
);
item
.
action
=
`
${
protocolStr
}
//iplayabc-worker-queue-videolmh.oss-cn-beijing.aliyuncs.com`
;
// 标记开始时间
this
.
startTime
=
new
Date
().
getTime
();
this
.
uploadStatus
=
null
axios
({
method
:
'
post
'
,
url
:
item
.
action
,
headers
:
{
'
Access-Control-Allow-Origin
'
:
'
*
'
},
onUploadProgress
:
(
progressEvent
)
=>
{
// 更新上传进度
this
.
uploadPercentage
=
Math
.
ceil
(
progressEvent
.
progress
*
100
);
// 计算上传速率
this
.
endTime
=
new
Date
().
getTime
();
const
timeDiff
=
this
.
endTime
-
this
.
startTime
;
this
.
startTime
=
new
Date
().
getTime
();
if
(
timeDiff
===
0
)
return
;
const
sent
=
progressEvent
.
loaded
-
this
.
loaded
;
this
.
loaded
=
progressEvent
.
loaded
;
const
speed
=
(
sent
/
timeDiff
)
*
1000
;
// Convert to KB/s
const
spNum
=
Math
.
round
(
speed
/
1024
);
if
(
spNum
>
1024
)
{
this
.
uploadSpeed
=
`
${(
spNum
/
1024
).
toFixed
(
2
)}
MB/s`
;
}
else
{
this
.
uploadSpeed
=
`
${
spNum
}
KB/s`
;
}
this
.
$emit
(
'
onProgress
'
,
progressEvent
);
},
data
:
formData
}).
then
(
async
event
=>
{
// 上传完成,延迟隐藏状态
const
tid
=
setTimeout
(()
=>
{
this
.
resetUploadVars
();
this
.
uploading
=
false
;
this
.
transcoding
=
true
;
this
.
videoDone
=
false
;
const
idIndex
=
this
.
setTimeoutIds
.
indexOf
(
tid
);
if
(
idIndex
>
-
1
)
{
this
.
setTimeoutIds
.
splice
(
idIndex
,
1
);
}
},
1000
);
// 编辑为成功状态
this
.
uploadStatus
=
"
success
"
;
this
.
videoUrl
=
`
${
baseCDN
}
/
${
fileName
}
_h.mp4`
;
this
.
$emit
(
"
input
"
,
this
.
videoUrl
);
this
.
$emit
(
'
change
'
,
{
url
:
this
.
videoUrl
,
file
:
item
});
this
.
$emit
(
'
success
'
,
{
url
:
this
.
videoUrl
,
file
:
item
});
this
.
setTimeoutIds
.
push
(
tid
);
this
.
uploading
=
false
;
this
.
$message
({
type
:
'
info
'
,
message
:
'
上传成功,开始转码...
'
});
}).
catch
(
err
=>
{
// 报错
const
tid
=
setTimeout
(()
=>
{
this
.
uploading
=
false
;
this
.
transcoding
=
false
;
this
.
videoDone
=
false
;
const
idIndex
=
this
.
setTimeoutIds
.
indexOf
(
tid
);
if
(
idIndex
>
-
1
)
{
this
.
setTimeoutIds
.
splice
(
idIndex
,
1
);
}
},
1000
);
this
.
uploadStatus
=
"
error
"
;
this
.
$emit
(
'
error
'
,
err
);
this
.
setTimeoutIds
.
push
(
tid
);
})
});
});
})
return
false
},
// 检查资源是否可用
checkResourceAvaliable
()
{
return
new
Promise
(
resovle
=>
{
let
intervalId
=
null
;
intervalId
=
setInterval
(()
=>
{
this
.
httpHeadCall
(
this
.
videoUrl
,
(
isSuccess
)
=>
{
if
(
isSuccess
)
{
if
(
intervalId
)
{
const
index
=
this
.
setIntervalIds
.
indexOf
(
intervalId
)
clearInterval
(
intervalId
)
this
.
setIntervalIds
.
splice
(
index
,
1
)
}
resovle
(
true
)
}
});
},
800
)
this
.
setIntervalIds
.
push
(
intervalId
)
})
},
async
download
()
{
const
url
=
this
.
videoUrl
const
fileName
=
url
.
split
(
'
/
'
).
pop
()
const
response
=
await
fetch
(
url
)
//发送GET请求
if
(
response
.
ok
)
{
const
blob
=
await
response
.
blob
()
//获取Blob对象
const
url
=
window
.
URL
.
createObjectURL
(
blob
)
//创建Blob对象的URL
const
link
=
document
.
createElement
(
'
a
'
)
//创建一个新的
<
a
>
元素
link
.
href
=
url
//设置
<
a
>
元素的
href
属性为
Blob
对象的
URL
link
.
download
=
fileName
//设置下载文件的文件名
document
.
body
.
appendChild
(
link
)
//将
<
a
>
元素添加到文档中
link
.
click
()
//模拟点击
<
a
>
元素以触发文件下载
this
.
$message
({
type
:
'
success
'
,
message
:
'
下载完成
'
});
}
else
{
//处理API响应失败的情况,例如显示错误消息
this
.
$message
({
type
:
'
error
'
,
message
:
'
下载失败
'
});
}
},
captureVideo
()
{
const
fullFileName
=
this
.
videoUrl
.
split
(
'
/
'
).
pop
();
const
{
name
,
ext
}
=
this
.
parseFileName
(
fullFileName
)
const
video
=
this
.
$refs
.
refVideo
;
const
cvs
=
document
.
createElement
(
'
canvas
'
);
const
ctx
=
cvs
.
getContext
(
"
2d
"
);
cvs
.
width
=
video
.
videoWidth
;
cvs
.
height
=
video
.
videoHeight
;
ctx
.
drawImage
(
video
,
0
,
0
,
cvs
.
width
,
cvs
.
height
);
const
screenshotDataUrl
=
cvs
.
toDataURL
(
'
image/png
'
);
const
link
=
document
.
createElement
(
'
a
'
)
//创建一个新的
<
a
>
元素
link
.
href
=
screenshotDataUrl
//设置
<
a
>
元素的
href
属性为
Blob
对象的
URL
link
.
download
=
name
+
"
_
"
+
Math
.
floor
(
video
.
currentTime
*
1000
)
+
"
.png
"
//设置下载文件的文件名
document
.
body
.
appendChild
(
link
)
//将
<
a
>
元素添加到文档中
link
.
click
()
//模拟点击
<
a
>
元素以触发文件下载
this
.
$message
({
type
:
'
success
'
,
message
:
'
截图成功
'
});
},
deleteVideo
()
{
this
.
$confirm
(
'
确定要删除吗?
'
,
'
提示
'
,
{
confirmButtonText
:
'
确定
'
,
cancelButtonText
:
'
取消
'
,
type
:
'
warning
'
}).
then
(()
=>
{
this
.
videoUrl
=
""
;
this
.
videoDone
=
false
;
this
.
$emit
(
"
input
"
,
""
);
this
.
$emit
(
'
change
'
,
{
url
:
this
.
videoUrl
,
file
:
null
});
}).
catch
(()
=>
{
this
.
$message
({
type
:
'
info
'
,
message
:
'
已取消删除
'
});
});
},
// 计算文件MD5
getFileMD5
(
file
)
{
return
new
Promise
(
resovle
=>
{
let
fileReader
=
new
FileReader
();
let
Spark
=
new
SparkMD5
.
ArrayBuffer
();
fileReader
.
readAsArrayBuffer
(
file
);
fileReader
.
onload
=
function
(
e
)
{
Spark
.
append
(
e
.
target
.
result
);
let
md5
=
Spark
.
end
();
resovle
(
md5
);
}
})
},
},
// 组件销毁时清除定时器
beforeDestroy
()
{
this
.
setTimeoutIds
.
forEach
(
tid
=>
{
clearTimeout
(
tid
);
});
this
.
setIntervalIds
.
forEach
(
iid
=>
{
clearInterval
(
iid
)
})
}
}
</
script
>
<
style
scoped
>
.buttons
{
text-align
:
center
}
.buttons
.el-button
{
padding
:
4px
12px
;
font-size
:
18px
;
}
/* MESH LOADER */
.mesh-loader
{
overflow
:
hidden
;
height
:
inherit
;
width
:
inherit
;
}
.mesh-loader
.circle
{
width
:
10px
;
height
:
10px
;
position
:
absolute
;
background
:
#f44336
;
border-radius
:
50%
;
margin
:
-5px
;
-webkit-animation
:
mesh
3s
ease-in-out
infinite
-1.5s
;
animation
:
mesh
3s
ease-in-out
infinite
-1.5s
;
}
.mesh-loader
>
div
.circle
:last-child
{
-webkit-animation-delay
:
0s
;
animation-delay
:
0s
;
}
.mesh-loader
>
div
{
position
:
absolute
;
top
:
50%
;
left
:
50%
;
}
.mesh-loader
>
div
:last-child
{
-webkit-transform
:
rotate
(
90deg
);
-ms-transform
:
rotate
(
90deg
);
transform
:
rotate
(
90deg
);
}
@keyframes
mesh
{
0
%
{
-webkit-transform-origin
:
50%
-100%
;
transform-origin
:
50%
-100%
;
-webkit-transform
:
rotate
(
0
);
transform
:
rotate
(
0
);
}
50
%
{
-webkit-transform-origin
:
50%
-100%
;
transform-origin
:
50%
-100%
;
-webkit-transform
:
rotate
(
360deg
);
transform
:
rotate
(
360deg
);
}
50
.1
%
{
-webkit-transform-origin
:
50%
200%
;
transform-origin
:
50%
200%
;
-webkit-transform
:
rotate
(
0deg
);
transform
:
rotate
(
0deg
);
}
100
%
{
-webkit-transform-origin
:
50%
200%
;
transform-origin
:
50%
200%
;
-webkit-transform
:
rotate
(
360deg
);
transform
:
rotate
(
360deg
);
}
}
</
style
>
\ No newline at end of file
form_vue/src/main.js
View file @
c810faa8
...
@@ -9,8 +9,10 @@ Vue.use(ElementUI);
...
@@ -9,8 +9,10 @@ Vue.use(ElementUI);
Vue
.
config
.
productionTip
=
false
Vue
.
config
.
productionTip
=
false
Vue
.
prototype
.
$getData
=
()
=>
{
Vue
.
prototype
.
$getData
=
()
=>
{
console
.
log
(
"
===== getData =====
"
);
return
new
Promise
((
resolve
,
reject
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
(
window
).
courseware
.
getData
((
data
)
=>
{
(
window
).
courseware
.
getData
((
data
)
=>
{
console
.
log
(
data
);
resolve
(
data
)
resolve
(
data
)
},
"
default_key
"
)
},
"
default_key
"
)
})
})
...
@@ -19,6 +21,8 @@ Vue.prototype.$getData = () => {
...
@@ -19,6 +21,8 @@ Vue.prototype.$getData = () => {
Vue
.
prototype
.
$getDPEData
=
getDPEData
;
Vue
.
prototype
.
$getDPEData
=
getDPEData
;
Vue
.
prototype
.
$setData
=
(
data
)
=>
{
Vue
.
prototype
.
$setData
=
(
data
)
=>
{
console
.
log
(
"
===== setData =====
"
);
console
.
log
(
data
);
(
window
).
courseware
.
setData
(
data
,
null
,
"
default_key
"
)
(
window
).
courseware
.
setData
(
data
,
null
,
"
default_key
"
)
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment