| @@ -25,7 +25,7 @@ | |||
| </div> | |||
| {{ $t("myCreating.title") }} | |||
| </div> | |||
| <!-- 测试功能页 --> | |||
| <!-- 测试功能页 v-if="false"--> | |||
| <div | |||
| v-if="false" | |||
| class="myRouter-item" | |||
| @@ -8,10 +8,13 @@ export {} | |||
| declare module '@vue/runtime-core' { | |||
| export interface GlobalComponents { | |||
| Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default'] | |||
| ElAlert: typeof import('element-plus/es')['ElAlert'] | |||
| ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] | |||
| ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] | |||
| ElButton: typeof import('element-plus/es')['ElButton'] | |||
| ElCard: typeof import('element-plus/es')['ElCard'] | |||
| ElCol: typeof import('element-plus/es')['ElCol'] | |||
| ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] | |||
| ElDialog: typeof import('element-plus/es')['ElDialog'] | |||
| ElDivider: typeof import('element-plus/es')['ElDivider'] | |||
| ElDropdown: typeof import('element-plus/es')['ElDropdown'] | |||
| @@ -21,35 +24,54 @@ 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'] | |||
| ElLink: typeof import('element-plus/es')['ElLink'] | |||
| ElOption: typeof import('element-plus/es')['ElOption'] | |||
| ElPagination: typeof import('element-plus/es')['ElPagination'] | |||
| ElPopover: typeof import('element-plus/es')['ElPopover'] | |||
| ElRadio: typeof import('element-plus/es')['ElRadio'] | |||
| ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] | |||
| ElRow: typeof import('element-plus/es')['ElRow'] | |||
| ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] | |||
| ElSelect: typeof import('element-plus/es')['ElSelect'] | |||
| ElSwitch: typeof import('element-plus/es')['ElSwitch'] | |||
| ElTable: typeof import('element-plus/es')['ElTable'] | |||
| ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] | |||
| ElTabPane: typeof import('element-plus/es')['ElTabPane'] | |||
| ElTabs: typeof import('element-plus/es')['ElTabs'] | |||
| ElTag: typeof import('element-plus/es')['ElTag'] | |||
| ElTooltip: typeof import('element-plus/es')['ElTooltip'] | |||
| ElTree: typeof import('element-plus/es')['ElTree'] | |||
| ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] | |||
| ElUpload: typeof import('element-plus/es')['ElUpload'] | |||
| GithubCorner: typeof import('./../components/GithubCorner/index.vue')['default'] | |||
| Hamburger: typeof import('./../components/Hamburger/index.vue')['default'] | |||
| IconSelect: typeof import('./../components/IconSelect/index.vue')['default'] | |||
| IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default'] | |||
| IEpCaretTop: typeof import('~icons/ep/caret-top')['default'] | |||
| IEpClose: typeof import('~icons/ep/close')['default'] | |||
| IEpCloseBold: typeof import('~icons/ep/close-bold')['default'] | |||
| IEpCollection: typeof import('~icons/ep/collection')['default'] | |||
| IEpDelete: typeof import('~icons/ep/delete')['default'] | |||
| IEpDocumentAdd: typeof import('~icons/ep/document-add')['default'] | |||
| IEpDownload: typeof import('~icons/ep/download')['default'] | |||
| IEpEdit: typeof import('~icons/ep/edit')['default'] | |||
| IEpFolder: typeof import('~icons/ep/folder')['default'] | |||
| IEpFolderAdd: typeof import('~icons/ep/folder-add')['default'] | |||
| IEpGoods: typeof import('~icons/ep/goods')['default'] | |||
| IEpHeadset: typeof import('~icons/ep/headset')['default'] | |||
| IEpLoading: typeof import('~icons/ep/loading')['default'] | |||
| IEpLock: typeof import('~icons/ep/lock')['default'] | |||
| IEpMenu: typeof import('~icons/ep/menu')['default'] | |||
| IEpMic: typeof import('~icons/ep/mic')['default'] | |||
| IEpPlus: typeof import('~icons/ep/plus')['default'] | |||
| IEpPosition: typeof import('~icons/ep/position')['default'] | |||
| IEpRefresh: typeof import('~icons/ep/refresh')['default'] | |||
| IEpRefreshLeft: typeof import('~icons/ep/refresh-left')['default'] | |||
| IEpSearch: typeof import('~icons/ep/search')['default'] | |||
| IEpSetting: typeof import('~icons/ep/setting')['default'] | |||
| IEpSwitchButton: typeof import('~icons/ep/switch-button')['default'] | |||
| IEpTop: typeof import('~icons/ep/top')['default'] | |||
| IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default'] | |||
| IEpVideoCamera: typeof import('~icons/ep/video-camera')['default'] | |||
| IEpVideoPause: typeof import('~icons/ep/video-pause')['default'] | |||
| IEpVideoPlay: typeof import('~icons/ep/video-play')['default'] | |||
| LangSelect: typeof import('./../components/LangSelect/index.vue')['default'] | |||
| @@ -64,10 +86,9 @@ declare module '@vue/runtime-core' { | |||
| SizeSelect: typeof import('./../components/SizeSelect/index.vue')['default'] | |||
| SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default'] | |||
| WangEditor: typeof import('./../components/WangEditor/index.vue')['default'] | |||
| Websocket: typeof import('./../components/websocket.vue')['default'] | |||
| Websoket: typeof import('./../components/websoket.vue')['default'] | |||
| } | |||
| export interface ComponentCustomProperties { | |||
| vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll'] | |||
| vLoading: typeof import('element-plus/es')['ElLoadingDirective'] | |||
| } | |||
| } | |||
| @@ -18,18 +18,42 @@ | |||
| :placeholder="$t('createVideo.typeTitle')" | |||
| /> | |||
| <el-icon class="icon-edit"><i-ep-edit /></el-icon> | |||
| <!-- 编辑字幕框 --> | |||
| </div> | |||
| <div v-if="imgUrl&&SubtitlesSwitch" class="editSubtitlesBox "> | |||
| <div class="item">X: <el-input | |||
| v-model="SubtitlesTop" | |||
| width="30" | |||
| /></div> | |||
| <div class="item">Y: <el-input | |||
| v-model="SubtitlesLeft" | |||
| width="30" | |||
| /></div> | |||
| <div class="item">Size: <el-input | |||
| v-model="SubtitlesSize" | |||
| width="30" | |||
| /></div> | |||
| <div class="item">Color: <el-color-picker v-model="SubtitlesColor" show-alpha /></div> | |||
| <div class="item">BGC: <el-color-picker v-model="SubtitlesBgc" show-alpha /></div> | |||
| </div> | |||
| </el-row> | |||
| <!-- 中间展示大图 --> | |||
| <div class="bigImg inmiddle"> | |||
| <div v-if="false" class="canvasBox"> | |||
| <canvas id="myCanvas" ref="myCanvasRef" > | |||
| </canvas> | |||
| </div> | |||
| <img id="canvasImg" ref="canvasImgRef" :src="imgUrl" alt="" class="inmiddle" /> | |||
| <div ref="canvasImgBox" class="bigImg inmiddle"> | |||
| <img id="canvasImg" :src="imgUrl" alt="" class="" /> | |||
| <div ref="SubtitlesBox" @mousedown="handleDrag" :style="SubtitlesStyle" v-if="SubtitlesSwitch" class="SubtitlesBox">请在文案区编辑您的正文 | |||
| <div class="stretchBox box1"></div> | |||
| <div class="stretchBox box2"></div> | |||
| <div class="stretchBox box3"></div> | |||
| <div class="stretchBox box4"></div> | |||
| <div class="stretchBox box5"></div> | |||
| <div class="stretchBox box6"></div> | |||
| <div class="stretchBox box7"></div> | |||
| <div class="stretchBox box8"></div> | |||
| </div> | |||
| </div> | |||
| <!-- 字幕开关 --> | |||
| <div v-if="false" class="SubtitlesSwitch"> | |||
| <div v-if="imgUrl" class="SubtitlesSwitch"> | |||
| <p>{{ $t('createVideo.subtitles') }}</p> | |||
| <el-switch v-model="SubtitlesSwitch" /> | |||
| </div> | |||
| @@ -1915,42 +1939,49 @@ function copyAnswerContent() { | |||
| //#endregion ---------------------------- | |||
| //#region 字幕部分 | |||
| const canvasImgBox=ref(null) //图片外盒子dom | |||
| const SubtitlesBox=ref(null) //字幕盒子dom | |||
| const SubtitlesSwitch=ref(false) //字幕开关 | |||
| const canvas = ref(null); //canvas对象 | |||
| const myCanvasRef = ref(null); //画布dom | |||
| const canvasImgRef = ref(null); //图片dom | |||
| const imgWidth=ref(null) | |||
| const imgHeight=ref(null) | |||
| const myText = ref("hello world"); | |||
| const iText = new fabric.IText(myText.value, { | |||
| left: 100, | |||
| top: 100, | |||
| }); | |||
| function setCanvasText() { | |||
| canvas.value = new fabric.Canvas("myCanvas",{ | |||
| width:imgWidth.value, | |||
| height:imgHeight.value, | |||
| }); | |||
| iText.on("mouseup", function () { | |||
| console.log(iText, "iText"); | |||
| console.log(iText.angle.toFixed(0), "angle"); | |||
| console.log(iText.left.toFixed(0), "left"); | |||
| console.log(iText.top.toFixed(0), "top"); | |||
| console.log(iText.width.toFixed(0), "width"); | |||
| console.log(iText.height.toFixed(0), "height"); | |||
| }); | |||
| canvas.value.add(iText); | |||
| } | |||
| const SubtitlesTop=ref(0) //字幕Y轴 | |||
| const SubtitlesLeft=ref(0) //字幕X轴 | |||
| const SubtitlesSize=ref(18) //字幕字号 | |||
| const SubtitlesColor=ref('rgba(0, 0, 0, 1)') //字幕字色 | |||
| const SubtitlesBgc=ref('rgba(255, 255, 255,1 )') //字幕背景色 | |||
| // 计算属性来计算盒子位置 | |||
| const SubtitlesStyle = computed(() => { | |||
| return { | |||
| top: SubtitlesTop.value + 'px', | |||
| left: SubtitlesLeft.value + 'px', | |||
| fontSize: SubtitlesSize.value+ 'px', | |||
| color:SubtitlesColor.value, | |||
| backgroundColor: SubtitlesBgc.value, | |||
| }; | |||
| }); | |||
| // 拖动盒子 | |||
| const handleDrag = (event) => { | |||
| event.preventDefault(); | |||
| const boxElement = canvasImgBox.value; | |||
| const imageElement = SubtitlesBox.value; | |||
| const offsetX = event.clientX - imageElement.offsetLeft; | |||
| const offsetY = event.clientY - imageElement.offsetTop; | |||
| const onMouseMove = (event) => { | |||
| const newX = event.clientX - offsetX; | |||
| const newY = event.clientY - offsetY; | |||
| SubtitlesLeft.value= newX; | |||
| SubtitlesTop.value = newY; | |||
| }; | |||
| const onMouseUp = () => { | |||
| document.removeEventListener('mousemove', onMouseMove); | |||
| document.removeEventListener('mouseup', onMouseUp); | |||
| }; | |||
| document.addEventListener('mousemove', onMouseMove); | |||
| document.addEventListener('mouseup', onMouseUp); | |||
| }; | |||
| // 监听字幕显示 | |||
| watch( | |||
| () => SubtitlesSwitch, | |||
| async (newValue, oldValue) => { | |||
| if(SubtitlesSwitch.value) { | |||
| setCanvasText() | |||
| } else { | |||
| canvas.value.remove(iText); | |||
| canvas.value=null; | |||
| } | |||
| }, | |||
| { deep: true } | |||
| ); | |||
| @@ -1958,11 +1989,8 @@ watch( | |||
| watch( | |||
| () => imgUrl.value, | |||
| async (newValue, oldValue) => { | |||
| await nextTick() | |||
| if(canvas.value) { | |||
| canvas.value.width=imgWidth.value, | |||
| canvas.value.height=imgHeight.value | |||
| } | |||
| await nextTick() | |||
| // console.log(getComputedStyle(document.getElementById("canvasImg"),null)); | |||
| }, | |||
| { deep: true } | |||
| ); | |||
| @@ -2013,7 +2041,7 @@ onMounted(async () => { | |||
| } | |||
| }); | |||
| createWS(); //创建websocket | |||
| }); | |||
| //#endregion ---------------------------- | |||
| @@ -2067,6 +2095,34 @@ onUnmounted(() => { | |||
| height: 67%; | |||
| // height: calc(70vh - 80px ); | |||
| margin-bottom: 20px; | |||
| .editSubtitlesBox{ | |||
| display: flex; | |||
| justify-content: flex-start; | |||
| padding-right: 80px; | |||
| margin-left: 10px; | |||
| margin-right: 80px; | |||
| border-radius: 20px; | |||
| flex: 1; | |||
| line-height: 35px; | |||
| background-color: #f1f2f6; | |||
| font-size: 16px; | |||
| .item { | |||
| display: flex; | |||
| align-items: center; | |||
| margin-left: 10px; | |||
| .el-input { | |||
| width: 60px; | |||
| height: 25px; | |||
| } | |||
| } | |||
| :deep(.el-input__wrapper) { | |||
| border: none; | |||
| border: 2px solid #000; | |||
| border-radius: 5px; | |||
| box-shadow: none; | |||
| } | |||
| } | |||
| .uploadBtn { | |||
| position: relative; | |||
| padding: 10px 15px; | |||
| @@ -2086,34 +2142,74 @@ onUnmounted(() => { | |||
| width: 50%; | |||
| height: 88%; | |||
| margin-top: 20px; | |||
| .canvasBox { | |||
| .SubtitlesBox { | |||
| position: absolute; | |||
| left: 0; | |||
| top: 0; | |||
| width: 100%; | |||
| height: 100%; | |||
| background-color: skyblue; | |||
| opacity: 0.5; | |||
| // z-index: 9; | |||
| .canvas-container { | |||
| // background-color: red; | |||
| // z-index: 9; | |||
| // width: 100%!important; | |||
| // height: 100%!important; | |||
| // #myCanvas { | |||
| // width: 100%!important; | |||
| // height: 100%!important; | |||
| // } | |||
| // .upper-canvas { | |||
| // width: 100%!important; | |||
| // height: 100%!important; | |||
| // } | |||
| user-select: none; | |||
| width: 300px; | |||
| padding: 5px 5px; | |||
| border: 2px solid #00ff00; | |||
| text-align: center; | |||
| background-color: #fff; | |||
| cursor: move; | |||
| .stretchBox { | |||
| position: absolute; | |||
| width: 10px; | |||
| height: 10px; | |||
| background-color: #fff; | |||
| } | |||
| .box1 { | |||
| top: -5px; | |||
| left: -5px; | |||
| cursor: nwse-resize; | |||
| } | |||
| .box2 { | |||
| top: -5px; | |||
| left: 50%; | |||
| transform: translateX(-50%); | |||
| cursor: ns-resize; | |||
| } | |||
| .box3 { | |||
| top: -5px; | |||
| left:100%; | |||
| transform: translateX(-5px); | |||
| cursor: nesw-resize; | |||
| } | |||
| .box4 { | |||
| top: 50%; | |||
| left: -5px; | |||
| transform: translateY(-5px); | |||
| cursor: ew-resize; | |||
| } | |||
| .box5 { | |||
| top: 50%; | |||
| left:100%; | |||
| transform: translate(-5px,-5px); | |||
| cursor: ew-resize; | |||
| } | |||
| .box6 { | |||
| top: 100%; | |||
| left: -5px; | |||
| transform: translateY(-5px); | |||
| cursor: nesw-resize; | |||
| } | |||
| .box7 { | |||
| top: 100%; | |||
| left: 50%; | |||
| transform: translate(-5px,-5px); | |||
| cursor: ns-resize; | |||
| } | |||
| .box8 { | |||
| top: 100%; | |||
| left: 100%; | |||
| transform: translate(-5px,-5px); | |||
| cursor: nwse-resize; | |||
| } | |||
| } | |||
| img { | |||
| // position: absolute; | |||
| // left: 0; | |||
| // top: 0; | |||
| position: absolute; | |||
| left: 0; | |||
| top: 0; | |||
| max-width: 100%; | |||
| max-height: 100%; | |||
| border-radius: 15px; | |||
| @@ -1,136 +1,92 @@ | |||
| <template> | |||
| <div class="page"> | |||
| <div class="canvasBox"> | |||
| <canvas id="myCanvas" width="960" height="540"> </canvas> | |||
| <img | |||
| style="display: none; width: 200px; height: 200px" | |||
| id="bjImg" | |||
| src="../../assets/img/star.png" | |||
| alt="" | |||
| /> | |||
| <div> | |||
| <img src="../../assets/img/trueImg.png" ref="image" /> | |||
| <div | |||
| class="box" | |||
| ref="box" | |||
| :style="{ top: boxPosition.top + 'px', left: boxPosition.left + 'px' }" | |||
| @mousedown="handleDrag" | |||
| > | |||
| <!-- 这里可以放置盒子的内容 --> | |||
| </div> | |||
| <div class="container"> | |||
| <div class="box red"></div> | |||
| <div class="box white"></div> | |||
| <div class="box green"></div> | |||
| <div class="resizable-box" v-if="isResizing" @mousedown="handleResize"> | |||
| 字幕 | |||
| </div> | |||
| <button @click="handleRotate">旋转</button> | |||
| </div> | |||
| </template> | |||
| <script setup> | |||
| //#region 导入 | |||
| import { fabric } from "fabric"; | |||
| import { ref, reactive } from "vue"; | |||
| //#endregion --------------------------- | |||
| const image = ref(null); | |||
| const box = ref(null); | |||
| const boxPosition = reactive({ top: 0, left: 0 }); | |||
| const isResizing = ref(false); | |||
| const rotationAngle = ref(0); | |||
| //#region 画布交互 | |||
| // let canvas = new fabric.Canvas("myCanvas"); | |||
| let canvas = null; | |||
| function setCanvasText() { | |||
| // let imgElement = document.getElementById("bjImg"); //声明我们的图片 | |||
| const myText = ref("hello world"); | |||
| const iText = new fabric.IText(myText.value, { | |||
| left: 100, | |||
| top: 100, | |||
| }); | |||
| canvas.add(iText); | |||
| iText.on("mouseup", function () { | |||
| console.log(iText, "iText"); | |||
| console.log(iText.angle.toFixed(0), "angle"); | |||
| console.log(iText.left.toFixed(0), "left"); | |||
| console.log(iText.top.toFixed(0), "top"); | |||
| console.log(iText.width.toFixed(0), "width"); | |||
| console.log(iText.height.toFixed(0), "height"); | |||
| }); | |||
| } | |||
| //#endregion --------------------------- | |||
| // 盒子位置拖动 | |||
| const handleDrag = (event) => { | |||
| event.preventDefault(); | |||
| const boxElement = box.value; | |||
| const imageElement = image.value; | |||
| onMounted(() => { | |||
| canvas = new fabric.Canvas("myCanvas"); | |||
| setCanvasText(); | |||
| // let imgInstance = new fabric.Image(imgElement, { | |||
| // //设置图片位置和样子 | |||
| // // left: 100, | |||
| // // top: 100, | |||
| // // width: 400, | |||
| // // height: 200, | |||
| // // angle: 30, //设置图形顺时针旋转角度 | |||
| // }); | |||
| // console.log(imgInstance); | |||
| // canvas.add(imgInstance); | |||
| }); | |||
| </script> | |||
| const offsetX = event.clientX - imageElement.offsetLeft; | |||
| const offsetY = event.clientY - imageElement.offsetTop; | |||
| <style lang="scss" scoped> | |||
| .page { | |||
| position: relative; | |||
| padding: 10px; | |||
| } | |||
| .canvasBox { | |||
| // position: absolute; | |||
| // top: 50%; | |||
| // left: 50%; | |||
| // transform: translate(-50%); | |||
| width: 100%; | |||
| height: 100%; | |||
| .canvas-container { | |||
| } | |||
| background-color: green; | |||
| .canvas { | |||
| background-color: skyblue; | |||
| .bjImg { | |||
| position: absolute; | |||
| top: 0; | |||
| left: 0; | |||
| width: 100px; | |||
| height: 100px; | |||
| z-index: 999; | |||
| img { | |||
| width: 100px; | |||
| height: 100px; | |||
| } | |||
| } | |||
| .people { | |||
| } | |||
| .other { | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| const onMouseMove = (event) => { | |||
| const newX = event.clientX - offsetX; | |||
| const newY = event.clientY - offsetY; | |||
| <style lang="scss" scoped> | |||
| .container { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| width: 80px; | |||
| height: 20px; | |||
| perspective: 500px; | |||
| transition: transform 0.5s; | |||
| } | |||
| boxPosition.left = newX; | |||
| boxPosition.top = newY; | |||
| }; | |||
| .box { | |||
| width: 20px; | |||
| height: 20px; | |||
| transition: transform 0.5s; | |||
| transform-style: preserve-3d; | |||
| } | |||
| const onMouseUp = () => { | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| .red { | |||
| background-color: red; | |||
| } | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| }; | |||
| .white { | |||
| background-color: white; | |||
| transform: translateZ(20px); | |||
| } | |||
| // 边框拖动 | |||
| const handleResize = () => { | |||
| isResizing.value = true; | |||
| const onMouseMove = (event) => { | |||
| // 处理拉伸逻辑 | |||
| }; | |||
| const onMouseUp = () => { | |||
| isResizing.value = false; | |||
| document.removeEventListener("mousemove", onMouseMove); | |||
| document.removeEventListener("mouseup", onMouseUp); | |||
| }; | |||
| .green { | |||
| background-color: green; | |||
| transform: translateZ(-20px); | |||
| document.addEventListener("mousemove", onMouseMove); | |||
| document.addEventListener("mouseup", onMouseUp); | |||
| }; | |||
| // 图片旋转 | |||
| const handleRotate = () => { | |||
| rotationAngle.value += 90; | |||
| }; | |||
| </script> | |||
| <style> | |||
| .box { | |||
| position: absolute; | |||
| width: 100px; | |||
| height: 100px; | |||
| background-color: rgba(0, 0, 0, 0.5); | |||
| cursor: move; | |||
| } | |||
| .container:hover .box { | |||
| transform: rotateY(180deg); | |||
| .resizable-box { | |||
| border: 2px dashed #000; | |||
| resize: both; | |||
| overflow: auto; | |||
| } | |||
| </style> | |||