index.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983
  1. // index.js
  2. // 获取应用实例
  3. import {
  4. VueLikePage
  5. } from "../../utils/page";
  6. import {
  7. CDN_URL,
  8. API_BASE_URL,
  9. VIDEO_BASE_URL,
  10. app
  11. } from "../../config/index";
  12. VueLikePage([], {
  13. data: {
  14. cdn_url: "",
  15. baseUrl: API_BASE_URL + "/",
  16. url_link: "",
  17. id: "1",
  18. type: "",
  19. loadCompele: false,
  20. filePath: "",
  21. projectid: '',
  22. isEditing: false,
  23. info: {
  24. resourceImg: {},
  25. banner: {},
  26. sceneTitleImg: {},
  27. recordTitleImg: {},
  28. rescan: {},
  29. activeSceneBdImg: {},
  30. },
  31. isZoom: false,
  32. ipsImgList: [],
  33. selectedIp: null,
  34. selectedIpIndex: -1,
  35. ipScaleX: 1,
  36. ipScaleY: 1,
  37. ipRotate: 0,
  38. ipConfirmed: false,
  39. ipLeft: 0,
  40. ipTop: 0,
  41. positionInitialized: false,
  42. canvasWidth: 375,
  43. canvasHeight: 600,
  44. widgetVisible: true,
  45. zoomScrollLeft: 0,
  46. scaleOrientation: '',
  47. baseUniformScale: 1,
  48. confirmedIps: [],
  49. // 1:贴纸,2:标题,3:日期
  50. tabIndex: 1,
  51. rgb: 'rgba(13, 121, 217, 1)', //初始值
  52. rgbIndex: 1, //0,1,2,3
  53. pick: false,
  54. titleDatas: [],
  55. title: '',
  56. //日期
  57. pickerValue: [0, 0, 0], // 年、月、日的选中索引
  58. years: [], // 年份数组
  59. months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 月份数组
  60. days: [], // 日期数组(根据年月动态生成)
  61. selectedDates: [],
  62. date:''
  63. },
  64. methods: {
  65. selectConfirm(e){
  66. this.confirmIp(e)
  67. // 拿到item,从confirmedIps去掉当前item
  68. const selectedItem = e.currentTarget.dataset.item
  69. console.log(selectedItem)
  70. this.setData({
  71. confirmedIps:this.data.confirmedIps.filter(i=>i.id!==selectedItem.id),
  72. // 再重新还原到选中状态 设置
  73. selectedIp: selectedItem.selectedIp,
  74. selectedIpIndex: selectedItem.selectedIpIndex,
  75. tabIndex: selectedItem.typeIndex, // 判断是贴图还是标题还是日期
  76. rgb: selectedItem.rgb,
  77. imgUrl:selectedItem.typeIndex==1? selectedItem?.imgUrl:'',
  78. title: selectedItem.typeIndex==2?selectedItem?.title:'',
  79. date: selectedItem.typeIndex==3?selectedItem?.date:this.data.date,
  80. ipScaleX: selectedItem.scaleX,
  81. ipScaleY: selectedItem.scaleY,
  82. ipRotate: selectedItem.rotate,
  83. ipLeft: selectedItem.left,
  84. ipTop: selectedItem.top,
  85. positionInitialized:true,
  86. pickerValue:selectedItem.date.split('-').map(Number).map((v, i) => v - [2016, 1, 1][i])
  87. })
  88. console.log(this.data)
  89. },
  90. loadDate() {
  91. const now = new Date();
  92. const currentYear = now.getFullYear();
  93. const years = [];
  94. for (let i = currentYear - 10; i <= currentYear + 10; i++) {
  95. years.push(i);
  96. }
  97. this.setData({
  98. years
  99. });
  100. // 2. 初始化当前日期(默认选中今天)
  101. const currentMonth = now.getMonth() + 1; // 月份从0开始,需+1
  102. const currentDay = now.getDate();
  103. // 计算当前年/月/日在数组中的索引
  104. const yearIndex = years.indexOf(currentYear);
  105. const monthIndex = currentMonth - 1;
  106. // 初始化日期数组(根据当前年月)
  107. this.setDays(currentYear, currentMonth);
  108. // 设置默认选中值
  109. this.setData({
  110. pickerValue: [yearIndex, monthIndex, currentDay - 1],
  111. selectedDates:[ `${currentYear}-${this.formatNum(currentMonth)}-${this.formatNum(currentDay)}`],
  112. date:`${currentYear}-${this.formatNum(currentMonth)}-${this.formatNum(currentDay)}`
  113. });
  114. },
  115. // 格式化数字(补0,比如1→01)
  116. formatNum(num) {
  117. return num < 10 ? `0${num}` : num;
  118. },
  119. // 根据年、月动态生成日期数组(处理2月、小月/大月)
  120. setDays(year, month) {
  121. // 计算当月最后一天
  122. const lastDay = new Date(year, month, 0).getDate();
  123. const days = [];
  124. for (let i = 1; i <= lastDay; i++) {
  125. days.push(i);
  126. }
  127. this.setData({
  128. days
  129. });
  130. },
  131. // 日期选择器滚动变化时触发
  132. onDateChange(e) {
  133. // this.selectIp(e)
  134. const [yearIndex, monthIndex, dayIndex] = e.detail.value;
  135. const {
  136. years,
  137. months,
  138. days
  139. } = this.data;
  140. // 获取选中的年、月、日
  141. const selectedYear = years[yearIndex];
  142. const selectedMonth = months[monthIndex];
  143. const selectedDay = days[dayIndex];
  144. // 重新计算日期数组(防止切换年月后,日期超出当月范围,比如31号切到小月)
  145. this.setDays(selectedYear, selectedMonth);
  146. // 更新选中日期和picker值(日期索引可能变化,需重新校准)
  147. const newDayIndex = Math.min(dayIndex, this.data.days.length - 1);
  148. this.setData({
  149. pickerValue: [yearIndex, monthIndex, newDayIndex],
  150. selectedDates: [...this.data.selectedDates,`${selectedYear}-${this.formatNum(selectedMonth)}-${this.formatNum(this.data.days[newDayIndex])}`],
  151. date:`${selectedYear}-${this.formatNum(selectedMonth)}-${this.formatNum(this.data.days[newDayIndex])}`
  152. });
  153. console.log(this.data.pickerValue,'date')
  154. },
  155. // 输入时实时更新
  156. onTitleInput(e) {
  157. this.setData({
  158. title: e.detail.value
  159. })
  160. console.log(123, this.data.title)
  161. },
  162. saveTitle(e) {
  163. const selectedItem = e.currentTarget.dataset.item
  164. const currentTitle = this.data.title.trim()
  165. console.log(selectedItem)
  166. const handle = e.currentTarget.dataset.click
  167. // 防空判断
  168. if (!currentTitle&&handle) {
  169. wx.showToast({
  170. title: '请输入标题',
  171. icon: 'none'
  172. })
  173. return
  174. }else if(!currentTitle){
  175. return
  176. }
  177. // 追加
  178. this.setData({
  179. titleDatas: [...this.data.titleDatas, currentTitle],
  180. })
  181. // 添加标签
  182. this.selectIp(e)
  183. console.log(this.data.titleDatas)
  184. },
  185. // 显示取色器
  186. toPick: function () {
  187. this.setData({
  188. pick: !this.data.pick,
  189. rgbIndex:5
  190. })
  191. },
  192. //取色结果回调
  193. pickColor(e) {
  194. let rgb = e.detail.color;
  195. this.setData({
  196. rgb: rgb
  197. })
  198. },
  199. //设置颜色
  200. setColor(e) {
  201. let rgb = e.currentTarget.dataset.rgb;
  202. let index = e.currentTarget.dataset.index;
  203. console.log(rgb)
  204. this.setData({
  205. rgb: rgb,
  206. rgbIndex: index
  207. })
  208. },
  209. // 切换tab
  210. handleTabTap(e) {
  211. this.confirmIp(e)
  212. const index = e.currentTarget.dataset.index;
  213. console.log(index, '11111')
  214. this.setData({
  215. tabIndex: index
  216. }
  217. )
  218. },
  219. zoom() {
  220. this.setData({
  221. isZoom: !this.data.isZoom,
  222. selectedIp: null,
  223. selectedIpIndex: -1,
  224. ipScaleX: 1,
  225. ipScaleY: 1,
  226. ipRotate: 0,
  227. ipConfirmed: false,
  228. confirmedIps: [],
  229. zoomScrollLeft: 0
  230. });
  231. },
  232. onLoad: function (options) {
  233. let {
  234. rdw,
  235. id,
  236. type,
  237. projectid
  238. } = options;
  239. if (!projectid) {
  240. projectid = 'ZHS2409020-1';
  241. }
  242. this.initIpsList(projectid);
  243. this.getData(projectid);
  244. let link = "";
  245. if (type == "0") {
  246. link = `${VIDEO_BASE_URL}4dvedio/${rdw}.mp4`;
  247. } else {
  248. link = `${VIDEO_BASE_URL}4dpic/${rdw}.jpg`;
  249. // test
  250. // link = 'https://pic.616pic.com/phototwo/00/06/02/618e27a7290161785.jpg'
  251. this.loadDate()
  252. }
  253. this.setData({
  254. url_link: link,
  255. id,
  256. type,
  257. projectid,
  258. cdn_url: CDN_URL + "/" + projectid,
  259. });
  260. this.downloadF((data) => {
  261. this.setData({
  262. filePath: data,
  263. });
  264. });
  265. },
  266. initIpsList(projectid) {
  267. const list = [];
  268. for (let i = 1; i <= 34; i++) {
  269. list.push({
  270. name: String(i),
  271. imgUrl: `${CDN_URL}/${projectid}/ip/${i}.png`,
  272. });
  273. }
  274. this.setData({
  275. ipsImgList: list,
  276. });
  277. },
  278. loadcompele() {
  279. this.setData({
  280. loadCompele: true,
  281. });
  282. },
  283. cancel() {
  284. if (this.data.isEditing) {
  285. this.setData({
  286. isEditing: false,
  287. });
  288. } else {
  289. // wx.reLaunch({
  290. // url: "/pages/work/index",
  291. // });
  292. wx.navigateBack();
  293. }
  294. },
  295. edit() {
  296. this.setData({
  297. isEditing: !this.data.isEditing,
  298. });
  299. },
  300. _wrapText(ctx, text, maxWidth) {
  301. const chars = text.split('');
  302. const lines = [];
  303. let currentLine = '';
  304. for (let char of chars) {
  305. const testLine = currentLine + char;
  306. if (ctx.measureText(testLine).width <= maxWidth || currentLine === '') {
  307. currentLine = testLine;
  308. } else {
  309. lines.push(currentLine);
  310. currentLine = char;
  311. }
  312. }
  313. if (currentLine) lines.push(currentLine);
  314. return lines;
  315. },
  316. downloadF(cb = () => {}) {
  317. let link = this.data.url_link,
  318. m_type = "";
  319. if (this.data.type == "1") {
  320. m_type = "jpeg";
  321. } else {
  322. m_type = "video";
  323. }
  324. wx.downloadFile({
  325. url: link,
  326. success: (res) => {
  327. if (res.statusCode == "404") {
  328. return app.showAlert("作品暂未生成,请稍后再试", () => {
  329. wx.navigateBack();
  330. });
  331. }
  332. //判断是否为数组
  333. let typeType =
  334. Object.prototype.toString.call(res.header["Content-Type"]) ==
  335. "[object String]" ?
  336. res.header["Content-Type"] :
  337. res.header["Content-Type"][0];
  338. //判断不是xml文件
  339. if (typeType.indexOf(m_type) > -1) {
  340. cb(res.tempFilePath);
  341. }
  342. },
  343. fail: () => {
  344. app.showAlert("作品暂未生成,请稍后再试");
  345. },
  346. });
  347. this.setData({
  348. showModal: false,
  349. });
  350. },
  351. drawRoundRect(ctx, x, y, width, height, radius) {
  352. ctx.beginPath();
  353. // 左上圆角
  354. ctx.moveTo(x + radius, y);
  355. ctx.arcTo(x + width, y, x + width, y + height, radius);
  356. // 右上圆角
  357. ctx.arcTo(x + width, y + height, x, y + height, radius);
  358. // 右下圆角
  359. ctx.arcTo(x, y + height, x, y, radius);
  360. // 左下圆角
  361. ctx.arcTo(x, y, x + width, y, radius);
  362. ctx.closePath(); // 闭合路径
  363. },
  364. async generateImage() {
  365. wx.showLoading({
  366. title: '生成中...',
  367. mask: true
  368. });
  369. try {
  370. const query = wx.createSelectorQuery();
  371. query.select('.w_video').boundingClientRect();
  372. if (this.data.selectedIp) {
  373. query.select('.ip-overlay').boundingClientRect();
  374. }
  375. query.selectAll('.confirmed-overlay').boundingClientRect();
  376. const res = await new Promise(resolve => query.exec(resolve));
  377. const container = res[0];
  378. const overlay = this.data.selectedIp ? res[1] : null;
  379. const confirmedRects = this.data.selectedIp ? (res[2] || []) : (res[1] || []);
  380. if (!container) throw new Error('Cannot find container');
  381. let width = container.width;
  382. let height = container.height;
  383. if (this.data.isZoom) {
  384. const imgRes = await new Promise((resolve, reject) => {
  385. wx.getImageInfo({
  386. src: this.data.url_link,
  387. success: resolve,
  388. fail: reject
  389. })
  390. });
  391. width = imgRes.width;
  392. height = imgRes.height;
  393. }
  394. // Limit canvas size to avoid incomplete rendering on some devices
  395. const dpr = wx.getSystemInfoSync().pixelRatio;
  396. const maxCanvasSize = 4096;
  397. let scale = 1;
  398. if (width * dpr > maxCanvasSize || height * dpr > maxCanvasSize) {
  399. scale = Math.min(maxCanvasSize / (width * dpr), maxCanvasSize / (height * dpr));
  400. }
  401. const canvasWidth = width * scale;
  402. const canvasHeight = height * scale;
  403. await this._resetWidget(canvasWidth, canvasHeight);
  404. const widget = this.selectComponent('#widget');
  405. const mainUrl = this.data.url_link;
  406. let wxml = '';
  407. if (this.data.isZoom) {
  408. wxml = `
  409. <view class="container">
  410. <image class="main" src="${mainUrl}"></image>
  411. </view>
  412. `;
  413. } else {
  414. const bgUrl = this.data.info.resourceImg.bg ? (this.data.cdn_url + this.data.info.resourceImg.bg) : '';
  415. wxml = `
  416. <view class="container">
  417. <image class="bg" src="${bgUrl}"></image>
  418. ${(this.data.type != '0') ? `<image class="main" src="${mainUrl}"></image>` : ''}
  419. </view>
  420. `;
  421. }
  422. const style = {
  423. container: {
  424. width: canvasWidth,
  425. height: canvasHeight,
  426. position: 'relative',
  427. overflow: 'hidden'
  428. },
  429. bg: {
  430. width: canvasWidth,
  431. height: canvasHeight,
  432. position: 'absolute',
  433. left: 0,
  434. top: 0
  435. },
  436. main: {
  437. width: canvasWidth,
  438. height: canvasHeight,
  439. position: 'absolute',
  440. left: 0,
  441. top: 0
  442. }
  443. };
  444. const ctx = widget.ctx;
  445. const use2d = widget.data.use2dCanvas;
  446. const ratio = canvasWidth / container.width;
  447. await widget.renderToCanvas({
  448. wxml,
  449. style
  450. });
  451. // Draw confirmed overlays
  452. for (let i = 0; i < this.data.confirmedIps.length; i++) {
  453. const ov = this.data.confirmedIps[i];
  454. const rect = confirmedRects[i];
  455. if (!ov || !rect) continue;
  456. const cx = ((rect.left - container.left + rect.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio;
  457. const cy = (rect.top - container.top + rect.height / 2) * ratio;
  458. const overlayWidth = rect.width * ratio;
  459. const overlayHeight = rect.height * ratio;
  460. // 贴纸
  461. console.log(ov)
  462. if (ov.typeIndex == 1) {
  463. const stickerInfo = await new Promise((resolve, reject) => {
  464. wx.getImageInfo({
  465. src: ov.imgUrl,
  466. success: resolve,
  467. fail: reject
  468. })
  469. });
  470. let imgToDraw = stickerInfo.path;
  471. if (use2d) {
  472. const canvas = widget.canvas;
  473. const img = canvas.createImage();
  474. await new Promise((resolve, reject) => {
  475. img.onload = resolve;
  476. img.onerror = reject;
  477. img.src = stickerInfo.path;
  478. });
  479. imgToDraw = img;
  480. }
  481. ctx.save();
  482. ctx.translate(cx, cy);
  483. ctx.rotate(ov.rotate * Math.PI / 180);
  484. ctx.scale(ov.scaleX, ov.scaleY);
  485. ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight);
  486. ctx.restore();
  487. } else if (ov.typeIndex == 2 || ov.typeIndex == 3) {
  488. const text = ov.typeIndex == 2 ? ov.title.trim() : ov.date.trim();
  489. if (!text) continue;
  490. console.log(text)
  491. ctx.save();
  492. ctx.translate(cx, cy);
  493. ctx.rotate((ov.rotate || 0) * Math.PI / 180);
  494. ctx.scale(ov.scaleX || 1, ov.scaleY || 1);
  495. // ── 参数定义 ──
  496. const fontSizeRpx = 40;
  497. const fontSizePx = fontSizeRpx * ratio * (container.width / 750);
  498. const maxWidth = canvasWidth;
  499. const minWidth = 80 * ratio;
  500. const minHeight = 45 * ratio;
  501. const lineHeight = 30;
  502. const paddingRpx = 10;
  503. const padding = paddingRpx * ratio;
  504. const borderRadius = 40 * ratio;
  505. ctx.font = ` ${fontSizePx}px cexwz`;
  506. ctx.fillStyle = ov.rgb || '#000000';
  507. ctx.textAlign = 'center';
  508. ctx.textBaseline = 'middle';
  509. // 计算换行
  510. let lines = [text];
  511. let textWidth = ctx.measureText(text).width;
  512. if (textWidth > maxWidth) {
  513. lines = this._wrapText(ctx, text, maxWidth);
  514. textWidth = Math.max(...lines.map(line => ctx.measureText(line).width));
  515. }
  516. // 应用 min-width
  517. const contentWidth = Math.max(textWidth, minWidth);
  518. // 计算总内容高度(文字行高总和)
  519. const contentHeight = lines.length * lineHeight;
  520. // 最终容器高度(文字高度 + 上下 padding + min-height)
  521. const finalHeight = Math.max(contentHeight + padding, minHeight);
  522. console.log(minHeight)
  523. // 最终容器宽度(文字宽度 + 左右 padding)
  524. const finalWidth = contentWidth + padding * 2;
  525. // 绘制圆角矩形背景
  526. ctx.fillStyle = 'rgba(255, 255, 255, 0.70)';
  527. ctx.beginPath();
  528. this.drawRoundRect(
  529. ctx,
  530. -finalWidth / 2,
  531. -finalHeight / 2,
  532. finalWidth,
  533. finalHeight,
  534. 20 // 圆角半径
  535. );
  536. ctx.fill();
  537. // ── 绘制文字(在背景之上) ──
  538. ctx.fillStyle = ov.rgb || '#000000'; // 恢复文字颜色
  539. let yOffset = -contentHeight / 2 + lineHeight / 2; // 文字从容器中间开始
  540. for (const line of lines) {
  541. ctx.fillText(line, 0, yOffset);
  542. yOffset += lineHeight;
  543. }
  544. ctx.restore();
  545. }
  546. if (!use2d) {
  547. await new Promise(resolve => ctx.draw(true, resolve));
  548. }
  549. }
  550. // Draw active overlay if exists
  551. if (this.data.selectedIp && overlay) {
  552. const stickerUrl = this.data.selectedIp.imgUrl;
  553. const stickerInfo = await new Promise((resolve, reject) => {
  554. wx.getImageInfo({
  555. src: stickerUrl,
  556. success: resolve,
  557. fail: reject
  558. })
  559. });
  560. let imgToDraw = stickerInfo.path;
  561. if (use2d) {
  562. const canvas = widget.canvas;
  563. const img = canvas.createImage();
  564. await new Promise((resolve, reject) => {
  565. img.onload = resolve;
  566. img.onerror = reject;
  567. img.src = stickerInfo.path;
  568. });
  569. imgToDraw = img;
  570. }
  571. const cx = ((overlay.left - container.left + overlay.width / 2) + (this.data.isZoom ? this.data.zoomScrollLeft : 0)) * ratio;
  572. const cy = (overlay.top - container.top + overlay.height / 2) * ratio;
  573. const overlayWidth = overlay.width * ratio;
  574. const overlayHeight = overlay.height * ratio;
  575. ctx.save();
  576. ctx.translate(cx, cy);
  577. ctx.rotate(this.data.ipRotate * Math.PI / 180);
  578. ctx.scale(this.data.ipScaleX, this.data.ipScaleY);
  579. ctx.drawImage(imgToDraw, -overlayWidth / 2, -overlayHeight / 2, overlayWidth, overlayHeight);
  580. ctx.restore();
  581. if (!use2d) {
  582. await new Promise(resolve => ctx.draw(true, resolve));
  583. }
  584. }
  585. const {
  586. tempFilePath
  587. } = await widget.canvasToTempFilePath();
  588. // Save
  589. wx.saveImageToPhotosAlbum({
  590. filePath: tempFilePath,
  591. success: () => {
  592. wx.showModal({
  593. title: "提示",
  594. content: "已保存到相册,快去分享吧",
  595. showCancel: false,
  596. });
  597. },
  598. fail: (e) => {
  599. if (!(e.errMsg.indexOf("cancel") > -1)) {
  600. wx.showModal({
  601. title: "提示",
  602. content: "保存失败,请检查是否开启相册保存权限",
  603. showCancel: false,
  604. });
  605. }
  606. }
  607. });
  608. } catch (e) {
  609. console.error(e);
  610. wx.showToast({
  611. title: '生成失败',
  612. icon: 'none'
  613. });
  614. } finally {
  615. wx.hideLoading();
  616. }
  617. },
  618. async _resetWidget(width, height) {
  619. this.setData({
  620. widgetVisible: false
  621. });
  622. await new Promise(r => setTimeout(r, 50));
  623. this.setData({
  624. canvasWidth: width,
  625. canvasHeight: height,
  626. widgetVisible: true
  627. });
  628. await new Promise(r => setTimeout(r, 120));
  629. },
  630. onZoomScroll(e) {
  631. this.setData({
  632. zoomScrollLeft: e.detail.scrollLeft || 0
  633. });
  634. },
  635. saveAlbum() {
  636. let type = this.data.type;
  637. if (this.data.projectid == 'ZHS2409020-1' && type !== '0') {
  638. if (this.data.selectedIp && !this.data.ipConfirmed) {
  639. wx.showToast({
  640. title: '请先确认标签',
  641. icon: 'none'
  642. })
  643. return;
  644. }
  645. this.generateImage();
  646. return;
  647. }
  648. wx.showLoading({
  649. title: "保存中…",
  650. mask: true,
  651. });
  652. if (this.data.filePath) {
  653. let api =
  654. type == "0" ? "saveVideoToPhotosAlbum" : "saveImageToPhotosAlbum";
  655. wx[api]({
  656. filePath: this.data.filePath,
  657. success() {
  658. wx.showModal({
  659. title: "提示",
  660. content: "已保存到相册,快去分享吧",
  661. showCancel: false,
  662. });
  663. },
  664. fail: (e) => {
  665. if (!(e.errMsg.indexOf("cancel") > -1)) {
  666. wx.showModal({
  667. title: "提示",
  668. content: "保存失败,请检查是否开启相册保存权限,可在「右上角」 - 「设置」里查看",
  669. showCancel: false,
  670. });
  671. }
  672. },
  673. complete: () => {
  674. wx.hideLoading();
  675. },
  676. });
  677. }
  678. },
  679. // 横琴是ZHS2409020-1,替换 ZHS2305758-1
  680. getData(prjId = "ZHS2305758-1") {
  681. wx.showLoading({
  682. title: "资源加载中",
  683. });
  684. this.setData({
  685. cdn_url: CDN_URL + "/" + prjId,
  686. });
  687. wx.request({
  688. url: `${VIDEO_BASE_URL}project/4dage-sxb/${prjId}/config.json`,
  689. success: ({
  690. data: {
  691. title,
  692. ...rest
  693. }
  694. }) => {
  695. this.setData({
  696. info: rest,
  697. },
  698. () => {
  699. wx.hideLoading();
  700. }
  701. );
  702. wx.setNavigationBarTitle({
  703. title: title,
  704. });
  705. },
  706. });
  707. },
  708. selectIp(e) {
  709. const index = e.currentTarget.dataset.index ;
  710. const item = this.data.ipsImgList[index];
  711. // 这里日期和标题选择的index都没用,但是也需要传,相当于限制了添加上限为ips数组长度
  712. if (!item) return;
  713. if (this.data.selectedIp && !this.data.ipConfirmed) {
  714. this.setData({
  715. selectedIpIndex: index,
  716. selectedIp: item
  717. });
  718. } else {
  719. this.setData({
  720. selectedIpIndex: index,
  721. selectedIp: item,
  722. ipScaleX: 1,
  723. ipScaleY: 1,
  724. ipRotate: 0,
  725. ipConfirmed: false,
  726. positionInitialized: false,
  727. }, () => {
  728. this.getOverlayRect();
  729. const query = wx.createSelectorQuery();
  730. query.select('.w_video').boundingClientRect();
  731. query.select('.ip-overlay').boundingClientRect();
  732. query.exec((res) => {
  733. const container = res[0];
  734. const overlay = res[1];
  735. if (container && overlay) {
  736. this.setData({
  737. ipLeft: overlay.left - container.left,
  738. ipTop: overlay.top - container.top,
  739. positionInitialized: true
  740. });
  741. }
  742. });
  743. });
  744. }
  745. // 选中即确定
  746. this.confirmIp(e)
  747. },
  748. dragStart(e) {
  749. if (this.data.ipConfirmed) return;
  750. const touch = e.touches[0];
  751. this.setData({
  752. dragStartX: touch.clientX,
  753. dragStartY: touch.clientY,
  754. startIpLeft: this.data.ipLeft,
  755. startIpTop: this.data.ipTop
  756. });
  757. },
  758. dragMove(e) {
  759. if (this.data.ipConfirmed) return;
  760. const touch = e.touches[0];
  761. const dx = touch.clientX - this.data.dragStartX;
  762. const dy = touch.clientY - this.data.dragStartY;
  763. this.setData({
  764. ipLeft: this.data.startIpLeft + dx,
  765. ipTop: this.data.startIpTop + dy
  766. });
  767. },
  768. getOverlayRect() {
  769. const query = wx.createSelectorQuery();
  770. query.select('.ip-overlay').boundingClientRect(rect => {
  771. if (rect) {
  772. this.setData({
  773. centerX: rect.left + rect.width / 2,
  774. centerY: rect.top + rect.height / 2
  775. });
  776. }
  777. }).exec();
  778. },
  779. rotateStart(e) {
  780. this.getOverlayRect();
  781. const touch = e.touches[0];
  782. const dx = touch.clientX - this.data.centerX;
  783. const dy = touch.clientY - this.data.centerY;
  784. const startAngle = Math.atan2(dy, dx) * 180 / Math.PI;
  785. this.setData({
  786. startRotateAngle: startAngle,
  787. baseIpRotate: this.data.ipRotate
  788. });
  789. },
  790. rotateMove(e) {
  791. const touch = e.touches[0];
  792. const dx = touch.clientX - this.data.centerX;
  793. const dy = touch.clientY - this.data.centerY;
  794. const currentAngle = Math.atan2(dy, dx) * 180 / Math.PI;
  795. const diff = currentAngle - this.data.startRotateAngle;
  796. let nextRotate = this.data.baseIpRotate + diff;
  797. this.setData({
  798. ipRotate: nextRotate
  799. });
  800. },
  801. scaleStart(e) {
  802. const touch = e.touches[0];
  803. this.setData({
  804. startX: touch.clientX,
  805. startY: touch.clientY,
  806. baseUniformScale: (this.data.ipScaleX + this.data.ipScaleY) / 2
  807. });
  808. },
  809. scaleMove(e) {
  810. const touch = e.touches[0];
  811. const dy = touch.clientY - this.data.startY;
  812. const factor = 0.005;
  813. let nextScale = this.data.baseUniformScale + (-dy) * factor;
  814. if (nextScale < 0.2) nextScale = 0.2;
  815. if (nextScale > 4) nextScale = 4;
  816. this.setData({
  817. ipScaleX: nextScale,
  818. ipScaleY: nextScale
  819. });
  820. },
  821. rotateIp() {
  822. // 兼容旧的点击事件,如果不需要可以删除,但保留也不会出错
  823. if (!this.data.selectedIp) return;
  824. const nextRotate = (this.data.ipRotate + 15) % 360;
  825. this.setData({
  826. ipRotate: nextRotate,
  827. });
  828. },
  829. scaleIp() {
  830. if (!this.data.selectedIp) return;
  831. let nextScaleX = this.data.ipScaleX + 0.25;
  832. let nextScaleY = this.data.ipScaleY + 0.25;
  833. if (nextScaleX > 2.5 || nextScaleY > 2.5) {
  834. nextScaleX = 1;
  835. nextScaleY = 1;
  836. }
  837. this.setData({
  838. ipScaleX: nextScaleX,
  839. ipScaleY: nextScaleY,
  840. });
  841. },
  842. deleteIp() {
  843. this.setData({
  844. selectedIp: null,
  845. selectedIpIndex: -1,
  846. ipScaleX: 1,
  847. ipScaleY: 1,
  848. ipRotate: 0,
  849. ipConfirmed: false,
  850. });
  851. },
  852. confirmIp(e) {
  853. const selectedItem2 = e.currentTarget.dataset.item
  854. console.log(this.data.tabIndex=='2')
  855. // 为了修改内容后保存能生效,需要重新设置追加最新title到titleDatas
  856. if(this.data.tabIndex=='2'){
  857. this.setData({
  858. titleDatas: [...this.data.titleDatas, this.data.title],
  859. })
  860. }
  861. if (!this.data.selectedIp) return;
  862. const overlayItem = {
  863. typeIndex: this.data.tabIndex, // 判断是贴图还是标题还是日期
  864. id: Date.now(),
  865. rgb: this.data.rgb,
  866. imgUrl: this.data.selectedIp.imgUrl,
  867. title: this.data.titleDatas[this.data.titleDatas.length - 1],
  868. date: this.data.date,
  869. scaleX: this.data.ipScaleX,
  870. scaleY: this.data.ipScaleY,
  871. rotate: this.data.ipRotate,
  872. left: this.data.ipLeft,
  873. top: this.data.ipTop,
  874. selectedIp:this.data.selectedIp,
  875. selectedIpIndex:this.data.selectedIpIndex
  876. };
  877. this.setData({
  878. confirmedIps: [...this.data.confirmedIps, overlayItem],
  879. selectedIp: null,
  880. selectedIpIndex: -1,
  881. ipScaleX: 1,
  882. ipScaleY: 1,
  883. ipRotate: 0,
  884. ipConfirmed: false,
  885. positionInitialized: false,
  886. title:''
  887. });
  888. },
  889. },
  890. });