|  | @@ -285,6 +285,174 @@ const EMPTY_BOOK = (() => {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  // 获取最大别名编码
 | 
	
		
			
				|  |  | +  const getMaxClassCode = (priceInfoSummary) => {
 | 
	
		
			
				|  |  | +    let maxClassCode = '';
 | 
	
		
			
				|  |  | +    let maxClassNumber = 0;
 | 
	
		
			
				|  |  | +    priceInfoSummary.forEach(item => {
 | 
	
		
			
				|  |  | +      if (!item.classCode) {
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      const numMatch = item.classCode.match(/\d+/);
 | 
	
		
			
				|  |  | +      if (numMatch && +numMatch[0] > maxClassNumber) {
 | 
	
		
			
				|  |  | +        maxClassNumber = +numMatch[0];
 | 
	
		
			
				|  |  | +        maxClassCode = item.classCode;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +    // return { maxClassNumber, maxClassCode };
 | 
	
		
			
				|  |  | +    return maxClassCode;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // 在最大别名编码基础上+1;
 | 
	
		
			
				|  |  | +  const getNewMaxClassCode = (maxClassCode) => {
 | 
	
		
			
				|  |  | +    const numMatch = maxClassCode.match(/\d+/);
 | 
	
		
			
				|  |  | +    if (!numMatch) {
 | 
	
		
			
				|  |  | +      return 1;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    const maxNumber = +numMatch[0];
 | 
	
		
			
				|  |  | +    let newMaxClassCode = String(maxNumber + 1);
 | 
	
		
			
				|  |  | +    // 补齐的位数
 | 
	
		
			
				|  |  | +    const pattern = numMatch[0].length - newMaxClassCode.length;
 | 
	
		
			
				|  |  | +    if (pattern > 0) {
 | 
	
		
			
				|  |  | +      for (let i = 0; i < pattern; i++) {
 | 
	
		
			
				|  |  | +        newMaxClassCode = `0${newMaxClassCode}`;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return newMaxClassCode;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ai填值
 | 
	
		
			
				|  |  | +  const aiMatch = async () => {
 | 
	
		
			
				|  |  | +    try {
 | 
	
		
			
				|  |  | +      // 获取信息价总表
 | 
	
		
			
				|  |  | +      const priceInfoSummary = await ajaxPost('/priceInfoSummary/getData', {}, 1000 * 60 * 5);
 | 
	
		
			
				|  |  | +      const summaryGroupMap = _.groupBy(priceInfoSummary, 'code');
 | 
	
		
			
				|  |  | +      const noCodeSummary = priceInfoSummary.filter(item => !item.code);
 | 
	
		
			
				|  |  | +      const totalRows = workBookObj.sheet.getRowCount();
 | 
	
		
			
				|  |  | +      const changedCells = [];
 | 
	
		
			
				|  |  | +      const noMatchRows = []; // 没有匹配、ai没有命中的行,后续需要自动生成别名编码(最大的别名编码+1)
 | 
	
		
			
				|  |  | +      for (let i = 0; i < totalRows; i++) {
 | 
	
		
			
				|  |  | +        const rowData = getRowData(workBookObj.sheet, i, setting.header);
 | 
	
		
			
				|  |  | +        const code = rowData.code || '';
 | 
	
		
			
				|  |  | +        const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
 | 
	
		
			
				|  |  | +        if (toMatchSummary.length) {
 | 
	
		
			
				|  |  | +          changedCells.push({ row: i });
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          noMatchRows.push(i);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      const classCodeCol = setting.header.findIndex(h => h.dataCode === 'classCode');
 | 
	
		
			
				|  |  | +      const expStringCol = setting.header.findIndex(h => h.dataCode === 'expString');
 | 
	
		
			
				|  |  | +      const chunks = _.chunk(changedCells, 20);
 | 
	
		
			
				|  |  | +      let percent = 0;
 | 
	
		
			
				|  |  | +      $.bootstrapLoading.progressStart('AI填值', false);
 | 
	
		
			
				|  |  | +      $("#progress_modal_body").text('正在进行AI填值,请稍后...');
 | 
	
		
			
				|  |  | +      await setTimeoutSync(500);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // 分块进行ai匹配
 | 
	
		
			
				|  |  | +      const step = 100 / (chunks.length || 1);
 | 
	
		
			
				|  |  | +      for (const chunk of chunks) {
 | 
	
		
			
				|  |  | +        const listA = [];
 | 
	
		
			
				|  |  | +        const listB = [];
 | 
	
		
			
				|  |  | +        const summaryData = [];
 | 
	
		
			
				|  |  | +        chunk.forEach(item => {
 | 
	
		
			
				|  |  | +          const rowData = getRowData(workBookObj.sheet, item.row, setting.header);
 | 
	
		
			
				|  |  | +          listA.push(`${rowData.name || ''} ${rowData.specs}`);
 | 
	
		
			
				|  |  | +          const code = rowData.code || '';
 | 
	
		
			
				|  |  | +          const toMatchSummary = code ? summaryGroupMap[code] || [] : noCodeSummary;
 | 
	
		
			
				|  |  | +          summaryData.push(toMatchSummary);
 | 
	
		
			
				|  |  | +          const summaryKeys = toMatchSummary.map(summary => `${summary.name || ''} ${summary.specs || ''}`);
 | 
	
		
			
				|  |  | +          listB.push(summaryKeys)
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        const test = listB.map(item => item.length);
 | 
	
		
			
				|  |  | +        console.log(test);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const matchRes = await ajaxPost('/priceInfoSummary/aiMatch', { listA, listB }, 1000 * 60 * 5);
 | 
	
		
			
				|  |  | +        // 填匹配值到表格,不实时保存,因为需要人工核查
 | 
	
		
			
				|  |  | +        workBookObj.sheet.suspendEvent();
 | 
	
		
			
				|  |  | +        workBookObj.sheet.suspendPaint();
 | 
	
		
			
				|  |  | +        matchRes.forEach((item, index) => {
 | 
	
		
			
				|  |  | +          const firstMatch = item[0];
 | 
	
		
			
				|  |  | +          const chunkItem = chunk[index];
 | 
	
		
			
				|  |  | +          // 相似度过低的不命中
 | 
	
		
			
				|  |  | +          if (firstMatch.similarity < 50) {
 | 
	
		
			
				|  |  | +            noMatchRows.push(chunkItem.row);
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +          };
 | 
	
		
			
				|  |  | +          const summaryIndex = item[0].index;
 | 
	
		
			
				|  |  | +          const summaryItem = summaryData[index][summaryIndex];
 | 
	
		
			
				|  |  | +          if (chunkItem && summaryItem) {
 | 
	
		
			
				|  |  | +            workBookObj.sheet.setValue(chunkItem.row, classCodeCol, summaryItem.classCode);
 | 
	
		
			
				|  |  | +            // 如果实际行存在珠海地区的,才填计算式
 | 
	
		
			
				|  |  | +            const tableItems = getItemsFromTableItem(cache[chunkItem.row]);
 | 
	
		
			
				|  |  | +            const needExpString = tableItems.some(tItem => {
 | 
	
		
			
				|  |  | +              const area = AREA_BOOK.cache.find(areaItem => areaItem.ID === tItem.areaID)
 | 
	
		
			
				|  |  | +              return area && area.name && /珠海/.test(area.name);
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +            if (needExpString) {
 | 
	
		
			
				|  |  | +              workBookObj.sheet.setValue(chunkItem.row, expStringCol, summaryItem.expString);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        workBookObj.sheet.resumeEvent();
 | 
	
		
			
				|  |  | +        workBookObj.sheet.resumePaint();
 | 
	
		
			
				|  |  | +        percent += step;
 | 
	
		
			
				|  |  | +        $("#progress_modal_bar").css('width', `${percent}%`);
 | 
	
		
			
				|  |  | +        await setTimeoutSync(500);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // 没匹配到的行,自动生成别名编码
 | 
	
		
			
				|  |  | +      workBookObj.sheet.suspendEvent();
 | 
	
		
			
				|  |  | +      workBookObj.sheet.suspendPaint();
 | 
	
		
			
				|  |  | +      let curMaxClassCode = getMaxClassCode(priceInfoSummary);
 | 
	
		
			
				|  |  | +      for (const row of noMatchRows) {
 | 
	
		
			
				|  |  | +        const newClassCode = getNewMaxClassCode(curMaxClassCode);
 | 
	
		
			
				|  |  | +        workBookObj.sheet.setValue(row, classCodeCol, newClassCode);
 | 
	
		
			
				|  |  | +        curMaxClassCode = newClassCode;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      workBookObj.sheet.resumeEvent();
 | 
	
		
			
				|  |  | +      workBookObj.sheet.resumePaint();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    } catch (error) {
 | 
	
		
			
				|  |  | +      console.log(error);
 | 
	
		
			
				|  |  | +      alert(error);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    await setTimeoutSync(500);
 | 
	
		
			
				|  |  | +    $.bootstrapLoading.progressEnd();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // 保存ai填值
 | 
	
		
			
				|  |  | +  const saveData = async () => {
 | 
	
		
			
				|  |  | +    try {
 | 
	
		
			
				|  |  | +      $.bootstrapLoading.progressStart('保存AI填值', false);
 | 
	
		
			
				|  |  | +      $("#progress_modal_body").text('正在保存AI填值,请稍后...');
 | 
	
		
			
				|  |  | +      await setTimeoutSync(500);
 | 
	
		
			
				|  |  | +      // 分批保存数据,以免数据库压力过大
 | 
	
		
			
				|  |  | +      const totalRows = workBookObj.sheet.getRowCount();
 | 
	
		
			
				|  |  | +      const changedCells = [];
 | 
	
		
			
				|  |  | +      for (let i = 0; i < totalRows; i++) {
 | 
	
		
			
				|  |  | +        changedCells.push({ row: i });
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      const chunks = _.chunk(changedCells, 100);
 | 
	
		
			
				|  |  | +      let percent = 0;
 | 
	
		
			
				|  |  | +      const step = 100 / (chunks.length || 1);
 | 
	
		
			
				|  |  | +      for (const chunk of chunks) {
 | 
	
		
			
				|  |  | +        await handleEdit(chunk);
 | 
	
		
			
				|  |  | +        percent += parseInt(`${step}`);
 | 
	
		
			
				|  |  | +        $("#progress_modal_bar").css('width', `${percent}%`);
 | 
	
		
			
				|  |  | +        await setTimeoutSync(200);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    } catch (error) {
 | 
	
		
			
				|  |  | +      console.log(error);
 | 
	
		
			
				|  |  | +      alert(error);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    setTimeout(() => {
 | 
	
		
			
				|  |  | +      $.bootstrapLoading.progressEnd();
 | 
	
		
			
				|  |  | +    }, 500);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    return {
 | 
	
		
			
				|  |  |      buildWorkBook,
 | 
	
	
		
			
				|  | @@ -294,6 +462,8 @@ const EMPTY_BOOK = (() => {
 | 
	
		
			
				|  |  |      workBookObj,
 | 
	
		
			
				|  |  |      updateRowCode,
 | 
	
		
			
				|  |  |      saveInSummary,
 | 
	
		
			
				|  |  | +    aiMatch,
 | 
	
		
			
				|  |  | +    saveData,
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  })();
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -314,4 +484,14 @@ $(document).ready(() => {
 | 
	
		
			
				|  |  |    $('#save-in-summary').click(() => {
 | 
	
		
			
				|  |  |      EMPTY_BOOK.saveInSummary();
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // AI填值
 | 
	
		
			
				|  |  | +  $('#ai-match').click(() => {
 | 
	
		
			
				|  |  | +    EMPTY_BOOK.aiMatch();
 | 
	
		
			
				|  |  | +  })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // 保存AI填值
 | 
	
		
			
				|  |  | +  $('#save-data').click(() => {
 | 
	
		
			
				|  |  | +    EMPTY_BOOK.saveData();
 | 
	
		
			
				|  |  | +  })
 | 
	
		
			
				|  |  |  });
 |