index.js 27 KB

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