LuckySheet.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. import React from 'react';
  2. import { message } from 'antd';
  3. import exportExcel, { getExcelBolob } from '@/utils/exportExcl';
  4. import LuckyExcel from 'luckyexcel';
  5. import { getToken } from '@/utils/utils';
  6. const hintText = '禁止编辑!请先点击编辑按钮。';
  7. const DIFF_COLOR = '#ff0000';
  8. const ADD_COLOR = '#00ff00';
  9. class LuckySheet extends React.Component {
  10. constructor(props) {
  11. super(props);
  12. this.sheetRef = React.createRef();
  13. this.luckysheet = null;
  14. this.updateCell = {
  15. add: [],
  16. diff: [],
  17. };
  18. this.renderTimer = null;
  19. this.chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
  20. }
  21. componentWillUnmount() {
  22. this.luckysheet?.destroy();
  23. }
  24. componentDidUpdate(prveProps) {
  25. const prevVersion = prveProps.version || {};
  26. const curVersion = this.props.version || {};
  27. if (prevVersion?.id != curVersion?.id || prevVersion?.flow_id != curVersion.flow_id) {
  28. console.log(prevVersion, curVersion);
  29. this.renderSheet();
  30. }
  31. }
  32. getUUID(len = 8, radix = 16) {
  33. var chars = this.chars;
  34. var uuid = [],
  35. i;
  36. radix = radix || chars.length;
  37. if (len) {
  38. // Compact form
  39. for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
  40. } else {
  41. // rfc4122, version 4 form
  42. var r;
  43. // rfc4122 requires these characters
  44. uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
  45. uuid[14] = '4';
  46. // Fill in random data. At i==19 set the high bits of clock sequence as
  47. // per rfc4122, sec. 4.1.5
  48. for (i = 0; i < 36; i++) {
  49. if (!uuid[i]) {
  50. r = 0 | (Math.random() * 16);
  51. uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
  52. }
  53. }
  54. }
  55. return uuid.join('');
  56. }
  57. renderSheet(currentData) {
  58. const { onClickCell, version, getUser, onUpdate, templateId } = this.props;
  59. const data = currentData || this.props.data;
  60. if (!this.luckysheet) {
  61. clearTimeout(this.renderTimer);
  62. this.renderTimer = setTimeout(() => {
  63. this.renderSheet(currentData);
  64. }, 300);
  65. return;
  66. }
  67. let token = getToken();
  68. let option = {
  69. lang: 'zh',
  70. showinfobar: false,
  71. showstatisticBar: false,
  72. // forceCalculation: true,
  73. hook: {
  74. cellMousedown: (cell, position, sheet) => {
  75. onClickCell && onClickCell(cell, position, sheet);
  76. },
  77. cellPasteBefore: cell => {
  78. console.log(cell);
  79. if (!cell) return;
  80. if (cell.cid) delete cell.cid;
  81. if (cell.bg == DIFF_COLOR || cell.bg == ADD_COLOR) {
  82. delete cell.bg;
  83. }
  84. },
  85. updated(operate) {
  86. console.log(operate);
  87. onUpdate && onUpdate();
  88. },
  89. sheetActivate: sheet => {
  90. console.log(sheet);
  91. setTimeout(() => {
  92. this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
  93. }, 100);
  94. },
  95. },
  96. };
  97. if (version) {
  98. option = {
  99. ...option,
  100. allowUpdate: true,
  101. gridKey: version.id,
  102. templateId: templateId,
  103. // flowId: version.flow_id,
  104. loadUrl: `/api/v1/purchase/record/sheet?gridKey=${version.id}&JWT-TOKEN=${token}`,
  105. updateUrl: `ws://${location.host}/api/v1/ws?id=${version.id}&sid=${templateId}&JWT-TOKEN=${token}`,
  106. // updateUrl: `ws://120.55.44.4:8896/api/v1/ws?id=${version.id}&sid=${templateId}&JWT-TOKEN=${token}`,
  107. authorityUrl: `/api/v1/purchase/bom/user/excel/col?depId=${localStorage.depId}&JWT-TOKEN=${token}`,
  108. getUser,
  109. // workbookCreateBefore(luckysheet) {
  110. // console.log('===============================', luckysheet);
  111. // let oldConfig = JSON.parse(JSON.stringify(luckysheet.getConfig()));
  112. // setTimeout(() => {
  113. // luckysheet.setConfig({
  114. // ...oldConfig,
  115. // authority: { sheet: true, hintText },
  116. // });
  117. // }, 300);
  118. // },
  119. };
  120. console.log(version);
  121. if (version.flow_id) {
  122. option.authority = {
  123. sheet: true,
  124. hintText: '当前处于审批节点,禁止编辑!',
  125. };
  126. } else if (version.audit_status != 0 || version.status == 1) {
  127. option.authority = {
  128. sheet: true,
  129. hintText: '当前清单不可编辑!',
  130. };
  131. }
  132. } else if (data && data.length > 0) {
  133. option.data = JSON.parse(JSON.stringify(data));
  134. option.data.forEach(item => {
  135. if (item.celldata) {
  136. item.celldata.forEach(cell => {
  137. // 生成uuid
  138. if (!cell.v.cid) cell.v.cid = this.getUUID();
  139. });
  140. }
  141. // 默认禁止编辑
  142. // item.config.authority = { sheet: true, hintText };
  143. });
  144. } else {
  145. // 默认sheet页数据
  146. data.data = [
  147. {
  148. name: 'sheet1',
  149. // config: {
  150. // authority: { sheet: true, hintText },
  151. // },
  152. },
  153. ];
  154. }
  155. this.luckysheet.destroy();
  156. this.luckysheet.create(option);
  157. // 比对模式会导致单元格出现[Object object]的情况 任意编辑后才会正常显示
  158. // 所以默认设置第一个单元格的背景色
  159. setTimeout(() => {
  160. this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
  161. }, 500);
  162. }
  163. // componentDidUpdate(prevProps) {
  164. // const { data } = this.props;
  165. // if (prevProps.data != data) {
  166. // this.renderSheet(data);
  167. // }
  168. // }
  169. handleLoad() {
  170. const { version } = this.props;
  171. let contentWindow = this.sheetRef.current.contentWindow;
  172. this.luckysheet = contentWindow.luckysheet;
  173. // this.luckysheet = this.luckysheet;
  174. // version存在 则需调用render
  175. if (version) {
  176. this.renderSheet();
  177. }
  178. // onLoad && onLoad();
  179. }
  180. selectCell(row, col, order) {
  181. this.luckysheet.setRangeShow({ row: [row, row], column: [col, col] }, { order });
  182. }
  183. toggleSheet(order) {
  184. this.luckysheet.setSheetActive(order);
  185. }
  186. getSheetJson() {
  187. let data = JSON.parse(JSON.stringify(this.luckysheet.toJson()));
  188. data.data.forEach(sheet => {
  189. let allCell = {},
  190. unknowCid = [];
  191. // 将cell以cid为界分别存储
  192. (sheet.celldata || []).forEach(cell => {
  193. if (!cell.v.cid) {
  194. unknowCid.push(cell);
  195. } else {
  196. allCell[cell.v.cid] = cell;
  197. }
  198. // 清除比对样式
  199. if (cell.v.bg == DIFF_COLOR || cell.v.bg == ADD_COLOR) {
  200. delete cell.v.bg;
  201. }
  202. });
  203. unknowCid.forEach(cell => {
  204. // 根据坐标生成唯一key,重复则增加后缀直至不重复
  205. let key = `${cell.r}-${cell.c}`;
  206. while (allCell[key]) {
  207. key += '|c';
  208. }
  209. cell.v.cid = key;
  210. allCell[key] = cell;
  211. });
  212. sheet.celldata = Object.values(allCell);
  213. });
  214. return data;
  215. }
  216. // 切换编辑状态
  217. toggleEdit(edit) {
  218. let luckysheet = this.luckysheet;
  219. if (edit) {
  220. let config = luckysheet.getConfig();
  221. luckysheet.setConfig({
  222. ...config,
  223. authority: { sheet: !edit, hintText },
  224. });
  225. } else {
  226. luckysheet.exitEditMode();
  227. }
  228. }
  229. // 切换比对状态
  230. toggleCompare(isCompare, compareData, callback) {
  231. let luckysheet = this.luckysheet;
  232. let diff = [];
  233. let add = [];
  234. const { onCompareSuccess } = this.props;
  235. // 判断dom是否加载完成
  236. if (!luckysheet) {
  237. setTimeout(() => {
  238. this.toggleCompare(isCompare, compareData, callback);
  239. }, 300);
  240. return;
  241. }
  242. if (isCompare) {
  243. // let currentData = this.luckysheet.toJson();
  244. let currentData = JSON.parse(JSON.stringify(this.props.data));
  245. currentData.forEach((sheet, index) => {
  246. let celldata1 = sheet.celldata;
  247. let celldata2 = compareData[index]?.celldata || [];
  248. celldata1.forEach(item => {
  249. // 不判断空字符串
  250. if (this.isEmpty(item)) return;
  251. var d2Item = celldata2.find(item2 => item2.v.cid == item.v.cid);
  252. if (d2Item && !this.isEmpty(d2Item)) {
  253. // v.ct.s相同,不做处理
  254. if (item.v.ct?.s && JSON.stringify(item.v.ct?.s) == JSON.stringify(d2Item.v.ct?.s))
  255. return;
  256. // v.v相同,不做处理
  257. if (d2Item.v.v == item.v.v) return;
  258. // 内容不同,标记diff颜色
  259. diff.push({
  260. ...item,
  261. sheetOrder: index,
  262. });
  263. item.v.bg = DIFF_COLOR;
  264. // luckysheet.setCellFormat(item.r, item.c, 'bg', DIFF_COLOR);
  265. } else {
  266. // 找不到同cid的单元格,标记add颜色
  267. add.push({
  268. ...item,
  269. sheetOrder: index,
  270. });
  271. item.v.bg = ADD_COLOR;
  272. // luckysheet.setCellFormat(item.r, item.c, 'bg', ADD_COLOR);
  273. }
  274. });
  275. });
  276. console.log(currentData);
  277. this.renderSheet(currentData);
  278. // luckysheet.refresh()
  279. } else {
  280. this.renderSheet(this.props.data);
  281. }
  282. this.updateCell = {
  283. diff,
  284. add,
  285. };
  286. callback && callback(this.updateCell);
  287. }
  288. isEmpty(item) {
  289. return (item?.v?.v ?? '') === '' && !item?.v?.ct?.s;
  290. }
  291. mergeExcl(updateCell = {}) {
  292. const { diff = [], add = [] } = updateCell;
  293. let currentData = this.luckysheet.toJson().data;
  294. let luckysheet = this.luckysheet;
  295. console.log(updateCell);
  296. diff.forEach(item => {
  297. let sheet = currentData[item.sheetOrder];
  298. let d1Item = sheet.celldata.find(item2 => item2.v.cid == item.v.cid);
  299. // 将差异项覆盖至当前文档
  300. d1Item.v = {
  301. ...item.v,
  302. bg: undefined,
  303. };
  304. });
  305. add.forEach(item => {
  306. // 将新增项添加至当前文档
  307. let sheet = currentData[item.sheetOrder];
  308. let d1Item = sheet.celldata.find(item2 => item2.r == item.r && item2.c == item.c);
  309. if (d1Item) {
  310. d1Item.v = {
  311. ...item.v,
  312. bg: undefined,
  313. };
  314. } else {
  315. sheet.celldata.push({
  316. ...item,
  317. sheetOrder: undefined,
  318. });
  319. }
  320. });
  321. currentData.forEach(sheet => {
  322. delete sheet.data;
  323. });
  324. this.renderSheet(currentData);
  325. // currentData.data.forEach((sheet, index) => {
  326. // if (!mergeData[index]) return;
  327. // let celldata1 = sheet.celldata;
  328. // let celldata2 = mergeData[index].celldata;
  329. // celldata2.forEach(item => {
  330. // let bg = item.v?.bg;
  331. // let d1Item;
  332. // if (bg == DIFF_COLOR) {
  333. // delete item.v.bg;
  334. // d1Item = celldata1.find(item2 => item2.v.cid == item.v.cid);
  335. // // 将差异项覆盖至当前文档
  336. // d1Item.v = item.v;
  337. // // luckysheet.setCellValue(d1Item.r, d1Item.c, item.v);
  338. // } else if (bg == ADD_COLOR) {
  339. // delete item.v.bg;
  340. // // 将新增项添加至当前文档
  341. // // luckysheet.setCellValue(item.r, item.c, item.v);
  342. // d1Item = celldata1.find(item2 => item2.r == item.r && item2.c == item.c);
  343. // if (d1Item) {
  344. // d1Item.v = item.v;
  345. // } else {
  346. // celldata1.push(item);
  347. // }
  348. // }
  349. // });
  350. // });
  351. // this.renderSheet(currentData);
  352. }
  353. /**
  354. * 导入excl
  355. * @param {*} files input:file的evt.target.files
  356. * @returns
  357. */
  358. uploadExcel(files, callback) {
  359. if (files == null || files.length == 0) {
  360. return;
  361. }
  362. let name = files[0].name;
  363. let suffixArr = name.split('.'),
  364. suffix = suffixArr[suffixArr.length - 1];
  365. if (suffix != 'xlsx') {
  366. alert('Currently only supports the import of xlsx files');
  367. message.error('只支持xlsx格式的文件!');
  368. return;
  369. }
  370. LuckyExcel.transformExcelToLucky(files[0], (exportJson, luckysheetfile) => {
  371. if (exportJson.sheets == null || exportJson.sheets.length == 0) {
  372. message.error('读取xlsx文件失败!');
  373. return;
  374. }
  375. // this.luckysheet.destroy();
  376. // 同步当前文档内容
  377. let data = this.props.data;
  378. exportJson.sheets.forEach((sheet, index) => {
  379. if (!data || !data[index]) return;
  380. sheet.celldata.forEach(cell => {
  381. if (this.isEmpty(cell)) return;
  382. // return (item.v.v ?? '') === '' && !item.v.ct?.s;
  383. let dCell = (data[index].celldata || []).find(dCell => {
  384. return dCell.r == cell.r && dCell.c == cell.c;
  385. });
  386. if (this.isEmpty(dCell)) return;
  387. // 判断v.ct是否相同
  388. // if (cell?.v?.ct?.s && dCell.v.ct?.s && cell.v.ct?.s.join('') != dCell.v.ct?.s.join('')) return;
  389. if (cell?.v?.ct?.s && dCell.v.ct?.s) {
  390. if (cell.v.ct?.s.join('') != dCell.v.ct?.s.join('')) return;
  391. let cellS = cell.v.ct.s;
  392. let dCellS = dCell.v.ct.s;
  393. let isEqul = cellS.every((cur, idx) => {
  394. return JSON.stringify(cur) === JSON.stringify(dCellS[idx]);
  395. });
  396. if (!isEqul) return;
  397. }
  398. // 判断v.v是否相同
  399. if (cell?.v?.v && dCell.v.v != cell.v.v) return;
  400. // 内容相同则复制cid
  401. cell.cid = dCell.cid;
  402. });
  403. });
  404. this.renderSheet(exportJson.sheets);
  405. callback && callback();
  406. });
  407. }
  408. // 根据url导入excl
  409. // selectExcel(item) {
  410. // const {value,name} = item
  411. // if (value == '') {
  412. // return;
  413. // }
  414. // LuckyExcel.transformExcelToLuckyByUrl(value, name, (exportJson, luckysheetfile) => {
  415. // if (exportJson.sheets == null || exportJson.sheets.length == 0) {
  416. // alert(
  417. // 'Failed to read the content of the excel file, currently does not support xls files!'
  418. // );
  419. // return;
  420. // }
  421. // this.luckysheet.destroy();
  422. // this.luckysheet.create({
  423. // container: 'luckysheet', //luckysheet is the container id
  424. // showinfobar: false,
  425. // data: exportJson.sheets,
  426. // title: exportJson.info.name,
  427. // userInfo: exportJson.info.name.creator,
  428. // });
  429. // });
  430. // }
  431. getExcelData(checkValue = null) {
  432. let resultList = [];
  433. console.log(this.luckysheet.getAllSheets());
  434. let currentData = this.luckysheet.getAllSheets();
  435. currentData.forEach(sheet => {
  436. let data = sheet.data;
  437. let celldata = sheet.celldata;
  438. let colList = [];
  439. data[0]?.forEach((rowOneItem, colIdx) => {
  440. if (rowOneItem) {
  441. if (!checkValue || checkValue.indexOf(rowOneItem.cid) !== -1) {
  442. colList.indexOf(colIdx) == -1 ? colList.push(colIdx) : true;
  443. }
  444. }
  445. });
  446. const newData = [];
  447. data.forEach(item => {
  448. if (item !== null) {
  449. let arr = item.filter((cur, idx) => {
  450. return item && colList.includes(idx);
  451. });
  452. newData.push(arr);
  453. }
  454. });
  455. sheet.data = newData;
  456. //消除空列后都列下标
  457. let newColIdxList = colList.map((cur, idx) => {
  458. return idx;
  459. });
  460. //处理celldata
  461. const newCellData = [];
  462. celldata.forEach(item => {
  463. let idx = colList.indexOf(item.c);
  464. if (idx !== -1) {
  465. item.c = newColIdxList[idx];
  466. newCellData.push(item);
  467. }
  468. });
  469. sheet.celldata = newCellData;
  470. });
  471. return currentData;
  472. }
  473. getExcelBolb() {
  474. let currentData = this.getExcelData();
  475. return getExcelBolob(currentData);
  476. }
  477. downloadExcel(checkValue) {
  478. let currentData = this.getExcelData(checkValue);
  479. exportExcel(currentData, '下载');
  480. }
  481. render() {
  482. return (
  483. <iframe
  484. onLoad={e => {
  485. this.handleLoad(e);
  486. }}
  487. ref={this.sheetRef}
  488. src="/luckysheet.html"
  489. ></iframe>
  490. );
  491. }
  492. }
  493. export default LuckySheet;