图片、视频加载方式

This commit is contained in:
zhujingzhou
2025-07-20 11:36:46 +07:00
parent 2ab6513b73
commit fa06b597e5
17 changed files with 1204 additions and 375 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
+19 -1
View File
@@ -1,3 +1,9 @@
<!--
* @Date: 2024-05-21 21:33:04
* @LastEditors: Administrator admin@example.com
* @LastEditTime: 2024-06-17 17:59:57
* @FilePath: /txsm_webAdmin/src/App.vue
-->
<template>
<div id="app">
<router-view />
@@ -5,17 +11,29 @@
</template>
<script>
import mixinSyscfg from './mixins/mixinSyscfg'
export default {
name: 'App'
name: 'App',
mixins: [mixinSyscfg],
async created() {
await this.getSyscfg()
}
}
</script>
<style lang="scss">
.el-tooltip__popper {
max-width: 600px;
}
.el-table {
font-size: 12px !important;
color: rgb(56, 51, 51) !important;
}
.tag-wrap {
// height: 200px;
overflow: scroll;
overflow-x: hidden;
}
</style>
+57 -14
View File
@@ -8,7 +8,11 @@
-->
<template>
<div>
<quill-editor v-model="content" ref="myQuillEditor" :options="editorOption" @ready="onEditorReady($event)"></quill-editor>
<quill-editor
v-model="content"
ref="myQuillEditor"
:options="editorOption"
@ready="onEditorReady($event)"></quill-editor>
<!-- el-uploader不好手动触发上传功能 -->
<uploader
:style="{ display: 'none' }"
@@ -22,6 +26,7 @@
</div>
</template>
<script>
import { handleVerAutoImg } from '@/utils/index.js'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
@@ -31,6 +36,7 @@ Link.PROTOCOL_WHITELIST = ['http', 'https', 'mailto', 'tel', 'yinseinner']
import { quillEditor } from 'vue-quill-editor'
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
import { data } from 'qrcode.vue'
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
@@ -49,6 +55,7 @@ export default {
textNode: {
default: ''
}
},
computed: {
options() {
@@ -69,7 +76,8 @@ export default {
const _that = this
return {
content: '',
domain: this.$store.getters.getSystemConfig.webImgReq,
imgPath: [],
domain: this.$store.getters.getSystemConfig.webImgReq, // 不能暴露源站地址
attrsImg: {
accept: ['image/*']
},
@@ -105,7 +113,7 @@ export default {
}
},
// //配置富文本框图片大小调整功能
// imageDrop: true,
imageDrop: true,
imageResize: {
displayStyles: {
backgroundColor: 'black',
@@ -114,6 +122,7 @@ export default {
},
modules: ['Resize', 'DisplaySize', 'Toolbar']
}
}
}
@@ -121,33 +130,67 @@ export default {
}
},
mounted() {},
methods: {
// 父组件获取数据
getdata() {
const str = this.content.replace(this.domain, '')
// 获取 Quill 编辑器中的所有 img 标签
const imgElements = this.$refs.myQuillEditor.quill.container.querySelectorAll('img')
// 存储自定义属性值
const customAttrValues = []
// 遍历所有 img 标签并获取自定义属性值
imgElements.forEach(img => {
const customAttrValue = img.getAttribute('data-custom') // 假设自定义属性是 data-custom
if (customAttrValue) {
customAttrValues.push(customAttrValue) // 添加到数组中
}
})
// 替换 img 标签的 src
for (let i = 0; i < imgElements.length; i++) {
const newSrc = customAttrValues[i]
const regex = new RegExp(`<img\\s+([^>]*?)src="([^"]*)"([^>]*?)>`, 'i')
return str
this.content = this.content.replace(regex, (match, beforeSrc, oldSrc, afterSrc) => {
// 使用 oldSrc 设置 data-custom 属性,同时替换 src
return `<img ${beforeSrc} src="${newSrc}" data-custom="${oldSrc}"${afterSrc}>`
})
}
return this.content
},
// 当文本框加载完成时,编译插入数据
async onEditorReady(e) {
const data = this.textNode.replace('img src="', 'img src="' + this.domain)
e.container.querySelector('.ql-blank').innerHTML = data
this.content = this.textNode.replace(/<img\s+([^>]*?)src="([^"]*?)"[^>]*data-custom="([^"]*?)"([^>]*?)>/g, (match, before, srcValue, customValue, after) => {
// 互换 src 和 data-custom 的值
return `<img ${before}src="${customValue}" data-custom="${srcValue}"${after}>`
})
},
imageSuccess(rootFile, file, message) {
async imageSuccess(rootFile, file, message) {
const { code, data } = JSON.parse(message)
if (code === 200) {
this.storageBase64 = await handleVerAutoImg(this.domain + data.path)
const reader = new FileReader()
reader.readAsDataURL(file.file)
reader.onload = e => {
reader.onload = async(e) => {
const base64Data = e.target.result // 获取转换后的 Data URL
const myeditor = this.$refs.myQuillEditor.quill
// editor方法获取当前光标位置
// 获取当前光标位置
const imgElement = document.createElement('img')
imgElement.src = base64Data
// 将 imgElement 插入到编辑器中
const index = myeditor.getSelection().index
myeditor.insertEmbed(index, 'image', this.domain + data.path)
// 调整光标到图片之后的位置上
myeditor.insertEmbed(index, 'image', imgElement.src)
// 通过 Quill 的 API 将图片插入后,直接修改 DOM 来添加自定义属性
const imgNode = myeditor.container.querySelector(`img[src="${imgElement.src}"]`)
if (imgNode) {
imgNode.setAttribute('data-custom', data.path) // 设置自定义属性
}
myeditor.setSelection(index + 1)
}
// 读取 Blob 数据
reader.readAsDataURL(file.file)
}
},
+10 -9
View File
@@ -6,10 +6,10 @@
* @LastEditTime: 2020-11-24 17:04:24
-->
<template>
<el-dialog append-to-body class="img_dialog" :title="title" align="center" :visible.sync="dialogVisible" width="40%" :before-close="handleClose" :close-on-click-modal="false" :modal-append-to-body='false'>
<el-dialog class="img_dialog" append-to-body :title="title" align="center" :visible.sync="dialogVisible" width="40%" :before-close="handleClose" :close-on-click-modal="false" :modal-append-to-body='false'>
<el-carousel v-if="isArry" indicator-position="outside">
<el-carousel-item v-for="item in mediaUrl" :key="item">
<div class="media-box" id="img-box" > <img :src="domain+item" /></div>
<div class="media-box" id="img-box" > <img :src="changeImg(item)" /></div>
</el-carousel-item>
</el-carousel>
<div v-else class="media-box" id="img-box" > <img :src="mediaUrl" /></div>
@@ -17,6 +17,7 @@
</template>
<script>
import { handleVerAutoImg } from '@/utils/index.js'
export default {
data() {
return {
@@ -32,10 +33,14 @@ export default {
token() {
return this.$store.state.token
}
},
mounted() {
},
methods: {
async changeImg(url) {
return await handleVerAutoImg(this.domain + url)
},
handleClose() {
if (this.player) {
this.player.dispose()
@@ -58,11 +63,6 @@ export default {
</script>
<style scoped lang="scss">
.my_data{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.img_dialog{
::v-deep{
@@ -75,14 +75,14 @@ export default {
}
}
.media-box {
width: 100%;
height: 400px;
text-align: center;
overflow: hidden;
img {
height: 100%;
max-width: 100%;
width: 100%;
}
#my-video {
width: 100% !important;
@@ -90,4 +90,5 @@ export default {
border: none !important;
}
}
</style>
+13 -15
View File
@@ -13,7 +13,7 @@
</div>
<div class="showImg">
<div v-if="showNoImg">暂无</div>
<el-image v-else :src="src" style="width: 100%, height: 100%" @click="checkMedia()" lazy >
<el-image v-else :src="src" @click="checkMedia()" lazy >
<div slot="placeholder" class="image-slot" :style="{width: width, height: height, lineHeight: height}">
<i class="el-icon-loading"></i>加载中
</div>
@@ -28,6 +28,7 @@
</template>
<script>
import Dialog from './components/dialog'
import { handleVerAutoImg } from '@/utils/index.js'
export default {
data() {
return {
@@ -43,15 +44,12 @@ export default {
},
props: ['width', 'height', 'urls'],
watch: {
urls(newval, oldval) {
async urls(newval, oldval) {
this.urls = newval
// if (this.type && this.type === 'daichong') {
// this.domain = ''
// }
this.imgsUrl = []
if (this.urls) { // 判断是否存在
if (typeof (this.urls) === 'string') { // 判断是单张图片还是数组
this.src = this.domain + this.urls
this.src = await handleVerAutoImg(this.domain + this.urls)
this.showNoImg = false
} else {
if (this.urls.length) { // 判断是否为空数组
@@ -60,8 +58,8 @@ export default {
} else {
this.showChangeBtn = true
}
this.urls.forEach(item => {
this.imgsUrl.push(this.domain + item)
this.urls.forEach(async item => {
this.imgsUrl.push(await handleVerAutoImg(this.domain + this.urls))
})
this.src = this.imgsUrl[0]
this.showNoImg = false
@@ -75,11 +73,11 @@ export default {
}
}
},
mounted() {
async mounted() {
this.imgsUrl = []
if (this.urls) { // 判断是否存在
if (typeof (this.urls) === 'string') { // 判断是单张图片还是数组
this.src = this.domain + this.urls
this.src = await handleVerAutoImg(this.domain + this.urls)
} else {
if (this.urls.length) { // 判断是否为空数组
if (this.urls.length === 1) { // 判断数组中是否只有一张图片
@@ -87,8 +85,8 @@ export default {
} else {
this.showChangeBtn = true
}
this.urls.forEach(item => {
this.imgsUrl.push(this.domain + item)
this.urls.forEach(async item => {
this.imgsUrl.push(await handleVerAutoImg(this.domain + item))
})
this.src = this.imgsUrl[0]
} else {
@@ -118,12 +116,12 @@ export default {
}
},
// 查看媒体
checkMedia() {
async checkMedia() {
if (this.urls) { // 判断是否存在
if (typeof (this.urls) === 'string') { // 判断是单张图片还是数组
this.$refs.dialog.open(this.domain + this.urls, 'img')
this.$refs.dialog.open(await handleVerAutoImg(this.domain + this.urls), 'img')
} else {
this.$refs.dialog.open(this.urls, 'img')
this.$refs.dialog.open(this.imgsUrl, 'img')
}
}
}
+23 -5
View File
@@ -14,7 +14,7 @@
<span>视频时长:{{ this.myData.playTime + "s" }}</span>
</div>
<!-- <div class="media-box" id="media-box"></div> -->
<div v-if="mediaUrl" style="width: 400px; heigth: 130px; background: red;margin-top:10px">
<div v-if="mediaUrl" style="width: 100%; heigth: 200px; background: red">
<DPlayer
ref="player"
:video="{
@@ -79,6 +79,7 @@ export default {
// 视频审查列表
videoFeviewList() {
videoFeviewList(this.params, this.id).then((res) => {
// this.myData = res.data,
})
},
@@ -93,18 +94,35 @@ export default {
this.myData = { ...data }
this.dialogVisible = true
this.title = '视频查看'
this.mediaUrl =
this.domain + (url.match(RegExp(/.m3u8/)) ? url : url + '.m3u8')
if (url.indexOf('mp4') !== -1) {
this.mediaUrl = this.$store.getters.getSystemConfig.webImgReq + url
this.mediaUrl = this.$store.getters.getSystemConfig.mp4Url + url
this.type = 'mp4'
} else {
this.mediaUrl =
this.domain + (url.match(RegExp(/.m3u8/)) ? url : url + '.m3u8')
this.videoFeviewList()
}
// + '&token=' + this.token
// this.id = id;
// this.$nextTick(function () {
// document.getElementById("media-box").innerHTML =
// '<video id="my-video" class="video-js vjs-default-skin" controls preload="auto" style="width:100%;height:100%"></video>';
// this.player = videojs(
// "my-video",
// {
// bigPlayButton: false,
// textTrackDisplay: false,
// posterImage: true,
// errorDisplay: true,
// controlBar: true,
// },
// function () {
// this.play();
// }
// );
// this.player.src(this.mediaUrl);
// });
}
}
}
+40 -8
View File
@@ -2,28 +2,35 @@
* @Author: 王涛
* @Mail: no
* @Date: 2020-09-29 19:56:44
* @LastEditors: Please set LastEditors
* @LastEditTime: 2022-06-21 20:05:54
* @LastEditors: Administrator admin@example.com
* @LastEditTime: 2024-11-19 10:14:08
-->
<template>
<!-- 使用方法 <UploadImg @changeImgUrl='changeMobileAvatar' :img='form.mobileAvatar' :width='"100px"' :height='"100px"'></UploadImg>
changeMobileAvatar(data) {
this.form.mobileAvatar = data
}, -->
<el-upload class="avatar-uploader" action="/api/web/file/uploadImg" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<el-upload
class="avatar-uploader"
action="/api/web/file/uploadImg"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<div ref="uploader">
<img v-if="imageUrl" :src="domain + imageUrl" class="avatar" />
<img v-if="newImg" :src="newImg" class="avatar" />
<i v-if="imageUrl" @click.stop="handleRemove" class="el-icon-delete"></i>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</div>
</el-upload>
</template>
<script>
import { handleVerAutoImg } from '@/utils/index.js'
export default {
data() {
return {
domain: this.$store.getters.getSystemConfig.webImgReq,
imageUrl: ''
imageUrl: '',
newImg: ''
}
},
props: {
@@ -38,11 +45,23 @@ export default {
height: {
type: String,
default: '180px'
},
index: {
type: Number,
default: 0
},
showIndex: {
type: Boolean,
default: false
}
},
watch: {
img: function(val, oldval) {
this.imageUrl = val
},
imageUrl: async function(val, oldval) {
const a = await handleVerAutoImg(this.domain + val)
this.newImg = a
}
},
mounted() {
@@ -56,6 +75,9 @@ export default {
}
},
methods: {
async changeImg(url) {
},
handleAvatarSuccess(res, file) {
if (res.code === 200) {
this.$message({
@@ -74,11 +96,19 @@ export default {
}
},
changeImgUrl() {
if (this.showIndex) {
this.$emit('changeImgUrl', { url: this.imageUrl, index: this.index })
} else {
this.$emit('changeImgUrl', this.imageUrl)
}
},
handleRemove(f) {
this.imageUrl = ''
if (this.showIndex) {
this.$emit('onRemove', { key: f, index: this.index })
} else {
this.$emit('onRemove', f)
}
},
// 上传前进行压缩
beforeAvatarUpload(file) {
@@ -142,19 +172,21 @@ export default {
}
</script>
<style scoped>
.avatar-uploader .el-upload {
.avatar-uploader {
.el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
.el-upload:hover {
border-color: #409eff;
}
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
color: #d9d9d9;
width: 100%;
height: 100%;
line-height: 100%;
+16 -4
View File
@@ -9,11 +9,11 @@
<div>
<draggable v-model="locaImg" class="list-group" tag="ul" v-bind="dragOptions" @start="drag = true" @end="drag = false">
<div v-for="(img,index) in locaImg" :key="index" class="image-list" style="float: left;">
<img class="el-upload-list__item-thumbnail image-detail" :src="domain+img" alt="">
<img class="el-upload-list__item-thumbnail image-detail" :src="imgSrc[index]" alt="">
<!-- 遮罩层 -->
<a href="#">
<div class="image-mask">
<span class="add" @click="handlePictureCardPreview(domain+img)">
<span class="add" @click="handlePictureCardPreview(imgSrc[index])">
<i class="el-icon-view"></i>
</span>
<span class="delete" @click="removeImage(index)">
@@ -33,6 +33,7 @@
</template>
<script>
import draggable from 'vuedraggable'
import { handleVerAutoImg } from '@/utils/index.js'
export default {
components: { draggable },
data() {
@@ -46,7 +47,8 @@ export default {
// { name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }
],
drag: false, // 拖拽效果是否开启
locaImg: []
locaImg: [],
imgSrc: []
}
},
props: {
@@ -57,6 +59,7 @@ export default {
}
}
},
computed: {
dragOptions() {
return {
@@ -66,6 +69,7 @@ export default {
ghostClass: 'ghost'
}
}
},
watch: {
// 监听拖动事件改变时,传出新的图片数组
@@ -80,15 +84,23 @@ export default {
}
},
mounted() {
async mounted() {
this.locaImg = []
if (this.img) {
this.img.filter((item) => {
this.locaImg.push(item)
})
}
// 在组件挂载时加载图像 URL
this.imgSrc = await Promise.all(this.locaImg.map(img => this.changeImg(img)))
},
methods: {
async changeImg(img) {
// 这里应该替换为真实的异步代码
const a = await handleVerAutoImg(this.domain + img)
return a
},
handleChange(file, fileList) {
if (file.status === 'success') {
this.locaImg.push(file.response.data.path)
+8 -1
View File
@@ -8,7 +8,7 @@
<template>
<div class="box">
<div v-on:paste="handlePaste" v-drag="handleDrag" >
<img v-if='imageUrl' ref="preview" :src="domain+imageUrl" alt="" style="width:300px;height:300px;">
<img v-if='newImg' ref="preview" :src="newImg" alt="" style="width:300px;height:300px;">
<div v-else style="width:300px;height:300px;border:1px solid;padding:20px;">
<div style="text-indent:2em;">将图片按<span style="color:red">cmd+C复制</span>到粘贴板<span style="color:red">cmd+V粘贴</span>至此处</div>
<div style="text-indent:2em;">或者将图片拖拽至此处</div>
@@ -18,6 +18,8 @@
</template>
<script>
import { mediaUploadImg } from '@/api/components/UploadImgPaste'
import { handleVerAutoImg } from '@/utils/index.js'
export default {
data() {
return {
@@ -34,6 +36,10 @@ export default {
watch: {
img: function(val) {
this.imageUrl = val
},
imageUrl: async function(val, oldval) {
const a = await handleVerAutoImg(this.domain + val)
this.newImg = a
}
},
mounted() {
@@ -44,6 +50,7 @@ export default {
}
},
methods: {
handleDrag(event) {
const items = Array.from(event.dataTransfer.items)
let file = null
+22
View File
@@ -0,0 +1,22 @@
import { getSyscfg } from '@/api/system/systemConfig'
import { getToken } from '@/utils/auth'
export default {
methods: {
async getSyscfg() {
const tk = getToken()
if (!tk) return
try {
const res = await getSyscfg({})
if (res.code === 200) {
this.form = res.data
this.$store.dispatch('system/setConfig', res.data)
} else {
this.$message.error('请求系统配置失败')
}
} catch (error) {
console.error('get syscfg error:::', error)
}
}
}
}
+3 -3
View File
@@ -2,8 +2,8 @@
* @Author: 王涛
* @Mail: no
* @Date: 2020-10-10 14:27:58
* @LastEditors: Please set LastEditors
* @LastEditTime: 2022-03-14 15:29:54
* @LastEditors: 江涛
* @LastEditTime: 2021-03-24 19:34:23
*/
const state = {
config: {
@@ -15,7 +15,7 @@ const state = {
driverGroup: undefined, // 开车群
headerM3U8: undefined, // 片头m3u8
id: 0,
webImgReq: 'http://resourceyuan.bbaazz.me/', // 图片请求域名(web使用)
webImgReq: '', // 图片请求域名(web使用)
imgReq: undefined, // 图片请求域名(app使用)
imgUpload: undefined, // 图片上传地址
inviteAddr: undefined, // 用户邀请地址
+102
View File
@@ -0,0 +1,102 @@
// import { Buffer } from 'buffer/'
// 判断是否加解密,如果字节前几位对应上,则不需要解密
const encryptMagicNumber = new Uint8Array([
0x88,
0xA8,
0x30,
0xCB,
0x10,
0x76
])
// 解密密钥
// const _decryptKey = Buffer.from('2019ysapp7527') // 加密key
// 避免vite单独引入buffer库 提前进行编码
const _decryptKey = [50, 48, 49, 57, 121, 115, 97, 112, 112, 55, 53, 50, 55]// 加密key
const _encryptedLen = 100 // 加密图片的数据长度
// / 加密密钥(????,不解就去找游总解答)
const ENCRYPT_KEY = 0xA3
// 三种图片 type类型,根据前几子节可以判断出类型,这是固定的。如需要增加其他类型则自己上网找固定对应的字节。
const _featuresList = [
new Uint8Array([0xFF, 0xD8, 0xFF]), // jpg,jpeg
new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), // png
new Uint8Array([0x47, 0x49, 0x46]) // gif
]
/**
* 解密入口
*/
export function decryptImage(imgBytes) {
// 判断是否为空,如果传入的字节为空则不处理
if (imgBytes === null || imgBytes.length === 0) { return }
// 增加一个变量来判断是否存在
let isAll = false
// 循环魔法密钥,确定这是需要解密的图片是全部长度,而不是前100字节
for (let i = 0; i < encryptMagicNumber.length; i++) {
if (encryptMagicNumber[i] !== imgBytes[i]) { continue }
isAll = true
}
if (isAll) {
imgBytes = xorBaseAllLength(imgBytes)
} else {
// 是需要解密的
if (isEncryptedImage(imgBytes)) {
// 解密结果
imgBytes = xorBaseLength(imgBytes, _decryptKey, _encryptedLen)
}
}
return imgBytes
}
/**
* 返回一个判断值,true则代表 是png gif jpg三种图片中的一种,则需要解密
*/
function isEncryptedImage(imgBytes) {
let isDecrypted = false
const _featuresLen = _featuresList.length
for (let i = 0; i < _featuresLen; i++) {
// 每次进入则初始化变量的值为假
isDecrypted = false
for (let j = 0; j < _featuresList[i].length; j++) {
if (_featuresList[i][j] !== imgBytes[j]) {
isDecrypted = true
break
}
}
if (isDecrypted) { continue } else { break }
}
return isDecrypted
}
// 解密算法
function xorBaseLength(src, key, length) {
const srcLen = src.length
const keyLen = key.length
if (length > srcLen || length <= 0) { length = srcLen }
for (let i = 0; i < length; i += keyLen) {
for (let j = 0; j < keyLen && i + j < length; j++) { src[i + j] ^= key[j] }
}
return src
}
// 全部长度解密,解密算法
function xorBaseAllLength(src) {
let index = -1
// 这里map数组内 所有的字节做位异或运数,返回的是解密后的数组
const dest = src.map((it) => {
index++
if (
index < encryptMagicNumber.length &&
it === encryptMagicNumber[index]
) { return null }
return it ^ ENCRYPT_KEY
})
return dest
}
+86 -1
View File
@@ -1,7 +1,7 @@
/**
* Created by PanJiaChen on 16/11/18.
*/
import { decryptImage } from './imgCode.js'
/**
* Parse the time to string
* @param {(Object|string|number)} time
@@ -227,3 +227,88 @@ export function getQueryString(name) {
}
return null
}
export async function handleVerAutoImg(imageUrl) {
// 参数校验
if (!imageUrl || typeof imageUrl !== 'string') {
console.warn('无效的图片URL:', imageUrl)
return imageUrl // 返回原始URL
}
// 检查URL是否已经是Blob URL
if (imageUrl.startsWith('blob:')) {
return imageUrl
}
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10秒超时
const response = await fetch(imageUrl, {
signal: controller.signal,
headers: {
'Accept': 'image/*'
}
}).finally(() => clearTimeout(timeoutId))
if (!response.ok) {
console.warn(`图片加载失败: ${response.status} ${response.statusText}`)
return imageUrl // 返回原始URL
}
const arrayBuffer = await response.arrayBuffer()
const bytes = new Uint8Array(arrayBuffer)
// 基本验证:检查是否为有效的图片数据
if (bytes.length === 0) {
// console.warn('图片数据为空')
return imageUrl
}
try {
// 尝试解密图片数据
const decryptedData = decryptImage(bytes)
// 验证解密后的数据
if (!decryptedData || decryptedData.length === 0) {
// console.warn('图片解密结果无效')
return imageUrl
}
// 创建Blob URL前检查数据有效性
const blob = new Blob([decryptedData], { type: 'image/jpeg' })
if (blob.size === 0) {
// console.warn('解密后的图片数据为空')
return imageUrl
}
const blobUrl = URL.createObjectURL(blob)
// 添加清理逻辑
window.addEventListener('unload', () => {
try {
URL.revokeObjectURL(blobUrl)
} catch (e) {
// 忽略清理错误
console.log(e, 'e')
}
})
return blobUrl
} catch (decryptError) {
console.warn('图片解密失败,使用原始URL:', decryptError)
return imageUrl
}
} catch (error) {
// 处理特定类型的错误
if (error.name === 'AbortError') {
// console.warn('图片加载超时')
} else if (error instanceof TypeError) {
// console.warn('网络请求失败')
} else {
// console.warn('图片处理失败:', error)
}
// 返回原始URL而不是抛出错误
return imageUrl
}
}
@@ -1,25 +1,45 @@
<template>
<el-dialog :title="title" :visible.sync="Visible" width="780px" center class="ac-dialog" :close-on-click-modal="false"
<el-dialog
:title="title"
:visible.sync="Visible"
width="780px"
center
class="ac-dialog"
:close-on-click-modal="false"
@close="close">
<el-row :gutter="24">
<el-col >
<h3>AI生成的视频URl</h3>
<div v-if="form.vidUrl" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer ref="player" v-if="form.vidUrl.indexOf('mp4') === -1" :video="{
<DPlayer
ref="player"
v-if="form.vidUrl.indexOf('mp4') === -1"
:video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.vidUrl),
type: 'hls',
pic: '',
}" hotkey ></DPlayer>
<DPlayer ref="player" v-else :video="{
url: this.$store.getters.getSystemConfig.webImgReq + form.vidUrl,
}"
hotkey ></DPlayer>
<DPlayer
ref="player"
v-else
:video="{
url: this.$store.getters.getSystemConfig.mp4Url + form.vidUrl,
type: 'mp4',
pic: '',
}" hotkey ></DPlayer>
}"
hotkey ></DPlayer>
</div>
</el-col>
</el-row>
<el-form :inline="true" :model="form" :rules="rules" ref="form" class="demo-form-inline" label-width="120px"
<el-form
:inline="true"
:model="form"
:rules="rules"
ref="form"
class="demo-form-inline"
label-width="120px"
label-position="right">
<el-row :gutter="24">
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
@@ -44,7 +64,6 @@
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="Visible = false"> </el-button>
<el-button type="primary" @click="submit('form')"> </el-button>
@@ -76,15 +95,15 @@ export default {
formData: {},
// 表单数据
form: {
"byWay": undefined,
"id": undefined,
"modifyImgs": undefined,
"vidUrl": undefined,
"reason": undefined,
"status": 0,
"updatedAt": undefined
'byWay': undefined,
'id': undefined,
'modifyImgs': undefined,
'vidUrl': undefined,
'reason': undefined,
'status': 0,
'updatedAt': undefined
},
rules: {},
rules: {}
}
},
mounted() {
@@ -102,7 +121,6 @@ export default {
},
// TODO:编辑应用列表数据
update(data) {
aiorderFacechangeUpdate(data).then((res) => {
if (res && res.code === 200) {
this.$message.success(res.data ? res.data : '更新成功')
@@ -110,7 +128,6 @@ export default {
this.$message.error(res.data ? res.data : '更新失败')
}
this.Visible = false
})
},
// 提交按钮
@@ -153,8 +170,8 @@ export default {
onClear(data) {
this.form[data] = undefined
},
},
}
}
}
</script>
@@ -1,26 +1,46 @@
<template>
<el-dialog :title="title" :visible.sync="Visible" width="780px" center class="ac-dialog" :close-on-click-modal="false"
<el-dialog
:title="title"
:visible.sync="Visible"
width="780px"
center
class="ac-dialog"
:close-on-click-modal="false"
@close="close">
<el-row :gutter="24">
<el-col>
<h3>AI生成的视频URl</h3>
<div v-if="form.modifyVideoUrl" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer ref="player" v-if="form.modifyVideoUrl.indexOf('mp4') === -1" :video="{
<DPlayer
ref="player"
v-if="form.modifyVideoUrl.indexOf('mp4') === -1"
:video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.modifyVideoUrl),
type: 'hls',
pic: '',
}" hotkey ></DPlayer>
<DPlayer ref="player" v-else :video="{
url: this.$store.getters.getSystemConfig.webImgReq + form.modifyVideoUrl,
}"
hotkey ></DPlayer>
<DPlayer
ref="player"
v-else
:video="{
url: this.$store.getters.getSystemConfig.mp4Url + form.modifyVideoUrl,
type: 'mp4',
pic: '',
}" hotkey ></DPlayer>
}"
hotkey ></DPlayer>
</div>
</el-col>
</el-row>
<el-form :inline="true" :model="form" :rules="rules" ref="form" class="demo-form-inline" label-width="120px"
<el-form
:inline="true"
:model="form"
:rules="rules"
ref="form"
class="demo-form-inline"
label-width="120px"
label-position="right">
<el-row :gutter="24">
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
@@ -31,7 +51,10 @@
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="换脸方式">
<el-select placeholder="请选择换脸方式" clearable v-model="form.byWay">
<el-option :key="item.value" :label="item.label" :value="item.value"
<el-option
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in aiOrderFaceChangeList" />
</el-select>
</el-form-item>
@@ -51,7 +74,6 @@
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="Visible = false"> </el-button>
<el-button type="primary" @click="submit('form')"> </el-button>
@@ -83,15 +105,15 @@ export default {
formData: {},
// 表单数据
form: {
"byWay": undefined,
"id": undefined,
"modifyImgs": undefined,
"modifyVideoUrl": undefined,
"reason": undefined,
"status": 0,
"updatedAt": undefined
'byWay': undefined,
'id': undefined,
'modifyImgs': undefined,
'modifyVideoUrl': undefined,
'reason': undefined,
'status': 0,
'updatedAt': undefined
},
rules: {},
rules: {}
}
},
mounted() {
@@ -109,10 +131,7 @@ export default {
},
// TODO:编辑应用列表数据
update(data) {
aiorderStripUpdate(data).then((res) => {
if (res && res.code === 200) {
this.$message.success(res.data ? res.data : '更新成功')
} else {
@@ -161,8 +180,8 @@ export default {
onClear(data) {
this.form[data] = undefined
},
},
}
}
}
</script>
@@ -1,151 +1,100 @@
<template>
<el-dialog :title="title" :visible.sync="Visible" width="1800px" center class="ac-dialog" :close-on-click-modal="false" @close="close">
<el-dialog
:title="title"
:visible.sync="Visible"
width="1800px"
center
class="ac-dialog"
:close-on-click-modal="false"
@close="close">
<el-form :inline="true" :model="form" class="demo-form-inline" label-width="120px" label-position="right">
<el-row :gutter="24">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<div>
<el-form-item label="标题">
<el-input v-model="form.title" style="width: 315px" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="封面类型">
<el-select style="width: 180px" placeholder="请选择封面类型" v-model="form.coverType">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in coverTypeList" />
</div>
<div>
<el-form-item label="封面">
<UploadImg @changeImgUrl="changeCoverImgUrl" :img="form.coverImg" @onRemove="handleRemove('coverImg')">
</UploadImg>
</el-form-item>
</div>
<el-form-item label="影片类型">
<el-select clearable placeholder="影片类型" v-model="form.type" style="width: 180px">
<el-option label="长视频" value="longvideo" />
<el-option label="短视频" value="shortvideo" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="视频归类">
<el-select placeholder="请选择视频归类" v-model="videoType" style="width: 180px">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in formVideoTypeList" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="封面">
<UploadImg @changeImgUrl="changeCoverImgUrl" :img="form.coverImg"></UploadImg>
</el-form-item>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="番号">
<el-input v-model="form.bango" style="width: 315px" />
</el-form-item>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="演员">
<el-input v-model="form.actors" placeholder="可填多人,中间用'/。/,'等符号分割" style="width:315px" />
</el-form-item>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="视频归属">
<el-input v-model="form.mediaSet" style="width: 315px" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="一级标签:">
<el-input v-model="form.firstTag" placeholder="请输入一级标签,中间用'/。/,'等符号分割" style="width:315px" />
</el-form-item>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="二级标签">
<el-input v-model="form.secondTags" placeholder="请输入二级标签,中间用'/。/,'等符号分割" style="width:315px" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<div>
<el-form-item label="APP">
<el-select multiple placeholder="APP" v-model="form.appids" style="width: 180px">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in appidsList" />
<el-option label="麻豆APP" :value="1" />
<el-option label="麻豆官网" :value="2" />
<el-option label="逼喱逼喱" :value="3" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="视频类型">
<el-select placeholder="请选择视频类型" v-model="form.formatType" style="width: 180px">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in formatTypeList" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="内容类型">
<el-select placeholder="请选择内容类型" v-model="form.contentType" style="width: 180px">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in contentTypeList" />
</el-select>
</el-form-item>
</el-col>
<el-col :lg="8" :md="8" :sm="8" :xl="8" :xs="8">
<el-form-item label="发布者">
<el-input v-model="form.publish" placeholder="请输入发布者" style="width: 180px" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="8" :sm="8" :xl="8" :xs="8">
<el-form-item label="所有者">
<el-input v-model="form.studio" placeholder="请输入所有者" style="width: 180px" />
</el-form-item>
</el-col>
<el-col :lg="8" :md="8" :sm="8" :xl="8" :xs="8">
</div>
<div>
<el-form-item label="上架时间">
<el-date-picker style="width: 180px" v-model="form.addedTime" type="date" placeholder="选择日期" value-format="yyyy-MM-ddTHH:mm:ssZ">
<el-date-picker
style="width: 180px"
v-model="form.addedTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-ddTHH:mm:ssZ">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :lg="12" :md="12" :sm="12" :xl="12" :xs="12">
</div>
<div>
<el-form-item label="正片地址">
<el-input v-model="form.fieldNameFs" style="width: 515px" disabled="disabled" />
</el-form-item>
</el-col>
<el-col :lg="12" :md="12" :sm="12" :xl="12" :xs="12">
</div>
<div>
<el-form-item label="预览地址">
<el-input v-model="form.preFileName" style="width: 515px">
<el-button slot="append" @click="innerVisible = true">自定义设置预览</el-button>
</el-input>
</el-form-item>
</el-col>
<el-col :lg="24" :md="24" :sm="24" :xl="24" :xs="24">
</div>
<el-form-item label="详细描述">
<el-input v-model="form.desc" type="textarea" style="width: 515px" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div style="position: absolute;top: 50px;right: 50px">
<div style="float:left">
<h3>预览</h3>
<div v-if="form.preFileName" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer ref="player" v-if="form.preFileName.indexOf('mp4') === -1" :video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.preFileName),
type: 'hls',
pic: '',
}" hotkey></DPlayer>
<DPlayer ref="player" v-else :video="{
url: this.$store.getters.getSystemConfig.webImgReq + form.preFileName,
type: 'mp4',
pic: '',
}" hotkey></DPlayer>
</div>
</div>
<div style="float:left">
<div style="position: absolute; top: 50px; right: 10px">
<h3>正片</h3>
<div v-if="form.fieldNameFs" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer ref="player" v-if="form.fieldNameFs.indexOf('mp4') === -1" :video="{
<DPlayer
ref="player"
v-if="form.fieldNameFs.indexOf('mp4') === -1"
:video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.fieldNameFs),
type: 'hls',
pic: '',
}" hotkey></DPlayer>
<DPlayer ref="player" v-else :video="{
url: this.$store.getters.getSystemConfig.webImgReq + form.fieldNameFs,
}"
hotkey></DPlayer>
<DPlayer
ref="player"
v-else
:video="{
url: this.$store.getters.getSystemConfig.mp4Url + form.fieldNameFs,
type: 'mp4',
pic: '',
}" hotkey></DPlayer>
}"
hotkey></DPlayer>
</div>
<UploadVideo v-if="videoType" @changeVideoUrl="changeVideoUrl" :type="videoType" @repeatVideo="repeatVideo"
<UploadVideo
v-if="videoType"
@changeVideoUrl="changeVideoUrl"
:type="videoType"
@repeatVideo="repeatVideo"
style="max-width: 520px"></UploadVideo>
<div v-else class="check_upload" @click.prevent="checkUpload">
<div>
@@ -153,7 +102,28 @@
</div>
</div>
</div>
<div style="position: absolute; top: 50px; right: 410px">
<h3>预览</h3>
<div v-if="form.preFileName" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer
ref="player"
v-if="form.preFileName.indexOf('mp4') === -1"
:video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.preFileName),
type: 'hls',
pic: '',
}"
hotkey></DPlayer>
<DPlayer
ref="player"
v-else
:video="{
url: this.$store.getters.getSystemConfig.mp4Url + form.preFileName,
type: 'mp4',
pic: '',
}"
hotkey></DPlayer>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click.native.prevent="Visible = false"> </el-button>
@@ -169,7 +139,6 @@
:
<el-input-number :min="0" :max="24" size="mini" v-model="previewTimeData.h"></el-input-number>
</div>
<div>
:
<el-input-number :min="0" :max="59" size="mini" v-model="previewTimeData.m"></el-input-number>
@@ -193,68 +162,68 @@
<script>
import DPlayer from '@/components/VueDplayerHls/index'
import UploadImg from '@/components/UploadImg/index'
import UploadVideo from '@/components/UploadVideoLibrary/index'
import UploadVideo from '@/components/UploadVideo/index'
import 'videojs-contrib-hls'
import { mediaUpdate, updatePreTime } from '@/api/mediaLibrary/videoList'
import { stringToArry, arryToString, current } from '@/filters/index'
import {
coverTypeList,
appidsList,
formatTypeList,
formVideoTypeList
} from '@/filters/mediaLibrary/videoList'
import { stringToArry, arryToString } from '@/filters/index'
import { StatusList, formVideoTypeList } from '@/filters/mediaLibrary/videoList'
import { current } from '@/filters/index'
export default {
name: 'AddOrEdit',
props: ['data','contentTypeList'],
props: ['data'],
components: {
UploadImg,
UploadVideo,
DPlayer,
DPlayer
},
computed: {
totalTime() {
return this.data.data.videoTime
},
}
},
data() {
return {
coverTypeList: coverTypeList(),
appidsList: appidsList(),
formatTypeList: formatTypeList(),
formVideoTypeList: formVideoTypeList(),
type: undefined,
videoType: undefined,
innerVisible: false,
Visible: true,
title: '',
defaltTime: current() + 'T12:00:00.000Z',
taskId: '',
StatusList: StatusList(),
formVideoTypeList: formVideoTypeList(),
tags: null,
fieldNameFs: '', // 正片地址全称
form: {
formatType: undefined, // 视频类型 1:长视频 2:短视频 3:预览
mediaSet: undefined, // 视频归属
contentType: undefined, // 内容类型 1:av(日本) 2:国产(短视频) 3:动漫
coverType: undefined, // 封面类型 1:竖版 2:横版 3:系统
secondTags: [],
FirstTagList: [],
title: undefined, // 'string',标题
addedTime: undefined, // 'string',上架时间
publish: undefined, // 'string',视频发布者
firstTag: undefined, // 'string',一级标签
secondTags: undefined, // [],二级标签标签
bango: undefined, // 'string',番号
form: {
appids: [],
desc: undefined, // 'string',详细描述
preFileName: undefined, // 'string',预览文件
coverImg: undefined, // 封面
actors: undefined, // [],演员
addedTime: undefined, // 'string',上架时间
bango: undefined, // 'string',番号
checkSum: undefined, // 'string',md5校验码
country: undefined, // 'string',国家
coverImg: undefined, // 封面
desc: undefined, // 'string',详细描述
directors: undefined, // [],导演
firstTag: undefined, // 'string',一级标签
preFileName: undefined, // 'string',预览文件
preview: undefined, // 0,预览普通视频
publish: undefined, // 'string',视频发布者
secondTags: undefined, // [],二级标签标签
size: undefined, // 0,文件大小
status: undefined, // 'string',当前状态
studio: undefined, // 'string',工作室/所有者
title: undefined, // 'string',标题
type: undefined, // 'string',长视频/短视频
videoTime: undefined // 0,影片长度
},
previewTimeData: {
h: 0,
m: 0,
s: 0,
s: 0
},
type: undefined,
videoType: undefined
}
},
@@ -263,8 +232,7 @@ export default {
if (this.title === '编辑') {
this.form = { ...this.data.data }
this.form.actors = arryToString(this.form.actors)
this.form.secondTags = arryToString(this.form.secondTags)
this.form.mediaSet = arryToString(this.form.mediaSet)
this.form.directors = arryToString(this.form.directors)
if (this.form.fieldNameFs) {
this.open()
}
@@ -273,16 +241,44 @@ export default {
this.player.dispose()
}
},
methods: {
checkUpload() {
if (!this.type) {
if (!this.videoType) {
return this.$message({
type: 'error',
message: '请选择视频归类!!'
})
}
},
//TODO:编辑影片列表数据
// TODO:新增视频时绑定煤资库ID
async subimitPTime() {
let s = 0
if (this.previewTimeData.h) {
s += this.previewTimeData.h * 60 * 60
}
if (this.previewTimeData.m) {
s += this.previewTimeData.m * 60
}
s += this.previewTimeData.s
if (s > 0 && s + 15 <= this.data.data.videoTime - 15) {
const data = {
id: this.data.data.id,
preFileTime: s
}
updatePreTime(data).then((res) => {
if (res.code === 200) {
this.$message.success('预览时长设置成功')
this.innerVisible = false
} else {
this.$message.error('预览时长设置失败')
}
})
} else {
this.$message.error('预览时长设置不能为零或不能超过播放时长')
}
},
// TODO:提交视频列表数据
update(data) {
mediaUpdate(data).then((res) => {
if (res.code === 200) {
@@ -296,6 +292,9 @@ export default {
filterM3u8(v) {
return /\.m3u8/g.test(v) ? v : v + '.m3u8'
},
handleRemove(key) {
this.form[key] = ''
},
// 清空某一项筛选列表
onClear(key) {
this.form.param[key] = []
@@ -304,57 +303,52 @@ export default {
this.previewTimeData = {
h: 0,
m: 0,
s: 0,
s: 0
}
this.innerVisible = false
},
async subimitPTime() {
let s = 0
if (this.previewTimeData.h) {
s += this.previewTimeData.h * 60 * 60
}
if (this.previewTimeData.m) {
s += this.previewTimeData.m * 60
}
s += this.previewTimeData.s
if (s > 0 && s + 15 <= this.data.data.videoTime - 15) {
const res = await updatePreTime({
id: this.data.data.id,
preFileTime: s,
})
if (res.code === 200) {
this.$message.success('预览时长设置成功')
this.innerVisible = false
} else {
this.$message.error('预览时长设置失败')
}
} else {
this.$message.error('预览时长设置不能为零或不能超过播放时长')
}
},
// 提交按钮
submit() {
const data = { ...this.form }
data.actors = data.actors ? stringToArry(data.actors) : []
data.secondTags = data.secondTags ? stringToArry(data.secondTags) : []
data.mediaSet = data.mediaSet ? stringToArry(data.mediaSet) : []
data.addedTime = data.addedTime
? data.addedTime === '0001-01-01T00:00:00Z'
? this.defaltTime
: data.addedTime
: this.defaltTime
data.directors = data.directors ? stringToArry(data.directors) : []
const YY = new Date().getUTCFullYear()
const MM =
new Date().getMonth() + 1 < 10
? `0${new Date().getMonth() + 1}`
: new Date().getMonth() + 1
const DD =
new Date().getUTCDate() < 10
? `0${new Date().getUTCDate()}`
: new Date().getUTCDate()
const HH =
new Date().getUTCHours() < 10
? `0${new Date().getUTCHours()}`
: new Date().getUTCHours()
const MU =
new Date().getUTCMinutes() < 10
? `0${new Date().getUTCMinutes()}`
: new Date().getUTCMinutes()
const SS =
new Date().getUTCSeconds() < 10
? `0${new Date().getUTCSeconds()}`
: new Date().getUTCSeconds()
const MS =
new Date().getUTCMilliseconds() < 10
? `0${new Date().getUTCMilliseconds()}`
: new Date().getUTCMilliseconds()
data.addedTime =
data.addedTime || `${YY}-${MM}-${DD}T${HH}:${MU}:${SS}.${MS}Z`
this.update(data)
},
// 上传视频返回地址
changeVideoUrl(data) {
this.form.id = data.id
// this.$set(this.form, 'fieldNameFs', data.videoUri)
this.fieldNameFs = data.videoUri
// this.form.fieldNameFs = data.videoUri
// this.form.videoTime = parseInt(data.duration)
// this.form.size = data.size
// this.open()
this.form.videoTime = parseInt(data.duration)
this.form.size = data.size
},
// 重复上传视频
@@ -381,23 +375,10 @@ export default {
onPlayerEnded() { },
open() {
this.mediaUrl =
'api/web/media/m3u8/' +
'api/web/media/m3u8' +
(this.form.fieldNameFs.match(RegExp(/.m3u8/))
? this.form.fieldNameFs
: this.form.fieldNameFs + '.m3u8')
// this.$nextTick(function () {
// document.getElementById("media-box").innerHTML =
// '<video id="my-video" class="video-js vjs-default-skin" controls preload="auto" style="width:100%;height:100%; " playsinline="true"></video>';
// this.player = videojs("my-video", {
// bigPlayButton: false,
// textTrackDisplay: false,
// posterImage: true,
// errorDisplay: true,
// controlBar: true,
// playbackRates: [0.5, 1, 1.5, 2, 3, 5, 10],
// });
// this.player.src(this.mediaUrl);
// });
},
// 关闭编辑
@@ -406,8 +387,8 @@ export default {
this.player.dispose()
}
this.$emit('close')
},
},
}
}
}
</script>
<style lang="scss">
@@ -423,10 +404,12 @@ export default {
max-height: calc(100% - 30px);
max-width: calc(100% - 30px);
}
.el-dialog .el-dialog__body {
flex: 1;
overflow: auto;
}
.videoBox {
display: flex;
justify-content: center;
@@ -436,15 +419,18 @@ export default {
.el-textarea.is-disabled.ti .el-textarea__inner {
color: red;
}
.media-box {
width: 100%;
height: 400px;
text-align: center;
overflow: hidden;
img {
height: 100%;
max-width: 100%;
}
#my-video {
width: 100% !important;
height: 100% !important;
@@ -0,0 +1,469 @@
<template>
<el-dialog
:title="title"
:visible.sync="Visible"
width="1800px"
center
class="ac-dialog"
:close-on-click-modal="false"
@close="close">
<el-form :inline="true" :model="form" class="demo-form-inline" label-width="120px" label-position="right">
<div>
<el-form-item label="标题">
<el-input v-model="form.title" style="width: 315px" />
</el-form-item>
</div>
<div>
<el-form-item label="封面">
<UploadImg @changeImgUrl="changeCoverImgUrl" :img="form.coverImg" @onRemove="handleRemove('coverImg')">
</UploadImg>
</el-form-item>
</div>
<el-form-item label="影片类型">
<el-select clearable placeholder="影片类型" v-model="form.type" style="width: 180px">
<el-option label="长视频" value="longvideo" />
<el-option label="短视频" value="shortvideo" />
</el-select>
</el-form-item>
<el-form-item label="视频归类">
<el-select placeholder="请选择视频归类" v-model="videoType" style="width: 180px">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in formVideoTypeList" />
</el-select>
</el-form-item>
<div>
<el-form-item label="APP">
<el-select placeholder="APP" v-model="form.appids" style="width: 180px">
<el-option label="麻豆APP" :value="1" />
<el-option label="麻豆官网" :value="2" />
<el-option label="逼喱逼喱" :value="3" />
</el-select>
</el-form-item>
</div>
<div>
<el-form-item label="上架时间">
<el-date-picker
style="width: 180px"
v-model="form.addedTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-ddTHH:mm:ssZ">
</el-date-picker>
</el-form-item>
</div>
<div>
<el-form-item label="正片地址">
<el-input v-model="form.fieldNameFs" style="width: 515px" disabled="disabled" />
</el-form-item>
</div>
<div>
<el-form-item label="预览地址">
<el-input v-model="form.preFileName" style="width: 515px">
<el-button slot="append" @click="innerVisible = true">自定义设置预览</el-button>
</el-input>
</el-form-item>
</div>
<el-form-item label="详细描述">
<el-input v-model="form.desc" type="textarea" style="width: 515px" />
</el-form-item>
</el-form>
<div style="position: absolute; top: 50px; right: 10px">
<h3>正片</h3>
<div v-if="form.fieldNameFs" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer
ref="player"
v-if="form.fieldNameFs.indexOf('mp4') === -1"
:video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.fieldNameFs),
type: 'hls',
pic: ''
}"
hotkey></DPlayer>
<DPlayer
ref="player"
v-else
:video="{
url:
this.$store.getters.getSystemConfig.mp4Url + form.fieldNameFs,
type: 'mp4',
pic: ''
}"
hotkey></DPlayer>
</div>
<UploadVideo
v-if="videoType"
@changeVideoUrl="changeVideoUrl"
:appId="form.appids ? form.appids : 888"
:type="videoType"
@repeatVideo="repeatVideo"
style="max-width: 520px"></UploadVideo>
<div v-else class="check_upload" @click.prevent="checkUpload">
<div>
选择文件
</div>
</div>
</div>
<div style="position: absolute; top: 50px; right: 410px">
<h3>预览</h3>
<div v-if="form.preFileName" style="width: 380px; height: 240px; overflow: hidden">
<DPlayer
ref="player"
v-if="form.preFileName.indexOf('mp4') === -1"
:video="{
url: '/api/web/media/m3u8/' + filterM3u8(form.preFileName),
type: 'hls',
pic: ''
}"
hotkey></DPlayer>
<DPlayer
ref="player"
v-else
:video="{
url:
this.$store.getters.getSystemConfig.mp4Url + form.preFileName,
type: 'mp4',
pic: ''
}"
hotkey></DPlayer>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click.native.prevent="Visible = false"> </el-button>
<el-button type="primary" @click.native.prevent="submit('ruleForm')">
</el-button>
</div>
<el-dialog width="30%" title="设置预览时长" :visible.sync="innerVisible" append-to-body>
<el-row>
<el-col :span="24" style="text-align: center">
<div><span>总时长:</span>{{ totalTime }}s</div>
<div>
:
<el-input-number :min="0" :max="24" size="mini" v-model="previewTimeData.h"></el-input-number>
</div>
<div>
:
<el-input-number :min="0" :max="59" size="mini" v-model="previewTimeData.m"></el-input-number>
</div>
<div>
:
<el-input-number :min="0" :max="59" size="mini" v-model="previewTimeData.s"></el-input-number>
</div>
</el-col>
<el-col :span="24" style="text-align: center; margin: 10px 0">
<el-button-group>
<el-button type="info" @click.native.prevent="restTime">取消</el-button>
<el-button type="primary" @click.native.prevent="subimitPTime">确定
</el-button>
</el-button-group>
</el-col>
</el-row>
</el-dialog>
</el-dialog>
</template>
<script>
import DPlayer from '@/components/VueDplayerHls/index'
import UploadImg from '@/components/UploadImg/index'
import UploadVideo from '@/components/UploadVideoLibraryV2/index'
import 'videojs-contrib-hls'
import { mediaUpdateV2, updatePreTime } from '@/api/mediaLibrary/videoList'
import { stringToArry, arryToString } from '@/filters/index'
import {
StatusList,
formVideoTypeList
} from '@/filters/mediaLibrary/videoList'
import { current } from '@/filters/index'
export default {
name: 'AddOrEdit',
props: ['data'],
components: {
UploadImg,
UploadVideo,
DPlayer
},
computed: {
totalTime() {
return this.data.data.videoTime
}
},
data() {
return {
innerVisible: false,
Visible: true,
title: '',
defaltTime: current() + 'T12:00:00.000Z',
taskId: '',
StatusList: StatusList(),
formVideoTypeList: formVideoTypeList(),
tags: null,
fieldNameFs: '', // 正片地址全称
secondTags: [],
FirstTagList: [],
form: {
appids: undefined,
actors: undefined, // [],演员
addedTime: undefined, // 'string',上架时间
bango: undefined, // 'string',番号
checkSum: undefined, // 'string',md5校验码
country: undefined, // 'string',国家
coverImg: undefined, // 封面
desc: undefined, // 'string',详细描述
directors: undefined, // [],导演
firstTag: undefined, // 'string',一级标签
preFileName: undefined, // 'string',预览文件
preview: undefined, // 0,预览普通视频
publish: undefined, // 'string',视频发布者
secondTags: undefined, // [],二级标签标签
size: undefined, // 0,文件大小
status: undefined, // 'string',当前状态
studio: undefined, // 'string',工作室/所有者
title: undefined, // 'string',标题
type: undefined, // 'string',长视频/短视频
videoTime: undefined // 0,影片长度
},
previewTimeData: {
h: 0,
m: 0,
s: 0
},
type: undefined,
videoType: undefined
}
},
created() {
this.title = this.data.title
if (this.title === '编辑') {
this.form = { ...this.data.data }
this.form.actors = arryToString(this.form.actors)
this.form.directors = arryToString(this.form.directors)
if (this.form.fieldNameFs) {
this.open()
}
}
if (this.player) {
this.player.dispose()
}
},
methods: {
checkUpload() {
if (!this.videoType) {
return this.$message({
type: 'error',
message: '请选择视频归类!!'
})
}
},
// TODO:新增视频时绑定煤资库ID
async subimitPTime() {
let s = 0
if (this.previewTimeData.h) {
s += this.previewTimeData.h * 60 * 60
}
if (this.previewTimeData.m) {
s += this.previewTimeData.m * 60
}
s += this.previewTimeData.s
if (s > 0 && s + 15 <= this.data.data.videoTime - 15) {
const data = {
id: this.data.data.id,
preFileTime: s
}
updatePreTime(data).then(res => {
if (res.code === 200) {
this.$message.success('预览时长设置成功')
this.innerVisible = false
} else {
this.$message.error('预览时长设置失败')
}
})
} else {
this.$message.error('预览时长设置不能为零或不能超过播放时长')
}
},
// TODO:提交视频列表数据
update(data) {
mediaUpdateV2(data).then(res => {
if (res.code === 200) {
this.$message.success('更新成功')
this.close()
} else {
this.$message.error('更新失败')
}
})
},
filterM3u8(v) {
return /\.m3u8/g.test(v) ? v : v + '.m3u8'
},
handleRemove(key) {
this.form[key] = ''
},
// 清空某一项筛选列表
onClear(key) {
this.form.param[key] = []
},
restTime() {
this.previewTimeData = {
h: 0,
m: 0,
s: 0
}
this.innerVisible = false
},
// 提交按钮
submit() {
const data = { ...this.form }
data.appids = data.appids ? [data.appids] : []
data.actors = data.actors ? stringToArry(data.actors) : []
data.directors = data.directors ? stringToArry(data.directors) : []
const YY = new Date().getUTCFullYear()
const MM =
new Date().getMonth() + 1 < 10
? `0${new Date().getMonth() + 1}`
: new Date().getMonth() + 1
const DD =
new Date().getUTCDate() < 10
? `0${new Date().getUTCDate()}`
: new Date().getUTCDate()
const HH =
new Date().getUTCHours() < 10
? `0${new Date().getUTCHours()}`
: new Date().getUTCHours()
const MU =
new Date().getUTCMinutes() < 10
? `0${new Date().getUTCMinutes()}`
: new Date().getUTCMinutes()
const SS =
new Date().getUTCSeconds() < 10
? `0${new Date().getUTCSeconds()}`
: new Date().getUTCSeconds()
const MS =
new Date().getUTCMilliseconds() < 10
? `0${new Date().getUTCMilliseconds()}`
: new Date().getUTCMilliseconds()
data.addedTime =
data.addedTime || `${YY}-${MM}-${DD}T${HH}:${MU}:${SS}.${MS}Z`
this.update(data)
},
// 上传视频返回地址
changeVideoUrl(data) {
console.log(data)
this.form.id = data.id
this.form.fieldNameFs = data.videoUri
this.form.videoTime = parseInt(data.duration)
this.form.size = data.size
},
// 重复上传视频
repeatVideo(data) {
this.taskId = data.taskId
this.getData()
},
// 上传图片返回地址
changeCoverImgUrl(data) {
this.form.coverImg = data
},
// / 监听视频播放的回调函数
onPlayerPlay() { },
// / 监听暂停播放的回调函数
onPlayerPause() { },
// / 监听当前视频数据加载完成
onPlayerLoadeddata() { },
// / 监听当前视频数据加载失败
onPlayerError() { },
// / 监听当前视频数据加载ing
onPlayerWaiting() { },
// / 监听当前视频播放完成
onPlayerEnded() { },
open() {
this.mediaUrl =
'api/web/media/m3u8' +
(this.form.fieldNameFs.match(RegExp(/.m3u8/))
? this.form.fieldNameFs
: this.form.fieldNameFs + '.m3u8')
},
// 关闭编辑
close() {
if (this.player) {
this.player.dispose()
}
this.$emit('close')
}
}
}
</script>
<style lang="scss">
.el-dialog {
display: flex;
flex-direction: column;
margin: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/*height:600px;*/
max-height: calc(100% - 30px);
max-width: calc(100% - 30px);
}
.el-dialog .el-dialog__body {
flex: 1;
overflow: auto;
}
.videoBox {
display: flex;
justify-content: center;
overflow: hidden;
}
.el-textarea.is-disabled.ti .el-textarea__inner {
color: red;
}
.media-box {
width: 100%;
height: 400px;
text-align: center;
overflow: hidden;
img {
height: 100%;
max-width: 100%;
}
#my-video {
width: 100% !important;
height: 100% !important;
border: none !important;
}
}
.check_upload {
position: relative;
padding: 10px;
overflow: hidden;
border: 1px dashed #ccc;
background-color: #f5f5f5;
div {
display: inline-block;
position: relative;
padding: 4px 8px;
font-size: 100%;
line-height: 1.4;
color: #666;
border: 1px solid #666;
cursor: pointer;
border-radius: 2px;
background: none;
outline: none;
}
}
</style>