19 changed files with 312 additions and 6907 deletions
@ -1,107 +1,106 @@ |
|||
// import axios from 'axios';
|
|||
import { he } from 'element-plus/es/locale'; |
|||
import { request } from '../axios'; |
|||
import { request } from "../axios"; |
|||
|
|||
// 预警统计
|
|||
export const getWarningStatistic = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/warning/statistic', |
|||
method: 'post', |
|||
params |
|||
url: "/run/api/warning/statistic", |
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
|
|||
// 预警列表
|
|||
export const getWarningList = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/warning/list', |
|||
method: 'post', |
|||
params |
|||
url: "/run/api/warning/list", |
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
|
|||
// 查询行政区划数据
|
|||
export const getAreasData = () => { |
|||
return request({ |
|||
url: '/run/api/common/xzgh/getGuangDong', |
|||
method: 'get' |
|||
url: "/run/api/common/xzgh/getGuangDong", |
|||
method: "get", |
|||
}); |
|||
}; |
|||
|
|||
// 根据字典类型查询字典数据信息
|
|||
export function getDicts(dictType: any) { |
|||
return request({ |
|||
url: '/run/api/common/dict/type/' + dictType, |
|||
method: 'get' |
|||
url: "/run/api/common/dict/type/" + dictType, |
|||
method: "get", |
|||
}); |
|||
} |
|||
|
|||
// 通过类型和编码查询对象详细信息
|
|||
export const getObjectInfo = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/object/info/' + params.type + '/' + params.code, |
|||
method: 'get' |
|||
url: "/run/api/object/info/" + params.type + "/" + params.code, |
|||
method: "get", |
|||
}); |
|||
}; |
|||
|
|||
// 视频图像
|
|||
export const getVideoWarningList = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/video/warning/list', // /run/api/video/warning/list
|
|||
method: 'post', |
|||
params |
|||
url: "/run/api/video/warning/list", // /run/api/video/warning/list
|
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
// 运行指标列表
|
|||
export const getIndicatorList = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/indicator/list', |
|||
method: 'post', |
|||
params |
|||
url: "/run/api/indicator/list", |
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
|
|||
// 按小时统计运行坐标
|
|||
export const getIndicatorHour = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/indicator/hour', |
|||
method: 'post', |
|||
params |
|||
url: "/run/api/indicator/hour", |
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
// 最大最小平均值
|
|||
export const getIndicatorStatistic = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/indicator/statistic', |
|||
method: 'post', |
|||
params |
|||
url: "/run/api/indicator/statistic", |
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
|
|||
// 水利对象统计
|
|||
export const getObjectStatistic = (params: any) => { |
|||
return request({ |
|||
url: '/run/api/object/statistic', |
|||
method: 'post', |
|||
params |
|||
url: "/run/api/object/statistic", |
|||
method: "post", |
|||
params, |
|||
}); |
|||
}; |
|||
function getCookie(name: string): string | null { |
|||
const value = `; ${document.cookie}`; |
|||
const parts = value.split(`; ${name}=`); |
|||
if (parts.length === 2) return parts.pop()?.split(';').shift() || null; |
|||
if (parts.length === 2) return parts.pop()?.split(";").shift() || null; |
|||
return null; |
|||
} |
|||
export function getlStatisticChart(data: any) { |
|||
// 获取cookie
|
|||
const shuili = getCookie('Admin-Token'); |
|||
const shuili = getCookie("Admin-Token"); |
|||
return request({ |
|||
url: `/run/statistic/chart`, |
|||
method: 'post', |
|||
method: "post", |
|||
data, |
|||
headers: { |
|||
// 'Admin-Token': shuili,
|
|||
shuili: 'water ' + shuili |
|||
} |
|||
shuili: "water " + shuili, |
|||
}, |
|||
}); |
|||
} |
|||
|
Binary file not shown.
@ -1,209 +0,0 @@ |
|||
<script lang="ts"> |
|||
import { defineComponent, ref } from 'vue'; |
|||
import { ElMessage } from 'element-plus'; |
|||
import { Plus } from '@element-plus/icons-vue'; |
|||
import { imagesAgent } from '@/utils'; |
|||
import { useFileUpload } from './useFileUpload'; |
|||
import type { PropType } from 'vue'; |
|||
import type { UploadUserFile } from 'element-plus/es/components/upload/src/upload'; |
|||
const { fileUpload } = useFileUpload(); |
|||
export default defineComponent({ |
|||
name: 'upload-frag', |
|||
components: { Plus }, |
|||
props: { |
|||
action: { |
|||
type: String, |
|||
default: '#' |
|||
}, |
|||
limit: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
accept: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
showPer: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
multiple: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
bucketName: { |
|||
type: String, |
|||
default: 'marker' |
|||
}, |
|||
// fileType: () => [], |
|||
showFileList: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
fileList: { |
|||
type: Array as PropType<UploadUserFile[]>, |
|||
default: () => [] |
|||
}, |
|||
autoUpload: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
fileSize: { |
|||
type: Number, |
|||
default: 20 |
|||
}, |
|||
isCanRefreshUpload: { |
|||
// 允许重新上传 |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
setup(props: any, { emit }) { |
|||
const upload = ref(); |
|||
const percentage = ref(0); |
|||
|
|||
// 清空文件列表的方法,供父组件使用 |
|||
const clearFiles = () => { |
|||
upload.value?.clearFiles(); |
|||
}; |
|||
// 检查图片 |
|||
const beforeUpload = (file: any) => { |
|||
if (props.accept !== '*') { |
|||
const fileSuffix = file.name.substring(file.name.lastIndexOf('.') + 1); |
|||
const whiteList = props.accept; |
|||
if (whiteList.indexOf(fileSuffix) === -1) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `上传文件只能是${props.accept}格式` |
|||
}); |
|||
return false; |
|||
} |
|||
} |
|||
const isLt2M = file.size / 1024 / 1024 <= props.fileSize; |
|||
if (!isLt2M) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `上传文件不能大于${props.fileSize}M` |
|||
}); |
|||
return false; |
|||
} |
|||
if (props.fileList.length > props.limit) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `上传文件数不能超过${props.limit}` |
|||
}); |
|||
return false; |
|||
} |
|||
return true; |
|||
}; |
|||
const handleFileUpload = (fileObject: any): any => { |
|||
const params = { |
|||
bucketName: props.bucketName, |
|||
file: fileObject.file |
|||
}; |
|||
fileUpload(params, (res: any) => { |
|||
percentage.value = Math.floor(res) || 0; |
|||
if (res.result) { |
|||
emit('uploadSuccess', { |
|||
...res, |
|||
bucketName: props.bucketName, |
|||
file: fileObject.file |
|||
}); |
|||
} |
|||
}); |
|||
}; |
|||
const onRemove = (file: any) => { |
|||
emit('fileRemove', file); |
|||
}; |
|||
const onPreview = (file: any) => { |
|||
emit('onPreview', file); |
|||
}; |
|||
const onSuccess = (file: any) => { |
|||
// emit('uploadSuccess', file); |
|||
}; |
|||
const handleChange = (file: any) => { |
|||
// emit('uploadChange', file); |
|||
}; |
|||
return { |
|||
upload, |
|||
imagesAgent, |
|||
clearFiles, |
|||
percentage, |
|||
beforeUpload, |
|||
handleFileUpload, |
|||
onRemove, |
|||
onPreview, |
|||
onSuccess, |
|||
handleChange |
|||
}; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="image-wrapper"> |
|||
<el-upload |
|||
ref="upload" |
|||
:action="action" |
|||
v-bind="$attrs" |
|||
:show-file-list="showFileList" |
|||
:limit="limit" |
|||
:accept="accept" |
|||
:file-list="fileList" |
|||
:auto-upload="autoUpload" |
|||
:before-upload="beforeUpload" |
|||
:http-request="handleFileUpload" |
|||
:on-remove="onRemove" |
|||
:on-preview="onPreview" |
|||
:on-success="onSuccess" |
|||
:on-change="handleChange" |
|||
> |
|||
<div class="progress" v-if="percentage && showPer"> |
|||
<el-icon><Plus /></el-icon> |
|||
<ElProgress :stroke-width="8" :percentage="percentage" /> |
|||
</div> |
|||
<slot v-else></slot> |
|||
</el-upload> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="less" scoped> |
|||
.image-wrapper { |
|||
width: 100%; |
|||
& > div { |
|||
display: flex; |
|||
flex-direction: column-reverse; |
|||
} |
|||
.sy-upload--picture-card { |
|||
width: 64px !important; |
|||
height: 64px !important; |
|||
margin-right: 8px; |
|||
line-height: 64px; |
|||
color: rgba(255, 255, 255, 0.8); |
|||
background: rgba(255, 255, 255, 0.12); |
|||
border: none; |
|||
i { |
|||
line-height: unset; |
|||
} |
|||
} |
|||
.sy-upload-list { |
|||
display: none; |
|||
margin-top: 8px; |
|||
// display: none; |
|||
.sy-upload-list__item { |
|||
width: 196px !important; |
|||
height: 96px !important; |
|||
margin: 0 8px 8px 0; |
|||
& > div { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
.sy-icon-check { |
|||
position: relative; |
|||
top: -16px; |
|||
left: 0px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -1,420 +0,0 @@ |
|||
<script lang="ts"> |
|||
import { type PropType, defineComponent, ref, watch } from 'vue'; |
|||
import { ElMessage, type UploadUserFile } from 'element-plus'; |
|||
import { Plus, ZoomIn, Refresh, Delete } from '@element-plus/icons-vue'; |
|||
import { imagesAgent } from '@/utils'; |
|||
import { useFileUpload } from './useFileUpload'; |
|||
const { fileUpload } = useFileUpload(); |
|||
export default defineComponent({ |
|||
name: 'upload-image', |
|||
components: { Plus, ZoomIn, Refresh, Delete }, |
|||
props: { |
|||
action: { |
|||
type: String, |
|||
default: '#' |
|||
}, |
|||
limit: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
accept: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
showPer: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
multiple: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
bucketName: { |
|||
type: String, |
|||
default: 'marker' |
|||
}, |
|||
// fileType: () => [], |
|||
showFileList: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
autoUpload: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
fileSize: { |
|||
type: Number, |
|||
default: 20 |
|||
}, |
|||
isRefreshUpload: { |
|||
// 允许重新上传 |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
fileList: { |
|||
type: Array as PropType<UploadUserFile[]>, |
|||
default: () => [] |
|||
} |
|||
}, |
|||
setup(props: any, { emit }) { |
|||
const upload = ref(); |
|||
const percentage = ref(0); |
|||
const uploadImg = ref(); |
|||
const previewList = ref<any[]>([]); |
|||
const loadFiles = ref<any[]>([]); |
|||
const isResetImg = ref(false); |
|||
const resetFile = ref<any>({}); |
|||
|
|||
watch( |
|||
() => props.fileList, |
|||
(val) => { |
|||
if (val) { |
|||
loadFiles.value = []; |
|||
previewList.value = []; |
|||
val.forEach((element: any) => { |
|||
loadFiles.value.push({ |
|||
...element, |
|||
url: imagesAgent(element.url) |
|||
}); |
|||
previewList.value.push(imagesAgent(element.url)); |
|||
}); |
|||
} |
|||
}, |
|||
{ |
|||
deep: true |
|||
} |
|||
); |
|||
// 清空文件列表的方法,供父组件使用 |
|||
const clearFiles = () => { |
|||
upload.value?.clearFiles(); |
|||
}; |
|||
// 检查图片 |
|||
const beforeUpload = (file: any) => { |
|||
if (props.accept !== '*') { |
|||
const fileSuffix = file.name.substring(file.name.lastIndexOf('.') + 1); |
|||
const whiteList = props.accept; |
|||
if (whiteList.indexOf(fileSuffix) === -1) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `上传文件只能是${props.accept}格式` |
|||
}); |
|||
return false; |
|||
} |
|||
} |
|||
const isLt2M = file.size / 1024 / 1024 <= props.fileSize; |
|||
if (!isLt2M) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `上传文件不能大于${props.fileSize}M` |
|||
}); |
|||
return false; |
|||
} |
|||
if (props.fileList.length > props.limit) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `上传文件数不能超过${props.limit}` |
|||
}); |
|||
return false; |
|||
} |
|||
return true; |
|||
}; |
|||
const handleFileUpload = (fileObject: any): any => { |
|||
const params = { |
|||
bucketName: props.bucketName, |
|||
file: fileObject.file |
|||
}; |
|||
fileUpload(params, (res: any) => { |
|||
percentage.value = Math.floor(res) || 0; |
|||
if (res.result) { |
|||
// 将图片地址拼接传给父组件 |
|||
res.result.name = res.result.fileName; |
|||
loadFiles.value.push(res.result); |
|||
previewList.value.push(imagesAgent(res.result.url)); |
|||
if (isResetImg.value && loadFiles.value && Array.isArray(loadFiles.value)) { |
|||
let indexImg = 0; |
|||
const lastImg = loadFiles.value[loadFiles.value.length - 1]; |
|||
loadFiles.value.forEach((item: any, index: any) => { |
|||
if (item.ossId === resetFile.value.ossId) { |
|||
indexImg = index; |
|||
item.fileName = lastImg.fileName; |
|||
} |
|||
}); |
|||
loadFiles.value = _deleteData(indexImg, loadFiles.value); |
|||
previewList.value = _deleteData(indexImg, previewList.value); |
|||
} |
|||
emit('uploadSuccess', loadFiles.value); |
|||
} |
|||
}); |
|||
}; |
|||
const onRemove = (file: any) => { |
|||
emit('fileRemove', file); |
|||
}; |
|||
const onPreview = (file: any) => { |
|||
emit('onPreview', file); |
|||
}; |
|||
const onSuccess = (file: any) => { |
|||
// emit('uploadSuccess', file); |
|||
}; |
|||
const handleChange = (file: any, fileData: any) => { |
|||
// emit('uploadChange', file); |
|||
}; |
|||
const _deleteData = (num: any, data: any) => { |
|||
const sliceData = data.slice(num + 1, data.length - 1) || []; |
|||
const lastData = data[data.length - 1]; |
|||
// num表示第几项,从0开始算起 num<0,不进行任何操作 |
|||
return num < 0 ? data : data.slice(0, num).concat(lastData).concat(sliceData); |
|||
}; |
|||
// 重新上传 |
|||
const handleResetUpload = (file: any) => { |
|||
resetFile.value = file; |
|||
isResetImg.value = true; |
|||
uploadImg.value?.click(); |
|||
}; |
|||
// 移除 |
|||
const handleRemove = (file: any) => { |
|||
loadFiles.value = loadFiles.value.filter((el) => el.ossId !== file.ossId) || []; |
|||
loadFiles.value.forEach((element) => { |
|||
previewList.value.push(imagesAgent(element.url)); |
|||
}); |
|||
emit('uploadSuccess', loadFiles.value); |
|||
}; |
|||
// 查看大图 |
|||
const handlePreview = (file: any) => { |
|||
loadFiles.value.forEach((item, index) => { |
|||
if (item.ossId === file.ossId) { |
|||
const dom = document.querySelectorAll('.media-list-img'); |
|||
dom?.[index].querySelector('img')?.click(); |
|||
} |
|||
}); |
|||
}; |
|||
return { |
|||
upload, |
|||
uploadImg, |
|||
previewList, |
|||
loadFiles, |
|||
isResetImg, |
|||
resetFile, |
|||
imagesAgent, |
|||
clearFiles, |
|||
percentage, |
|||
beforeUpload, |
|||
handleFileUpload, |
|||
onRemove, |
|||
onPreview, |
|||
onSuccess, |
|||
handleChange, |
|||
handlePreview, |
|||
handleRemove, |
|||
handleResetUpload |
|||
}; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="img-video-content"> |
|||
<el-upload |
|||
ref="upload" |
|||
:action="action" |
|||
v-bind="$attrs" |
|||
:show-file-list="showFileList" |
|||
:accept="accept" |
|||
:limit="limit" |
|||
:file-list="fileList" |
|||
:auto-upload="autoUpload" |
|||
:before-upload="beforeUpload" |
|||
:http-request="handleFileUpload" |
|||
:on-remove="onRemove" |
|||
:on-preview="onPreview" |
|||
:on-success="onSuccess" |
|||
:on-change="handleChange" |
|||
v-show="fileList.length < limit" |
|||
> |
|||
<div ref="uploadImg"> |
|||
<div class="progress" v-if="percentage && showPer"> |
|||
<el-icon><Plus /></el-icon> |
|||
<ElProgress :stroke-width="8" :percentage="percentage" /> |
|||
</div> |
|||
<slot v-else></slot> |
|||
</div> |
|||
</el-upload> |
|||
<div class="sy-image-container" v-if="isRefreshUpload"> |
|||
<div class="img-video-item" v-for="(file, index) in loadFiles" :key="file.ossId + index"> |
|||
<el-image |
|||
preview-teleported |
|||
class="media-list-img" |
|||
:alt="file.name" |
|||
:src="imagesAgent(file.url)" |
|||
fit="fill" |
|||
crossOrigin="anonymous" |
|||
:preview-src-list="[imagesAgent(file.url)]" |
|||
/> |
|||
<div class="img-icon-bt"> |
|||
<el-icon style="margin-right: 8px" @click.stop="handlePreview(file)"> |
|||
<ZoomIn /> |
|||
</el-icon> |
|||
<el-icon style="margin-right: 8px" @click.stop="handleResetUpload(file)"> |
|||
<Refresh /> |
|||
</el-icon> |
|||
<el-icon @click.stop="handleRemove(file)"><Delete /></el-icon> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="less" scoped> |
|||
.image-wrapper { |
|||
width: 100%; |
|||
|
|||
.sy-upload--picture-card { |
|||
width: 64px !important; |
|||
height: 64px !important; |
|||
margin-right: 8px; |
|||
line-height: 64px; |
|||
color: rgba(255, 255, 255, 0.8); |
|||
background: rgba(255, 255, 255, 0.12); |
|||
border: none; |
|||
i { |
|||
line-height: unset; |
|||
} |
|||
} |
|||
.sy-upload-list { |
|||
display: none; |
|||
margin-top: 8px; |
|||
// display: none; |
|||
.sy-upload-list__item { |
|||
width: 196px !important; |
|||
height: 96px !important; |
|||
margin: 0 8px 8px 0; |
|||
& > div { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
.sy-icon-check { |
|||
position: relative; |
|||
top: -16px; |
|||
left: 0px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.upload-btn { |
|||
width: 100%; |
|||
// margin-top: 8px; |
|||
.download-btn { |
|||
width: 293px; |
|||
margin: 0px; |
|||
border: none; |
|||
background: rgba(255, 255, 255, 0.12); |
|||
font-weight: 350; |
|||
line-height: 20px; |
|||
display: flex; |
|||
align-items: center; |
|||
letter-spacing: 0em; |
|||
color: rgba(255, 255, 255, 0.55); |
|||
img { |
|||
width: 20px; |
|||
height: 20px; |
|||
margin: 0px 8px 0px 0px; |
|||
} |
|||
.text { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
} |
|||
.img-card-btn { |
|||
height: 72px; |
|||
width: 72px; |
|||
background: rgba(255, 255, 255, 0.12); |
|||
box-sizing: border-box; |
|||
border: 1px solid rgba(255, 255, 255, 0.12); |
|||
margin: auto; |
|||
text-align: center; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
.sy-icon { |
|||
margin: 16px auto; |
|||
text-align: center; |
|||
} |
|||
|
|||
.img-card-text { |
|||
height: 72px; |
|||
width: 72px; |
|||
background: rgba(255, 255, 255, 0.12); |
|||
box-sizing: border-box; |
|||
border: 1px solid rgba(255, 255, 255, 0.12); |
|||
margin: auto; |
|||
text-align: center; |
|||
border-radius: 4px; |
|||
} |
|||
} |
|||
.text-card-btn { |
|||
border: none; |
|||
background: transparent; |
|||
right: 0; |
|||
position: absolute; |
|||
color: #409eff; |
|||
} |
|||
} |
|||
.img-video-content { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
float: left; |
|||
height: 72px; |
|||
.upload-btn { |
|||
width: 72px; |
|||
// margin: 0px 4px 0px 0px; |
|||
border-radius: 4px; |
|||
} |
|||
} |
|||
.sy-image-container { |
|||
display: flex; |
|||
align-items: center; |
|||
width: 212px; |
|||
height: 72px; |
|||
.img-video-item { |
|||
width: 72px; |
|||
height: 72px; |
|||
margin: auto 6px; |
|||
border-radius: 4px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
//padding: 4px 8px; |
|||
background: rgba(0, 0, 0, 0.4); |
|||
.media-list-img { |
|||
// margin: auto 12px; |
|||
width: 72px; |
|||
height: 72px; |
|||
} |
|||
&:hover { |
|||
.img-icon-bt { |
|||
display: block; |
|||
} |
|||
} |
|||
} |
|||
.img-icon-bt { |
|||
display: none; |
|||
position: absolute; |
|||
width: 72px; |
|||
height: 72px; |
|||
background: rgba(43, 44, 47, 0.5); |
|||
margin: auto; |
|||
text-align: center; |
|||
.sy-icon { |
|||
margin-top: 24px; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
.display-add { |
|||
.upload-btn { |
|||
display: none; |
|||
} |
|||
} |
|||
</style> |
@ -1,343 +0,0 @@ |
|||
import { |
|||
uploadNormalFile, |
|||
preChunkUpload, |
|||
chunkUpload, |
|||
chunkMerge |
|||
} from '@/api/upload'; |
|||
import { useMd5 } from '../useMd5'; |
|||
|
|||
interface UploadParams { |
|||
bucketName: string; |
|||
file: File; |
|||
} |
|||
|
|||
interface UploadResponse { |
|||
fileName: string; |
|||
ossId: string; |
|||
suffix: string; |
|||
url: string; |
|||
} |
|||
|
|||
export const useFileUpload = () => { |
|||
// 已上传的切片大小
|
|||
let chunkLoaded = {}; |
|||
// 文件大小
|
|||
let totalSize = 0; |
|||
// 已上传的文件大小
|
|||
let loadedSize = 0; |
|||
// 文件上传过程的回调
|
|||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|||
let uploadCallback: Function | undefined | null; |
|||
|
|||
// 计算文件 md5 的 hook
|
|||
const { computedFileMd5 } = useMd5(); |
|||
|
|||
// 文件上传,根据文件大小选择上传方式,大于50M的文件采用切片上传的方式,小于50M直接上传
|
|||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|||
const fileUpload = (params: UploadParams, callback?: Function) => { |
|||
const { file } = params; |
|||
totalSize = file.size; |
|||
uploadCallback = callback; |
|||
triggerUploadCallback({ |
|||
status: 'startUpload' |
|||
}); |
|||
if (file.size < 50 * 1024 * 1024) { |
|||
normalUpload(params); |
|||
} else { |
|||
largeUpload(params); |
|||
} |
|||
}; |
|||
|
|||
// 普通文件上传
|
|||
const normalUpload = (params: UploadParams) => { |
|||
uploadNormalFile(params, (e: any) => { |
|||
handleUploadProgress(e, 1); |
|||
}) |
|||
.then((res: any) => { |
|||
if (res.code === 200 && res.data.url) { |
|||
triggerUploadCallback({ |
|||
status: 'uploadSuccess', |
|||
result: { |
|||
fileName: res.data.fileName, |
|||
ossId: res.data.ossId, |
|||
suffix: res.data.suffix, |
|||
url: res.data.url |
|||
} satisfies UploadResponse |
|||
}); |
|||
} else { |
|||
triggerUploadCallback({ |
|||
status: 'uploadFail', |
|||
message: '文件上传失败', |
|||
error: res |
|||
}); |
|||
} |
|||
}) |
|||
.catch((e: any) => { |
|||
triggerUploadCallback({ |
|||
status: 'uploadFail', |
|||
message: '文件上传失败', |
|||
error: e |
|||
}); |
|||
}) |
|||
.finally(() => { |
|||
resetStatus(); |
|||
}); |
|||
}; |
|||
|
|||
// 大文件上传
|
|||
const largeUpload = (params: UploadParams) => { |
|||
const { file, bucketName } = params; |
|||
const fileChunks = createFileChunk(file); |
|||
const groupChunks = chunksGroup(fileChunks); |
|||
let uploadId = ''; |
|||
let filepath = ''; |
|||
computedFileMd5(fileChunks) |
|||
.then((md5: any) => { |
|||
return fetchUploadId({ |
|||
md5, |
|||
bucket: bucketName, |
|||
filename: file.name |
|||
}); |
|||
}) |
|||
.then((res: any) => { |
|||
uploadId = res.uploadId; |
|||
filepath = res.filepath; |
|||
if (res.sysOss) { |
|||
return Promise.resolve({ ossObj: res.sysOss }); |
|||
} else if (!res.parts) { |
|||
return fileChunkUpload({ |
|||
groupChunks, |
|||
chunkSize: fileChunks.length, |
|||
bucket: bucketName, |
|||
uploadId, |
|||
filepath |
|||
}); |
|||
} else if (res.parts && res.parts.length !== fileChunks.length) { |
|||
const unUploadChunks = computedUnUploadChunks(res.parts, fileChunks); |
|||
return fileChunkUpload({ |
|||
groupChunks: chunksGroup(unUploadChunks), |
|||
chunkSize: unUploadChunks.filter((chunk: any) => chunk).length, |
|||
bucket: bucketName, |
|||
uploadId, |
|||
filepath |
|||
}); |
|||
} else { |
|||
return Promise.resolve(res); |
|||
} |
|||
}) |
|||
.then((res: any) => { |
|||
if (res.ossObj) { |
|||
return Promise.resolve({ data: res.ossObj }); |
|||
} else { |
|||
return mergeChunks({ |
|||
uploadId, |
|||
filepath, |
|||
bucket: bucketName, |
|||
filename: file.name |
|||
}); |
|||
} |
|||
}) |
|||
.then((res: any) => { |
|||
triggerUploadCallback({ |
|||
status: 'uploadSuccess', |
|||
result: { |
|||
fileName: res.data.originalName, |
|||
ossId: res.data.ossId, |
|||
suffix: res.data.fileSuffix, |
|||
url: res.data.url |
|||
} satisfies UploadResponse |
|||
}); |
|||
}) |
|||
.catch((e: any) => { |
|||
triggerUploadCallback({ |
|||
status: 'uploadFail', |
|||
message: '文件上传失败', |
|||
error: e |
|||
}); |
|||
}) |
|||
.finally(() => { |
|||
resetStatus(); |
|||
}); |
|||
}; |
|||
|
|||
// 获取上传id
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|||
// @ts-expect-error
|
|||
const fetchUploadId = ({ md5, bucket, filename }) => { |
|||
return new Promise((resolve, reject) => { |
|||
preChunkUpload({ |
|||
md5, |
|||
bucket, |
|||
filename |
|||
}) |
|||
.then((res: any) => { |
|||
if (res.code === 200 && (res.data.uploadId || res.data.sysOss)) { |
|||
resolve({ |
|||
uploadId: res.data.uploadId, |
|||
filepath: res.data.filepath, |
|||
parts: res.data.parts, |
|||
sysOss: res.data.sysOss |
|||
}); |
|||
} else { |
|||
// eslint-disable-next-line prefer-promise-reject-errors
|
|||
reject({ |
|||
message: '获取文件上传id失败', |
|||
error: res |
|||
}); |
|||
} |
|||
}) |
|||
.catch((e: any) => { |
|||
// eslint-disable-next-line prefer-promise-reject-errors
|
|||
reject({ |
|||
message: '获取文件上传id失败', |
|||
error: e |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// 切片上传
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|||
const fileChunkUpload = ({ |
|||
groupChunks, |
|||
chunkSize, |
|||
bucket, |
|||
uploadId, |
|||
filepath |
|||
}: any) => { |
|||
// eslint-disable-next-line @typescript-eslint/no-misused-promises,no-async-promise-executor
|
|||
return new Promise(async (resolve, reject) => { |
|||
const responseBuffer: any[] = []; |
|||
try { |
|||
for (let i = 0; i < groupChunks.length; i++) { |
|||
const groupRequest: any[] = []; |
|||
for (let j = 0; j < groupChunks[i].length; j++) { |
|||
if (groupChunks[i][j]) { |
|||
const params = { |
|||
file: groupChunks[i][j], |
|||
bucket, |
|||
uploadId, |
|||
filepath, |
|||
partNum: i * 3 + (j + 1) + '' |
|||
}; |
|||
groupRequest.push( |
|||
chunkUpload(params, (e: any) => { |
|||
handleUploadProgress(e, i * 3 + (j + 1)); |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
const res = await Promise.all(groupRequest); |
|||
responseBuffer.push(...res); |
|||
} |
|||
} catch (e) { |
|||
// eslint-disable-next-line prefer-promise-reject-errors
|
|||
reject({ |
|||
message: '切片上传失败', |
|||
error: e |
|||
}); |
|||
} finally { |
|||
if (responseBuffer.length === chunkSize) { |
|||
resolve(responseBuffer); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
// 合并切换
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|||
// @ts-expect-error
|
|||
const mergeChunks = async ({ uploadId, filepath, bucket, filename }) => { |
|||
return await new Promise((resolve, reject) => { |
|||
chunkMerge({ |
|||
uploadId, |
|||
filepath, |
|||
bucket, |
|||
filename |
|||
}) |
|||
.then((res: any) => { |
|||
resolve(res); |
|||
}) |
|||
.catch((e: any) => { |
|||
// eslint-disable-next-line prefer-promise-reject-errors
|
|||
reject({ |
|||
message: '文件合并失败', |
|||
error: e |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// 创建文件切片
|
|||
const createFileChunk = (file: File, size = 5 * 1024 * 1024) => { |
|||
const chunkList: Blob[] = []; |
|||
let currentSize = 0; |
|||
while (currentSize < file.size) { |
|||
chunkList.push(file.slice(currentSize, currentSize + size)); |
|||
currentSize += size; |
|||
} |
|||
return chunkList; |
|||
}; |
|||
|
|||
// 切片分组
|
|||
const chunksGroup = (chunks: any) => { |
|||
const groups: any[][] = []; |
|||
for (let i = 0; i < chunks.length; i += 3) { |
|||
groups.push(chunks.slice(i, i + 3)); |
|||
} |
|||
return groups; |
|||
}; |
|||
|
|||
const computedUnUploadChunks = (parts: any, chunks: any) => { |
|||
parts.forEach((item: any) => { |
|||
if (Number(item.num)) { |
|||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|||
delete chunks[Number(item.num) - 1]; |
|||
} |
|||
}); |
|||
return chunks; |
|||
}; |
|||
|
|||
// 切片文件上传进度处理
|
|||
const handleUploadProgress = (e: ProgressEvent, chunkIndex: any) => { |
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|||
// @ts-expect-error
|
|||
chunkLoaded[chunkIndex] = e.loaded; |
|||
computedUploadProgress(); |
|||
}; |
|||
|
|||
// 计算文件上传总进度
|
|||
const computedUploadProgress = () => { |
|||
let tmpSize = 0; |
|||
for (const key in chunkLoaded) { |
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|||
// @ts-expect-error
|
|||
tmpSize += chunkLoaded[key]; |
|||
} |
|||
loadedSize = tmpSize; |
|||
const uploadResult = (loadedSize / totalSize) * 100; |
|||
const percentage = (uploadResult > 100 ? 100 : uploadResult).toFixed(2); |
|||
triggerUploadCallback({ |
|||
status: 'uploading', |
|||
percentage: Number(percentage) |
|||
}); |
|||
}; |
|||
|
|||
// 重置上传文件涉及到的变量状态
|
|||
const resetStatus = () => { |
|||
chunkLoaded = {}; |
|||
totalSize = 0; |
|||
loadedSize = 0; |
|||
uploadCallback = null; |
|||
}; |
|||
|
|||
const triggerUploadCallback = (options: any) => { |
|||
if (uploadCallback instanceof Function) { |
|||
uploadCallback(options); |
|||
} |
|||
}; |
|||
|
|||
return { |
|||
fileUpload |
|||
}; |
|||
}; |
@ -1,57 +0,0 @@ |
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|||
// @ts-expect-error
|
|||
import SparkMD5 from 'spark-md5'; |
|||
|
|||
export const useMd5 = () => { |
|||
const computedFileMd5 = (file: File | Blob[]) => { |
|||
// eslint-disable-next-line @typescript-eslint/no-misused-promises,no-async-promise-executor
|
|||
return new Promise(async (resolve, reject) => { |
|||
try { |
|||
let md5 = ''; |
|||
const spark = new SparkMD5.ArrayBuffer(); |
|||
const fileReader = new FileReader(); |
|||
if (Array.isArray(file)) { |
|||
for (let i = 0; i < file.length; i++) { |
|||
const res: any = await readFileAsArrayBuffer(fileReader, file[i]); |
|||
spark.append(res?.target?.result); |
|||
} |
|||
md5 = spark.end(); |
|||
} else { |
|||
const res: any = await readFileAsBinaryString(fileReader, file); |
|||
md5 = SparkMD5.hashBinary(res?.target?.result); |
|||
} |
|||
resolve(md5); |
|||
} catch (e) { |
|||
// eslint-disable-next-line prefer-promise-reject-errors
|
|||
reject({ |
|||
message: '计算文件md5失败', |
|||
error: e |
|||
}); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
// 读取文件并转为ArrayBuffer
|
|||
const readFileAsArrayBuffer = (fileReader: any, blob: any) => { |
|||
return new Promise((resolve) => { |
|||
fileReader.readAsArrayBuffer(blob); |
|||
fileReader.onload = function (e: any) { |
|||
resolve(e); |
|||
}; |
|||
}); |
|||
}; |
|||
|
|||
// 读取文件并转为BinaryString
|
|||
const readFileAsBinaryString = (fileReader: any, file: any) => { |
|||
return new Promise((resolve) => { |
|||
fileReader.readAsBinaryString(file); |
|||
fileReader.onload = function (e: any) { |
|||
resolve(e); |
|||
}; |
|||
}); |
|||
}; |
|||
|
|||
return { |
|||
computedFileMd5 |
|||
}; |
|||
}; |
@ -1,289 +0,0 @@ |
|||
<script lang="ts"> |
|||
import { defineComponent, ref, reactive, nextTick, watch } from 'vue'; |
|||
import { getProxyPath } from '@/utils/index'; |
|||
import { getVideoStreamInfo } from '@/api/video'; |
|||
import { ElMessage } from 'element-plus'; |
|||
import SyPlayer from './lib/syPlayer.js'; |
|||
import createGuid from '@/utils/uuid'; |
|||
import { isEqual } from 'lodash-es'; |
|||
export default defineComponent({ |
|||
name: 'sy-video', |
|||
components: {}, |
|||
props: { |
|||
playInfo: { |
|||
type: Object, |
|||
default: () => {} |
|||
}, |
|||
autoPlay: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
// // 在线视频数据 |
|||
// { |
|||
// "videoType": "netvideo", |
|||
// "videoName": "录像文件#", |
|||
// "isStream": false, |
|||
// "videoUrl": "http://172.16.32.102:9000/camera/20230322/8209ba0965174072b3fc97d862548d15.mp4", |
|||
// "isFull": false, |
|||
// "isVisible": false, |
|||
// "deviceIndex": "641a6e91e4b06c8e49163229", |
|||
// "cameraPosition": { |
|||
// "lng": 0, |
|||
// "lat": 0, |
|||
// "alt": 0 |
|||
// } |
|||
// } |
|||
// // 监控视频数据 |
|||
// { |
|||
// "videoType": "surveillanceVideo", |
|||
// "videoName": "大华摄像头IP32", |
|||
// "isStream": true, |
|||
// "videoUrl": "", |
|||
// "isFull": false, |
|||
// "isVisible": false, |
|||
// "deviceIndex": "64dcb7dfe4b0956ea8c53590", |
|||
// "cameraPosition": { |
|||
// "lng": 0, |
|||
// "lat": 0, |
|||
// "alt": 0 |
|||
// }, |
|||
// "platform": "datametatech" |
|||
// } |
|||
// // 本地视频数据 |
|||
// { |
|||
// "videoType": "localvideo", |
|||
// "videoName": "本地视频", |
|||
// "isStream": false, |
|||
// "videoUrl": "http://minio:9000/images/20240110/ab983754109f4df09831bc1538c6870e.mp4", |
|||
// "isFull": false, |
|||
// "isVisible": false, |
|||
// "deviceIndex": "1744914088635305985", |
|||
// "platform": "" |
|||
// } |
|||
setup(props: any) { |
|||
const isPlay = ref(false); |
|||
const videoRef = ref(); |
|||
let videoPlayer = ref(); |
|||
|
|||
// 播放与暂停 |
|||
const playItem = () => { |
|||
if (!isPlay.value) { |
|||
isPlay.value = !isPlay.value; |
|||
play(); |
|||
} |
|||
}; |
|||
// 创建视频节点 |
|||
const createVideo = (url: any, isStream: any) => { |
|||
if (!videoRef.value) return; |
|||
videoRef.value.style.width = '100%'; |
|||
if (isStream) { |
|||
const deviceIndex = props.playInfo.deviceIndex; |
|||
if (!deviceIndex) return; |
|||
if (!url) { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: `获取视频流失败` |
|||
}); |
|||
isPlay.value = false; |
|||
return; |
|||
} |
|||
if (videoPlayer.value) { |
|||
videoPlayer.value?.close(); |
|||
videoPlayer.value = null; |
|||
} |
|||
videoPlayer.value = new SyPlayer(videoRef.value, url); |
|||
nextTick(() => { |
|||
videoPlayer.value?.open(); |
|||
}); |
|||
return; |
|||
} |
|||
}; |
|||
const videoPlayInfo = reactive({ |
|||
id: '', |
|||
videoType: '', // netvideo-在线视频,surveillanceVideo-监控视频, localvideo-本地视频 |
|||
videoName: '', |
|||
isStream: false, |
|||
videoUrl: '', |
|||
isFull: false, |
|||
isVisible: false, |
|||
deviceIndex: '', |
|||
cameraPosition: { |
|||
lng: 0, |
|||
lat: 0, |
|||
alt: 0 |
|||
}, |
|||
deviceIPCAddress: null, |
|||
platform: '' |
|||
}); |
|||
const play = async () => { |
|||
const { |
|||
videoType, |
|||
videoName, |
|||
isStream, |
|||
videoUrl, |
|||
isFull, |
|||
isVisible, |
|||
deviceIndex, |
|||
cameraPosition, |
|||
deviceIPCAddress, |
|||
platform, |
|||
id |
|||
} = props.playInfo; |
|||
videoPlayInfo.videoType = videoType; |
|||
videoPlayInfo.videoName = videoName; |
|||
videoPlayInfo.isFull = isFull; |
|||
videoPlayInfo.isVisible = isVisible; |
|||
videoPlayInfo.deviceIndex = deviceIndex; |
|||
videoPlayInfo.cameraPosition = cameraPosition; |
|||
videoPlayInfo.deviceIPCAddress = deviceIPCAddress; |
|||
videoPlayInfo.platform = platform; |
|||
videoPlayInfo.id = id ?? createGuid(); |
|||
if (['netvideo', 'localvideo'].includes(videoType)) { |
|||
videoPlayInfo.isStream = false; |
|||
videoPlayInfo.videoUrl = |
|||
videoUrl && videoUrl.includes('//minio:') && !videoUrl.includes('/objs/') |
|||
? getProxyPath(videoUrl, 'objs') |
|||
: videoUrl; |
|||
createVideo(videoPlayInfo.videoUrl, isStream); |
|||
isPlay.value = true; |
|||
} else { |
|||
const videoStream = await getStreamVideo(deviceIndex); |
|||
if (videoStream) { |
|||
videoPlayInfo.videoUrl = videoStream.videoUrl; |
|||
videoPlayInfo.isStream = videoStream.isStream; |
|||
videoPlayInfo.platform = videoStream.platform; |
|||
isPlay.value = true; |
|||
} |
|||
} |
|||
}; |
|||
// 获取视频流数据 |
|||
const getStreamVideo = async (id: any, _isPlay = true) => { |
|||
try { |
|||
const res = await getVideoStreamInfo({ |
|||
deviceIndex: id |
|||
}); |
|||
if (res?.url) { |
|||
const { url, platform } = res; |
|||
let videoPlayInfo = { |
|||
isStream: true, |
|||
videoUrl: url, |
|||
platform: platform |
|||
}; |
|||
_isPlay && createVideo(url, true); |
|||
return videoPlayInfo; |
|||
} else { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: res.msg |
|||
}); |
|||
isPlay.value = false; |
|||
|
|||
return false; |
|||
} |
|||
} catch (error) { |
|||
isPlay.value = false; |
|||
return false; |
|||
} |
|||
}; |
|||
const handleCanPlay = () => { |
|||
if (props.autoPlay && videoRef.value) { |
|||
videoRef.value.play?.(); |
|||
nextTick(() => { |
|||
isPlay.value = !videoRef.value.paused; |
|||
}); |
|||
} |
|||
}; |
|||
nextTick(() => { |
|||
if ( |
|||
!props.autoPlay && |
|||
((!props.playInfo.isStream && props.playInfo.videoUrl) || |
|||
(props.playInfo.isStream && props.playInfo.deviceIndex)) |
|||
) { |
|||
play(); |
|||
} |
|||
}); |
|||
|
|||
watch( |
|||
() => props.playInfo, |
|||
(val, old) => { |
|||
if (!isEqual(val, old)) { |
|||
nextTick(() => { |
|||
// if (props.autoPlay) { |
|||
play(); |
|||
// } |
|||
}); |
|||
} |
|||
}, |
|||
{ deep: true } |
|||
); |
|||
return { |
|||
isPlay, |
|||
videoPlayInfo, |
|||
videoRef, |
|||
createVideo, |
|||
playItem, |
|||
play, |
|||
handleCanPlay |
|||
}; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="video-wrapper"> |
|||
<video |
|||
:id="videoPlayInfo.id" |
|||
v-if="!playInfo.isStream" |
|||
ref="videoRef" |
|||
:src="videoPlayInfo.videoUrl" |
|||
width="100%" |
|||
controls |
|||
:autoplay="autoPlay" |
|||
loop |
|||
muted |
|||
@canplay="handleCanPlay" |
|||
:poster="playInfo.imgUrl" |
|||
> |
|||
<source type="video/mp4" /> |
|||
</video> |
|||
<video |
|||
:id="videoPlayInfo.id" |
|||
v-else |
|||
ref="videoRef" |
|||
width="100%" |
|||
controls |
|||
autoplay |
|||
muted |
|||
@canplay="handleCanPlay" |
|||
></video> |
|||
<div @click="playItem" v-if="!isPlay" class="play-icon"> |
|||
<iconpark-icon name="play" fill="#fff" size="22"></iconpark-icon> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="less"> |
|||
.video-wrapper { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
video { |
|||
width: 100%; |
|||
height: 100%; |
|||
// width: 528px; |
|||
// height: 297px; |
|||
border-radius: 8px; |
|||
} |
|||
.play-icon { |
|||
position: absolute; |
|||
bottom: 37px; |
|||
left: 13px; |
|||
cursor: pointer; |
|||
opacity: 0.7; |
|||
&:hover { |
|||
opacity: 1; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -1,16 +0,0 @@ |
|||
declare module '*/mp4box.all.min.js' { |
|||
class MP4Box { |
|||
onReady: Function |
|||
appendBuffer: Function |
|||
} |
|||
export default MP4Box |
|||
} |
|||
|
|||
declare module '*/syPlayer.js' { |
|||
class SyPlayer { |
|||
open: Function |
|||
close: Function |
|||
dispose: Function |
|||
} |
|||
export default SyPlayer(element, url) |
|||
} |
File diff suppressed because it is too large
@ -1,125 +0,0 @@ |
|||
'use strict'; |
|||
import MP4Box from './mp4box.all.min'; |
|||
function syPlayer(videoEl, wsUrl) { |
|||
this.videoEl = videoEl; |
|||
this.wsUrl = wsUrl; |
|||
this.ws = null; |
|||
this.frameQueue = []; |
|||
} |
|||
|
|||
syPlayer.prototype.open = function () { |
|||
let sourcebuffer = null; |
|||
this.ws = new WebSocket(this.wsUrl); |
|||
this.ws.binaryType = 'arraybuffer'; |
|||
let firstMessage = true; |
|||
|
|||
let demux_moov = function (info) { |
|||
let codecs = []; |
|||
for (let i = 0; i < info.tracks.length; i++) { |
|||
codecs.push(info.tracks[i].codec); |
|||
} |
|||
let video = this.videoEl; |
|||
let mediasource = new MediaSource(); |
|||
video.src = URL.createObjectURL(mediasource); |
|||
|
|||
// 设置播放速度,如果是实时流,可以设置为1.1,降低延迟,如果是视频文件,则设置为1.0
|
|||
video.playbackRate = 1.1; |
|||
|
|||
let pre_pos = 0; |
|||
mediasource.onsourceopen = function () { |
|||
sourcebuffer = mediasource.addSourceBuffer( |
|||
'video/mp4; codecs="' + codecs.join(', ') + '"' |
|||
); |
|||
sourcebuffer.onupdate = function () { |
|||
let pos = video.currentTime; |
|||
if (video.buffered.length > 0) { |
|||
let start = video.buffered.start(video.buffered.length - 1); |
|||
let end = video.buffered.end(video.buffered.length - 1); |
|||
|
|||
if (pos < start) { |
|||
video.currentTime = start; |
|||
} |
|||
|
|||
if (pos > end) { |
|||
// console.warn('chase frame pos=' + pos + ',start=' + start + ',end=' + end + ',pre_pose=' + pre_pos);
|
|||
video.currentTime = end; |
|||
} |
|||
|
|||
if (pos - pre_pos != 0 && end - pos > 3) { |
|||
video.currentTime = end; |
|||
} |
|||
|
|||
for (let i = 0; i < video.buffered.length - 1; i++) { |
|||
let prestart = video.buffered.start(i); |
|||
let preend = video.buffered.end(i); |
|||
if (!sourcebuffer.updating) { |
|||
sourcebuffer.remove(prestart, preend); |
|||
} |
|||
} |
|||
|
|||
if (pos - start > 10 && !sourcebuffer.updating) { |
|||
// console.warn('remove start pos=' + pos + ',start=' + start + ',end=' + end);
|
|||
sourcebuffer.remove(0, pos - 3); |
|||
} |
|||
|
|||
if (end - pos > 10 && !sourcebuffer.updating) { |
|||
// console.warn('remove end pos=' + pos + ',start=' + start + ',end=' + end);
|
|||
sourcebuffer.remove(0, end - 3); |
|||
} |
|||
|
|||
// 如果是实时流,则根据缓冲区的大小,动态调整播放速度
|
|||
if (this.is_stream) { |
|||
if (end - pos > 0.5) { |
|||
video.playbackRate = 1.1; |
|||
} else { |
|||
video.playbackRate = 1.0; |
|||
} |
|||
} |
|||
} |
|||
pre_pos = pos; |
|||
}; |
|||
sourcebuffer.onupdateend = pushFrame; |
|||
}; |
|||
}.bind(this); |
|||
|
|||
let pushFrame = function () { |
|||
if (this.frameQueue.length == 0) { |
|||
return; |
|||
} |
|||
|
|||
if (!sourcebuffer || sourcebuffer.updating) { |
|||
return; |
|||
} |
|||
|
|||
let frame = this.frameQueue.shift(); |
|||
try { |
|||
// 视频流有可能出现错误,这里可以避免追入,然后引发cesium奔溃
|
|||
sourcebuffer.appendBuffer(frame); |
|||
} catch (error) {} |
|||
}.bind(this); |
|||
|
|||
this.ws.onmessage = function (e) { |
|||
if (firstMessage) { |
|||
firstMessage = false; |
|||
let moov = e.data; |
|||
let mp4Box = new MP4Box(); |
|||
mp4Box.onReady = demux_moov; |
|||
moov.fileStart = 0; |
|||
mp4Box.appendBuffer(moov); |
|||
} |
|||
|
|||
this.frameQueue.push(e.data); |
|||
pushFrame(); |
|||
}.bind(this); |
|||
this.ws.onclose = function (ev) { |
|||
console.log('🚀 ws: close~', ev); |
|||
}; |
|||
}; |
|||
|
|||
syPlayer.prototype.close = function () { |
|||
this.ws && this.ws.close(); |
|||
}; |
|||
syPlayer.prototype.dispose = function () { |
|||
this.ws && this.ws.close(); |
|||
}; |
|||
export default syPlayer; |
@ -1,132 +1,131 @@ |
|||
import { defineConfig, loadEnv } from 'vite'; |
|||
import vue from '@vitejs/plugin-vue'; |
|||
import vueJsx from '@vitejs/plugin-vue-jsx'; |
|||
import path from 'path'; |
|||
import { viteStaticCopy } from 'vite-plugin-static-copy'; |
|||
import AutoImport from 'unplugin-auto-import/vite'; |
|||
import Components from 'unplugin-vue-components/vite'; |
|||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; |
|||
import gzipPlugin from 'rollup-plugin-gzip'; |
|||
import { defineConfig, loadEnv } from "vite"; |
|||
import vue from "@vitejs/plugin-vue"; |
|||
import vueJsx from "@vitejs/plugin-vue-jsx"; |
|||
import path from "path"; |
|||
import { viteStaticCopy } from "vite-plugin-static-copy"; |
|||
import AutoImport from "unplugin-auto-import/vite"; |
|||
import Components from "unplugin-vue-components/vite"; |
|||
import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; |
|||
import gzipPlugin from "rollup-plugin-gzip"; |
|||
// import eslintPlugin from 'vite-plugin-eslint';
|
|||
|
|||
const pathSrc = path.resolve(__dirname, 'src'); |
|||
const pathSrc = path.resolve(__dirname, "src"); |
|||
const root = process.cwd(); |
|||
// 开发
|
|||
const apiTarget = 'http://gateway.product.dev.com:30115/'; |
|||
const apiSluiceTarget = 'http://shuili-admin.product.dev.com:30115'; |
|||
const apiTarget = "http://gateway.product.dev.com:30115/"; |
|||
const apiSluiceTarget = "http://shuili-admin.product.dev.com:30115"; |
|||
// const apiTarget = 'http://sy.datametatech.com:60006/';
|
|||
// https://vitejs.dev/config/
|
|||
export default defineConfig(({ mode }) => { |
|||
const env = loadEnv(mode, path.resolve(root, './env')); |
|||
const env = loadEnv(mode, path.resolve(root, "./env")); |
|||
return { |
|||
envDir: './env', |
|||
envDir: "./env", |
|||
base: env.VITE_BASE_URL, |
|||
resolve: { |
|||
alias: { |
|||
'@': pathSrc, |
|||
'~/': `${pathSrc}/` |
|||
} |
|||
"@": pathSrc, |
|||
"~/": `${pathSrc}/`, |
|||
}, |
|||
}, |
|||
css: { |
|||
preprocessorOptions: { |
|||
scss: { |
|||
additionalData: `@use "~/theme/element/index.scss" as *;` |
|||
} |
|||
} |
|||
additionalData: `@use "~/theme/element/index.scss" as *;`, |
|||
}, |
|||
}, |
|||
}, |
|||
plugins: [ |
|||
vue({ |
|||
template: { |
|||
compilerOptions: { |
|||
isCustomElement: (tag) => tag.startsWith('iconpark-') |
|||
} |
|||
} |
|||
isCustomElement: (tag) => tag.startsWith("iconpark-"), |
|||
}, |
|||
}, |
|||
}), |
|||
vueJsx(), |
|||
AutoImport({ |
|||
resolvers: [ElementPlusResolver()] |
|||
resolvers: [ElementPlusResolver()], |
|||
}), |
|||
Components({ |
|||
// allow auto load markdown components under `./src/components/`
|
|||
extensions: ['vue', 'md'], |
|||
extensions: ["vue", "md"], |
|||
// allow auto import and register components used in markdown
|
|||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/], |
|||
resolvers: [ |
|||
ElementPlusResolver({ |
|||
importStyle: 'sass' |
|||
}) |
|||
importStyle: "sass", |
|||
}), |
|||
], |
|||
dts: 'src/components.d.ts' |
|||
dts: "src/components.d.ts", |
|||
}), |
|||
viteStaticCopy({ |
|||
targets: [ |
|||
{ |
|||
src: './node_modules/sy-cesium-sdk/dist/resources', |
|||
dest: '' |
|||
} |
|||
] |
|||
}) |
|||
src: "./node_modules/sy-cesium-sdk/dist/resources", |
|||
dest: mode === "development" ? "/sgcyy-slgcyxgl/" : "", |
|||
}, |
|||
], |
|||
}), |
|||
], |
|||
server: { |
|||
host: '0.0.0.0', |
|||
host: "0.0.0.0", |
|||
port: 6050, |
|||
proxy: { |
|||
|
|||
'/proxy': { |
|||
"/proxy": { |
|||
target: apiTarget, |
|||
changeOrigin: true |
|||
changeOrigin: true, |
|||
}, |
|||
'/iserver': { |
|||
target: 'http://172.16.34.41:8090', |
|||
changeOrigin: true |
|||
"/iserver": { |
|||
target: "http://172.16.34.41:8090", |
|||
changeOrigin: true, |
|||
}, |
|||
"/api/iserver": { |
|||
target: "http://172.16.34.41:8090", |
|||
changeOrigin: true, |
|||
rewrite: (path) => path.replace(/^\/api\/iserver/, "/iserver"), |
|||
}, |
|||
'/api/iserver': { |
|||
target: 'http://172.16.34.41:8090', |
|||
"/api/run": { |
|||
// target: "http://shuili.product.dev.com:30115/",
|
|||
target: "http://172.16.34.80:18082", |
|||
changeOrigin: true, |
|||
rewrite: (path) => path.replace(/^\/api\/iserver/, '/iserver') |
|||
}, |
|||
// '/api/run': {
|
|||
// // target: "http://shuili.product.dev.com:30115/",
|
|||
// target: 'http://172.16.34.80:18082',
|
|||
// changeOrigin: true,
|
|||
// rewrite: (path) => path.replace(/^\/api/, '/tianhui-admin-web')
|
|||
// },
|
|||
'/api': { |
|||
target: 'http://shuili.product.dev.com:30115', // 'http://172.16.34.59:18083'
|
|||
rewrite: (path) => path.replace(/^\/api/, "/tianhui-admin-web"), |
|||
}, |
|||
"/api": { |
|||
target: "http://shuili.product.dev.com:30115", // 'http://172.16.34.59:18083'
|
|||
changeOrigin: true, |
|||
rewrite: (path) => path.replace(/^\/api/, '/tianhui-admin-web') |
|||
rewrite: (path) => path.replace(/^\/api/, "/tianhui-admin-web"), |
|||
}, |
|||
'/geoserver': { |
|||
target: 'http://172.16.32.77:8700', |
|||
changeOrigin: true |
|||
"/geoserver": { |
|||
target: "http://172.16.32.77:8700", |
|||
changeOrigin: true, |
|||
}, |
|||
'/mapserver': { |
|||
target: 'http://172.16.34.71:8083/mapdata', |
|||
"/mapserver": { |
|||
target: "http://172.16.34.71:8083/mapdata", |
|||
changeOrigin: true, |
|||
rewrite: (path) => path.replace(/^\/mapserver/, '') |
|||
rewrite: (path) => path.replace(/^\/mapserver/, ""), |
|||
}, |
|||
'/images': { |
|||
"/images": { |
|||
target: apiTarget, |
|||
changeOrigin: true |
|||
changeOrigin: true, |
|||
}, |
|||
'/objs': { |
|||
"/objs": { |
|||
target: apiTarget, |
|||
changeOrigin: true |
|||
changeOrigin: true, |
|||
// rewrite: (path) => path.replace(/^\/sy-mixengine-ui/, '')
|
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
build: { |
|||
rollupOptions: { |
|||
output: { |
|||
manualChunks: { |
|||
sycim: ['sy-cesium-sdk'], |
|||
vue: ['vue'] |
|||
} |
|||
sycim: ["sy-cesium-sdk"], |
|||
vue: ["vue"], |
|||
}, |
|||
}, |
|||
plugins: [gzipPlugin()], |
|||
}, |
|||
plugins: [gzipPlugin()] |
|||
chunkSizeWarningLimit: 6000, |
|||
}, |
|||
chunkSizeWarningLimit: 6000 |
|||
} |
|||
} as any; |
|||
}); |
|||
|
Loading…
Reference in new issue