19 changed files with 312 additions and 6907 deletions
@ -1,107 +1,106 @@ |
|||||
// import axios from 'axios';
|
// import axios from 'axios';
|
||||
import { he } from 'element-plus/es/locale'; |
import { request } from "../axios"; |
||||
import { request } from '../axios'; |
|
||||
|
|
||||
// 预警统计
|
// 预警统计
|
||||
export const getWarningStatistic = (params: any) => { |
export const getWarningStatistic = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/warning/statistic', |
url: "/run/api/warning/statistic", |
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
|
|
||||
// 预警列表
|
// 预警列表
|
||||
export const getWarningList = (params: any) => { |
export const getWarningList = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/warning/list', |
url: "/run/api/warning/list", |
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
|
|
||||
// 查询行政区划数据
|
// 查询行政区划数据
|
||||
export const getAreasData = () => { |
export const getAreasData = () => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/common/xzgh/getGuangDong', |
url: "/run/api/common/xzgh/getGuangDong", |
||||
method: 'get' |
method: "get", |
||||
}); |
}); |
||||
}; |
}; |
||||
|
|
||||
// 根据字典类型查询字典数据信息
|
// 根据字典类型查询字典数据信息
|
||||
export function getDicts(dictType: any) { |
export function getDicts(dictType: any) { |
||||
return request({ |
return request({ |
||||
url: '/run/api/common/dict/type/' + dictType, |
url: "/run/api/common/dict/type/" + dictType, |
||||
method: 'get' |
method: "get", |
||||
}); |
}); |
||||
} |
} |
||||
|
|
||||
// 通过类型和编码查询对象详细信息
|
// 通过类型和编码查询对象详细信息
|
||||
export const getObjectInfo = (params: any) => { |
export const getObjectInfo = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/object/info/' + params.type + '/' + params.code, |
url: "/run/api/object/info/" + params.type + "/" + params.code, |
||||
method: 'get' |
method: "get", |
||||
}); |
}); |
||||
}; |
}; |
||||
|
|
||||
// 视频图像
|
// 视频图像
|
||||
export const getVideoWarningList = (params: any) => { |
export const getVideoWarningList = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/video/warning/list', // /run/api/video/warning/list
|
url: "/run/api/video/warning/list", // /run/api/video/warning/list
|
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
// 运行指标列表
|
// 运行指标列表
|
||||
export const getIndicatorList = (params: any) => { |
export const getIndicatorList = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/indicator/list', |
url: "/run/api/indicator/list", |
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
|
|
||||
// 按小时统计运行坐标
|
// 按小时统计运行坐标
|
||||
export const getIndicatorHour = (params: any) => { |
export const getIndicatorHour = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/indicator/hour', |
url: "/run/api/indicator/hour", |
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
// 最大最小平均值
|
// 最大最小平均值
|
||||
export const getIndicatorStatistic = (params: any) => { |
export const getIndicatorStatistic = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/indicator/statistic', |
url: "/run/api/indicator/statistic", |
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
|
|
||||
// 水利对象统计
|
// 水利对象统计
|
||||
export const getObjectStatistic = (params: any) => { |
export const getObjectStatistic = (params: any) => { |
||||
return request({ |
return request({ |
||||
url: '/run/api/object/statistic', |
url: "/run/api/object/statistic", |
||||
method: 'post', |
method: "post", |
||||
params |
params, |
||||
}); |
}); |
||||
}; |
}; |
||||
function getCookie(name: string): string | null { |
function getCookie(name: string): string | null { |
||||
const value = `; ${document.cookie}`; |
const value = `; ${document.cookie}`; |
||||
const parts = value.split(`; ${name}=`); |
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; |
return null; |
||||
} |
} |
||||
export function getlStatisticChart(data: any) { |
export function getlStatisticChart(data: any) { |
||||
// 获取cookie
|
// 获取cookie
|
||||
const shuili = getCookie('Admin-Token'); |
const shuili = getCookie("Admin-Token"); |
||||
return request({ |
return request({ |
||||
url: `/run/statistic/chart`, |
url: `/run/statistic/chart`, |
||||
method: 'post', |
method: "post", |
||||
data, |
data, |
||||
headers: { |
headers: { |
||||
// 'Admin-Token': shuili,
|
// '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 { defineConfig, loadEnv } from "vite"; |
||||
import vue from '@vitejs/plugin-vue'; |
import vue from "@vitejs/plugin-vue"; |
||||
import vueJsx from '@vitejs/plugin-vue-jsx'; |
import vueJsx from "@vitejs/plugin-vue-jsx"; |
||||
import path from 'path'; |
import path from "path"; |
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'; |
import { viteStaticCopy } from "vite-plugin-static-copy"; |
||||
import AutoImport from 'unplugin-auto-import/vite'; |
import AutoImport from "unplugin-auto-import/vite"; |
||||
import Components from 'unplugin-vue-components/vite'; |
import Components from "unplugin-vue-components/vite"; |
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; |
import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; |
||||
import gzipPlugin from 'rollup-plugin-gzip'; |
import gzipPlugin from "rollup-plugin-gzip"; |
||||
// import eslintPlugin from 'vite-plugin-eslint';
|
// import eslintPlugin from 'vite-plugin-eslint';
|
||||
|
|
||||
const pathSrc = path.resolve(__dirname, 'src'); |
const pathSrc = path.resolve(__dirname, "src"); |
||||
const root = process.cwd(); |
const root = process.cwd(); |
||||
// 开发
|
// 开发
|
||||
const apiTarget = 'http://gateway.product.dev.com:30115/'; |
const apiTarget = "http://gateway.product.dev.com:30115/"; |
||||
const apiSluiceTarget = 'http://shuili-admin.product.dev.com:30115'; |
const apiSluiceTarget = "http://shuili-admin.product.dev.com:30115"; |
||||
// const apiTarget = 'http://sy.datametatech.com:60006/';
|
// const apiTarget = 'http://sy.datametatech.com:60006/';
|
||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => { |
export default defineConfig(({ mode }) => { |
||||
const env = loadEnv(mode, path.resolve(root, './env')); |
const env = loadEnv(mode, path.resolve(root, "./env")); |
||||
return { |
return { |
||||
envDir: './env', |
envDir: "./env", |
||||
base: env.VITE_BASE_URL, |
base: env.VITE_BASE_URL, |
||||
resolve: { |
resolve: { |
||||
alias: { |
alias: { |
||||
'@': pathSrc, |
"@": pathSrc, |
||||
'~/': `${pathSrc}/` |
"~/": `${pathSrc}/`, |
||||
} |
}, |
||||
}, |
}, |
||||
css: { |
css: { |
||||
preprocessorOptions: { |
preprocessorOptions: { |
||||
scss: { |
scss: { |
||||
additionalData: `@use "~/theme/element/index.scss" as *;` |
additionalData: `@use "~/theme/element/index.scss" as *;`, |
||||
} |
}, |
||||
} |
}, |
||||
}, |
}, |
||||
plugins: [ |
plugins: [ |
||||
vue({ |
vue({ |
||||
template: { |
template: { |
||||
compilerOptions: { |
compilerOptions: { |
||||
isCustomElement: (tag) => tag.startsWith('iconpark-') |
isCustomElement: (tag) => tag.startsWith("iconpark-"), |
||||
} |
}, |
||||
} |
}, |
||||
}), |
}), |
||||
vueJsx(), |
vueJsx(), |
||||
AutoImport({ |
AutoImport({ |
||||
resolvers: [ElementPlusResolver()] |
resolvers: [ElementPlusResolver()], |
||||
}), |
}), |
||||
Components({ |
Components({ |
||||
// allow auto load markdown components under `./src/components/`
|
// allow auto load markdown components under `./src/components/`
|
||||
extensions: ['vue', 'md'], |
extensions: ["vue", "md"], |
||||
// allow auto import and register components used in markdown
|
// allow auto import and register components used in markdown
|
||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/], |
include: [/\.vue$/, /\.vue\?vue/, /\.md$/], |
||||
resolvers: [ |
resolvers: [ |
||||
ElementPlusResolver({ |
ElementPlusResolver({ |
||||
importStyle: 'sass' |
importStyle: "sass", |
||||
}) |
}), |
||||
], |
], |
||||
dts: 'src/components.d.ts' |
dts: "src/components.d.ts", |
||||
}), |
}), |
||||
viteStaticCopy({ |
viteStaticCopy({ |
||||
targets: [ |
targets: [ |
||||
{ |
{ |
||||
src: './node_modules/sy-cesium-sdk/dist/resources', |
src: "./node_modules/sy-cesium-sdk/dist/resources", |
||||
dest: '' |
dest: mode === "development" ? "/sgcyy-slgcyxgl/" : "", |
||||
} |
}, |
||||
] |
], |
||||
}) |
}), |
||||
], |
], |
||||
server: { |
server: { |
||||
host: '0.0.0.0', |
host: "0.0.0.0", |
||||
port: 6050, |
port: 6050, |
||||
proxy: { |
proxy: { |
||||
|
"/proxy": { |
||||
'/proxy': { |
|
||||
target: apiTarget, |
target: apiTarget, |
||||
changeOrigin: true |
changeOrigin: true, |
||||
}, |
}, |
||||
'/iserver': { |
"/iserver": { |
||||
target: 'http://172.16.34.41:8090', |
target: "http://172.16.34.41:8090", |
||||
changeOrigin: true |
changeOrigin: true, |
||||
|
}, |
||||
|
"/api/iserver": { |
||||
|
target: "http://172.16.34.41:8090", |
||||
|
changeOrigin: true, |
||||
|
rewrite: (path) => path.replace(/^\/api\/iserver/, "/iserver"), |
||||
}, |
}, |
||||
'/api/iserver': { |
"/api/run": { |
||||
target: 'http://172.16.34.41:8090', |
// target: "http://shuili.product.dev.com:30115/",
|
||||
|
target: "http://172.16.34.80:18082", |
||||
changeOrigin: true, |
changeOrigin: true, |
||||
rewrite: (path) => path.replace(/^\/api\/iserver/, '/iserver') |
rewrite: (path) => path.replace(/^\/api/, "/tianhui-admin-web"), |
||||
}, |
}, |
||||
// '/api/run': {
|
"/api": { |
||||
// // target: "http://shuili.product.dev.com:30115/",
|
target: "http://shuili.product.dev.com:30115", // 'http://172.16.34.59:18083'
|
||||
// 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'
|
|
||||
changeOrigin: true, |
changeOrigin: true, |
||||
rewrite: (path) => path.replace(/^\/api/, '/tianhui-admin-web') |
rewrite: (path) => path.replace(/^\/api/, "/tianhui-admin-web"), |
||||
}, |
}, |
||||
'/geoserver': { |
"/geoserver": { |
||||
target: 'http://172.16.32.77:8700', |
target: "http://172.16.32.77:8700", |
||||
changeOrigin: true |
changeOrigin: true, |
||||
}, |
}, |
||||
'/mapserver': { |
"/mapserver": { |
||||
target: 'http://172.16.34.71:8083/mapdata', |
target: "http://172.16.34.71:8083/mapdata", |
||||
changeOrigin: true, |
changeOrigin: true, |
||||
rewrite: (path) => path.replace(/^\/mapserver/, '') |
rewrite: (path) => path.replace(/^\/mapserver/, ""), |
||||
}, |
}, |
||||
'/images': { |
"/images": { |
||||
target: apiTarget, |
target: apiTarget, |
||||
changeOrigin: true |
changeOrigin: true, |
||||
}, |
}, |
||||
'/objs': { |
"/objs": { |
||||
target: apiTarget, |
target: apiTarget, |
||||
changeOrigin: true |
changeOrigin: true, |
||||
// rewrite: (path) => path.replace(/^\/sy-mixengine-ui/, '')
|
// rewrite: (path) => path.replace(/^\/sy-mixengine-ui/, '')
|
||||
} |
}, |
||||
} |
}, |
||||
}, |
}, |
||||
build: { |
build: { |
||||
rollupOptions: { |
rollupOptions: { |
||||
output: { |
output: { |
||||
manualChunks: { |
manualChunks: { |
||||
sycim: ['sy-cesium-sdk'], |
sycim: ["sy-cesium-sdk"], |
||||
vue: ['vue'] |
vue: ["vue"], |
||||
} |
}, |
||||
|
}, |
||||
|
plugins: [gzipPlugin()], |
||||
}, |
}, |
||||
plugins: [gzipPlugin()] |
chunkSizeWarningLimit: 6000, |
||||
}, |
}, |
||||
chunkSizeWarningLimit: 6000 |
|
||||
} |
|
||||
} as any; |
} as any; |
||||
}); |
}); |
||||
|
Loading…
Reference in new issue