tousujianyi.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. const app = getApp();
  2. Page({
  3. data: {
  4. contact: '',
  5. phone: '',
  6. description: '',
  7. imageList: [],
  8. showNotification: true,
  9. countDown: 3,
  10. isFormValid: false,
  11. category: '投诉',
  12. categoryValue: '1',
  13. isPreviewMode: false,
  14. title: '',
  15. content: '',
  16. replyTime: '',
  17. replyContent: '',
  18. formSubmitted: false,
  19. isSubmitting: false,
  20. lastSubmitTime: 0,
  21. categoryMap: [
  22. {name: '投诉', value: '1'},
  23. {name: '建议', value: '2'}
  24. ],
  25. },
  26. onLoad: function (options) {
  27. this.startCountDown();
  28. // 检查是否是预览模式
  29. if (options.mode === 'preview') {
  30. const id = options.id;
  31. // 获取数据
  32. this.getDataById(id);
  33. // 设置为预览模式
  34. this.setData({
  35. isPreviewMode: true
  36. });
  37. } else {
  38. // 非预览模式才检查表单有效性
  39. this.checkFormValidity();
  40. }
  41. },
  42. onShow: function () {
  43. // 检查是否是从成功页面返回
  44. if (this.data.formSubmitted) {
  45. // 重置表单数据
  46. this.resetForm();
  47. // 重置提交状态标记
  48. this.setData({
  49. formSubmitted: false
  50. });
  51. }
  52. },
  53. startCountDown: function () {
  54. let that = this;
  55. let timer = setInterval(function () {
  56. if (that.data.countDown > 0) {
  57. that.setData({
  58. countDown: that.data.countDown - 1
  59. });
  60. } else {
  61. clearInterval(timer);
  62. }
  63. }, 1000);
  64. },
  65. goBack: function () {
  66. wx.navigateBack();
  67. },
  68. radioChange: function (e) {
  69. const value = e.detail.value;
  70. const selectedCategory = this.data.categoryMap.find(item => item.value === value || item.name === value);
  71. this.setData({
  72. category: selectedCategory.name,
  73. categoryValue: selectedCategory.value
  74. });
  75. this.checkFormValidity();
  76. },
  77. inputContact: function (e) {
  78. this.setData({
  79. contact: e.detail.value
  80. });
  81. this.checkFormValidity();
  82. },
  83. inputPhone: function (e) {
  84. const value = e.detail.value;
  85. const phoneNumber = value.replace(/\D/g, '');
  86. this.setData({
  87. phone: phoneNumber
  88. });
  89. this.checkFormValidity();
  90. },
  91. validatePhone: function (phone) {
  92. const phoneReg = /^1[3-9]\d{9}$/;
  93. return phoneReg.test(phone);
  94. },
  95. inputDescription: function (e) {
  96. this.setData({
  97. description: e.detail.value
  98. });
  99. this.checkFormValidity();
  100. },
  101. chooseImage: function () {
  102. let that = this;
  103. if (that.data.imageList.length >= 10) {
  104. wx.showToast({
  105. title: '最多只能上传10张图片',
  106. icon: 'none'
  107. });
  108. return;
  109. }
  110. wx.chooseMedia({
  111. count: 10 - that.data.imageList.length,
  112. mediaType: ['image'],
  113. sourceType: ['album', 'camera'],
  114. sizeType: ['compressed'],
  115. success: function (res) {
  116. let tempFiles = res.tempFiles;
  117. let validFiles = [];
  118. for (let i = 0; i < tempFiles.length; i++) {
  119. const file = tempFiles[i];
  120. if (file.size <= 10 * 1024 * 1024) {
  121. // 为每个文件添加唯一标识
  122. validFiles.push({
  123. ...file,
  124. uid: `${Date.now()}_${Math.random().toString(36).substr(2, 9)}_${i}`
  125. });
  126. } else {
  127. wx.showToast({
  128. title: '图片大小不能超过10M',
  129. icon: 'none'
  130. });
  131. }
  132. }
  133. if (validFiles.length > 0) {
  134. wx.showLoading({
  135. title: '图片处理中...',
  136. mask: true
  137. });
  138. // 创建副本保存待处理的图片列表
  139. const filesToProcess = [...validFiles];
  140. // 使用Map结构存储结果,确保不会丢失
  141. let processedFilesMap = new Map();
  142. let processCount = 0;
  143. // 获取当前已有图片的路径列表,用于去重检查
  144. const existingPaths = that.data.imageList.map(img =>
  145. typeof img === 'string' ? img : img.tempFilePath
  146. );
  147. // 处理每个图片
  148. filesToProcess.forEach((file) => {
  149. // 检查是否已经存在相同路径的图片
  150. if (existingPaths.includes(file.tempFilePath)) {
  151. console.log("跳过重复图片:", file.tempFilePath);
  152. processCount++;
  153. // 检查是否所有图片都已处理完成
  154. if (processCount === filesToProcess.length) {
  155. finishProcessing();
  156. }
  157. return;
  158. }
  159. that.compressImage(file.tempFilePath).then(compressedPath => {
  160. processCount++;
  161. // 如果返回路径与原路径不同,则认为已成功压缩
  162. if (compressedPath !== file.tempFilePath) {
  163. // 使用Map结构通过uid存储处理结果,保证一一对应
  164. processedFilesMap.set(file.uid, {
  165. tempFilePath: compressedPath,
  166. size: file.size, // 这里使用原始大小,实际上压缩后大小会变小
  167. uid: file.uid,
  168. originalPath: file.tempFilePath // 保存原始路径以便追踪
  169. });
  170. } else {
  171. // 如果路径没变,可能是压缩失败或无需压缩的小图片
  172. processedFilesMap.set(file.uid, {
  173. tempFilePath: compressedPath,
  174. size: file.size,
  175. uid: file.uid
  176. });
  177. }
  178. // 检查是否所有图片都已处理完成
  179. if (processCount === filesToProcess.length) {
  180. finishProcessing();
  181. }
  182. }).catch(err => {
  183. console.error('图片压缩失败:', err);
  184. processCount++;
  185. // 即使压缩失败,也将原图添加到结果中
  186. processedFilesMap.set(file.uid, {
  187. tempFilePath: file.tempFilePath,
  188. size: file.size,
  189. uid: file.uid
  190. });
  191. if (processCount === filesToProcess.length) {
  192. finishProcessing();
  193. }
  194. });
  195. });
  196. // 统一处理添加图片到列表的逻辑
  197. function finishProcessing() {
  198. // 将Map转换为数组,保持原有顺序
  199. let processedFiles = filesToProcess.map(file => processedFilesMap.get(file.uid)).filter(Boolean);
  200. // 再次检查去重,防止压缩后的图片路径与已有图片重复
  201. let currentImagePaths = that.data.imageList.map(img =>
  202. typeof img === 'string' ? img : img.tempFilePath
  203. );
  204. let newFiles = processedFiles.filter(file =>
  205. !currentImagePaths.includes(file.tempFilePath)
  206. );
  207. console.log(`添加 ${newFiles.length} 张新图片,跳过 ${processedFiles.length - newFiles.length} 张重复图片`);
  208. if (newFiles.length > 0) {
  209. let newImageList = that.data.imageList.concat(newFiles);
  210. that.setData({
  211. imageList: newImageList
  212. });
  213. }
  214. wx.hideLoading();
  215. }
  216. }
  217. }
  218. });
  219. },
  220. // 使用canvas进行图片压缩
  221. compressImage: function(imagePath) {
  222. return new Promise((resolve, reject) => {
  223. // 对于已经压缩过的图片(通常路径中会包含"compressed"),直接返回
  224. if (imagePath.indexOf("compressed") > -1) {
  225. console.log("图片已压缩,直接使用:", imagePath);
  226. resolve(imagePath);
  227. return;
  228. }
  229. // 获取图片信息
  230. wx.getImageInfo({
  231. src: imagePath,
  232. success: (res) => {
  233. // 先获取原图大小,单位KB
  234. wx.getFileInfo({
  235. filePath: imagePath,
  236. success: (fileInfo) => {
  237. const originalSize = fileInfo.size / 1024; // 原始大小,单位KB
  238. // 如果原图已经很小(小于300KB),可以直接使用
  239. if (originalSize < 300) {
  240. console.log("图片较小,无需压缩:", originalSize, "KB");
  241. resolve(imagePath);
  242. return;
  243. }
  244. // 根据原图大小确定压缩质量
  245. let targetQuality = 0.8; // 默认压缩质量
  246. if (originalSize <= 500) {
  247. // 如果原图已经小于500KB,保持较高质量
  248. targetQuality = 0.9;
  249. } else if (originalSize <= 1024) {
  250. // 1MB以下,适当压缩
  251. targetQuality = 0.7;
  252. } else if (originalSize <= 2048) {
  253. // 2MB以下,中等压缩
  254. targetQuality = 0.5;
  255. } else if (originalSize <= 5120) {
  256. // 5MB以下,较大压缩
  257. targetQuality = 0.3;
  258. } else {
  259. // 超过5MB,大幅压缩
  260. targetQuality = 0.2;
  261. }
  262. // 创建canvas上下文
  263. wx.createSelectorQuery()
  264. .select('#compressCanvas')
  265. .fields({ node: true, size: true })
  266. .exec((canvasRes) => {
  267. if (!canvasRes || !canvasRes[0] || !canvasRes[0].node) {
  268. // 如果找不到canvas节点,则返回原图
  269. console.log("找不到canvas节点,使用原图");
  270. resolve(imagePath);
  271. return;
  272. }
  273. const canvas = canvasRes[0].node;
  274. const ctx = canvas.getContext('2d');
  275. const image = canvas.createImage();
  276. image.onload = () => {
  277. // 计算要缩放的尺寸,保持宽高比
  278. let ratio = 1;
  279. // 根据原图大小调整尺寸
  280. if (originalSize > 5120) { // 5MB以上
  281. ratio = Math.min(1, 1200 / res.width, 1200 / res.height);
  282. } else if (originalSize > 2048) { // 2MB以上
  283. ratio = Math.min(1, 1500 / res.width, 1500 / res.height);
  284. } else if (originalSize > 1024) { // 1MB以上
  285. ratio = Math.min(1, 1800 / res.width, 1800 / res.height);
  286. } else {
  287. ratio = Math.min(1, 2000 / res.width, 2000 / res.height);
  288. }
  289. const targetWidth = Math.round(res.width * ratio);
  290. const targetHeight = Math.round(res.height * ratio);
  291. // 设置canvas尺寸
  292. canvas.width = targetWidth;
  293. canvas.height = targetHeight;
  294. // 清除画布
  295. ctx.clearRect(0, 0, targetWidth, targetHeight);
  296. // 绘制图片
  297. ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
  298. // 生成带有标记的压缩后路径名称
  299. const compressedName = `compressed_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
  300. // 压缩参数
  301. const compressOptions = {
  302. canvas: canvas,
  303. width: targetWidth,
  304. height: targetHeight,
  305. destWidth: targetWidth,
  306. destHeight: targetHeight,
  307. fileType: 'jpg',
  308. quality: targetQuality,
  309. // 添加压缩标记
  310. filePath: `${wx.env.USER_DATA_PATH}/${compressedName}.jpg`
  311. };
  312. // 执行一次压缩
  313. wx.canvasToTempFilePath({
  314. ...compressOptions,
  315. success: (result) => {
  316. console.log("压缩成功,新路径:", result.tempFilePath);
  317. resolve(result.tempFilePath);
  318. },
  319. fail: (error) => {
  320. console.error('Canvas转图片失败:', error);
  321. // 如果转换失败则返回原图
  322. resolve(imagePath);
  323. }
  324. });
  325. };
  326. image.onerror = () => {
  327. console.error('图片加载失败');
  328. resolve(imagePath);
  329. };
  330. image.src = imagePath;
  331. });
  332. },
  333. fail: (error) => {
  334. console.error('获取原始图片信息失败:', error);
  335. resolve(imagePath);
  336. }
  337. });
  338. },
  339. fail: (error) => {
  340. console.error('获取图片信息失败:', error);
  341. resolve(imagePath);
  342. }
  343. });
  344. });
  345. },
  346. previewImage: function (e) {
  347. let index = e.currentTarget.dataset.index;
  348. let urls = this.data.imageList.map(item => {
  349. // 处理不同格式的图片对象
  350. return typeof item === 'string' ? item : item.tempFilePath;
  351. });
  352. wx.previewImage({
  353. current: urls[index],
  354. urls: urls
  355. });
  356. },
  357. deleteImage: function (e) {
  358. let index = e.currentTarget.dataset.index;
  359. let imageList = this.data.imageList;
  360. imageList.splice(index, 1);
  361. this.setData({
  362. imageList: imageList
  363. });
  364. },
  365. checkFormValidity: function () {
  366. const {
  367. contact,
  368. phone,
  369. description,
  370. category
  371. } = this.data;
  372. // 只检查必填项:联系人、联系电话和内容说明
  373. const isValid = contact.trim() !== '' && this.validatePhone(phone) && description.trim() !== '';
  374. this.setData({
  375. isFormValid: isValid
  376. });
  377. return isValid;
  378. },
  379. onInputChange: function (e) {
  380. const {
  381. field
  382. } = e.currentTarget.dataset;
  383. const {
  384. value
  385. } = e.detail;
  386. this.setData({
  387. [field]: value
  388. });
  389. this.checkFormValidity();
  390. },
  391. bindPickerChange: function (e) {
  392. this.checkFormValidity();
  393. },
  394. // 添加submitForm方法
  395. submitForm: function () {
  396. // 如果正在提交中,直接返回
  397. if (this.data.isSubmitting) {
  398. return;
  399. }
  400. if (!this.checkFormValidity()) {
  401. let errorMsg = '';
  402. if (!this.data.contact.trim()) {
  403. errorMsg = '请填写联系人';
  404. } else if (!this.validatePhone(this.data.phone)) {
  405. errorMsg = '请输入正确的联系电话';
  406. } else if (!this.data.description.trim()) {
  407. errorMsg = '请填写内容说明';
  408. }
  409. wx.showToast({
  410. title: errorMsg,
  411. icon: 'none'
  412. });
  413. return;
  414. }
  415. const now = Date.now();
  416. const lastSubmitTime = this.data.lastSubmitTime;
  417. // 如果距离上次提交时间小于 2 秒,直接返回
  418. if (now - lastSubmitTime < 2000) {
  419. // wx.showToast({
  420. // title: '请勿重复提交',
  421. // icon: 'none',
  422. // });
  423. return;
  424. }
  425. // 更新上次提交时间
  426. this.setData({
  427. lastSubmitTime: now,
  428. });
  429. const fileManager = wx.getFileSystemManager();
  430. this.data.imageList.map(imgInfo => {
  431. const base64 = fileManager.readFileSync(imgInfo.tempFilePath, 'base64');
  432. imgInfo.base64 = base64;
  433. return imgInfo;
  434. })
  435. const submitData = {
  436. category: this.data.categoryValue,
  437. contact: this.data.contact,
  438. phone: this.data.phone,
  439. description: this.data.description,
  440. images: this.data.imageList,
  441. userName: app.globalData.currentAccountInfo.username,
  442. userNum: app.globalData.currentAccountInfo.usernumber
  443. };
  444. debugger
  445. // 设置正在提交中 防止重复点击提交按钮
  446. this.setData({
  447. isSubmitting: true,
  448. });
  449. console.log('提交的数据:', submitData);
  450. wx.showLoading({
  451. title: '提交中...',
  452. });
  453. wx.request({
  454. url: app.globalData.interfaceUrls.feedback,
  455. method: 'POST',
  456. header: {
  457. 'content-type': 'application/json', // 默认值
  458. 'token': app.globalData.userWxInfo.token,
  459. 'source': "wc",
  460. '!SAAS_LOGIN_TOKEN_!': app.globalData.currentAccountInfo.dsKey
  461. },
  462. data: submitData,
  463. success(res) {
  464. wx.hideLoading();
  465. if (res.data.code == '200') {
  466. wx.navigateTo({
  467. url: '/pages/tousujianyiSuccess/tousujianyiSuccess',
  468. });
  469. }
  470. },
  471. fail(error) {
  472. wx.hideLoading()
  473. utils.simleInfo('登记失败,请稍后再试')
  474. },
  475. complete: () => {
  476. this.setData({
  477. isSubmitting: false, // 提交完成,重置标志位,可继续提交
  478. formSubmitted: true // 在提交成功后设置标记,返回将重置表单
  479. });
  480. },
  481. })
  482. // 在提交成功后设置标记,返回将重置表单
  483. // this.setData({
  484. // formSubmitted: true
  485. // });
  486. },
  487. submitRepair: function () {
  488. this.submitForm();
  489. },
  490. // 根据ID获取数据
  491. getDataById: function (id) {
  492. // 这里应该是从服务器获取数据
  493. // 但为了演示,我们从本地数据中获取
  494. const pages = getCurrentPages();
  495. const prevPage = pages[pages.length - 2]; // 获取上一个页面
  496. if (prevPage && prevPage.data.noticeList) {
  497. const item = prevPage.data.noticeList.find(item => item.id == id);
  498. if (item) {
  499. const formatTime = (timeString) => {
  500. if (!timeString) return ''; // 如果时间为空,返回空字符串
  501. const date = new Date(timeString);
  502. const year = date.getFullYear();
  503. const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份补零
  504. const day = String(date.getDate()).padStart(2, '0'); // 日期补零
  505. return `${year}-${month}-${day}`;
  506. };
  507. let categoryName = '投诉';
  508. const categoryItem = this.data.categoryMap.find(cat => cat.value === item.category);
  509. if (categoryItem) {
  510. categoryName = categoryItem.name;
  511. }
  512. this.setData({
  513. category: categoryName,
  514. categoryValue: item.category || '1',
  515. contact: item.feedbackperson || '',
  516. description: item.replynote || '',
  517. phone: item.contactnumber || '',
  518. imageList: item.attachments || [],
  519. replyTime: formatTime(item.replytime) || '',
  520. replyContent: item.replycontent || ''
  521. });
  522. }
  523. }
  524. },
  525. resetForm: function () {
  526. this.setData({
  527. contact: '',
  528. phone: '',
  529. description: '',
  530. imageList: [],
  531. category: '投诉',
  532. categoryValue: '1'
  533. });
  534. }
  535. });