index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import React, { useContext, useState } from 'react';
  2. import style from '../../index.less';
  3. import { App, Button, Empty, Space, Upload } from 'antd';
  4. import useNewToken from '@/Hook/useNewToken';
  5. import SettingsUserInherit from './settingsUserlnherit';
  6. import dayjs from 'dayjs';
  7. import { getUserInDataData, headerJsMustStyle, headerJsStyle, TIME_TYPE } from '../../const';
  8. import ExcelJS from 'exceljs';
  9. import { saveAs } from 'file-saver';
  10. import FilterUserText from '@/pages/weComTask/components/filterUser/filterUserText';
  11. import { RcFile } from 'antd/es/upload';
  12. import { groupBy, readFileAsBuffer } from '@/utils/utils';
  13. import PreviewUserInherit from './previewUserlnherit';
  14. import { DispatchTaskCreate } from '../..';
  15. /**
  16. * 客户继承
  17. * @returns
  18. */
  19. const UserInherit: React.FC = () => {
  20. /***************************************************/
  21. const { token } = useNewToken()
  22. const { message } = App.useApp()
  23. const { setSettings, settings, bookPlatForm, bookList, onPreviewReset } = useContext(DispatchTaskCreate)!;
  24. const [newVisible, setNewVisible] = React.useState(false);
  25. const [downloadLoading, setDownloadLoading] = useState<boolean>(false)
  26. /***************************************************/
  27. // 导出Excel
  28. const exportExcel = async () => {
  29. setDownloadLoading(true)
  30. const workbook = new ExcelJS.Workbook(); // 创建空工作簿
  31. const worksheet = workbook.addWorksheet('Sheet1'); // 添加工作表
  32. const headerRowIndex = 1; // 表头行索引
  33. // 表头冻结
  34. worksheet.views = [
  35. {
  36. state: 'frozen',
  37. ySplit: 1, // 冻结行数
  38. topLeftCell: 'A2' // 冻结后可见区域的起始单元格
  39. }
  40. ];
  41. // 设置全局禁止
  42. worksheet.protect('yourPwd', {
  43. deleteColumns: false, // 禁止删除列
  44. sort: false, // 禁止排序
  45. autoFilter: false // 禁止自动筛选
  46. });
  47. worksheet.columns = [
  48. { key: 'A1', width: 30, header: '账号(原跟进企微号)' },
  49. { key: 'A2', width: 20, header: '继承标题' },
  50. { key: 'A3', width: 30, header: '策略信息' },
  51. { key: 'A4', width: 50, header: '继承对象' },
  52. { key: 'A5', width: 40, header: '继承后文本内容' },
  53. { key: 'A6', width: 30, header: '接替客户企微号' },
  54. { key: 'A7', width: 30, header: '接替客户企微号ID' },
  55. ];
  56. // 表头设置样式
  57. worksheet.getRow(headerRowIndex).height = 30;
  58. Array(7).fill(0).forEach((_, index) => {
  59. const col = index + 1; // 从第1列开始
  60. // 设置表头字体样式
  61. worksheet.getCell(headerRowIndex, col).style = index >= 5 ? headerJsMustStyle as any : headerJsStyle as any;
  62. })
  63. const userInData = getUserInDataData(settings?.userInherit?.schedulingStrategyDTO, settings?.userInherit?.taskName)
  64. const corpUserListLength = settings?.corpUsers?.length || 0;
  65. const dataLength = userInData.length || 0;
  66. // 解放填写限制区域
  67. const unLockArea = [];
  68. let dataRow = 1
  69. // 数据内容
  70. settings?.corpUsers?.forEach((item, i) => {
  71. userInData.forEach((dataItem, ii) => {
  72. unLockArea.push(`${dataRow}_6`)
  73. unLockArea.push(`${dataRow}_7`)
  74. dataRow++;
  75. worksheet.addRow([
  76. // 账号
  77. `${item.name}(ID:${item.corpUserId})`,
  78. // 继承标题
  79. `${dataItem?.taskName}`,
  80. // 策略信息
  81. {
  82. richText: [
  83. { text: `(策略${dataItem?.strategyIndex})\n`, font: { bold: true } },
  84. { text: `名称:${dataItem?.strategyData?.strategyName || '<空>'}\n` },
  85. {
  86. text: (`执行类型:${TIME_TYPE[dataItem?.strategyData?.timeRepeatType]}\n`) +
  87. (dataItem?.strategyData?.sendDay ? `执行时间:${dataItem?.strategyData?.sendDay}\n` : '') +
  88. (dataItem?.strategyData?.startTime ? `执行日期:${dataItem?.strategyData?.startTime}~${dataItem?.strategyData?.endTime ? dataItem?.strategyData?.endTime : '长期执行'}\n` : '') +
  89. (dataItem?.strategyData?.sendTime ? '执行时间:' + dataItem?.strategyData?.sendTime + '\n' : '') +
  90. (dataItem?.strategyData?.repeatArray ? `执行天数:${dataItem?.strategyData?.repeatArray.join('、')}\n` : '')
  91. }
  92. ]
  93. },
  94. // 转移对象
  95. {
  96. richText: [
  97. { text: `(转移对象${dataItem?.inheritIndex})\n`, font: { bold: true } },
  98. {
  99. text: `类型:${dataItem?.inheritData?.transferType === 'all' ? '全部' : '指定'}\n` +
  100. (dataItem?.inheritData?.transferType === 'specify' && dataItem?.inheritData?.transferUserDto ? FilterUserText({
  101. data: dataItem?.inheritData?.transferUserDto?.configContent,
  102. configName: dataItem?.inheritData?.transferUserDto?.configName,
  103. bookCityList: bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey })),
  104. bookPlatForm,
  105. bookList
  106. }) : '')
  107. }
  108. ]
  109. },
  110. // 成功发送内容
  111. dataItem.inheritData?.transferSuccessMsg || '<空>',
  112. // 图文链接
  113. '请输入',
  114. // 小程序APPID
  115. '请输入'
  116. ])
  117. })
  118. })
  119. console.log('userInData', userInData)
  120. Array(corpUserListLength * dataLength).fill(0).forEach((_, index) => {
  121. worksheet.getRow(index + headerRowIndex + 1).height = 100; // 设置行高
  122. worksheet.getRow(index + headerRowIndex + 1).alignment = {
  123. vertical: 'middle',
  124. wrapText: true
  125. }
  126. // 分组设置背景色
  127. if (index % (dataLength * 2) < dataLength) {
  128. worksheet.getRow(index + headerRowIndex + 1).eachCell(function (cell, rowNumber) {
  129. if (rowNumber <= 8) {
  130. cell.border = {
  131. top: { style: 'thin', color: { argb: 'D4D4D4' } },
  132. bottom: { style: 'thin', color: { argb: 'D4D4D4' } },
  133. left: { style: 'thin', color: { argb: 'D4D4D4' } },
  134. right: { style: 'thin', color: { argb: 'D4D4D4' } }
  135. }
  136. cell.fill = {
  137. type: 'pattern',
  138. pattern: 'solid',
  139. fgColor: { argb: 'f0f0f0' }
  140. }
  141. }
  142. })
  143. } else {
  144. worksheet.getRow(index + headerRowIndex + 1).eachCell(function (cell, rowNumber) {
  145. if (rowNumber <= 8) {
  146. cell.border = {
  147. top: { style: 'thin', color: { argb: 'D4D4D4' } },
  148. bottom: { style: 'thin', color: { argb: 'D4D4D4' } },
  149. left: { style: 'thin', color: { argb: 'D4D4D4' } },
  150. right: { style: 'thin', color: { argb: 'D4D4D4' } }
  151. }
  152. }
  153. })
  154. }
  155. })
  156. // 设置可填写区域
  157. for (let rowNum = 1; rowNum <= corpUserListLength * dataLength; rowNum++) {
  158. for (let colNum = 1; colNum <= 3; colNum++) {
  159. if (unLockArea.includes(`${rowNum}_${colNum + 5}`)) {
  160. const cell = worksheet.getCell(rowNum + 1, colNum + 5);
  161. cell.protection = { locked: false };
  162. cell.fill = {
  163. type: 'pattern',
  164. pattern: 'solid',
  165. fgColor: { argb: 'ffadd2' }
  166. }
  167. }
  168. }
  169. }
  170. // 生成文件内容
  171. const buffer = await workbook.xlsx.writeBuffer();
  172. // 通过 Blob 下载
  173. const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  174. saveAs(blob, `客户继承配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`);
  175. setTimeout(() => setDownloadLoading(false), 100)
  176. }
  177. // 读取Excel
  178. const readExcelContent = async (file: RcFile) => {
  179. const buffer: any = await readFileAsBuffer(file);
  180. const workbook = new ExcelJS.Workbook();
  181. await workbook.xlsx.load(buffer); // 加载 Excel 内容
  182. const worksheet = workbook.worksheets[0]; // 获取第一个工作表
  183. const data = [];
  184. worksheet.eachRow((row) => {
  185. const rowData = {};
  186. row.eachCell((cell, colNumber) => {
  187. if (cell.isHyperlink) {
  188. rowData[`col${colNumber}`] = cell.text || '';
  189. } else {
  190. rowData[`col${colNumber}`] = cell.value || '';
  191. }
  192. });
  193. data.push(rowData);
  194. });
  195. data.shift(); // 删除表头
  196. const NULLDATA = ['请输入', '']
  197. if (data?.some(item => (NULLDATA.includes(item.col6) || !item?.col6 || NULLDATA.includes(item.col7) || !item?.col7))) {
  198. message.error('请正确填写Excel内容!')
  199. return
  200. }
  201. const msgData = getUserInDataData(settings?.userInherit?.schedulingStrategyDTO, settings?.userInherit?.taskName)
  202. console.log('读取的Excel数据', data, msgData)
  203. if (msgData.length * settings?.corpUsers?.length !== data?.length) {
  204. message.error('数量对不上,请正确填写Excel内容!')
  205. return
  206. }
  207. const groupData = groupBy(data, (item) => [item['col1']]).reduce((acc, cur) => {
  208. const id = cur[0]['col1'].split('ID:')[1].replace(/\)$/, '');
  209. acc[id] = cur
  210. return acc;
  211. }, {});
  212. const corpUsers = settings?.corpUsers?.map(item => {
  213. const externalUserTransferContent = []
  214. groupData[item.corpUserId]?.forEach((i: any) => {
  215. const strategyIndex = i.col3.richText[0].text.split('(策略')[1]?.split(')')[0]
  216. const sendDataIndex = i.col4.richText[0].text.split('(转移对象')[1]?.split(')')[0]
  217. const layer1 = strategyIndex - 1;
  218. if (!externalUserTransferContent[layer1]) externalUserTransferContent[layer1] = []; // 初始化第二层
  219. externalUserTransferContent[strategyIndex - 1][sendDataIndex - 1] = {
  220. corpUserName: i.col6 === '<空>' ? undefined : i.col6?.toString()?.trim(),
  221. corpUserId: i.col7 === '<空>' ? undefined : i.col7?.toString()?.trim()
  222. }
  223. })
  224. console.log('内容--->', externalUserTransferContent)
  225. return {
  226. ...item,
  227. externalUserTransferContent
  228. }
  229. })
  230. console.log('corpUsers', corpUsers)
  231. setSettings({
  232. ...settings,
  233. corpUsers
  234. })
  235. }
  236. return <>
  237. <div className={`${style.settingsBody_content_row}`}>
  238. <div className={`${style.settingsBody_content_col}`}>
  239. <div className={style.title}>
  240. <span>客户继承</span>
  241. {(settings?.userInherit && Object.keys(settings?.userInherit)?.length) && <Space>
  242. <Button
  243. type="link"
  244. style={{ padding: 0, fontSize: 12 }}
  245. loading={downloadLoading}
  246. onClick={() => { exportExcel() }}
  247. >下载</Button>
  248. <Upload
  249. accept={'.xlsx'}
  250. action="#"
  251. showUploadList={false}
  252. customRequest={() => { }}
  253. beforeUpload={async (file: RcFile): Promise<any> => {
  254. readExcelContent(file)
  255. onPreviewReset()
  256. }}
  257. >
  258. <Button
  259. type="link"
  260. style={{ padding: 0, fontSize: 12 }}
  261. >{settings?.corpUsers?.every(item => item?.externalUserTransferContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
  262. </Upload>
  263. <Button
  264. type="link"
  265. danger
  266. style={{ padding: 0, fontSize: 12 }}
  267. onClick={() => {
  268. const corpUsers = settings?.corpUsers?.map(item => {
  269. delete item?.externalUserTransferContent
  270. return item
  271. })
  272. setSettings({
  273. ...settings,
  274. corpUsers,
  275. userInherit: undefined
  276. })
  277. onPreviewReset()
  278. }}
  279. >清空</Button>
  280. </Space>}
  281. </div>
  282. <div className={style.detail}>
  283. <div className={style.detail_title}>客户继承配置</div>
  284. <div className={style.detail_body}>
  285. {settings?.userInherit && Object.keys(settings?.userInherit).length > 0 ? <>
  286. <PreviewUserInherit userInherit={settings?.userInherit} />
  287. </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
  288. </div>
  289. </div>
  290. <div className={style.detail_footer}>
  291. <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} onClick={() => setNewVisible(true)}>编辑</Button>
  292. </div>
  293. </div>
  294. </div>
  295. {/* 新增客户继承模板 */}
  296. {newVisible && <SettingsUserInherit
  297. visible={newVisible}
  298. value={settings?.userInherit}
  299. onChange={(values) => {
  300. setSettings({
  301. ...settings,
  302. userInherit: values
  303. })
  304. setNewVisible(false)
  305. onPreviewReset()
  306. }}
  307. onClose={() => {
  308. setNewVisible(false)
  309. }}
  310. />}
  311. </>
  312. };
  313. export default React.memo(UserInherit);