Commit d8eba72c58f003e6e8824396724e134b2ac316cb
1 parent
78e283b6
feat(src/packages): 组合里单个摄像头新增封面上传
Showing
4 changed files
with
146 additions
and
17 deletions
| 1 | <template> | 1 | <template> |
| 2 | <div class="videoPlay"> | 2 | <div class="videoPlay"> |
| 3 | - <video crossOrigin="anonymous" ref="videoRef" class="video-js vjs-default-skin vjs-big-play-centered" controls> | 3 | + <video |
| 4 | + style="object-fit: cover" | ||
| 5 | + :poster="poster" | ||
| 6 | + crossOrigin="anonymous" | ||
| 7 | + ref="videoRef" | ||
| 8 | + class="video-js vjs-default-skin vjs-big-play-centered" | ||
| 9 | + controls | ||
| 10 | + > | ||
| 4 | <source :src="path" /> | 11 | <source :src="path" /> |
| 5 | </video> | 12 | </video> |
| 6 | </div> | 13 | </div> |
| 7 | </template> | 14 | </template> |
| 8 | <script lang="ts" setup> | 15 | <script lang="ts" setup> |
| 9 | -import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' | 16 | +import { nextTick, onBeforeUnmount, onMounted, ref, watch, toRefs } from 'vue' |
| 10 | import videojs, { VideoJsPlayer } from 'video.js' | 17 | import videojs, { VideoJsPlayer } from 'video.js' |
| 11 | import type { VideoJsPlayerOptions } from 'video.js' | 18 | import type { VideoJsPlayerOptions } from 'video.js' |
| 12 | import 'video.js/dist/video-js.css' | 19 | import 'video.js/dist/video-js.css' |
| @@ -17,6 +24,7 @@ const props = withDefaults( | @@ -17,6 +24,7 @@ const props = withDefaults( | ||
| 17 | path: string | 24 | path: string |
| 18 | autoPlay?: boolean | 25 | autoPlay?: boolean |
| 19 | h?: number | 26 | h?: number |
| 27 | + poster?: string | ||
| 20 | }>(), | 28 | }>(), |
| 21 | { autoPlay: false } | 29 | { autoPlay: false } |
| 22 | ) | 30 | ) |
| @@ -33,19 +41,22 @@ const initPlay = async () => { | @@ -33,19 +41,22 @@ const initPlay = async () => { | ||
| 33 | controls: true, | 41 | controls: true, |
| 34 | autoplay: props.autoPlay, | 42 | autoplay: props.autoPlay, |
| 35 | src: props?.path, | 43 | src: props?.path, |
| 44 | + poster: props?.poster, | ||
| 36 | language: 'zh-CN', | 45 | language: 'zh-CN', |
| 37 | - techOrder: ['html5'] | 46 | + techOrder: ['html5'], |
| 47 | + preload: 'none' | ||
| 38 | } | 48 | } |
| 39 | player = videojs(videoRef.value, options, () => { | 49 | player = videojs(videoRef.value, options, () => { |
| 40 | - videojs.log('播放器已经准备好了!') | 50 | + videojs.log('Video is reading') |
| 41 | if (props.autoPlay) { | 51 | if (props.autoPlay) { |
| 42 | player.play() | 52 | player.play() |
| 43 | } | 53 | } |
| 44 | player.on('ended', () => { | 54 | player.on('ended', () => { |
| 45 | - videojs.log('播放结束了!') | 55 | + videojs.log('Play end') |
| 46 | }) | 56 | }) |
| 47 | player.on('error', () => { | 57 | player.on('error', () => { |
| 48 | - videojs.log('播放器解析出错!') | 58 | + player.errorDisplay.close() |
| 59 | + videojs.log('Play parse error') | ||
| 49 | }) | 60 | }) |
| 50 | }) | 61 | }) |
| 51 | } | 62 | } |
| @@ -58,10 +69,10 @@ onMounted(() => { | @@ -58,10 +69,10 @@ onMounted(() => { | ||
| 58 | watch( | 69 | watch( |
| 59 | () => props.path, | 70 | () => props.path, |
| 60 | () => { | 71 | () => { |
| 61 | - player?.pause() | ||
| 62 | - player?.src(props.path) | ||
| 63 | - player?.load() | ||
| 64 | - if (props.path) { | 72 | + if (props.path && props.autoPlay) { |
| 73 | + player?.pause() | ||
| 74 | + player?.load() | ||
| 75 | + player?.src(props.path) | ||
| 65 | player?.play() | 76 | player?.play() |
| 66 | } | 77 | } |
| 67 | }, | 78 | }, |
| @@ -74,11 +85,15 @@ onBeforeUnmount(() => { | @@ -74,11 +85,15 @@ onBeforeUnmount(() => { | ||
| 74 | player?.dispose() | 85 | player?.dispose() |
| 75 | }) | 86 | }) |
| 76 | </script> | 87 | </script> |
| 88 | +<style> | ||
| 89 | +.vjs-poster { | ||
| 90 | + background-size: cover !important; | ||
| 91 | +} | ||
| 92 | +</style> | ||
| 77 | <style lang="scss" scoped> | 93 | <style lang="scss" scoped> |
| 78 | .videoPlay { | 94 | .videoPlay { |
| 79 | flex: 1; | 95 | flex: 1; |
| 80 | height: v-bind('`${h}px`'); | 96 | height: v-bind('`${h}px`'); |
| 81 | - | ||
| 82 | .video-js { | 97 | .video-js { |
| 83 | height: 100%; | 98 | height: 100%; |
| 84 | width: 100%; | 99 | width: 100%; |
| @@ -6,6 +6,7 @@ import cloneDeep from 'lodash/cloneDeep' | @@ -6,6 +6,7 @@ import cloneDeep from 'lodash/cloneDeep' | ||
| 6 | export const option = { | 6 | export const option = { |
| 7 | dataset: '', | 7 | dataset: '', |
| 8 | autoplay: false, | 8 | autoplay: false, |
| 9 | + poster: '' | ||
| 9 | } | 10 | } |
| 10 | 11 | ||
| 11 | export default class Config extends PublicConfigClass implements CreateComponentType { | 12 | export default class Config extends PublicConfigClass implements CreateComponentType { |
| 1 | <template> | 1 | <template> |
| 2 | <CollapseItem name="播放器配置" :expanded="true"> | 2 | <CollapseItem name="播放器配置" :expanded="true"> |
| 3 | + <setting-item-box name="上传图片" :alone="true"> | ||
| 4 | + <setting-item> | ||
| 5 | + <n-card class="upload-box"> | ||
| 6 | + <n-upload | ||
| 7 | + :show-file-list="false" | ||
| 8 | + v-model:file-list="uploadFileListRef" | ||
| 9 | + :customRequest="customRequest" | ||
| 10 | + :onBeforeUpload="beforeUploadHandle" | ||
| 11 | + > | ||
| 12 | + <n-upload-dragger> | ||
| 13 | + <img v-if="optionData.poster" class="upload-show" :src="optionData.poster" alt="背景" /> | ||
| 14 | + <div class="upload-img" v-show="!optionData.poster"> | ||
| 15 | + <img src="@/assets/images/canvas/noImage.png" /> | ||
| 16 | + <n-text class="upload-desc" depth="3"> | ||
| 17 | + 图片需小于 {{ backgroundImageSize }}M ,格式为 png/jpg/gif 的文件 | ||
| 18 | + </n-text> | ||
| 19 | + </div> | ||
| 20 | + </n-upload-dragger> | ||
| 21 | + </n-upload> | ||
| 22 | + </n-card> | ||
| 23 | + </setting-item> | ||
| 24 | + </setting-item-box> | ||
| 3 | <setting-item-box name="源地址" :alone="true"> | 25 | <setting-item-box name="源地址" :alone="true"> |
| 4 | <setting-item> | 26 | <setting-item> |
| 5 | <n-input-group> | 27 | <n-input-group> |
| @@ -16,14 +38,94 @@ | @@ -16,14 +38,94 @@ | ||
| 16 | </template> | 38 | </template> |
| 17 | 39 | ||
| 18 | <script setup lang="ts"> | 40 | <script setup lang="ts"> |
| 19 | -import { PropType } from 'vue' | 41 | +import { PropType, ref, nextTick } from 'vue' |
| 20 | import { option } from './config' | 42 | import { option } from './config' |
| 21 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' | 43 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 44 | +import { FileTypeEnum } from '@/enums/fileTypeEnum' | ||
| 45 | +import { uploadFile } from '@/api/external/contentSave/content' | ||
| 46 | +import { UploadCustomRequestOptions } from 'naive-ui' | ||
| 47 | +import { backgroundImageSize } from '@/settings/designSetting' | ||
| 48 | +import { fetchRouteParamsLocation } from '@/utils' | ||
| 22 | 49 | ||
| 23 | -defineProps({ | 50 | +const props = defineProps({ |
| 24 | optionData: { | 51 | optionData: { |
| 25 | type: Object as PropType<typeof option>, | 52 | type: Object as PropType<typeof option>, |
| 26 | required: true | 53 | required: true |
| 27 | } | 54 | } |
| 28 | }) | 55 | }) |
| 56 | + | ||
| 57 | +const uploadFileListRef = ref() | ||
| 58 | + | ||
| 59 | +// 上传图片前置处理 | ||
| 60 | +// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| 61 | +//@ts-ignore | ||
| 62 | +const beforeUploadHandle = async ({ file }) => { | ||
| 63 | + uploadFileListRef.value = [] | ||
| 64 | + const type = file.file.type | ||
| 65 | + const size = file.file.size | ||
| 66 | + | ||
| 67 | + if (size > 1024 * 1024 * backgroundImageSize) { | ||
| 68 | + window['$message'].warning(`图片超出 ${backgroundImageSize}M 限制,请重新上传!`) | ||
| 69 | + return false | ||
| 70 | + } | ||
| 71 | + if (type !== FileTypeEnum.PNG && type !== FileTypeEnum.JPEG && type !== FileTypeEnum.GIF) { | ||
| 72 | + window['$message'].warning('文件格式不符合,请重新上传!') | ||
| 73 | + return false | ||
| 74 | + } | ||
| 75 | + return true | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +// 自定义上传操作 | ||
| 79 | +const customRequest = (options: UploadCustomRequestOptions) => { | ||
| 80 | + const { file } = options | ||
| 81 | + nextTick(async () => { | ||
| 82 | + if (file.file) { | ||
| 83 | + // 修改名称 | ||
| 84 | + const newNameFile = new File([file.file], `${fetchRouteParamsLocation()}_index_background.png`, { | ||
| 85 | + type: file.file.type | ||
| 86 | + }) | ||
| 87 | + let uploadParams = new FormData() | ||
| 88 | + uploadParams.append('file', newNameFile) | ||
| 89 | + const uploadRes = await uploadFile(uploadParams) | ||
| 90 | + if (uploadRes) { | ||
| 91 | + props.optionData.poster = uploadRes?.fileStaticUri | ||
| 92 | + window['$message'].success('添加图片成功!') | ||
| 93 | + } | ||
| 94 | + } else { | ||
| 95 | + window['$message'].error('添加图片失败,请稍后重试!') | ||
| 96 | + } | ||
| 97 | + }) | ||
| 98 | +} | ||
| 29 | </script> | 99 | </script> |
| 100 | +<style lang="scss" scoped> | ||
| 101 | +$uploadHeight: 193px; | ||
| 102 | +.upload-box { | ||
| 103 | + cursor: pointer; | ||
| 104 | + margin-bottom: 20px; | ||
| 105 | + @include deep() { | ||
| 106 | + .n-card__content { | ||
| 107 | + padding: 0; | ||
| 108 | + overflow: hidden; | ||
| 109 | + } | ||
| 110 | + .n-upload-dragger { | ||
| 111 | + padding: 5px; | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | + .upload-show { | ||
| 115 | + width: -webkit-fill-available; | ||
| 116 | + height: $uploadHeight; | ||
| 117 | + border-radius: 5px; | ||
| 118 | + } | ||
| 119 | + .upload-img { | ||
| 120 | + display: flex; | ||
| 121 | + flex-direction: column; | ||
| 122 | + align-items: center; | ||
| 123 | + img { | ||
| 124 | + height: 150px; | ||
| 125 | + } | ||
| 126 | + .upload-desc { | ||
| 127 | + padding: 10px 0; | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | +} | ||
| 131 | +</style> |
| 1 | <template> | 1 | <template> |
| 2 | <div> | 2 | <div> |
| 3 | - <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" /> | 3 | + <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" :poster="option.poster" /> |
| 4 | </div> | 4 | </div> |
| 5 | </template> | 5 | </template> |
| 6 | <script setup lang="ts"> | 6 | <script setup lang="ts"> |
| @@ -18,10 +18,11 @@ const props = defineProps({ | @@ -18,10 +18,11 @@ const props = defineProps({ | ||
| 18 | 18 | ||
| 19 | const { h } = toRefs(props.chartConfig.attr) | 19 | const { h } = toRefs(props.chartConfig.attr) |
| 20 | 20 | ||
| 21 | -const { autoplay, dataset } = toRefs(props.chartConfig.option) | 21 | +const { autoplay, dataset, poster } = toRefs(props.chartConfig.option) |
| 22 | 22 | ||
| 23 | const option = shallowReactive({ | 23 | const option = shallowReactive({ |
| 24 | - dataset: configOption.dataset | 24 | + dataset: configOption.dataset, |
| 25 | + poster: configOption.poster | ||
| 25 | }) | 26 | }) |
| 26 | 27 | ||
| 27 | watch( | 28 | watch( |
| @@ -30,7 +31,17 @@ watch( | @@ -30,7 +31,17 @@ watch( | ||
| 30 | option.dataset = newData | 31 | option.dataset = newData |
| 31 | }, | 32 | }, |
| 32 | { | 33 | { |
| 33 | - immediate: true, | 34 | + immediate: true |
| 35 | + } | ||
| 36 | +) | ||
| 37 | + | ||
| 38 | +watch( | ||
| 39 | + () => poster?.value, | ||
| 40 | + (newData: string) => { | ||
| 41 | + option.poster = newData | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + immediate: true | ||
| 34 | } | 45 | } |
| 35 | ) | 46 | ) |
| 36 | </script> | 47 | </script> |