// index.js // 获取应用实例 import { VueLikePage } from "../../utils/page"; import { CDN_URL, API_BASE_URL, VIDEO_BASE_URL, app } from "../../config/index"; VueLikePage([], { data: { cdn_url: "", baseUrl: API_BASE_URL + "/", url_link: "", id: "1", type: "", loadCompele: false, filePath: "", projectid: '', isEditing: false, info: { resourceImg: {}, banner: {}, sceneTitleImg: {}, recordTitleImg: {}, rescan: {}, activeSceneBdImg: {}, }, isZoom: false, ipsImgList: [], selectedIp: null, selectedIpIndex: -1, ipScaleX: 1, ipScaleY: 1, ipRotate: 0, ipConfirmed: false, ipLeft: 0, ipTop: 0, positionInitialized: false, canvasWidth: 375, canvasHeight: 600, widgetVisible: true, zoomScrollLeft: 0, scaleOrientation: '', baseUniformScale: 1, confirmedIps: [] }, methods: { zoom() { this.setData({ isZoom: !this.data.isZoom, selectedIp: null, selectedIpIndex: -1, ipScaleX: 1, ipScaleY: 1, ipRotate: 0, ipConfirmed: false, confirmedIps: [], zoomScrollLeft: 0 }); }, onLoad: function (options) { let { rdw, id, type, projectid } = options; if(!projectid){ projectid = 'ZHS2409020-1'; } this.initIpsList(projectid); this.getData(projectid); let link = ""; if (type == "0") { link = `${VIDEO_BASE_URL}4dvedio/${rdw}.mp4`; } else { link = `${VIDEO_BASE_URL}4dpic/${rdw}.jpg`; // link='https://4dkk.4dage.com/fusion/test/file/797098a37e0c4588ae88f2ec4b1f12df.png' } this.setData({ url_link: link, id, type, projectid, cdn_url: CDN_URL + "/" + projectid, }); this.downloadF((data) => { this.setData({ filePath: data, }); }); }, initIpsList(projectid) { const list = []; for (let i = 1; i <= 34; i++) { list.push({ name: String(i), imgUrl: `${CDN_URL}/${projectid}/ip/${i}.png`, }); } this.setData({ ipsImgList: list, }); }, loadcompele() { this.setData({ loadCompele: true, }); }, cancel() { // wx.reLaunch({ // url: "/pages/work/index", // }); wx.navigateBack(); }, edit() { this.setData({ isEditing: !this.data.isEditing, }); }, downloadF(cb = () => {}) { let link = this.data.url_link, m_type = ""; if (this.data.type == "1") { m_type = "jpeg"; } else { m_type = "video"; } wx.downloadFile({ url: link, success: (res) => { if (res.statusCode == "404") { return app.showAlert("作品暂未生成,请稍后再试", () => { wx.navigateBack(); }); } //判断是否为数组 let typeType = Object.prototype.toString.call(res.header["Content-Type"]) == "[object String]" ? res.header["Content-Type"] : res.header["Content-Type"][0]; //判断不是xml文件 if (typeType.indexOf(m_type) > -1) { cb(res.tempFilePath); } }, fail: () => { app.showAlert("作品暂未生成,请稍后再试"); }, }); this.setData({ showModal: false, }); }, async generateImage() { wx.showLoading({ title: '生成中...', mask: true }); try { const query = wx.createSelectorQuery(); query.select('.w_video').boundingClientRect(); if (this.data.selectedIp) { query.select('.ip-overlay').boundingClientRect(); } query.selectAll('.confirmed-overlay').boundingClientRect(); const res = await new Promise(resolve => query.exec(resolve)); const container = res[0]; const overlay = this.data.selectedIp ? res[1] : null; const confirmedRects = this.data.selectedIp ? (res[2] || []) : (res[1] || []); if (!container) throw new Error('Cannot find container'); let width = container.width; let height = container.height; if (this.data.isZoom) { const imgRes = await new Promise((resolve, reject) => { wx.getImageInfo({ src: this.data.url_link, success: resolve, fail: reject }) }); width = imgRes.width; height = imgRes.height; } // Limit canvas size to avoid incomplete rendering on some devices const dpr = wx.getSystemInfoSync().pixelRatio; const maxCanvasSize = 4096; let scale = 1; if (width * dpr > maxCanvasSize || height * dpr > maxCanvasSize) { scale = Math.min(maxCanvasSize / (width * dpr), maxCanvasSize / (height * dpr)); } const canvasWidth = width * scale; const canvasHeight = height * scale; await this._resetWidget(canvasWidth, canvasHeight); const widget = this.selectComponent('#widget'); const mainUrl = this.data.url_link; let wxml = ''; if (this.data.isZoom) { wxml = ` `; } else { const bgUrl = this.data.info.resourceImg.bg ? (this.data.cdn_url + this.data.info.resourceImg.bg) : ''; wxml = ` ${(this.data.type != '0') ? `` : ''} `; } const style = { container: { width: canvasWidth, height: canvasHeight, position: 'relative', overflow: 'hidden' }, bg: { width: canvasWidth, height: canvasHeight, position: 'absolute', left: 0, top: 0 }, main: { width: canvasWidth, height: canvasHeight, position: 'absolute', left: 0, top: 0 } }; await widget.renderToCanvas({ wxml, style }); const ctx = widget.ctx; const use2d = widget.data.use2dCanvas; const ratio = canvasWidth / container.width; // Draw confirmed overlays for (let i = 0; i < this.data.confirmedIps.length; i++) { const ov = this.data.confirmedIps[i]; const rect = confirmedRects[i]; if (!ov || !rect) continue; const stickerInfo = await new Promise((resolve, reject) => { wx.getImageInfo({ src: ov.imgUrl, success: resolve, fail: reject }) }); let imgToDraw = stickerInfo.path; if (use2d) { const canvas = widget.canvas; const img = canvas.createImage(); await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = stickerInfo.path; }); imgToDraw = img; } const cx = ((rect.left - container.left + rect.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio; const cy = (rect.top - container.top + rect.height / 2) * ratio; const overlayWidth = rect.width * ratio; const overlayHeight = rect.height * ratio; ctx.save(); ctx.translate(cx, cy); ctx.rotate(ov.rotate * Math.PI / 180); ctx.scale(ov.scaleX, ov.scaleY); ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight); ctx.restore(); if (!use2d) { await new Promise(resolve => ctx.draw(true, resolve)); } } // Draw active overlay if exists if (this.data.selectedIp && overlay) { const stickerUrl = this.data.selectedIp.imgUrl; const stickerInfo = await new Promise((resolve, reject) => { wx.getImageInfo({ src: stickerUrl, success: resolve, fail: reject }) }); let imgToDraw = stickerInfo.path; if (use2d) { const canvas = widget.canvas; const img = canvas.createImage(); await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = stickerInfo.path; }); imgToDraw = img; } const cx = ((overlay.left - container.left + overlay.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio; const cy = (overlay.top - container.top + overlay.height / 2) * ratio; const overlayWidth = overlay.width * ratio; const overlayHeight = overlay.height * ratio; ctx.save(); ctx.translate(cx, cy); ctx.rotate(this.data.ipRotate * Math.PI / 180); ctx.scale(this.data.ipScaleX, this.data.ipScaleY); ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight); ctx.restore(); if (!use2d) { await new Promise(resolve => ctx.draw(true, resolve)); } } const { tempFilePath } = await widget.canvasToTempFilePath(); // Save wx.saveImageToPhotosAlbum({ filePath: tempFilePath, success: () => { wx.showModal({ title: "提示", content: "已保存到相册,快去分享吧", showCancel: false, }); }, fail: (e) => { if (!(e.errMsg.indexOf("cancel") > -1)) { wx.showModal({ title: "提示", content: "保存失败,请检查是否开启相册保存权限", showCancel: false, }); } } }); } catch (e) { console.error(e); wx.showToast({ title: '生成失败', icon: 'none' }); } finally { wx.hideLoading(); } }, async _resetWidget(width, height) { this.setData({ widgetVisible: false }); await new Promise(r => setTimeout(r, 50)); this.setData({ canvasWidth: width, canvasHeight: height, widgetVisible: true }); await new Promise(r => setTimeout(r, 120)); }, onZoomScroll(e) { this.setData({ zoomScrollLeft: e.detail.scrollLeft || 0 }); }, saveAlbum() { let type = this.data.type; if (this.data.projectid == 'ZHS2409020-1' && type !== '0') { if (this.data.selectedIp && !this.data.ipConfirmed) { wx.showToast({ title: '请先确认标签', icon: 'none' }) return; } this.generateImage(); return; } wx.showLoading({ title: "保存中…", mask: true, }); if (this.data.filePath) { let api = type == "0" ? "saveVideoToPhotosAlbum" : "saveImageToPhotosAlbum"; wx[api]({ filePath: this.data.filePath, success() { wx.showModal({ title: "提示", content: "已保存到相册,快去分享吧", showCancel: false, }); }, fail: (e) => { if (!(e.errMsg.indexOf("cancel") > -1)) { wx.showModal({ title: "提示", content: "保存失败,请检查是否开启相册保存权限,可在「右上角」 - 「设置」里查看", showCancel: false, }); } }, complete: () => { wx.hideLoading(); }, }); } }, // 横琴是ZHS2409020-1,替换 ZHS2305758-1 getData(prjId = "ZHS2305758-1") { wx.showLoading({ title: "资源加载中", }); this.setData({ cdn_url: CDN_URL + "/" + prjId, }); wx.request({ url: `${VIDEO_BASE_URL}project/4dage-sxb/${prjId}/config.json`, success: ({ data: { title, ...rest } }) => { this.setData( { info: rest, }, () => { wx.hideLoading(); } ); wx.setNavigationBarTitle({ title: title, }); }, }); }, selectIp(e) { const index = e.currentTarget.dataset.index; const item = this.data.ipsImgList[index]; if (!item) return; if (this.data.selectedIp && !this.data.ipConfirmed) { this.setData({ selectedIpIndex: index, selectedIp: item }); } else { this.setData({ selectedIpIndex: index, selectedIp: item, ipScaleX: 1, ipScaleY: 1, ipRotate: 0, ipConfirmed: false, positionInitialized: false, }, () => { this.getOverlayRect(); const query = wx.createSelectorQuery(); query.select('.w_video').boundingClientRect(); query.select('.ip-overlay').boundingClientRect(); query.exec((res) => { const container = res[0]; const overlay = res[1]; if (container && overlay) { this.setData({ ipLeft: overlay.left - container.left, ipTop: overlay.top - container.top, positionInitialized: true }); } }); }); } }, dragStart(e) { if (this.data.ipConfirmed) return; const touch = e.touches[0]; this.setData({ dragStartX: touch.clientX, dragStartY: touch.clientY, startIpLeft: this.data.ipLeft, startIpTop: this.data.ipTop }); }, dragMove(e) { if (this.data.ipConfirmed) return; const touch = e.touches[0]; const dx = touch.clientX - this.data.dragStartX; const dy = touch.clientY - this.data.dragStartY; this.setData({ ipLeft: this.data.startIpLeft + dx, ipTop: this.data.startIpTop + dy }); }, getOverlayRect() { const query = wx.createSelectorQuery(); query.select('.ip-overlay').boundingClientRect(rect => { if (rect) { this.setData({ centerX: rect.left + rect.width / 2, centerY: rect.top + rect.height / 2 }); } }).exec(); }, rotateStart(e) { this.getOverlayRect(); const touch = e.touches[0]; const dx = touch.clientX - this.data.centerX; const dy = touch.clientY - this.data.centerY; const startAngle = Math.atan2(dy, dx) * 180 / Math.PI; this.setData({ startRotateAngle: startAngle, baseIpRotate: this.data.ipRotate }); }, rotateMove(e) { const touch = e.touches[0]; const dx = touch.clientX - this.data.centerX; const dy = touch.clientY - this.data.centerY; const currentAngle = Math.atan2(dy, dx) * 180 / Math.PI; const diff = currentAngle - this.data.startRotateAngle; let nextRotate = this.data.baseIpRotate + diff; this.setData({ ipRotate: nextRotate }); }, scaleStart(e) { const touch = e.touches[0]; this.setData({ startX: touch.clientX, startY: touch.clientY, baseUniformScale: (this.data.ipScaleX + this.data.ipScaleY) / 2 }); }, scaleMove(e) { const touch = e.touches[0]; const dy = touch.clientY - this.data.startY; const factor = 0.005; let nextScale = this.data.baseUniformScale + (-dy) * factor; if (nextScale < 0.2) nextScale = 0.2; if (nextScale > 4) nextScale = 4; this.setData({ ipScaleX: nextScale, ipScaleY: nextScale }); }, rotateIp() { // 兼容旧的点击事件,如果不需要可以删除,但保留也不会出错 if (!this.data.selectedIp) return; const nextRotate = (this.data.ipRotate + 15) % 360; this.setData({ ipRotate: nextRotate, }); }, scaleIp() { if (!this.data.selectedIp) return; let nextScaleX = this.data.ipScaleX + 0.25; let nextScaleY = this.data.ipScaleY + 0.25; if (nextScaleX > 2.5 || nextScaleY > 2.5) { nextScaleX = 1; nextScaleY = 1; } this.setData({ ipScaleX: nextScaleX, ipScaleY: nextScaleY, }); }, deleteIp() { this.setData({ selectedIp: null, selectedIpIndex: -1, ipScaleX: 1, ipScaleY: 1, ipRotate: 0, ipConfirmed: false, }); }, confirmIp() { if (!this.data.selectedIp) return; const overlayItem = { id: Date.now(), imgUrl: this.data.selectedIp.imgUrl, scaleX: this.data.ipScaleX, scaleY: this.data.ipScaleY, rotate: this.data.ipRotate, left: this.data.ipLeft, top: this.data.ipTop }; this.setData({ confirmedIps: [...this.data.confirmedIps, overlayItem], selectedIp: null, selectedIpIndex: -1, ipScaleX: 1, ipScaleY: 1, ipRotate: 0, ipConfirmed: false, positionInitialized: false }); }, }, });