Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
suidao
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
xinzhedeai
suidao
Commits
69c832b2
Commit
69c832b2
authored
Mar 03, 2025
by
xinzhedeai
Browse files
Options
Browse Files
Download
Plain Diff
fix: merge
parents
aab20dfd
e1caa03f
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1486 additions
and
2 deletions
+1486
-2
perSet.js
src/api/system/perSet.js
+44
-0
Logo.vue
src/layout/components/Sidebar/Logo.vue
+17
-2
index.vue
src/views/system/perSet/index.vue
+197
-0
index - 副本.vue
src/views/videoManage/index - 副本.vue
+301
-0
index copy.vue
src/views/videoManage/index copy.vue
+296
-0
index.vue
src/views/videoManage/manage/index.vue
+3
-0
index.vue
src/views/videoManage/video/index.vue
+213
-0
VideoPlayer.vue
src/views/videoManage/videoHis/VideoPlayer.vue
+148
-0
index.vue
src/views/videoManage/videoHis/index.vue
+267
-0
No files found.
src/api/system/perSet.js
0 → 100644
View file @
69c832b2
import
request
from
'
@/utils/request
'
// 查询【请填写功能名称】列表
export
function
listSet
(
query
)
{
return
request
({
url
:
'
/slope/set/list
'
,
method
:
'
get
'
,
params
:
query
})
}
// 查询【请填写功能名称】详细
export
function
getSet
(
id
)
{
return
request
({
url
:
'
/system/set/
'
+
id
,
method
:
'
get
'
})
}
// 新增【请填写功能名称】
export
function
addSet
(
data
)
{
return
request
({
url
:
'
/slope/set/add
'
,
method
:
'
post
'
,
data
:
data
})
}
// 修改【请填写功能名称】
export
function
updateSet
(
data
)
{
return
request
({
url
:
'
/slope/set/update
'
,
method
:
'
put
'
,
data
:
data
})
}
// 删除【请填写功能名称】
export
function
delSet
(
id
)
{
return
request
({
url
:
'
/system/set/
'
+
id
,
method
:
'
delete
'
})
}
src/layout/components/Sidebar/Logo.vue
View file @
69c832b2
...
...
@@ -10,7 +10,7 @@
>
<transition
name=
"sidebarLogoFade"
class=
"logo-header"
>
<router-link
class=
"sidebar-logo-link logo-header"
to=
"/"
>
<
!--
<img
:src=
"logo"
class=
"sidebar-logo"
/>
--
>
<
img
:src=
"logo"
class=
"sidebar-logo"
/
>
<h1
class=
"sidebar-title"
:style=
"
{
...
...
@@ -46,7 +46,7 @@
import
logoImg
from
"
@/assets/logo/logo_ks.png
"
;
import
variables
from
"
@/assets/styles/variables.scss
"
;
import
{
Navbar
}
from
"
../../components
"
;
import
{
listSet
}
from
"
@/api/system/perSet
"
;
export
default
{
name
:
"
SidebarLogo
"
,
components
:
{
...
...
@@ -72,6 +72,21 @@ export default {
logo
:
logoImg
,
};
},
created
()
{
this
.
getList
()
},
methods
:
{
getList
()
{
listSet
().
then
(
response
=>
{
if
(
response
.
code
===
200
&&
response
&&
response
.
rows
&&
response
.
rows
[
0
]){
this
.
title
=
response
.
rows
[
0
].
title
||
"
隧道结构稳定性监测系统
"
;
this
.
logo
=
this
.
previewUrl
+
response
.
rows
[
0
].
url
||
logoImg
;
}
});
},
}
};
</
script
>
...
...
src/views/system/perSet/index.vue
0 → 100644
View file @
69c832b2
<
template
>
<div
class=
"app-container"
>
<el-form
ref=
"form"
:model=
"form"
label-width=
"80px"
>
<el-form-item
label=
"标题logo"
prop=
"description"
>
<div
style=
"width: 50%; margin: 0 auto"
>
<img
:src=
"previewUrl + form.url"
style=
"width: 100px; object-fit: fill"
/>
</div>
</el-form-item>
<el-form-item
label=
"logo"
prop=
"url"
>
<el-upload
ref=
"upload"
:action=
"uploadUrl"
:on-success=
"
(res, file) =>
{
handleUploadSuccess(res, file, 'pic');
}
"
:on-error="handleUploadError"
name="file"
:show-file-list="false"
:headers="headers"
accept=".jpg, .jpeg, .png"
>
<el-button
size=
"small"
>
选择标题logo
<i
class=
"el-icon-upload el-icon--right"
></i>
</el-button>
<div
slot=
"tip"
class=
"el-upload__tip"
>
只能上传jpg/png/jpeg文件
</div>
</el-upload>
</el-form-item>
<el-form-item
label=
"标题"
prop=
"title"
>
<el-input
v-model=
"form.title"
placeholder=
"请输入标题"
/>
</el-form-item>
</el-form>
<div
slot=
"footer"
class=
"dialog-footer"
>
<el-button
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
</div>
</div>
</
template
>
<
script
>
import
{
listSet
,
getSet
,
delSet
,
addSet
,
updateSet
}
from
"
@/api/system/perSet
"
;
import
{
getToken
}
from
"
@/utils/auth
"
;
export
default
{
name
:
"
Set
"
,
data
()
{
return
{
previewUrl
:
process
.
env
.
VUE_APP_BASE_API
,
uploadUrl
:
process
.
env
.
VUE_APP_BASE_API
+
"
/common/upload
"
,
// 上传的图片服务器地址
headers
:
{
Authorization
:
"
Bearer
"
+
getToken
(),
},
// 遮罩层
loading
:
true
,
// 选中数组
ids
:
[],
// 非单个禁用
single
:
true
,
// 非多个禁用
multiple
:
true
,
// 显示搜索条件
showSearch
:
true
,
// 总条数
total
:
0
,
// 【个性化设置】表格数据
setList
:
[],
// 弹出层标题
title
:
""
,
// 是否显示弹出层
open
:
false
,
// 查询参数
queryParams
:
{
pageNum
:
1
,
pageSize
:
10
,
title
:
null
,
url
:
null
},
// 表单参数
form
:
{
id
:
null
,
url
:
null
,
title
:
null
,
},
// 表单校验
rules
:
{
},
upload
:
{
// 是否显示弹出层(用户导入)
open
:
false
,
title
:
""
,
// 是否禁用上传
isUploading
:
false
,
updateSupport
:
0
,
// 设置上传的请求头部
headers
:
{
Authorization
:
"
Bearer
"
+
getToken
()
},
// 上传的地址
url
:
process
.
env
.
VUE_APP_BASE_API
+
"
/common/upload
"
},
};
},
created
()
{
this
.
getList
()
},
methods
:
{
getList
()
{
listSet
().
then
(
response
=>
{
if
(
response
.
code
===
200
&&
response
&&
response
.
rows
&&
response
.
rows
[
0
]){
this
.
form
=
response
.
rows
[
0
];
}
else
{
this
.
form
=
{
id
:
null
,
url
:
null
,
title
:
null
,
}
}
});
},
submitForm
()
{
this
.
$refs
[
"
form
"
].
validate
(
valid
=>
{
if
(
valid
)
{
if
(
this
.
form
.
id
!=
null
)
{
updateSet
(
this
.
form
).
then
(
response
=>
{
this
.
$modal
.
msgSuccess
(
"
修改成功
"
);
this
.
getList
();
});
}
else
{
addSet
(
this
.
form
).
then
(
response
=>
{
this
.
$modal
.
msgSuccess
(
"
新增成功
"
);
this
.
getList
();
});
}
}
});
},
/**文档预览**/
preView
(
file
)
{
console
.
log
(
file
);
let
originUrl
=
process
.
env
.
VUE_APP_BASE_API
+
file
;
//要预览文件的访问地址'
window
.
open
(
originUrl
);
},
//上传图片
// 上传前校检格式和大小
handleBeforeUpload
(
file
)
{
// 校检文件大小
if
(
this
.
fileSize
)
{
const
isLt
=
file
.
size
/
1024
/
1024
<
this
.
fileSize
;
if
(
!
isLt
)
{
this
.
$message
.
error
(
`上传文件大小不能超过
${
this
.
fileSize
}
MB!`
);
return
false
;
}
}
return
true
;
},
handleUploadSuccess
(
response
,
file
,
type
)
{
console
.
log
(
response
,
file
,
type
,
'
response, file, type
'
);
// 获取富文本组件实例
// 如果上传成功
if
(
response
.
code
==
200
)
{
if
(
type
===
"
pic
"
)
{
this
.
form
.
url
=
response
.
fileName
;
}
if
(
type
===
"
pdf
"
)
{
this
.
form
.
file
.
push
(
response
.
fileName
);
this
.
form
.
fileName
.
push
(
response
.
originalFilename
);
}
// 获取光标所在位置
}
else
{
this
.
$message
.
error
(
"
上传失败
"
);
}
},
delName
(
index
)
{
// 在点击时删除数组中的相应项
this
.
form
.
file
.
splice
(
index
,
1
);
this
.
form
.
fileName
.
splice
(
index
,
1
);
},
handleUploadError
()
{
this
.
$message
.
error
(
"
上传失败
"
);
},
}
};
</
script
>
<
style
lang=
"scss"
scoped
>
.app-container
{
background-color
:
#fff
;
}
</
style
>
\ No newline at end of file
src/views/videoManage/index - 副本.vue
0 → 100644
View file @
69c832b2
<
template
>
<div
class=
"box-main"
>
<div
class=
"card"
>
<div
class=
"top-title"
>
<span
class=
"line"
></span
>
<span
class=
"title-text"
>
实时监控
</span>
</div>
<el-form
:model=
"queryParams"
size=
"small"
:inline=
"true"
label-width=
"90px"
class=
"searchform"
>
<el-form-item
label=
"摄像头编号:"
prop=
"equipmentName"
>
<el-input
v-model=
"queryParams.equipmentName"
placeholder=
"请输入摄像头编号"
/>
</el-form-item>
<el-form-item
label=
"时间:"
prop=
"time"
>
<el-date-picker
:default-time=
"['00:00:00', '23:59:59']"
style=
"width: 350px !important;"
v-model=
"queryParams.time"
type=
"datetimerange"
range-separator=
"至"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
clearable
value-format=
"yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
icon=
"el-icon-search"
@
click=
"toSearch"
style=
"margin-left: 20px;"
>
搜索
</el-button>
</el-form-item>
</el-form>
<div
class=
"box-bottom"
>
<div
class=
"boxvideo"
>
<video-player
class=
"video-player vjs-custom-skin"
ref=
"videoPlayer"
:playsinline=
"true"
:options=
"playerOptions"
/>
</div>
<div
class=
"box-table"
>
<el-table
:data=
"tableData"
border
>
<el-table-column
prop=
"equno"
label=
"摄像头编号"
align=
"center"
>
</el-table-column>
<el-table-column
prop=
"time"
label=
"监控时间"
align=
"center"
>
</el-table-column>
<el-table-column
prop=
"address"
label=
"操作"
align=
"center"
>
<template
slot-scope=
"scope"
>
<el-button
size=
"mini"
type=
"primary"
icon=
"el-icon-edit"
@
click.native.prevent=
"addEqupment()"
>
查看记录
</el-button
>
</
template
>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</template>
<
script
>
import
'
video.js/dist/video-js.css
'
import
{
videoPlayer
}
from
'
vue-video-player
'
export
default
{
components
:
{
videoPlayer
},
data
()
{
return
{
queryParams
:{
},
tableData
:
[{
date
:
'
2016-05-02
'
,
equno
:
'
1
'
,
},
{
date
:
'
2016-05-04
'
,
equno
:
'
2
'
,
},
{
date
:
'
2016-05-01
'
,
equno
:
'
3
'
,
},
{
date
:
'
2016-05-03
'
,
equno
:
'
4
'
,
}],
playerOptions
:
{
playbackRates
:
[
0.5
,
1.0
,
1.5
,
2.0
],
// 可选的播放速度
autoplay
:
false
,
// 如果为true,浏览器准备好时开始回放。
muted
:
false
,
// 默认情况下将会消除任何音频。
loop
:
false
,
// 是否视频一结束就重新开始。
preload
:
'
auto
'
,
// 建议浏览器在
<
video
>
加载元素后是否应该开始下载视频数据。
auto
浏览器选择最佳行为
,
立即开始加载视频(如果浏览器支持)
language
:
'
zh-CN
'
,
aspectRatio
:
'
16:9
'
,
// 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid
:
true
,
// 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources
:
[{
type
:
"
video/mp4
"
,
// 类型
src
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
// url地址
}],
notSupportedMessage
:
'
此视频暂无法播放,请稍后再试
'
,
// 允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar
:
{
timeDivider
:
true
,
// 当前时间和持续时间的分隔符
durationDisplay
:
true
,
// 显示持续时间
remainingTimeDisplay
:
false
,
// 是否显示剩余时间功能
fullscreenToggle
:
true
// 是否显示全屏按钮
}
}
}
},
methods
:{
// 播放回调
onPlayerPlay
(
player
)
{
console
.
log
(
'
player play!
'
,
player
)
},
// 暂停回调
onPlayerPause
(
player
)
{
console
.
log
(
'
player pause!
'
,
player
)
},
// 视频播完回调
onPlayerEnded
(
$event
)
{
console
.
log
(
player
)
},
// DOM元素上的readyState更改导致播放停止
onPlayerWaiting
(
$event
)
{
console
.
log
(
player
)
},
// 已开始播放回调
onPlayerPlaying
(
$event
)
{
console
.
log
(
player
)
},
// 当播放器在当前播放位置下载数据时触发
onPlayerLoadeddata
(
$event
)
{
console
.
log
(
player
)
},
// 当前播放位置发生变化时触发。
onPlayerTimeupdate
(
$event
)
{
console
.
log
(
player
)
},
//媒体的readyState为HAVE_FUTURE_DATA或更高
onPlayerCanplay
(
player
)
{
// console.log('player Canplay!', player)
},
//媒体的readyState为HAVE_ENOUGH_DATA或更高。这意味着可以在不缓冲的情况下播放整个媒体文件。
onPlayerCanplaythrough
(
player
)
{
// console.log('player Canplaythrough!', player)
},
//播放状态改变回调
playerStateChanged
(
playerCurrentState
)
{
console
.
log
(
'
player current update state
'
,
playerCurrentState
)
},
//将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。
playerReadied
(
player
)
{
console
.
log
(
'
example player 1 readied
'
,
player
);
}
}
}
</
script
>
<
style
rel=
"stylesheet/scss"
lang=
"scss"
scope
>
.box-main
{
width
:
98%
;
height
:
50rem
;
margin
:
0
auto
;
.card
{
width
:
100%
;
height
:
100%
;
background-color
:
#fff
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
.top-title
{
width
:
100%
;
height
:
75px
;
display
:
flex
;
justify-content
:
flex-start
;
align-items
:center
;
padding-left
:
50px
;
.line
{
width
:
6px
;
height
:
22px
;
background
:
#0d85f4
;
}
.title-text
{
font-size
:
18px
;
font-weight
:bold
;
color
:
#333333
;
line-height
:
30px
;
padding-left
:
10px
;
}
}
}
.searchform
{
width
:
100%
;
height
:
80px
;
background-color
:
#fff
;
display
:
flex
;
justify-content
:
flex-start
;
align-items
:
center
;
padding-left
:
60px
;
.el-form-item
{
margin-bottom
:
0px
!
important
;
}
}
.box-bottom
{
width
:
100%
;
height
:
80%
;
display
:
flex
;
align-items
:
center
;
}
.boxvideo
{
width
:
50%
;
height
:
80%
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
}
.video-player
{
width
:
98%
!
important
;
height
:
98%
!
important
;
}
.vjs-paused
.vjs-big-play-button
,
.vjs-paused.vjs-has-started
.vjs-big-play-button
{
display
:
block
;
}
.video-js
.vjs-big-play-button
{
font-size
:
1
.5rem
;
line-height
:
4
.5rem
;
height
:
4
.5rem
;
width
:
4
.5rem
;
border-radius
:
2
.5rem
;
background-color
:
#73859f
;
background-color
:
rgba
(
115
,
133
,
159
,
.5
);
border-width
:
0
.15rem
;
margin-top
:
25%
;
margin-left
:
45%
;
}
/* 中间的播放箭头 */
.vjs-big-play-button
.vjs-icon-placeholder
{
font-size
:
3
.63rem
;
}
/* 加载圆圈 */
.vjs-loading-spinner
{
font-size
:
2
.5em
;
width
:
2em
;
height
:
2em
;
border-radius
:
1em
;
margin-top
:
-1em
;
margin-left
:
-1
.5em
;
}
.box-table
{
width
:
50%
;
height
:
80%
;
.el-table
{
width
:
90%
;
height
:
100%
;
margin-left
:
40px
}
}
}
</
style
>
src/views/videoManage/index copy.vue
0 → 100644
View file @
69c832b2
<
template
>
<div
class=
"box-main"
>
<div
class=
"card"
>
<div
class=
"top-title"
>
<span
class=
"line"
></span
>
<span
class=
"title-text"
>
实时监控
</span>
</div>
<el-form
:model=
"queryParams"
size=
"small"
:inline=
"true"
label-width=
"90px"
class=
"searchform"
>
<el-form-item
label=
"摄像头编号:"
prop=
"equipmentName"
>
<el-input
v-model=
"queryParams.equipmentName"
placeholder=
"请输入摄像头编号"
/>
</el-form-item>
<el-form-item
label=
"时间:"
prop=
"time"
>
<el-date-picker
:default-time=
"['00:00:00', '23:59:59']"
style=
"width: 350px !important;"
v-model=
"queryParams.time"
type=
"datetimerange"
range-separator=
"至"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
clearable
value-format=
"yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
icon=
"el-icon-search"
@
click=
"toSearch"
style=
"margin-left: 20px;"
>
搜索
</el-button>
</el-form-item>
</el-form>
<div
class=
"box-bottom"
>
<div
class=
"boxvideo"
>
<video-player
class=
"video-player vjs-custom-skin"
ref=
"videoPlayer"
:playsinline=
"true"
:options=
"playerOptions"
/>
</div>
<div
class=
"box-table"
>
<el-table
:data=
"tableData"
border
>
<el-table-column
prop=
"equno"
label=
"摄像头编号"
align=
"center"
>
</el-table-column>
<el-table-column
prop=
"time"
label=
"监控时间"
align=
"center"
>
</el-table-column>
<el-table-column
prop=
"address"
label=
"操作"
align=
"center"
>
<template
slot-scope=
"scope"
>
<el-button
size=
"mini"
type=
"primary"
icon=
"el-icon-edit"
@
click.native.prevent=
"addEqupment()"
>
查看记录
</el-button
>
</
template
>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</template>
<
script
>
import
'
video.js/dist/video-js.css
'
import
{
videoPlayer
}
from
'
vue-video-player
'
export
default
{
mounted
()
{
this
.
$watermark
.
set
(
"
"
,
this
.
$refs
.
content
)
},
beforeDestroy
()
{
this
.
$watermark
.
set
(
""
,
this
.
$refs
.
content
);
},
components
:
{
videoPlayer
},
data
()
{
return
{
queryParams
:{
},
tableData
:
[{
date
:
'
2016-05-02
'
,
equno
:
'
1
'
,
},
{
date
:
'
2016-05-04
'
,
equno
:
'
2
'
,
},
{
date
:
'
2016-05-01
'
,
equno
:
'
3
'
,
},
{
date
:
'
2016-05-03
'
,
equno
:
'
4
'
,
}],
playerOptions
:
{
playbackRates
:
[
0.5
,
1.0
,
1.5
,
2.0
],
// 可选的播放速度
autoplay
:
false
,
// 如果为true,浏览器准备好时开始回放。
muted
:
false
,
// 默认情况下将会消除任何音频。
loop
:
false
,
// 是否视频一结束就重新开始。
preload
:
'
auto
'
,
// 建议浏览器在
<
video
>
加载元素后是否应该开始下载视频数据。
auto
浏览器选择最佳行为
,
立即开始加载视频(如果浏览器支持)
language
:
'
zh-CN
'
,
aspectRatio
:
'
16:9
'
,
// 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid
:
true
,
// 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources
:
[{
type
:
"
video/mp4
"
,
// 类型
src
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
// url地址
}],
notSupportedMessage
:
'
此视频暂无法播放,请稍后再试
'
,
// 允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar
:
{
timeDivider
:
true
,
// 当前时间和持续时间的分隔符
durationDisplay
:
true
,
// 显示持续时间
remainingTimeDisplay
:
false
,
// 是否显示剩余时间功能
fullscreenToggle
:
true
// 是否显示全屏按钮
}
}
}
},
methods
:{
//播放回调
onPlayerPlay
(
player
)
{
console
.
log
(
'
player play!
'
,
player
)
},
// 暂停回调
onPlayerPause
(
player
)
{
console
.
log
(
'
player pause!
'
,
player
)
},
// 视频播完回调
onPlayerEnded
(
$event
)
{
console
.
log
(
player
)
},
// DOM元素上的readyState更改导致播放停止
onPlayerWaiting
(
$event
)
{
console
.
log
(
player
)
},
// 已开始播放回调
onPlayerPlaying
(
$event
)
{
console
.
log
(
player
)
},
// 当播放器在当前播放位置下载数据时触发
onPlayerLoadeddata
(
$event
)
{
console
.
log
(
player
)
},
// 当前播放位置发生变化时触发。
onPlayerTimeupdate
(
$event
)
{
console
.
log
(
player
)
},
//媒体的readyState为HAVE_FUTURE_DATA或更高
onPlayerCanplay
(
player
)
{
},
//媒体的readyState为HAVE_ENOUGH_DATA或更高。这意味着可以在不缓冲的情况下播放整个媒体文件。
onPlayerCanplaythrough
(
player
)
{
// console.log('player Canplaythrough!', player)
},
//播放状态改变回调
playerStateChanged
(
playerCurrentState
)
{
console
.
log
(
'
player current update state
'
,
playerCurrentState
)
},
//将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。
playerReadied
(
player
)
{
console
.
log
(
'
example player 1 readied
'
,
player
);
}
}
}
</
script
>
<
style
rel=
"stylesheet/scss"
lang=
"scss"
scope
>
.box-main
{
width
:
98%
;
height
:
50rem
;
margin
:
0
auto
;
.card
{
width
:
100%
;
height
:
100%
;
background-color
:
#fff
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
.top-title
{
width
:
100%
;
height
:
75px
;
display
:
flex
;
justify-content
:
flex-start
;
align-items
:center
;
padding-left
:
50px
;
.line
{
width
:
6px
;
height
:
22px
;
background
:
#0d85f4
;
}
.title-text
{
font-size
:
18px
;
font-weight
:bold
;
color
:
#333333
;
line-height
:
30px
;
padding-left
:
10px
;
}
}
}
.searchform
{
width
:
100%
;
height
:
80px
;
background-color
:
#fff
;
display
:
flex
;
justify-content
:
flex-start
;
align-items
:
center
;
padding-left
:
60px
;
.el-form-item
{
margin-bottom
:
0px
!
important
;
}
}
.box-bottom
{
width
:
100%
;
height
:
80%
;
display
:
flex
;
align-items
:
center
;
}
.boxvideo
{
width
:
50%
;
height
:
80%
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
}
.video-player
{
width
:
98%
!
important
;
height
:
98%
!
important
;
}
.vjs-paused
.vjs-big-play-button
,
.vjs-paused.vjs-has-started
.vjs-big-play-button
{
display
:
block
;
}
.video-js
.vjs-big-play-button
{
font-size
:
1
.5rem
;
line-height
:
4
.5rem
;
height
:
4
.5rem
;
width
:
4
.5rem
;
border-radius
:
2
.5rem
;
background-color
:
#73859f
;
background-color
:
rgba
(
115
,
133
,
159
,
.5
);
border-width
:
0
.15rem
;
margin-top
:
25%
;
margin-left
:
45%
;
}
/* 中间的播放箭头 */
.vjs-big-play-button
.vjs-icon-placeholder
{
font-size
:
3
.63rem
;
}
/* 加载圆圈 */
.vjs-loading-spinner
{
font-size
:
2
.5em
;
width
:
2em
;
height
:
2em
;
border-radius
:
1em
;
margin-top
:
-1em
;
margin-left
:
-1
.5em
;
}
.box-table
{
width
:
50%
;
height
:
80%
;
.el-table
{
width
:
90%
;
height
:
100%
;
margin-left
:
40px
}
}
}
</
style
>
src/views/videoManage/manage/index.vue
0 → 100644
View file @
69c832b2
<
template
>
<div></div>
</
template
>
\ No newline at end of file
src/views/videoManage/video/index.vue
0 → 100644
View file @
69c832b2
<
template
>
<div
class=
"app-container"
>
<!-- 布局选择器 -->
<el-select
v-model=
"currentGrid"
class=
"grid-select"
>
<el-option
v-for=
"grid in [1,4,6,9,16]"
:key=
"grid"
:label=
"`$
{grid}宫格`"
:value="grid"
/>
</el-select>
<!-- 视频容器 -->
<div
class=
"video-container"
:style=
"gridStyle"
>
<div
v-for=
"video in currentVideos"
:key=
"video.id"
class=
"video-wrapper"
>
<video
ref=
"videoPlayers"
controls
class=
"video-element"
:src=
"video.url"
muted
playsinline
></video>
</div>
</div>
<!-- 分页控制 -->
<div
class=
"pagination"
v-if=
"totalPages > 1"
>
<el-button
@
click=
"prevPage"
:disabled=
"currentPage === 1"
>
<i
class=
"el-icon-arrow-left"
></i>
</el-button>
<span
class=
"page-text"
>
{{
currentPage
}}
/
{{
totalPages
}}
</span>
<el-button
@
click=
"nextPage"
:disabled=
"currentPage === totalPages"
>
<i
class=
"el-icon-arrow-right"
></i>
</el-button>
</div>
</div>
</
template
>
<
script
>
export
default
{
data
()
{
return
{
currentGrid
:
4
,
currentPage
:
1
,
videoList
:
[
{
id
:
1
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
aaa
'
},
{
id
:
2
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
bbb
'
},
{
id
:
3
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
ccc
'
},
{
id
:
4
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
ddd
'
},
{
id
:
5
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
eee
'
},
{
id
:
6
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
fff
'
},
{
id
:
7
,
url
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
,
name
:
'
ggg
'
},
],
gridLayoutMap
:
{
1
:
{
cols
:
1
,
rows
:
1
},
4
:
{
cols
:
2
,
rows
:
2
},
6
:
{
cols
:
3
,
rows
:
2
},
9
:
{
cols
:
3
,
rows
:
3
},
16
:
{
cols
:
4
,
rows
:
4
}
}
}
},
computed
:
{
gridStyle
()
{
const
{
cols
,
rows
}
=
this
.
gridLayoutMap
[
this
.
currentGrid
]
return
{
gridTemplateColumns
:
`repeat(
${
cols
}
, 1fr)`
,
gridTemplateRows
:
`repeat(
${
rows
}
, 1fr)`
,
aspectRatio
:
`
${
cols
}
/
${
rows
}
`
}
},
currentVideos
()
{
const
start
=
(
this
.
currentPage
-
1
)
*
this
.
currentGrid
return
this
.
videoList
.
slice
(
start
,
start
+
this
.
currentGrid
)
},
totalPages
()
{
return
Math
.
ceil
(
this
.
videoList
.
length
/
this
.
currentGrid
)
}
},
watch
:
{
currentGrid
()
{
this
.
currentPage
=
1
this
.
$nextTick
(
this
.
playCurrentPage
)
},
currentPage
()
{
this
.
$nextTick
(
this
.
playCurrentPage
)
}
},
methods
:
{
// 播放当前页所有视频
async
playCurrentPage
()
{
try
{
// 暂停所有视频
await
this
.
pauseAllVideos
()
// 等待DOM更新
await
this
.
$nextTick
()
// 获取当前页视频元素
const
currentPlayers
=
(
this
.
$refs
.
videoPlayers
||
[])
.
slice
(
0
,
this
.
currentVideos
.
length
)
// 批量播放处理
const
playTasks
=
currentPlayers
.
map
(
player
=>
{
player
.
muted
=
true
return
player
.
play
().
catch
(
error
=>
{
if
(
error
.
name
!==
'
AbortError
'
)
{
console
.
warn
(
'
视频播放失败:
'
,
error
)
}
})
})
await
Promise
.
all
(
playTasks
)
}
catch
(
error
)
{
console
.
error
(
'
页面播放异常:
'
,
error
)
}
},
// 暂停所有视频(优化版)
async
pauseAllVideos
()
{
const
allPlayers
=
this
.
$refs
.
videoPlayers
||
[]
const
pauseTasks
=
allPlayers
.
map
(
player
=>
{
return
new
Promise
(
resolve
=>
{
if
(
!
player
.
paused
)
{
player
.
addEventListener
(
'
pause
'
,
resolve
,
{
once
:
true
})
player
.
pause
()
}
else
{
resolve
()
}
})
})
await
Promise
.
all
(
pauseTasks
)
},
prevPage
()
{
if
(
this
.
currentPage
>
1
)
this
.
currentPage
--
},
nextPage
()
{
if
(
this
.
currentPage
<
this
.
totalPages
)
this
.
currentPage
++
}
},
mounted
()
{
this
.
playCurrentPage
()
}
}
</
script
>
<
style
scoped
>
.app-container
{
padding
:
20px
;
}
.grid-select
{
margin-bottom
:
20px
;
width
:
200px
;
}
.video-container
{
display
:
grid
;
gap
:
10px
;
margin
:
20px
0
;
background
:
#2c3e50
;
border-radius
:
8px
;
padding
:
10px
;
min-height
:
300px
;
}
.video-wrapper
{
position
:
relative
;
padding-top
:
56.25%
;
background
:
#000
;
border-radius
:
4px
;
overflow
:
hidden
;
}
.video-element
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
object-fit
:
cover
;
}
.pagination
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
gap
:
20px
;
margin-top
:
20px
;
}
.page-text
{
font-size
:
16px
;
color
:
#409EFF
;
font-weight
:
500
;
}
</
style
>
\ No newline at end of file
src/views/videoManage/videoHis/VideoPlayer.vue
0 → 100644
View file @
69c832b2
<
template
>
<div
class=
"video-player-component"
>
<!-- 视频播放器 -->
<video
ref=
"videoPlayer"
class=
"video"
@
timeupdate=
"onTimeUpdate"
>
<source
:src=
"videoUrl"
type=
"video/mp4"
/>
<span>
Your browser does not support the video tag
</span>
</video>
<!-- 控制栏 -->
<div
class=
"control-bar"
>
<button
@
click=
"playPause"
:class=
"
{ playing }">
{{
playing
?
'
暂停
'
:
'
播放
'
}}
</button>
<div
class=
"progress"
@
click=
"seekVideo"
>
<div
class=
"progress-filled"
:style=
"
{ width: `${currentTimePercentage}%` }">
</div>
<div
class=
"progress-handle"
:style=
"
{ left: `${currentTimePercentage}%` }">
</div>
</div>
<div
class=
"status"
>
<span
class=
"time"
>
{{
formattedCurrentTime
}}
</span>
/
<span
class=
"duration"
>
{{
formattedDuration
}}
</span>
</div>
</div>
</div>
</
template
>
<
script
>
export
default
{
name
:
'
VideoPlayer
'
,
props
:
{
videoUrl
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
playing
:
false
,
currentTime
:
0
,
duration
:
0
,
speed
:
1
,
};
},
computed
:
{
currentTimePercentage
()
{
if
(
this
.
duration
===
0
)
return
0
;
return
(
this
.
currentTime
/
this
.
duration
)
*
100
;
},
formattedCurrentTime
()
{
return
this
.
formatTime
(
this
.
currentTime
);
},
formattedDuration
()
{
return
this
.
formatTime
(
this
.
duration
);
},
},
mounted
()
{
this
.
$nextTick
(()
=>
{
this
.
$refs
.
videoPlayer
.
addEventListener
(
'
loadedmetadata
'
,
this
.
onLoadedMetadata
);
});
},
beforeDestroy
()
{
this
.
$refs
.
videoPlayer
.
removeEventListener
(
'
loadedmetadata
'
,
this
.
onLoadedMetadata
);
},
methods
:
{
playPause
()
{
const
video
=
this
.
$refs
.
videoPlayer
;
if
(
this
.
playing
)
{
video
.
pause
();
}
else
{
video
.
play
();
}
this
.
playing
=
!
this
.
playing
;
},
onLoadedMetadata
()
{
this
.
duration
=
this
.
$refs
.
videoPlayer
.
duration
;
},
onTimeUpdate
()
{
this
.
currentTime
=
this
.
$refs
.
videoPlayer
.
currentTime
;
},
seekVideo
(
event
)
{
if
(
!
this
.
duration
)
return
;
const
progress
=
this
.
$refs
.
videoPlayer
.
querySelector
(
'
.progress
'
);
const
rect
=
progress
.
getBoundingClientRect
();
const
x
=
event
.
clientX
-
rect
.
left
;
const
percentage
=
x
/
rect
.
width
;
this
.
currentTime
=
this
.
duration
*
percentage
;
this
.
$refs
.
videoPlayer
.
currentTime
=
this
.
currentTime
;
},
formatTime
(
timeInSeconds
)
{
const
date
=
new
Date
(
timeInSeconds
*
1000
);
const
hours
=
date
.
getUTCHours
();
const
minutes
=
date
.
getUTCMinutes
().
toString
().
padStart
(
2
,
'
0
'
);
const
seconds
=
date
.
getUTCSeconds
().
toString
().
padStart
(
2
,
'
0
'
);
return
hours
>
0
?
`
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
:
`
${
minutes
}
:
${
seconds
}
`
;
},
},
};
</
script
>
<
style
scoped
>
.video-player-component
{
width
:
100%
;
}
.video
{
width
:
100%
;
height
:
auto
;
}
.control-bar
{
background-color
:
#333
;
padding
:
10px
;
margin-top
:
10px
;
display
:
flex
;
align-items
:
center
;
}
button
{
margin
:
0
5px
;
padding
:
5px
10px
;
color
:
white
;
border
:
none
;
border-radius
:
3px
;
background-color
:
#444
;
}
button
:hover
{
background-color
:
#555
;
}
.progress
{
position
:
relative
;
width
:
250px
;
height
:
4px
;
background-color
:
#555
;
cursor
:
pointer
;
margin
:
0
10px
;
}
.progress-filled
{
height
:
100%
;
background-color
:
#4CAF50
;
position
:
absolute
;
left
:
0
;
top
:
0
;
}
.progress-handle
{
width
:
10px
\ No newline at end of file
src/views/videoManage/videoHis/index.vue
0 → 100644
View file @
69c832b2
<
template
>
<div
class=
"surveillance-player"
>
<!-- 摄像头选择 -->
<div
class=
"camera-selector"
>
<select
v-model=
"selectedCamera"
>
<option
v-for=
"camera in cameras"
:key=
"camera.id"
:value=
"camera"
>
{{
camera
.
name
}}
</option>
</select>
</div>
<!-- 时间选择 -->
<div
class=
"time-selector"
>
<div>
<label
for=
"startTime"
>
开始时间:
</label>
<input
type=
"datetime-local"
id=
"startTime"
v-model=
"startTime"
/>
</div>
<div>
<label
for=
"endTime"
>
结束时间:
</label>
<input
type=
"datetime-local"
id=
"endTime"
v-model=
"endTime"
/>
</div>
<button
@
click=
"loadVideo"
>
加载视频
</button>
</div>
<!-- 视频播放器 -->
<div
class=
"video-player"
>
<video
ref=
"videoPlayer"
@
timeupdate=
"onTimeUpdate"
>
<source
:src=
"videoUrl"
type=
"video/mp4"
/>
<span>
Your browser does not support the video tag
</span>
</video>
<!-- 控制栏 -->
<div
class=
"control-bar"
>
<button
@
click=
"playPause"
:class=
"
{ playing }">
{{
playing
?
'
暂停
'
:
'
播放
'
}}
</button>
<div
class=
"progress"
@
click=
"seekVideo"
>
<div
class=
"progress-filled"
:style=
"
{ width: `${currentTimePercentage}%` }">
</div>
<div
class=
"progress-handle"
:style=
"
{ left: `${currentTimePercentage}%` }">
</div>
</div>
<div
class=
"status"
>
<span
class=
"time"
>
{{
formattedCurrentTime
}}
</span>
/
<span
class=
"duration"
>
{{
formattedDuration
}}
</span>
</div>
<!-- 播放速度控制 -->
<div
class=
"speed-controls"
>
<button
@
click=
"setSpeed(0.5)"
>
0.5x
</button>
<button
@
click=
"setSpeed(1)"
>
1x
</button>
<button
@
click=
"setSpeed(2)"
>
2x
</button>
</div>
<!-- 时间轴 -->
<div
class=
"timeline"
>
<div
class=
"timeline-slider"
ref=
"timelineSlider"
@
mousedown=
"startDrag"
></div>
</div>
</div>
</div>
</div>
</
template
>
<
script
>
export
default
{
name
:
'
SurveillancePlayer
'
,
data
()
{
return
{
// 摄像头列表
cameras
:
[
{
id
:
1
,
name
:
'
入口
'
,
videoUrl
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
},
{
id
:
2
,
name
:
'
出口
'
,
videoUrl
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
},
{
id
:
3
,
name
:
'
大厅
'
,
videoUrl
:
'
http://vjs.zencdn.net/v/oceans.mp4
'
}
// ... 更多摄像头
],
selectedCamera
:
null
,
startTime
:
new
Date
().
toISOString
().
substr
(
0
,
16
),
endTime
:
new
Date
().
toISOString
().
substr
(
0
,
16
),
videoUrl
:
''
,
playing
:
false
,
currentTime
:
0
,
duration
:
0
,
timeDragging
:
false
,
speed
:
1
,
};
},
computed
:
{
currentTimePercentage
()
{
if
(
this
.
duration
===
0
)
return
0
;
return
(
this
.
currentTime
/
this
.
duration
)
*
100
;
},
formattedCurrentTime
()
{
return
this
.
formatTime
(
this
.
currentTime
);
},
formattedDuration
()
{
return
this
.
formatTime
(
this
.
duration
);
},
},
methods
:
{
// 加载视频
loadVideo
()
{
this
.
videoUrl
=
this
.
selectedCamera
.
videoUrl
;
// 实际应用中需根据 startTime 和 endTime 动态拼接视频 URL
this
.
$refs
.
videoPlayer
.
load
();
},
// 播放/暂停
playPause
()
{
const
video
=
this
.
$refs
.
videoPlayer
;
if
(
this
.
playing
)
{
video
.
pause
();
}
else
{
video
.
play
();
}
this
.
playing
=
!
this
.
playing
;
},
// 更新播放时间
onTimeUpdate
()
{
this
.
currentTime
=
this
.
$refs
.
videoPlayer
.
currentTime
;
},
// 跳转到特定时间
seekVideo
(
event
)
{
if
(
!
this
.
duration
||
!
event
)
return
;
const
progress
=
this
.
$refs
.
videoPlayer
.
querySelector
(
'
.progress
'
);
const
rect
=
progress
.
getBoundingClientRect
();
const
x
=
event
.
clientX
-
rect
.
left
;
const
percentage
=
x
/
rect
.
width
;
const
newTime
=
this
.
duration
*
percentage
;
this
.
$refs
.
videoPlayer
.
currentTime
=
newTime
;
},
// 设置播放速度
setSpeed
(
speed
)
{
this
.
speed
=
speed
;
this
.
$refs
.
videoPlayer
.
playbackRate
=
this
.
speed
;
},
// 格式化时间
formatTime
(
timeInSeconds
)
{
const
date
=
new
Date
(
timeInSeconds
*
1000
);
const
hours
=
date
.
getUTCHours
();
const
minutes
=
date
.
getUTCMinutes
().
toString
().
padStart
(
2
,
'
0
'
);
const
seconds
=
date
.
getUTCSeconds
().
toString
().
padStart
(
2
,
'
0
'
);
return
hours
>
0
?
`
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
:
`
${
minutes
}
:
${
seconds
}
`
;
},
// 开始拖动时间轴
startDrag
()
{
this
.
timeDragging
=
true
;
document
.
addEventListener
(
'
mousemove
'
,
this
.
onMouseMove
);
document
.
addEventListener
(
'
mouseup
'
,
this
.
stopDrag
);
},
// 响应鼠标移动
onMouseMove
(
event
)
{
if
(
!
this
.
timeDragging
)
return
;
const
sliderRect
=
this
.
$refs
.
timelineSlider
.
getBoundingClientRect
();
const
x
=
event
.
clientX
-
sliderRect
.
left
;
const
percentage
=
x
/
sliderRect
.
width
;
const
newTime
=
this
.
duration
*
percentage
;
this
.
$refs
.
videoPlayer
.
currentTime
=
newTime
;
},
// 停止拖动
stopDrag
()
{
this
.
timeDragging
=
false
;
document
.
removeEventListener
(
'
mousemove
'
,
this
.
onMouseMove
);
document
.
removeEventListener
(
'
mouseup
'
,
this
.
stopDrag
);
},
},
mounted
()
{
this
.
selectedCamera
=
this
.
cameras
[
0
];
},
};
</
script
>
<
style
scoped
>
.surveillance-player
{
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
background-color
:
#fff
;
}
.camera-selector
{
margin
:
10px
;
}
.time-selector
{
display
:
flex
;
align-items
:
center
;
margin
:
10px
;
}
.time-selector
div
{
margin
:
0
10px
;
}
.video-player
{
width
:
100%
;
max-width
:
800px
;
margin
:
10px
;
}
.video
{
width
:
100%
;
height
:
auto
;
}
.control-bar
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
background-color
:
#333
;
color
:
white
;
padding
:
5px
10px
;
}
button
{
margin
:
0
10px
;
background-color
:
#444
;
color
:
white
;
border
:
none
;
padding
:
5px
10px
;
border-radius
:
3px
;
cursor
:
pointer
;
}
button
:hover
{
background-color
:
#555
;
}
.progress
{
position
:
relative
;
width
:
200px
;
height
:
4px
;
background-color
:
#555
;
margin
:
0
10px
;
cursor
:
pointer
;
}
.progress-filled
{
position
:
absolute
;
height
:
100%
;
background-color
:
#4CAF50
;
left
:
0
;
top
:
0
;
}
.progress-handle
{
position
:
absolute
;
width
:
10px
;
height
:
10px
;
background-color
:
white
;
border-radius
:
50%
;
transform
:
translateX
(
-50%
);
top
:
-3px
;
}
.timeline
{
width
:
100%
;
height
:
20px
;
background-color
:
#ddd
;
margin
:
10px
0
;
}
.timeline-slider
{
height
:
20px
;
background-color
:
transparent
;
cursor
:
pointer
;
width
:
100%
;
}
</
style
>
\ No newline at end of file
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