LuckySheet.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  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, GetTokenFromUrl } 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.updateTimer = null;
  15. this.currentSheetIndex = null;
  16. this.updateCell = {
  17. add: [],
  18. diff: [],
  19. };
  20. this.renderTimer = null;
  21. this.chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
  22. }
  23. componentWillUnmount() {
  24. this.luckysheet?.destroy();
  25. }
  26. componentDidUpdate(prveProps) {
  27. const prevVersion = prveProps.version || {};
  28. const curVersion = this.props.version || {};
  29. if (prevVersion?.id != curVersion?.id || prevVersion?.flow_id != curVersion.flow_id) {
  30. console.log(prevVersion, curVersion);
  31. this.renderSheet();
  32. }
  33. }
  34. getUUID(len = 8, radix = 16) {
  35. var chars = this.chars;
  36. var uuid = [],
  37. i;
  38. radix = radix || chars.length;
  39. if (len) {
  40. // Compact form
  41. for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
  42. } else {
  43. // rfc4122, version 4 form
  44. var r;
  45. // rfc4122 requires these characters
  46. uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
  47. uuid[14] = '4';
  48. // Fill in random data. At i==19 set the high bits of clock sequence as
  49. // per rfc4122, sec. 4.1.5
  50. for (i = 0; i < 36; i++) {
  51. if (!uuid[i]) {
  52. r = 0 | (Math.random() * 16);
  53. uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
  54. }
  55. }
  56. }
  57. return uuid.join('');
  58. }
  59. renderSheet(currentData) {
  60. const { onClickCell, version, getUser, onUpdate, templateId, onDelSheet } = this.props;
  61. const data = currentData || this.props.data;
  62. const _this = this;
  63. if (!this.luckysheet) {
  64. clearTimeout(this.renderTimer);
  65. this.renderTimer = setTimeout(() => {
  66. this.renderSheet(currentData);
  67. }, 300);
  68. return;
  69. }
  70. let token = GetTokenFromUrl() || getToken();
  71. let option = {
  72. lang: 'zh',
  73. showinfobar: false,
  74. showstatisticBar: false,
  75. // forceCalculation: true,
  76. hook: {
  77. cellMousedown: (cell, position, sheet) => {
  78. onClickCell && onClickCell(cell, position, sheet);
  79. },
  80. cellPasteBefore: cell => {
  81. console.log(cell);
  82. if (!cell) return;
  83. if (cell.cid) delete cell.cid;
  84. if (cell.bg == DIFF_COLOR || cell.bg == ADD_COLOR) {
  85. delete cell.bg;
  86. }
  87. },
  88. updated(operate) {
  89. if (operate.type == 'datachange') {
  90. if (_this.currentSheetIndex != operate.sheetIndex) {
  91. _this.currentSheetIndex = operate.sheetIndex;
  92. return;
  93. }
  94. // 延迟1秒
  95. clearTimeout(_this.updateTimer);
  96. _this.updateTimer = setTimeout(() => {
  97. onUpdate.bind(_this);
  98. onUpdate();
  99. }, 1000);
  100. }
  101. },
  102. // 修改批注后保存sheet
  103. commentUpdateAfter() {
  104. clearTimeout(_this.updateTimer);
  105. _this.updateTimer = setTimeout(() => {
  106. onUpdate.bind(_this);
  107. onUpdate(true);
  108. }, 1000);
  109. },
  110. sheetActivate: sheet => {
  111. console.log(sheet);
  112. setTimeout(() => {
  113. this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
  114. }, 100);
  115. },
  116. sheetDeleteAfter: sheet => {
  117. onDelSheet && onDelSheet(sheet?.sheet.id);
  118. },
  119. sheetActivate: sheet => {
  120. console.log(sheet);
  121. setTimeout(() => {
  122. this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
  123. }, 100);
  124. },
  125. },
  126. };
  127. if (version) {
  128. const wsUrl =
  129. process.env.NODE_ENV == 'development'
  130. ? 'ws://47.96.12.136:8896/'
  131. : `ws://${location.host}/`;
  132. option = {
  133. ...option,
  134. allowUpdate: true,
  135. gridKey: version.id,
  136. templateId: templateId,
  137. // flowId: version.flow_id,
  138. loadUrl: `/api/v1/purchase/record/sheet?gridKey=${version.id}&JWT-TOKEN=${token}`,
  139. updateUrl: wsUrl + `api/v1/ws?id=${version.id}&sid=${templateId}&JWT-TOKEN=${token}`,
  140. // updateUrl: `ws://47.96.12.136:8896/api/v1/ws?id=${version.id}&sid=${templateId}&JWT-TOKEN=${token}`,
  141. // updateUrl: `ws://120.55.44.4:8896/api/v1/ws?id=${version.id}&sid=${templateId}&JWT-TOKEN=${token}`,
  142. authorityUrl: `/api/v1/purchase/bom/user/excel/col?depId=${localStorage.depId ||
  143. 0}&JWT-TOKEN=${token}`,
  144. getUser,
  145. // workbookCreateBefore(luckysheet) {
  146. // console.log('===============================', luckysheet);
  147. // let oldConfig = JSON.parse(JSON.stringify(luckysheet.getConfig()));
  148. // setTimeout(() => {
  149. // luckysheet.setConfig({
  150. // ...oldConfig,
  151. // authority: { sheet: true, hintText },
  152. // });
  153. // }, 300);
  154. // },
  155. };
  156. console.log(version);
  157. const unableEdit = option => {
  158. option.showtoolbar = false;
  159. option.enableAddRow = false;
  160. option.sheetFormulaBar = false;
  161. option.enableAddBackTop = false;
  162. option.showsheetbarConfig = {
  163. add: false,
  164. sheet: false,
  165. };
  166. option.cellRightClickConfig = {
  167. copy: false, // 复制
  168. copyAs: false, // 复制为
  169. paste: false, // 粘贴
  170. insertRow: false, // 插入行
  171. insertColumn: false, // 插入列
  172. deleteRow: false, // 删除选中行
  173. deleteColumn: false, // 删除选中列
  174. deleteCell: false, // 删除单元格
  175. hideRow: false, // 隐藏选中行和显示选中行
  176. hideColumn: false, // 隐藏选中列和显示选中列
  177. rowHeight: false, // 行高
  178. columnWidth: false, // 列宽
  179. clear: false, // 清除内容
  180. matrix: false, // 矩阵操作选区
  181. sort: false, // 排序选区
  182. filter: false, // 筛选选区
  183. chart: false, // 图表生成
  184. image: false, // 插入图片
  185. link: false, // 插入链接
  186. data: false, // 数据验证
  187. cellFormat: false, // 设置单元格格式
  188. };
  189. };
  190. if (version.flow_id) {
  191. option.authority = {
  192. sheet: true,
  193. hintText: '当前处于审批节点,禁止编辑!',
  194. };
  195. unableEdit(option);
  196. } else if (version.last_version) {
  197. option.authority = {
  198. sheet: true,
  199. hintText: '该清单已设置为最终版本,禁止编辑!',
  200. };
  201. unableEdit(option);
  202. } else if (version.audit_status != 0 || version.status == 1) {
  203. option.authority = {
  204. sheet: true,
  205. hintText: '当前清单不可编辑!',
  206. };
  207. unableEdit(option);
  208. }
  209. } else if (data && data.length > 0) {
  210. option.data = JSON.parse(JSON.stringify(data));
  211. option.data.forEach(item => {
  212. if (item.celldata) {
  213. item.celldata.forEach(cell => {
  214. // 生成uuid
  215. if (!cell.v.cid) cell.v.cid = this.getUUID();
  216. });
  217. }
  218. // 默认禁止编辑
  219. // item.config.authority = { sheet: true, hintText };
  220. });
  221. } else {
  222. // 默认sheet页数据
  223. data.data = [
  224. {
  225. name: 'sheet1',
  226. // config: {
  227. // authority: { sheet: true, hintText },
  228. // },
  229. },
  230. ];
  231. }
  232. this.luckysheet.destroy();
  233. this.luckysheet.create(option);
  234. // 比对模式会导致单元格出现[Object object]的情况 任意编辑后才会正常显示
  235. // 所以默认设置第一个单元格的背景色
  236. setTimeout(() => {
  237. this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
  238. }, 500);
  239. }
  240. // componentDidUpdate(prevProps) {
  241. // const { data } = this.props;
  242. // if (prevProps.data != data) {
  243. // this.renderSheet(data);
  244. // }
  245. // }
  246. handleLoad() {
  247. const { version } = this.props;
  248. let contentWindow = this.sheetRef.current.contentWindow;
  249. this.luckysheet = contentWindow.luckysheet;
  250. // this.luckysheet = this.luckysheet;
  251. // version存在 则需调用render
  252. if (version) {
  253. this.renderSheet();
  254. }
  255. // onLoad && onLoad();
  256. }
  257. selectCell(row, col, order) {
  258. this.luckysheet.setRangeShow({ row: [row, row], column: [col, col] }, { order });
  259. }
  260. toggleSheet(order) {
  261. this.luckysheet.setSheetActive(order);
  262. }
  263. getSheetJson() {
  264. let data = JSON.parse(JSON.stringify(this.luckysheet.toJson()));
  265. data.data.forEach(sheet => {
  266. let allCell = {},
  267. unknowCid = [];
  268. // 将cell以cid为界分别存储
  269. (sheet.celldata || []).forEach(cell => {
  270. if (!cell.v.cid) {
  271. unknowCid.push(cell);
  272. } else if (!allCell[cell.v.cid]) {
  273. allCell[cell.v.cid] = cell;
  274. } else {
  275. // 当存在相同cid时
  276. // 做异常处理
  277. delete cell.v.cid;
  278. unknowCid.push(cell);
  279. }
  280. if (cell.v.tb) cell.v.tb = Number(cell.v.tb);
  281. // 清除比对样式
  282. if (cell.v.bg == DIFF_COLOR || cell.v.bg == ADD_COLOR) {
  283. delete cell.v.bg;
  284. }
  285. });
  286. unknowCid.forEach(cell => {
  287. // 根据坐标生成唯一key,重复则增加后缀直至不重复
  288. let key = `${cell.r}-${cell.c}`;
  289. while (allCell[key]) {
  290. key += '|c';
  291. }
  292. cell.v.cid = key;
  293. allCell[key] = cell;
  294. });
  295. sheet.celldata = Object.values(allCell);
  296. });
  297. return data;
  298. }
  299. // 切换编辑状态
  300. toggleEdit(edit) {
  301. let luckysheet = this.luckysheet;
  302. if (edit) {
  303. let config = luckysheet.getConfig();
  304. luckysheet.setConfig({
  305. ...config,
  306. authority: { sheet: !edit, hintText },
  307. });
  308. } else {
  309. luckysheet.exitEditMode();
  310. }
  311. }
  312. // 切换比对状态
  313. toggleCompare(isCompare, compareData, callback) {
  314. let luckysheet = this.luckysheet;
  315. let diff = [];
  316. let add = [];
  317. const { onCompareSuccess } = this.props;
  318. // 判断dom是否加载完成
  319. if (!luckysheet) {
  320. setTimeout(() => {
  321. this.toggleCompare(isCompare, compareData, callback);
  322. }, 300);
  323. return;
  324. }
  325. if (isCompare) {
  326. // let currentData = this.luckysheet.toJson();
  327. let currentData = JSON.parse(JSON.stringify(this.props.data));
  328. currentData.forEach((sheet, index) => {
  329. let celldata1 = sheet.celldata;
  330. let celldata2 = compareData[index]?.celldata || [];
  331. celldata1.forEach(item => {
  332. // 不判断空字符串
  333. if (this.isEmpty(item)) return;
  334. var d2Item = celldata2.find(item2 => item2.v.cid == item.v.cid);
  335. if (d2Item && !this.isEmpty(d2Item)) {
  336. // v.ct.s相同,不做处理
  337. if (item.v.ct?.s && JSON.stringify(item.v.ct?.s) == JSON.stringify(d2Item.v.ct?.s))
  338. return;
  339. // v.v相同,不做处理
  340. if (d2Item.v.v == item.v.v) return;
  341. // 内容不同,标记diff颜色
  342. diff.push({
  343. ...item,
  344. sheetOrder: index,
  345. });
  346. item.v.bg = DIFF_COLOR;
  347. // luckysheet.setCellFormat(item.r, item.c, 'bg', DIFF_COLOR);
  348. } else {
  349. // 找不到同cid的单元格,标记add颜色
  350. add.push({
  351. ...item,
  352. sheetOrder: index,
  353. });
  354. item.v.bg = ADD_COLOR;
  355. // luckysheet.setCellFormat(item.r, item.c, 'bg', ADD_COLOR);
  356. }
  357. });
  358. });
  359. console.log(currentData);
  360. this.renderSheet(currentData);
  361. // luckysheet.refresh()
  362. } else {
  363. this.renderSheet(this.props.data);
  364. }
  365. this.updateCell = {
  366. diff,
  367. add,
  368. };
  369. callback && callback(this.updateCell);
  370. }
  371. isEmpty(item) {
  372. return (item?.v?.v ?? '') === '' && !item?.v?.ct?.s;
  373. }
  374. mergeExcl(updateCell = {}) {
  375. const { diff = [], add = [] } = updateCell;
  376. let currentData = this.luckysheet.toJson().data;
  377. let luckysheet = this.luckysheet;
  378. console.log(updateCell);
  379. diff.forEach(item => {
  380. let sheet = currentData[item.sheetOrder];
  381. let d1Item = sheet.celldata.find(item2 => item2.v.cid == item.v.cid);
  382. // 将差异项覆盖至当前文档
  383. d1Item.v = {
  384. ...item.v,
  385. bg: undefined,
  386. };
  387. });
  388. add.forEach(item => {
  389. // 将新增项添加至当前文档
  390. let sheet = currentData[item.sheetOrder];
  391. let d1Item = sheet.celldata.find(item2 => item2.r == item.r && item2.c == item.c);
  392. if (d1Item) {
  393. d1Item.v = {
  394. ...item.v,
  395. bg: undefined,
  396. };
  397. } else {
  398. sheet.celldata.push({
  399. ...item,
  400. sheetOrder: undefined,
  401. });
  402. }
  403. });
  404. currentData.forEach(sheet => {
  405. delete sheet.data;
  406. });
  407. this.renderSheet(currentData);
  408. // currentData.data.forEach((sheet, index) => {
  409. // if (!mergeData[index]) return;
  410. // let celldata1 = sheet.celldata;
  411. // let celldata2 = mergeData[index].celldata;
  412. // celldata2.forEach(item => {
  413. // let bg = item.v?.bg;
  414. // let d1Item;
  415. // if (bg == DIFF_COLOR) {
  416. // delete item.v.bg;
  417. // d1Item = celldata1.find(item2 => item2.v.cid == item.v.cid);
  418. // // 将差异项覆盖至当前文档
  419. // d1Item.v = item.v;
  420. // // luckysheet.setCellValue(d1Item.r, d1Item.c, item.v);
  421. // } else if (bg == ADD_COLOR) {
  422. // delete item.v.bg;
  423. // // 将新增项添加至当前文档
  424. // // luckysheet.setCellValue(item.r, item.c, item.v);
  425. // d1Item = celldata1.find(item2 => item2.r == item.r && item2.c == item.c);
  426. // if (d1Item) {
  427. // d1Item.v = item.v;
  428. // } else {
  429. // celldata1.push(item);
  430. // }
  431. // }
  432. // });
  433. // });
  434. // this.renderSheet(currentData);
  435. }
  436. /**
  437. * 导入excl
  438. * @param {*} files input:file的evt.target.files
  439. * @returns
  440. */
  441. uploadExcel(files, callback) {
  442. if (files == null || files.length == 0) {
  443. return;
  444. }
  445. let name = files[0].name;
  446. let suffixArr = name.split('.'),
  447. suffix = suffixArr[suffixArr.length - 1];
  448. if (suffix != 'xlsx') {
  449. alert('Currently only supports the import of xlsx files');
  450. message.error('只支持xlsx格式的文件!');
  451. return;
  452. }
  453. LuckyExcel.transformExcelToLucky(files[0], (exportJson, luckysheetfile) => {
  454. if (exportJson.sheets == null || exportJson.sheets.length == 0) {
  455. message.error('读取xlsx文件失败!');
  456. return;
  457. }
  458. // this.luckysheet.destroy();
  459. // 同步当前文档内容
  460. let data = this.props.data;
  461. exportJson.sheets.forEach((sheet, index) => {
  462. if (!data || !data[index]) return;
  463. sheet.celldata.forEach(cell => {
  464. if (this.isEmpty(cell)) return;
  465. // return (item.v.v ?? '') === '' && !item.v.ct?.s;
  466. let dCell = (data[index].celldata || []).find(dCell => {
  467. return dCell.r == cell.r && dCell.c == cell.c;
  468. });
  469. if (this.isEmpty(dCell)) return;
  470. // 判断v.ct是否相同
  471. // if (cell?.v?.ct?.s && dCell.v.ct?.s && cell.v.ct?.s.join('') != dCell.v.ct?.s.join('')) return;
  472. if (cell?.v?.ct?.s && dCell.v.ct?.s) {
  473. if (cell.v.ct?.s.join('') != dCell.v.ct?.s.join('')) return;
  474. let cellS = cell.v.ct.s;
  475. let dCellS = dCell.v.ct.s;
  476. let isEqul = cellS.every((cur, idx) => {
  477. return JSON.stringify(cur) === JSON.stringify(dCellS[idx]);
  478. });
  479. if (!isEqul) return;
  480. }
  481. // 判断v.v是否相同
  482. if (cell?.v?.v && dCell.v.v != cell.v.v) return;
  483. // 内容相同则复制cid
  484. cell.cid = dCell.cid;
  485. });
  486. });
  487. this.renderSheet(exportJson.sheets);
  488. callback && callback();
  489. });
  490. }
  491. // 根据url导入excl
  492. // selectExcel(item) {
  493. // const {value,name} = item
  494. // if (value == '') {
  495. // return;
  496. // }
  497. // LuckyExcel.transformExcelToLuckyByUrl(value, name, (exportJson, luckysheetfile) => {
  498. // if (exportJson.sheets == null || exportJson.sheets.length == 0) {
  499. // alert(
  500. // 'Failed to read the content of the excel file, currently does not support xls files!'
  501. // );
  502. // return;
  503. // }
  504. // this.luckysheet.destroy();
  505. // this.luckysheet.create({
  506. // container: 'luckysheet', //luckysheet is the container id
  507. // showinfobar: false,
  508. // data: exportJson.sheets,
  509. // title: exportJson.info.name,
  510. // userInfo: exportJson.info.name.creator,
  511. // });
  512. // });
  513. // }
  514. getExcelData(checkValue = null) {
  515. let resultList = [];
  516. console.log(this.luckysheet.getAllSheets());
  517. let currentData = this.luckysheet.getAllSheets();
  518. currentData.forEach(sheet => {
  519. let data = sheet.data;
  520. let celldata = sheet.celldata;
  521. let colList = [];
  522. data[0]?.forEach((rowOneItem, colIdx) => {
  523. if (rowOneItem) {
  524. if (!checkValue || checkValue.indexOf(rowOneItem.cid) !== -1) {
  525. colList.indexOf(colIdx) == -1 ? colList.push(colIdx) : true;
  526. }
  527. }
  528. });
  529. const newData = [];
  530. data.forEach(item => {
  531. if (item !== null) {
  532. let arr = item.filter((cur, idx) => {
  533. return item && colList.includes(idx);
  534. });
  535. newData.push(arr);
  536. }
  537. });
  538. sheet.data = newData;
  539. //消除空列后都列下标
  540. let newColIdxList = colList.map((cur, idx) => {
  541. return idx;
  542. });
  543. //处理celldata
  544. const newCellData = [];
  545. celldata.forEach(item => {
  546. let idx = colList.indexOf(item.c);
  547. if (idx !== -1) {
  548. item.c = newColIdxList[idx];
  549. newCellData.push(item);
  550. }
  551. });
  552. sheet.celldata = newCellData;
  553. });
  554. return currentData;
  555. }
  556. getExcelBolb() {
  557. let currentData = this.getExcelData();
  558. return getExcelBolob(currentData);
  559. }
  560. downloadExcel(checkValue) {
  561. let currentData = this.getExcelData(checkValue);
  562. exportExcel(currentData, '下载');
  563. }
  564. // 获取批注
  565. getComment() {
  566. let sheets = this.luckysheet.toJson().data;
  567. let comment = [];
  568. sheets.forEach(sheet => {
  569. sheet.celldata.forEach(cell => {
  570. // 判断是否含有批注
  571. if (cell?.v?.ps?.value) {
  572. comment.push({
  573. sheet: sheet.name,
  574. r: cell.r,
  575. c: cell.c,
  576. value: cell.v.ps.value || '',
  577. });
  578. }
  579. });
  580. });
  581. return comment;
  582. }
  583. render() {
  584. return (
  585. <iframe
  586. onLoad={e => {
  587. this.handleLoad(e);
  588. }}
  589. ref={this.sheetRef}
  590. src="/luckysheet.html"
  591. ></iframe>
  592. );
  593. }
  594. }
  595. export default LuckySheet;