| @@ -150,3 +150,29 @@ export function personPatchApi(id) { | |||
| method: 'get' | |||
| }) | |||
| } | |||
| /** | |||
| * @description:保存或修改用户作品 | |||
| * @param {object} data | |||
| * @return data | |||
| */ | |||
| export function saveOrUpdateApi(data) { | |||
| return request({ | |||
| url: `/api/userVideo/saveOrUpdate`, | |||
| method: 'post', | |||
| data: data | |||
| }) | |||
| } | |||
| /** | |||
| * @description 生成视频 | |||
| * @param {object} id | |||
| * @returns id | |||
| */ | |||
| export function createVideoApi(id) { | |||
| return request({ | |||
| url: `/api/userVideo/createVideo`, | |||
| method: 'post', | |||
| data: { id } | |||
| }) | |||
| } | |||
| @@ -6,9 +6,16 @@ import request from "@/utils/request.js"; | |||
| * @params pageNum, pageSize | |||
| * @returns data | |||
| */ | |||
| // export function userPhotoVideoList(pageNum, pageSize, title) { | |||
| // return request({ | |||
| // url: `/api/userPhotoVideo/list?pageNum=${pageNum}&pageSize=${pageSize}&title=${title}`, | |||
| // method: 'get' | |||
| // }) | |||
| // } | |||
| export function userPhotoVideoList(pageNum, pageSize, title) { | |||
| let videoStatuss = [1, 2, 3, 4, 5, 6] | |||
| return request({ | |||
| url: `/api/userPhotoVideo/list?pageNum=${pageNum}&pageSize=${pageSize}&title=${title}`, | |||
| url: `/api/userVideo/list?pageNum=${pageNum}&pageSize=${pageSize}&title=${title}&videoType=-1&videoStatuss=${videoStatuss}`, | |||
| method: 'get' | |||
| }) | |||
| } | |||
| @@ -23,4 +30,17 @@ export function delUserPhotoVideoByID(videoID) { | |||
| url: `/api/userPhotoVideo/del?id=${videoID}`, | |||
| method: 'get' | |||
| }) | |||
| } | |||
| /** | |||
| * @description 获取作品列表 | |||
| * @param {number} pageParams (pageNum, pageSize) | |||
| * @returns data | |||
| */ | |||
| export function getVideoList(pageNum, pageSize, videoType, videoStatuss) { | |||
| return request({ | |||
| url: `/api/userVideo/list?pageNum=${pageNum}&pageSize=${pageSize}&videoType=${videoType}&videoStatuss=${videoStatuss}`, | |||
| method: 'get' | |||
| }) | |||
| } | |||
| @@ -15,15 +15,74 @@ | |||
| :style="virtualStyle" | |||
| > | |||
| <img id="virtualImg" :src="materialList.virtual.material" alt="" /> | |||
| <div class="stretchBox box1"></div> | |||
| <div ref="box1" class="stretchBox box1"></div> | |||
| <div class="stretchBox box2"></div> | |||
| <div class="stretchBox box3"></div> | |||
| <div class="stretchBox box4"></div> | |||
| <div | |||
| v-show="showBorderActive == materialList.virtual.id" | |||
| ref="box4" | |||
| @mousedown.stop="changeScale" | |||
| class="stretchBox box4" | |||
| > | |||
| <img | |||
| style="width: 100px; height: 100px" | |||
| src="../assets/img/Scale.png" | |||
| alt="" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <!-- 素材图的图片 --> | |||
| <div | |||
| v-for="(item, index) in materialList.scList" | |||
| :key="item.id" | |||
| class="scListItem" | |||
| :class="showBorderActive == item.id ? 'border' : ''" | |||
| @mousedown="handleDragSc(item.id, index, $event)" | |||
| :style="{ | |||
| top: item.y + 'px', | |||
| left: item.x + 'px', | |||
| transform: 'scale(' + item.w / props.proportion + ') ', | |||
| transformOrigin: 'top left', | |||
| zIndex: item.z, | |||
| }" | |||
| > | |||
| <img id="scListItemImg" :src="item.material" alt="" /> | |||
| <div ref="box1" class="stretchBox box1"></div> | |||
| <div | |||
| v-show="showBorderActive == item.id" | |||
| class="stretchBox box2" | |||
| @click="delScListItem(index)" | |||
| > | |||
| <img | |||
| style="width: 100px; height: 100px" | |||
| src="../assets/img/delete2.png" | |||
| alt="" | |||
| /> | |||
| </div> | |||
| <div class="stretchBox box3"></div> | |||
| <div | |||
| v-show="showBorderActive == item.id" | |||
| ref="box4" | |||
| @mousedown.stop="changeScaleSc(index, $event)" | |||
| class="stretchBox box4" | |||
| > | |||
| <img | |||
| style="width: 100px; height: 100px" | |||
| src="../assets/img/Scale.png" | |||
| alt="" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script setup> | |||
| //#region 导入 | |||
| import { useI18n } from "vue-i18n"; | |||
| //#endregion -------------------------- | |||
| const { locale } = useI18n(); | |||
| const lanChange = ref(locale); | |||
| //#region 父组件传参 | |||
| const props = defineProps({ | |||
| // isLoading: { | |||
| @@ -38,6 +97,9 @@ const props = defineProps({ | |||
| proportion: { | |||
| required: true, | |||
| }, | |||
| showImgInfo: { | |||
| required: true, | |||
| }, | |||
| }); | |||
| //#endregion -------------------------- | |||
| @@ -63,6 +125,7 @@ const virtualStyle = computed(() => { | |||
| transform: | |||
| "scale(" + props.materialList.virtual.w / props.proportion + ") ", | |||
| transformOrigin: "top left", | |||
| zIndex: props.materialList.virtual.z, | |||
| }; | |||
| }); | |||
| // 拖动盒子 | |||
| @@ -94,6 +157,248 @@ const handleDrag = (id, event) => { | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| }; | |||
| // 缩放盒子 | |||
| const touchStart = ref({ | |||
| pageX: null, | |||
| pageY: null, | |||
| }); | |||
| const touchOver = ref({ | |||
| pageX: null, | |||
| pageY: null, | |||
| }); | |||
| const scaleNum = ref(0.004); | |||
| // 初始版 | |||
| // function changeScale(event) { | |||
| // event.preventDefault(); | |||
| // const x = event.clientX; | |||
| // const y = event.clientY; | |||
| // // touchStart = { | |||
| // // pageX: x, | |||
| // // pageY: y, | |||
| // // }; | |||
| // touchStart.pageX = x; | |||
| // touchStart.pageY = y; | |||
| // // 鼠标移动 | |||
| // const onMouseMove = (event) => { | |||
| // const MoveX = event.clientX; | |||
| // const MoveY = event.clientY; | |||
| // const flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| // if (flag) { | |||
| // // 限制最大缩放系数 | |||
| // if (props.materialList.virtual.w > 1.3) return; | |||
| // props.materialList.virtual.w += scaleNum.value; | |||
| // // 更新坐标位置 | |||
| // // touchStart = { | |||
| // // pageX: MoveX, | |||
| // // pageY: MoveY, | |||
| // // }; | |||
| // touchStart.pageX = MoveX; | |||
| // touchStart.pageY = MoveY; | |||
| // } else { | |||
| // // 限制最小缩放系数 | |||
| // if (props.materialList.virtual.w < 0.4) return; | |||
| // props.materialList.virtual.w -= scaleNum.value; | |||
| // // 更新坐标位置 | |||
| // // touchStart = { | |||
| // // pageX: MoveX, | |||
| // // pageY: MoveY, | |||
| // // }; | |||
| // touchStart.pageX = MoveX; | |||
| // touchStart.pageY = MoveY; | |||
| // } | |||
| // }; | |||
| // // 鼠标抬起 | |||
| // const onMouseUp = () => { | |||
| // document.removeEventListener("mousemove", onMouseMove); | |||
| // document.removeEventListener("mouseup", onMouseUp); | |||
| // }; | |||
| // document.addEventListener("mousemove", onMouseMove); | |||
| // document.addEventListener("mouseup", onMouseUp); | |||
| // } | |||
| // 改动版 | |||
| function changeScale(event) { | |||
| event.preventDefault(); | |||
| const x = event.clientX; | |||
| const y = event.clientY; | |||
| touchStart.pageX = x; | |||
| touchStart.pageY = y; | |||
| // 鼠标移动 | |||
| const onMouseMove = (event) => { | |||
| const MoveX = event.clientX; | |||
| const MoveY = event.clientY; | |||
| let flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| if (flag) { | |||
| // 限制最大缩放系数 | |||
| if (props.materialList.virtual.w > 1.3) return; | |||
| props.materialList.virtual.w += scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } else { | |||
| // 限制最小缩放系数 | |||
| if (props.materialList.virtual.w < 0.4) return; | |||
| props.materialList.virtual.w -= scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } | |||
| }; | |||
| // 鼠标抬起 | |||
| const onMouseUp = () => { | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| } | |||
| const box1 = ref(null); | |||
| const box2 = ref(null); | |||
| const box3 = ref(null); | |||
| const box4 = ref(null); | |||
| // 距离版 | |||
| // function changeScale(event) { | |||
| // const box1Element = box1.value; | |||
| // const box4Element = box4.value; | |||
| // if (!box1Element || !box4Element) { | |||
| // return; | |||
| // } | |||
| // touchStart.pageX = event.clientX; | |||
| // touchStart.pageY = event.clientY; | |||
| // const box1Rect = box1Element.getBoundingClientRect(); | |||
| // const box4Rect = box4Element.getBoundingClientRect(); | |||
| // const boxDistance = Math.sqrt( | |||
| // (box1Rect.left - box4Rect.left) ** 2 + (box1Rect.top - box4Rect.top) ** 2 | |||
| // ).toFixed(0); | |||
| // const onMouseMove = (event) => { | |||
| // const x = event.clientX; | |||
| // const y = event.clientY; | |||
| // const deltaX = touchStart.pageX - x; | |||
| // const deltaY = touchStart.pageY - y; | |||
| // const boxDistance2 = Math.sqrt(deltaX ** 2 + deltaY ** 2).toFixed(0); | |||
| // if (deltaX < 0) { | |||
| // props.materialList.virtual.w = Math.min( | |||
| // Math.max((boxDistance + boxDistance2) / boxDistance, 0.4), | |||
| // 1.3 | |||
| // ); | |||
| // } else { | |||
| // props.materialList.virtual.w = Math.min( | |||
| // Math.max((boxDistance - boxDistance2) / boxDistance, 0.4), | |||
| // 1.3 | |||
| // ); | |||
| // } | |||
| // // touchOver.pageX = event.clientX; | |||
| // // touchOver.pageY = event.clientY; | |||
| // }; | |||
| // // 鼠标抬起 | |||
| // const onMouseUp = () => { | |||
| // // touchStart.pageX = touchOver.pageX; | |||
| // // touchStart.pageY = touchOver.pageY; | |||
| // document.removeEventListener("mousemove", onMouseMove); | |||
| // document.removeEventListener("mouseup", onMouseUp); | |||
| // }; | |||
| // document.addEventListener("mousemove", onMouseMove); | |||
| // document.addEventListener("mouseup", onMouseUp); | |||
| // } | |||
| //#endregion -------------------------- | |||
| //#region 素材图逻辑 | |||
| const handleDragSc = (id, index, event) => { | |||
| showBorderActive.value = id; | |||
| event.preventDefault(); | |||
| const imageElement = fatherBox.value; | |||
| const startX = event.clientX; //保存初始值,避免闪烁 | |||
| const startY = event.clientY; | |||
| const startLeft = props.materialList.scList[index].x; | |||
| const startTop = props.materialList.scList[index].y; | |||
| // 鼠标移动 | |||
| const onMouseMove = (event) => { | |||
| const deltaX = event.clientX - startX; | |||
| const deltaY = event.clientY - startY; | |||
| props.materialList.scList[index].x = startLeft + deltaX; | |||
| props.materialList.scList[index].y = startTop + deltaY; | |||
| }; | |||
| // 鼠标抬起 | |||
| const onMouseUp = () => { | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| }; | |||
| function changeScaleSc(index, event) { | |||
| event.preventDefault(); | |||
| const x = event.clientX; | |||
| const y = event.clientY; | |||
| touchStart.pageX = x; | |||
| touchStart.pageY = y; | |||
| // 鼠标移动 | |||
| const onMouseMove = (event) => { | |||
| let MoveX = event.clientX; | |||
| let MoveY = event.clientY; | |||
| let flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| if (flag) { | |||
| // 限制最大缩放系数 | |||
| if (props.materialList.scList[index].w > 1.3) return; | |||
| props.materialList.scList[index].w += scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } else { | |||
| // 限制最小缩放系数 | |||
| if (props.materialList.scList[index].w < 0.4) return; | |||
| props.materialList.scList[index].w -= scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } | |||
| }; | |||
| // 鼠标抬起 | |||
| const onMouseUp = () => { | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| } | |||
| function delScListItem(index) { | |||
| const msg = | |||
| lanChange.value == "zh-cn" | |||
| ? "确定删除素材吗吗?" | |||
| : "Are you sure you want to delete the footage?"; | |||
| const notice = lanChange.value == "zh-cn" ? "提示" : "Notice"; | |||
| const confirm = lanChange.value == "zh-cn" ? "确定" : "Confirm"; | |||
| const cancel = lanChange.value == "zh-cn" ? "取消" : "Cancel"; | |||
| ElMessageBox.confirm(msg, notice, { | |||
| confirmButtonText: confirm, | |||
| cancelButtonText: cancel, | |||
| }).then(() => { | |||
| props.materialList.scList.splice(index, 1); | |||
| }); | |||
| } | |||
| //#endregion -------------------------- | |||
| //#region 控制台回显 | |||
| watch( | |||
| () => showBorderActive.value, | |||
| (newValue, oldValue) => { | |||
| props.showImgInfo.id = showBorderActive.value; | |||
| }, | |||
| { deep: true } | |||
| ); | |||
| //#endregion -------------------------- | |||
| //#region | |||
| @@ -122,6 +427,11 @@ const handleDrag = (id, event) => { | |||
| user-select: none; | |||
| border: rgba(0, 0, 0, 0) 5px solid; | |||
| } | |||
| .scListItem { | |||
| position: absolute; | |||
| user-select: none; | |||
| border: rgba(0, 0, 0, 0) 5px solid; | |||
| } | |||
| .border { | |||
| border: 5px solid #00ff00; | |||
| .stretchBox { | |||
| @@ -135,25 +445,48 @@ const handleDrag = (id, event) => { | |||
| top: -10px; | |||
| left: -10px; | |||
| cursor: nwse-resize; | |||
| display: none; | |||
| } | |||
| .box2 { | |||
| top: -10px; | |||
| top: -50px; | |||
| left: 100%; | |||
| transform: translateX(-10px); | |||
| cursor: nesw-resize; | |||
| transform: translateX(-50px); | |||
| // cursor: nesw-resize; | |||
| cursor: pointer; | |||
| background-color: rgba(0, 0, 0, 0); | |||
| width: 100px; | |||
| height: 100px; | |||
| img { | |||
| width: 100px; | |||
| height: 100px; | |||
| } | |||
| } | |||
| .box3 { | |||
| top: 100%; | |||
| left: -10px; | |||
| transform: translateY(-10px); | |||
| cursor: nesw-resize; | |||
| display: none; | |||
| } | |||
| .box4 { | |||
| top: 100%; | |||
| left: 100%; | |||
| transform: translate(-10px, -10px); | |||
| transform: translate(-50px, -50px); | |||
| cursor: nwse-resize; | |||
| // background-color: rgba(0, 0, 0, 0); | |||
| background-color: #fff; | |||
| width: 100px; | |||
| height: 100px; | |||
| border-radius: 50px; | |||
| img { | |||
| position: absolute; | |||
| top: 50%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%) rotate(90deg); | |||
| max-width: 65px; | |||
| max-height: 65px; | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| @@ -15,15 +15,74 @@ | |||
| :style="virtualStyle" | |||
| > | |||
| <img id="virtualImg" :src="materialList.virtual.material" alt="" /> | |||
| <div class="stretchBox box1"></div> | |||
| <div ref="box1" class="stretchBox box1"></div> | |||
| <div class="stretchBox box2"></div> | |||
| <div class="stretchBox box3"></div> | |||
| <div @mousedown.stop="changeScale" class="stretchBox box4"></div> | |||
| <div | |||
| v-show="showBorderActive == materialList.virtual.id" | |||
| ref="box4" | |||
| @mousedown.stop="changeScale" | |||
| class="stretchBox box4" | |||
| > | |||
| <img | |||
| style="width: 100px; height: 100px" | |||
| src="../assets/img/Scale.png" | |||
| alt="" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <!-- 素材图的图片 --> | |||
| <div | |||
| v-for="(item, index) in materialList.scList" | |||
| :key="item.id" | |||
| class="scListItem" | |||
| :class="showBorderActive == item.id ? 'border' : ''" | |||
| @mousedown="handleDragSc(item.id, index, $event)" | |||
| :style="{ | |||
| top: item.y / props.proportion + 'px', | |||
| left: item.x / props.proportion + 'px', | |||
| transform: 'scale(' + item.w / props.proportion + ') ', | |||
| transformOrigin: 'top left', | |||
| zIndex: item.z, | |||
| }" | |||
| > | |||
| <img id="scListItemImg" :src="item.material" alt="" /> | |||
| <div ref="box1" class="stretchBox box1"></div> | |||
| <div | |||
| v-show="showBorderActive == item.id" | |||
| class="stretchBox box2" | |||
| @click="delScListItem(index)" | |||
| > | |||
| <img | |||
| style="width: 100px; height: 100px" | |||
| src="../assets/img/delete2.png" | |||
| alt="" | |||
| /> | |||
| </div> | |||
| <div class="stretchBox box3"></div> | |||
| <div | |||
| v-show="showBorderActive == item.id" | |||
| ref="box4" | |||
| @mousedown.stop="changeScaleSc(index, $event)" | |||
| class="stretchBox box4" | |||
| > | |||
| <img | |||
| style="width: 100px; height: 100px" | |||
| src="../assets/img/Scale.png" | |||
| alt="" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script setup> | |||
| //#region 导入 | |||
| import { useI18n } from "vue-i18n"; | |||
| //#endregion -------------------------- | |||
| const { locale } = useI18n(); | |||
| const lanChange = ref(locale); | |||
| //#region 父组件传参 | |||
| const props = defineProps({ | |||
| // isLoading: { | |||
| @@ -38,6 +97,9 @@ const props = defineProps({ | |||
| proportion: { | |||
| required: true, | |||
| }, | |||
| showImgInfo: { | |||
| required: true, | |||
| }, | |||
| }); | |||
| //#endregion -------------------------- | |||
| @@ -58,11 +120,12 @@ const bgStyle = computed(() => { | |||
| // 计算属性来计算盒子位置和大小 | |||
| const virtualStyle = computed(() => { | |||
| return { | |||
| top: props.materialList.virtual.y + "px", | |||
| left: props.materialList.virtual.x + "px", | |||
| top: props.materialList.virtual.y / props.proportion + "px", | |||
| left: props.materialList.virtual.x / props.proportion + "px", | |||
| transform: | |||
| "scale(" + props.materialList.virtual.w / props.proportion + ") ", | |||
| transformOrigin: "top left", | |||
| zIndex: props.materialList.virtual.z, | |||
| }; | |||
| }); | |||
| // 拖动盒子 | |||
| @@ -81,8 +144,8 @@ const handleDrag = (id, event) => { | |||
| const onMouseMove = (event) => { | |||
| const deltaX = event.clientX - startX; | |||
| const deltaY = event.clientY - startY; | |||
| props.materialList.virtual.x = startLeft + deltaX; | |||
| props.materialList.virtual.y = startTop + deltaY; | |||
| props.materialList.virtual.x = startLeft + deltaX * props.proportion; | |||
| props.materialList.virtual.y = startTop + deltaY * props.proportion; | |||
| }; | |||
| // 鼠标抬起 | |||
| @@ -95,42 +158,92 @@ const handleDrag = (id, event) => { | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| }; | |||
| // 缩放盒子 | |||
| let touchStart = reactive({ | |||
| const touchStart = ref({ | |||
| pageX: null, | |||
| pageY: null, | |||
| }); | |||
| const touchOver = ref({ | |||
| pageX: null, | |||
| pageY: null, | |||
| }); | |||
| const scaleNum = ref(0.003); | |||
| const scaleNum = ref(0.004); | |||
| // 初始版 | |||
| // function changeScale(event) { | |||
| // event.preventDefault(); | |||
| // const x = event.clientX; | |||
| // const y = event.clientY; | |||
| // // touchStart = { | |||
| // // pageX: x, | |||
| // // pageY: y, | |||
| // // }; | |||
| // touchStart.pageX = x; | |||
| // touchStart.pageY = y; | |||
| // // 鼠标移动 | |||
| // const onMouseMove = (event) => { | |||
| // const MoveX = event.clientX; | |||
| // const MoveY = event.clientY; | |||
| // const flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| // if (flag) { | |||
| // // 限制最大缩放系数 | |||
| // if (props.materialList.virtual.w > 1.3) return; | |||
| // props.materialList.virtual.w += scaleNum.value; | |||
| // // 更新坐标位置 | |||
| // // touchStart = { | |||
| // // pageX: MoveX, | |||
| // // pageY: MoveY, | |||
| // // }; | |||
| // touchStart.pageX = MoveX; | |||
| // touchStart.pageY = MoveY; | |||
| // } else { | |||
| // // 限制最小缩放系数 | |||
| // if (props.materialList.virtual.w < 0.4) return; | |||
| // props.materialList.virtual.w -= scaleNum.value; | |||
| // // 更新坐标位置 | |||
| // // touchStart = { | |||
| // // pageX: MoveX, | |||
| // // pageY: MoveY, | |||
| // // }; | |||
| // touchStart.pageX = MoveX; | |||
| // touchStart.pageY = MoveY; | |||
| // } | |||
| // }; | |||
| // // 鼠标抬起 | |||
| // const onMouseUp = () => { | |||
| // document.removeEventListener("mousemove", onMouseMove); | |||
| // document.removeEventListener("mouseup", onMouseUp); | |||
| // }; | |||
| // document.addEventListener("mousemove", onMouseMove); | |||
| // document.addEventListener("mouseup", onMouseUp); | |||
| // } | |||
| // 改动版 | |||
| function changeScale(event) { | |||
| event.preventDefault(); | |||
| const x = event.clientX; | |||
| const y = event.clientY; | |||
| touchStart = { | |||
| pageX: x, | |||
| pageY: y, | |||
| }; | |||
| touchStart.pageX = x; | |||
| touchStart.pageY = y; | |||
| // 鼠标移动 | |||
| const onMouseMove = (event) => { | |||
| const MoveX = event.clientX; | |||
| const MoveY = event.clientY; | |||
| const flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| let flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| if (flag) { | |||
| // 限制最大缩放系数 | |||
| if (props.materialList.virtual.w > 1.3) return; | |||
| props.materialList.virtual.w += scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart = { | |||
| pageX: MoveX, | |||
| pageY: MoveY, | |||
| }; | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } else { | |||
| // 限制最小缩放系数 | |||
| if (props.materialList.virtual.w < 0.4) return; | |||
| props.materialList.virtual.w -= scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart = { | |||
| pageX: MoveX, | |||
| pageY: MoveY, | |||
| }; | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } | |||
| }; | |||
| @@ -143,6 +256,149 @@ function changeScale(event) { | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| } | |||
| const box1 = ref(null); | |||
| const box2 = ref(null); | |||
| const box3 = ref(null); | |||
| const box4 = ref(null); | |||
| // 距离版 | |||
| // function changeScale(event) { | |||
| // const box1Element = box1.value; | |||
| // const box4Element = box4.value; | |||
| // if (!box1Element || !box4Element) { | |||
| // return; | |||
| // } | |||
| // touchStart.pageX = event.clientX; | |||
| // touchStart.pageY = event.clientY; | |||
| // const box1Rect = box1Element.getBoundingClientRect(); | |||
| // const box4Rect = box4Element.getBoundingClientRect(); | |||
| // const boxDistance = Math.sqrt( | |||
| // (box1Rect.left - box4Rect.left) ** 2 + (box1Rect.top - box4Rect.top) ** 2 | |||
| // ).toFixed(0); | |||
| // const onMouseMove = (event) => { | |||
| // const x = event.clientX; | |||
| // const y = event.clientY; | |||
| // const deltaX = touchStart.pageX - x; | |||
| // const deltaY = touchStart.pageY - y; | |||
| // const boxDistance2 = Math.sqrt(deltaX ** 2 + deltaY ** 2).toFixed(0); | |||
| // if (deltaX < 0) { | |||
| // props.materialList.virtual.w = Math.min( | |||
| // Math.max((boxDistance + boxDistance2) / boxDistance, 0.4), | |||
| // 1.3 | |||
| // ); | |||
| // } else { | |||
| // props.materialList.virtual.w = Math.min( | |||
| // Math.max((boxDistance - boxDistance2) / boxDistance, 0.4), | |||
| // 1.3 | |||
| // ); | |||
| // } | |||
| // // touchOver.pageX = event.clientX; | |||
| // // touchOver.pageY = event.clientY; | |||
| // }; | |||
| // // 鼠标抬起 | |||
| // const onMouseUp = () => { | |||
| // // touchStart.pageX = touchOver.pageX; | |||
| // // touchStart.pageY = touchOver.pageY; | |||
| // document.removeEventListener("mousemove", onMouseMove); | |||
| // document.removeEventListener("mouseup", onMouseUp); | |||
| // }; | |||
| // document.addEventListener("mousemove", onMouseMove); | |||
| // document.addEventListener("mouseup", onMouseUp); | |||
| // } | |||
| //#endregion -------------------------- | |||
| //#region 素材图逻辑 | |||
| const handleDragSc = (id, index, event) => { | |||
| showBorderActive.value = id; | |||
| event.preventDefault(); | |||
| const imageElement = fatherBox.value; | |||
| const startX = event.clientX; //保存初始值,避免闪烁 | |||
| const startY = event.clientY; | |||
| const startLeft = props.materialList.scList[index].x; | |||
| const startTop = props.materialList.scList[index].y; | |||
| // 鼠标移动 | |||
| const onMouseMove = (event) => { | |||
| const deltaX = event.clientX - startX; | |||
| const deltaY = event.clientY - startY; | |||
| props.materialList.scList[index].x = startLeft + deltaX * props.proportion; | |||
| props.materialList.scList[index].y = startTop + deltaY * props.proportion; | |||
| }; | |||
| // 鼠标抬起 | |||
| const onMouseUp = () => { | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| }; | |||
| function changeScaleSc(index, event) { | |||
| event.preventDefault(); | |||
| const x = event.clientX; | |||
| const y = event.clientY; | |||
| touchStart.pageX = x; | |||
| touchStart.pageY = y; | |||
| // 鼠标移动 | |||
| const onMouseMove = (event) => { | |||
| let MoveX = event.clientX; | |||
| let MoveY = event.clientY; | |||
| let flag = Math.sign(x - touchStart.pageX) == -1 ? true : false; | |||
| if (flag) { | |||
| // 限制最大缩放系数 | |||
| if (props.materialList.scList[index].w > 1.3) return; | |||
| props.materialList.scList[index].w += scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } else { | |||
| // 限制最小缩放系数 | |||
| if (props.materialList.scList[index].w < 0.4) return; | |||
| props.materialList.scList[index].w -= scaleNum.value; | |||
| // 更新坐标位置 | |||
| touchStart.pageX = MoveX; | |||
| touchStart.pageY = MoveY; | |||
| } | |||
| }; | |||
| // 鼠标抬起 | |||
| const onMouseUp = () => { | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| } | |||
| function delScListItem(index) { | |||
| const msg = | |||
| lanChange.value == "zh-cn" | |||
| ? "确定删除素材吗吗?" | |||
| : "Are you sure you want to delete the footage?"; | |||
| const notice = lanChange.value == "zh-cn" ? "提示" : "Notice"; | |||
| const confirm = lanChange.value == "zh-cn" ? "确定" : "Confirm"; | |||
| const cancel = lanChange.value == "zh-cn" ? "取消" : "Cancel"; | |||
| ElMessageBox.confirm(msg, notice, { | |||
| confirmButtonText: confirm, | |||
| cancelButtonText: cancel, | |||
| }).then(() => { | |||
| props.materialList.scList.splice(index, 1); | |||
| }); | |||
| } | |||
| //#endregion -------------------------- | |||
| //#region 控制台回显 | |||
| watch( | |||
| () => showBorderActive.value, | |||
| (newValue, oldValue) => { | |||
| props.showImgInfo.id = showBorderActive.value; | |||
| }, | |||
| { deep: true } | |||
| ); | |||
| //#endregion -------------------------- | |||
| //#region | |||
| @@ -153,17 +409,23 @@ function changeScale(event) { | |||
| .myPage { | |||
| // height: 768px; | |||
| // width: 432px; | |||
| width: 100%; | |||
| height: 100%; | |||
| } | |||
| .content1 { | |||
| position: relative; | |||
| height: 768px; | |||
| width: 432px; | |||
| // height: 768px; | |||
| // width: 432px; | |||
| width: 100%; | |||
| height: 100%; | |||
| overflow: hidden; | |||
| } | |||
| .content2 { | |||
| position: relative; | |||
| height: 432px; | |||
| width: 768px; | |||
| // height: 432px; | |||
| // width: 768px; | |||
| width: 100%; | |||
| height: 100%; | |||
| overflow: hidden; | |||
| } | |||
| .virtual { | |||
| @@ -171,6 +433,11 @@ function changeScale(event) { | |||
| user-select: none; | |||
| border: rgba(0, 0, 0, 0) 5px solid; | |||
| } | |||
| .scListItem { | |||
| position: absolute; | |||
| user-select: none; | |||
| border: rgba(0, 0, 0, 0) 5px solid; | |||
| } | |||
| .border { | |||
| border: 5px solid #00ff00; | |||
| .stretchBox { | |||
| @@ -184,25 +451,48 @@ function changeScale(event) { | |||
| top: -10px; | |||
| left: -10px; | |||
| cursor: nwse-resize; | |||
| display: none; | |||
| } | |||
| .box2 { | |||
| top: -10px; | |||
| top: -50px; | |||
| left: 100%; | |||
| transform: translateX(-10px); | |||
| cursor: nesw-resize; | |||
| transform: translateX(-50px); | |||
| // cursor: nesw-resize; | |||
| cursor: pointer; | |||
| background-color: rgba(0, 0, 0, 0); | |||
| width: 100px; | |||
| height: 100px; | |||
| img { | |||
| width: 100px; | |||
| height: 100px; | |||
| } | |||
| } | |||
| .box3 { | |||
| top: 100%; | |||
| left: -10px; | |||
| transform: translateY(-10px); | |||
| cursor: nesw-resize; | |||
| display: none; | |||
| } | |||
| .box4 { | |||
| top: 100%; | |||
| left: 100%; | |||
| transform: translate(-10px, -10px); | |||
| transform: translate(-50px, -50px); | |||
| cursor: nwse-resize; | |||
| // background-color: rgba(0, 0, 0, 0); | |||
| background-color: #fff; | |||
| width: 100px; | |||
| height: 100px; | |||
| border-radius: 50px; | |||
| img { | |||
| position: absolute; | |||
| top: 50%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%) rotate(90deg); | |||
| max-width: 65px; | |||
| max-height: 65px; | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| @@ -17,12 +17,12 @@ | |||
| </div> | |||
| {{ $t("createVideo.title") }} | |||
| </div> | |||
| <div class="myRouter-item" @click="router.push('/moveImg')"> | |||
| <!-- <div class="myRouter-item" @click="router.push('/myCreating')"> | |||
| <div class="icon"> | |||
| <el-icon><i-ep-edit /></el-icon> | |||
| </div> | |||
| {{ $t("createVideo.title") }} | |||
| </div> | |||
| {{ $t("myCreating.title") }} | |||
| </div> --> | |||
| </div> | |||
| </div> | |||
| <!-- 右边显示部分 --> | |||
| @@ -24,6 +24,7 @@ declare module '@vue/runtime-core' { | |||
| ElFormItem: typeof import('element-plus/es')['ElFormItem'] | |||
| ElIcon: typeof import('element-plus/es')['ElIcon'] | |||
| ElInput: typeof import('element-plus/es')['ElInput'] | |||
| ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] | |||
| ElOption: typeof import('element-plus/es')['ElOption'] | |||
| ElPagination: typeof import('element-plus/es')['ElPagination'] | |||
| ElRow: typeof import('element-plus/es')['ElRow'] | |||
| @@ -92,20 +92,68 @@ | |||
| <div class="showModelImgBox"> | |||
| <!-- <img :src="modelImg" alt="" /> --> | |||
| <!-- 编辑图片位置组件 --> | |||
| <EditPosition | |||
| id="editPosition" | |||
| class="EditPosition" | |||
| :materialList="materialList" | |||
| :videoType="videoType" | |||
| :proportion="proportion" | |||
| <div | |||
| :class="videoType == 1 ? 'EditPositionBox1' : 'EditPositionBox2'" | |||
| class="" | |||
| > | |||
| </EditPosition> | |||
| <EditPosition | |||
| id="editPosition" | |||
| :materialList="materialList" | |||
| :videoType="videoType" | |||
| :proportion="proportion" | |||
| :showImgInfo="showImgInfo" | |||
| > | |||
| </EditPosition> | |||
| </div> | |||
| <!-- 控制台 --> | |||
| <div v-if="showImgInfo.data" class="showImgInfoBox"> | |||
| <!-- <div class="infoItem"> | |||
| </div> --> | |||
| <div class="ItemName">横坐标</div> | |||
| <div class="ItemValue"> | |||
| <el-input-number | |||
| v-model="showImgInfo.data.x" | |||
| size="large" | |||
| controls-position="right" | |||
| /> | |||
| </div> | |||
| <div class="ItemName">纵坐标</div> | |||
| <div class="ItemValue"> | |||
| <el-input-number | |||
| v-model="showImgInfo.data.y" | |||
| size="large" | |||
| controls-position="right" | |||
| /> | |||
| </div> | |||
| <div class="ItemName">层级</div> | |||
| <div class="ItemValue"> | |||
| <el-input-number | |||
| v-model="showImgInfo.data.z" | |||
| :min="1" | |||
| :max="10" | |||
| size="large" | |||
| controls-position="right" | |||
| /> | |||
| </div> | |||
| <div class="ItemName">缩放倍率</div> | |||
| <div class="ItemValue"> | |||
| <el-input-number | |||
| v-model="showImgInfo.data.w" | |||
| :min="0.4" | |||
| :max="1.3" | |||
| size="large" | |||
| controls-position="right" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <!-- 保存视频和生成视频 --> | |||
| <div class="btnBox" v-show="!showChooseModel"> | |||
| <div class="customBtn" style="margin-right: 15px"> | |||
| {{ $t("createVideo.saveVideo") }} | |||
| </div> | |||
| <div class="customBtn">{{ $t("createVideo.generateVideo") }}</div> | |||
| <div class="customBtn"> | |||
| {{ $t("createVideo.generateVideo") }} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -416,6 +464,8 @@ import { | |||
| personSCIListApi, | |||
| delImgApi, | |||
| personPatchApi, | |||
| saveOrUpdateApi, | |||
| createVideoApi, | |||
| } from "@/apis/createVideo"; | |||
| import { useI18n } from "vue-i18n"; | |||
| import Recorder from "js-audio-recorder"; //mp3插件 | |||
| @@ -534,7 +584,8 @@ const tabItemActive = ref(1); | |||
| //#endregion ---------------------------- | |||
| //#region 文案及其音频 | |||
| const title = ref(null); | |||
| const title = | |||
| lanChange.value == "zh-cn" ? ref("未定义标题") : ref("Undefined Title"); | |||
| const textareaContent = ref(""); | |||
| function insertIcon() { | |||
| const textarea = document.getElementById("textInputId"); | |||
| @@ -743,6 +794,8 @@ async function reGetpersonBGIList() { | |||
| const bjImgActive = ref(null); | |||
| function selectBjImg(img) { | |||
| bjImgActive.value = img.id; | |||
| materialList.BGI.material = img.material; | |||
| materialList.BGI.id = img.id; | |||
| } | |||
| //#endregion ---------------------------- | |||
| @@ -812,6 +865,7 @@ async function reGetpersonSCIList() { | |||
| const scImgActive = ref(null); | |||
| function selectScImg(img) { | |||
| scImgActive.value = img.id; | |||
| materialList.scList.push({ ...img, x: 0, y: 0, z: 0, w: 1, h: 1 }); | |||
| } | |||
| // 删除上传的背景或素材 | |||
| function delImg(id) { | |||
| @@ -849,8 +903,10 @@ watch( | |||
| (newValue, oldValue) => { | |||
| // 根据横竖屏来确定比例 1竖2横 | |||
| if (videoType.value == 1) { | |||
| proportion.value = 1080 / elementWidth.value; | |||
| // proportion.value = 1080 / elementWidth.value; | |||
| proportion.value = 1080 / 432; | |||
| } else if (videoType.value == 2) { | |||
| proportion.value = 1920 / 768; | |||
| proportion.value = 1920 / elementWidth.value; | |||
| } | |||
| console.log(proportion.value); | |||
| @@ -891,6 +947,101 @@ watch( | |||
| ); | |||
| //#endregion ---------------------------- | |||
| //#region 编辑区域控制台 | |||
| const showImgInfo = ref({ | |||
| id: null, | |||
| data: null, | |||
| }); //显示素材or数字人的id | |||
| // const showImgInfoData = ref(null); | |||
| watch( | |||
| () => showImgInfo, | |||
| (newValue, oldValue) => { | |||
| if (materialList.virtual.id == showImgInfo.value.id) { | |||
| showImgInfo.value.data = materialList.virtual; | |||
| } else { | |||
| showImgInfo.value.data = materialList.scList.find( | |||
| (item) => item.id == showImgInfo.value.id | |||
| ); | |||
| } | |||
| }, | |||
| { deep: true } | |||
| ); | |||
| //#endregion ---------------------------- | |||
| //#region 保存视频和生成草稿 | |||
| const workId = ref(null); //保存作品完成后会有一个id,用于生成视频 | |||
| async function saveOrUpdate() { | |||
| try { | |||
| if (!textareaContent.value) { | |||
| ElMessage.error( | |||
| lanChange.value == "zh-cn" | |||
| ? `您还没有输入文案` | |||
| : `You have not entered the copy yet` | |||
| ); | |||
| return; | |||
| } | |||
| if (!sound.value && !soundStyle.value) { | |||
| ElMessage.error( | |||
| lanChange.value == "zh-cn" | |||
| ? `您还没有选择声音` | |||
| : `You have not selected a sound` | |||
| ); | |||
| return; | |||
| } | |||
| // 处理数字人 | |||
| const shuziren = { | |||
| id: materialList.virtual.id, | |||
| type: 1, | |||
| material: materialList.virtual.material, | |||
| x: materialList.virtual.x, | |||
| y: materialList.virtual.y, | |||
| z: materialList.virtual.z, | |||
| w: materialList.virtual.w, | |||
| h: materialList.virtual.h, | |||
| }; | |||
| // 处理素材 | |||
| const scList = materialList.scList.map((item) => { | |||
| return { | |||
| id: item.id, | |||
| type: 5, | |||
| material: item.material, | |||
| x: item.x, | |||
| y: item.y, | |||
| z: item.z, | |||
| w: item.w, | |||
| h: item.h, | |||
| }; | |||
| }); | |||
| // 处理上传数据 | |||
| const data = { | |||
| id: workId.value, | |||
| videoType: videoType.value, //横屏竖屏 | |||
| personMouldId: materialList.virtual.id, //数字人id | |||
| voiceMouldIds: [soundStyle.value ? soundStyle.value : sound.value], //声音id | |||
| title: title.value, //标题 | |||
| paperwork: textareaContent.value, //语音内容 | |||
| backgroundId: materialList.BGI.id + "", //背景id | |||
| personJson: JSON.stringify(shuziren), // 数字人 | |||
| materialAllJson: JSON.stringify([...scList]), // 素材 | |||
| }; | |||
| const res = await saveOrUpdateApi(data); | |||
| workId.value = res.data.id; | |||
| } catch (error) { | |||
| ElMessage.error(error); | |||
| } | |||
| } | |||
| async function createVideo() { | |||
| try { | |||
| await saveOrUpdate(); | |||
| const res2 = await createVideoApi(workId.value); | |||
| } catch (error) { | |||
| ElMessage.error(error); | |||
| } | |||
| } | |||
| //#endregion ---------------------------- | |||
| //#region | |||
| //#endregion ---------------------------- | |||
| @@ -1080,13 +1231,54 @@ onMounted(async () => { | |||
| max-width: 800px; | |||
| max-height: 800px; | |||
| } | |||
| .EditPosition { | |||
| .EditPositionBox1 { | |||
| position: absolute; | |||
| top: 50%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%); | |||
| max-width: 800px; | |||
| max-height: 800px; | |||
| height: 768px; | |||
| width: 432px; | |||
| } | |||
| .EditPositionBox2 { | |||
| position: absolute; | |||
| top: 50%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%); | |||
| height: 432px; | |||
| width: 768px; | |||
| } | |||
| .showImgInfoBox { | |||
| position: absolute; | |||
| top: 200px; | |||
| right: 0px; | |||
| width: 120px; | |||
| background-color: #e9ecf1; | |||
| border-radius: 20px; | |||
| padding: 10px; | |||
| box-shadow: rgba(0, 0, 0, 0.5) 0px 0px 15px; | |||
| .ItemName { | |||
| width: 100%; | |||
| height: 40px; | |||
| text-align: center; | |||
| line-height: 40px; | |||
| background-color: #fff; | |||
| border-radius: 10px; | |||
| } | |||
| .ItemValue { | |||
| margin-bottom: 10px; | |||
| width: 100%; | |||
| height: 40px; | |||
| text-align: center; | |||
| line-height: 40px; | |||
| background-color: #f5f7fa; | |||
| border-radius: 10px; | |||
| .el-input-number--large { | |||
| width: 100px; | |||
| } | |||
| .el-input-number__increase { | |||
| width: none; | |||
| } | |||
| } | |||
| } | |||
| .btnBox { | |||
| position: absolute; | |||