我所收集的几种ComboBox填充方式

本文介绍了五种使用ExtJS框架填充ComboBox组件的方法,包括数组填充、本地Json数据填充、动态Json数据填充等,并提供了详细的代码示例。
我所收集的几种ComboBox填充方式: 第一种:数组 var data = [['1', 'Lislie', 'D005', 'male'], ['2', 'Merry', 'D004', 'female'], ['3', 'Edison', 'D003', 'male'], ['4', 'Mark', 'D002', 'male'], ['5', 'Leeon', 'D001', 'male']]; // 格式化数据 var ds = new Ext.data.Store({ proxy : new Ext.data.MemoryProxy(data), // 数据源 reader : new Ext.data.ArrayReader({}, [ // 如何解析 { name : 'id' }, { name : 'name' }, { name : 'depno' }, { name : 'sex' }]) }); ds.load(); //这一句很关键,表是打开页面就加载 this.storeList = new Ext.form.ComboBox({ store : ds, fieldLabel : 'dfsfsd', allowBlank : false, forceSelection : true, valueField : 'id', // option.value displayField : 'name', // option.text typeAhead : true, triggerAction : 'all', emptyText : 'Select a store...', mode : 'local', //如果前面用了load,那么这里就应该用local,默认为remote selectOnFocus : true, width : 135 }); 第二种:本地(内存)简单Json数据 var folders = [{ 'folderId' : '1', 'folderName' : '收信箱' }, { 'folderId' : '2', 'folderName' : '发信箱' }]; //用于下拉列表的store var foldersJsonStore = new Ext.data.SimpleStore({ fields: [{ name: 'folderId', mapping: 'folderId' }, { name: 'folderName', mapping: 'folderName' }], data: folders }); foldersJsonStore.loadData(folders); this.combo = new Ext.form.ComboBox({ fieldLabel : '文件夹', name : 'folderMoveTo', displayField : 'folderName', valueField : 'folderId', typeAhead : true, // 自动将第一个搜索到的选项补全输入 triggerAction : 'all', emptyText : '全部', selectOnFocus : true, forceSelection : true, store : foldersJsonStore, typeAhead : true, lazyInit : false, lazyRender : false, mode : 'local' }); 第三种:动态简单Json数据 // 用于下拉列表的store var foldersJsonStore = new Ext.data.SimpleStore({ proxy : new Ext.data.HttpProxy({ url : './jsp/comboxdata.jsp' }), // 数据源 fields : [{ name : 'folderId', mapping : 'folderId' }, { name : 'folderName', mapping : 'folderName' }] }); this.combo = new Ext.form.ComboBox({ fieldLabel : '文件夹', name : 'folderMoveTo', displayField : 'folderName', valueField : 'folderId', typeAhead : true, // 自动将第一个搜索到的选项补全输入 triggerAction : 'all', emptyText : '全部', selectOnFocus : true, forceSelection : true, store : foldersJsonStore, typeAhead : true, lazyInit : false, lazyRender : false, mode : 'remote' //这里的参数要注意 }); comboxdata.jsp: <%@ page language="java" import="java.util.*,com.googlecode.jsonplugin.JSONUtil"%> <% List list = new ArrayList(); HashMap hm = new HashMap(); hm.put("folderId", "1"); hm.put("folderName", "11111111111"); list.add(hm); hm = new HashMap(); hm.put("folderId", "2"); hm.put("folderName", "2222222222222"); list.add(hm); try { String json = JSONUtil.serialize(list); out.print(json); } catch (Exception ex) { ex.printStackTrace(); } %> 第四种:JsonReader读取到的数据 // 用于下拉列表的store var foldersJsonStore = new Ext.data.Store({ proxy : new Ext.data.HttpProxy({ url : './jsp/comboxdata.jsp' }), // 数据源 reader : new Ext.data.JsonReader({}, ['folderId', 'folderName']) }); this.combo = new Ext.form.ComboBox({ fieldLabel : '文件夹', name : 'folderMoveTo', displayField : 'folderName', valueField : 'folderId', typeAhead : true, // 自动将第一个搜索到的选项补全输入 triggerAction : 'all', emptyText : '全部', selectOnFocus : true, forceSelection : true, store : foldersJsonStore, typeAhead : true, lazyInit : false, lazyRender : false, mode : 'remote' }); comboxdata.jsp: <%@ page language="java" import="java.util.*,com.googlecode.jsonplugin.JSONUtil"%> <% List list = new ArrayList(); HashMap hm = new HashMap(); hm.put("folderId", "1"); hm.put("folderName", "11111111111"); list.add(hm); hm = new HashMap(); hm.put("folderId", "2"); hm.put("folderName", "2222222222222"); list.add(hm); try { String json = JSONUtil.serialize(list); out.print(json); } catch (Exception ex) { ex.printStackTrace(); } %> 第五种:ScriptTagProxy方式取得的数据 // construct the ComboBox's DataStore var dsCustomer = new Ext.data.Store({ proxy : new Ext.data.ScriptTagProxy({ url : './jsp/comboxdata_1.jsp' }), reader : new Ext.data.JsonReader({ root : 'gridRows', totalProperty : 'totalCount' }, [{ name : 'id', mapping : 'id' }, { name : 'name', mapping : 'name' }]) }); // construct a ComboBox this.customerCmb = new Ext.form.ComboBox({ fieldLabel : '区別', store : dsCustomer, displayField : 'name', valueField : 'id', typeAhead : true, loadingText : 'loading...', width : 160, hiddenName : 'name', //hideTrigger : true, allowBlank : false, minChars : 1, forceSelection : true, triggerAction : 'all' }); comboxdata_1.jsp : <%@ page language="java" import="java.util.*,com.googlecode.jsonplugin.JSONUtil"%> <% String cb = request.getParameter("callback"); if (cb != null) { response.setContentType("text/javascript,charset=utf8"); } List list = new ArrayList(); HashMap hm = new HashMap(); hm.put("id", "1"); hm.put("name", "11111111111"); list.add(hm); hm = new HashMap(); hm.put("id", "2"); hm.put("name", "2222222222222"); list.add(hm); HashMap rehm = new HashMap(); rehm.put("totalCount","1"); rehm.put("gridRows",list); try { String json = JSONUtil.serialize(rehm); System.out.println("json:"+json); out.print(cb + "("); out.print(json); out.print(");"); } catch (Exception ex) { ex.printStackTrace(); } /* System.out.println(cb); String json="{/"totalCount/": 1,/"gridRows/":["+"{/"id/" :1 ,/"name/":/"zhongxiaogang/"},{/"id/" :2 ,/"name/":/"linhuarui/"}]}"; out.write(cb + "("); out.print(json); out.write(");"); */ %> 示例文件: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>ComboBoxTest.html</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css" /> <script type="text/javascript" src="ext/ext-base.js"> </script> <script type="text/javascript" src="ext/ext-all.js"> </script> <script type="text/javascript" src="ext/ext-lang-zh_CN.js"> </script> <script> Ext.onReady(function(){ Ext.QuickTips.init(); var data = [['1', 'Lislie', 'D005', 'male'], ['2', 'Merry', 'D004', 'female'], ['3', 'Edison', 'D003', 'male'], ['4', 'Mark', 'D002', 'male'], ['5', 'Leeon', 'D001', 'male']]; // 格式化数据 var ds = new Ext.data.Store({ proxy: new Ext.data.MemoryProxy(data), // 数据源 reader: new Ext.data.ArrayReader({}, [ // 如何解析 { name: 'id' }, { name: 'name' }, { name: 'depno' }, { name: 'sex' }]) }); ds.load(); var storeList = new Ext.form.ComboBox({ store: ds, fieldLabel: 'dfsfsd', allowBlank: false, forceSelection: true, valueField: 'id', // option.value typeAhead: true, displayField: 'name', // option.text triggerAction: 'all', emptyText: 'Select a store...', mode: 'local', selectOnFocus: true, width: 135 }); var folders = [{ 'folderId': '1', 'folderName': '收信箱' }, { 'folderId': '2', 'folderName': '发信箱' }]; //用于下拉列表的store var foldersJsonStore = new Ext.data.SimpleStore({ fields: [{ name: 'folderId', mapping: 'folderId' }, { name: 'folderName', mapping: 'folderName' }], data: folders }); foldersJsonStore.loadData(folders); var combo = new Ext.form.ComboBox({ fieldLabel: '文件夹', name: 'folderMoveTo', store: foldersJsonStore, displayField: 'folderName', valueField: 'folderId', mode: 'local', typeAhead: true, //自动将第一个搜索到的选项补全输入 triggerAction: 'all', emptyText: '全部', selectOnFocus: true, forceSelection: true }); var provinces = [[1, '北京'], [2, '上海']]; var cities = new Array(); cities[1] = [[11, '海淀'], [22, '东城']]; cities[2] = [[33, '黄埔'], [44, '浦东'], [55, '静安']]; var comboProvinces = new Ext.form.ComboBox({ store: new Ext.data.SimpleStore({ fields: ["provinceId", "provinceName"], data: provinces }), listeners: { select: function(combo, record, index){ comboCities.clearValue(); comboCities.store.loadData(cities[record.data.provinceId]); } }, valueField: "provinceId", displayField: "provinceName", mode: 'local', forceSelection: true, blankText: '请选择省份', emptyText: '请选择省份', hiddenName: 'provinceId', editable: false, triggerAction: 'all', allowBlank: true, fieldLabel: '请选择省份', name: 'provinceId', width: 80 }); var comboCities = new Ext.form.ComboBox({ store: new Ext.data.SimpleStore({ fields: ["cityId", 'cityName'], data: [] }), valueField: "cityId", displayField: "cityName", mode: 'local', forceSelection: true, blankText: '选择地区', emptyText: '选择地区', hiddenName: 'cityId', editable: false, triggerAction: 'all', allowBlank: true, fieldLabel: '选择地区', name: 'cityId', width: 80 }); var form = new Ext.form.FormPanel({ region: 'center', labelWidth: 100, border: false, labelAlign: 'right', applyTo: 'local1', height: 200, width: 400, layout: 'form', items: [storeList, combo,comboProvinces,comboCities] }); }); </script> </head> <body> <div id="local1"> </div> </body> </html>
继续统计:所有需求如下: 1、统计"差分種別"列无颜色填充的行对应的"备注"列值填入对应的P列 2、统计"差分種別"列有颜色填充的行对应的"备注"列值填入对应的W列 3、统计"差分種別"列,单元格为”追加“无颜色填充的行对应的"备注"列值填入对应的AA列 4、统计"差分種別"列,单元格为”追加“有颜色填充的行对应的"备注"列值填入对应的AD列 5、统计"差分種別"列,单元格为”変更“无颜色填充的行对应的"备注"列值填入对应的AH列 6、统计"差分種別"列,单元格为”変更“有颜色填充的行对应的"备注"列值填入对应的AK列 7、统计"差分種別"列,单元格为”削除“无颜色填充的行对应的"备注"列值填入对应的AP列 8、统计"差分種別"列,单元格为”削除“有颜色填充的行对应的"备注"列值填入对应的AS列 9、统计"差分の判断有意/無効"列,单元格为”有意“无颜色填充的行对应的"备注"列值填入对应的AX列 10、统计"差分の判断有意/無効"列,单元格为”有意“有颜色填充的行对应的"备注"列值填入对应的BA列 11、统计"差分の判断有意/無効"列,单元格为”無効“无颜色填充的行对应的"备注"列值填入对应的BE列 12、统计"差分の判断有意/無効"列,单元格为”無効“有颜色填充的行对应的"备注"列值填入对应的BH列 13、统计"差分の判断有意/無効"列,单元格为”有意“且列”判断理由“无颜色填充的行对应的"备注"列值填入对应的BL列 14、统计"差分の判断有意/無効"列,单元格为”有意“且列”判断理由“有颜色填充的行对应的"备注"列值填入对应的BO列 15、统计"差分の判断有意/無効"列,单元格为”無効“且列”判断理由“无颜色填充的行对应的"备注"列值填入对应的BS列 16、统计"差分の判断有意/無効"列,单元格为”無効“且列”判断理由“有颜色填充的行对应的"备注"列值填入对应的BV列 17、统计"変更背景"列,无颜色填充的行对应的"备注"列值填入对应的BZ列 18、统计"変更背景"列,有颜色填充的行对应的"备注"列值填入对应的CG列 未查找到或者说是统计数量为0则填入符号/
最新发布
08-06
<think>我们面临的任务是扩展已有的SCL文件处理系统,增加多种统计条件,并将结果写入Excel的不同列中。由于条件复杂,我们需要重构代码以支持多种统计规则。 设计思路: 1. 定义一个规则列表,每个规则包含: - 目标列(写入Excel的列) - 条件函数(判断行是否满足条件) 2. 对于每个SCL文件,遍历每一行,应用所有规则,将符合条件的行对应的备注列值收集起来 3. 对每个规则收集到的值进行计数,然后按指定格式写入Excel的相应列 具体步骤: 1. 在SCLProcessor类中增加一个规则列表,用于存储各种统计规则。 2. 修改process_scl_file方法,使其能够处理多条规则。 3. 为每种规则编写条件函数(使用lambda或定义内部函数)。 4. 在遍历行时,对每个规则分别收集符合条件的备注值。 5. 处理完所有行后,对每个规则收集到的值进行计数,并生成结果字符串。 6. 将每个规则的结果字符串写入主Excel文件的对应列(在原始文件的当前行,不同的列)。 注意:由于规则较多,我们将规则定义为一个元组列表,每个元组包括: (规则名称, 目标列索引, 条件函数) 条件函数格式: def condition(row_index, diff_col_idx, note_col_idx, diff_valid_col_idx, ...): # 根据规则检查该行是否满足条件 # 返回True/False 由于条件函数可能需要访问多个列(如“差分種別”、“差分の判断有意/無効”、“判断理由”、“変更背景”等),我们需要在规则定义之前先找到这些列的列索引。 实现步骤: 1. 在打开SCL文件后,使用ColumnFinder查找所有需要的列,并记录列索引。 2. 定义规则列表,每个规则包含目标列(在主Excel文件中的列字母,如'P')和条件函数。 3. 初始化一个字典来存储每个规则的结果(列表,用于收集备注值)。 4. 遍历SCL文件的每一行(从标题行下一行开始): 对于每个规则,检查当前行是否满足条件,如果满足且备注值不为空,则收集备注值。 5. 遍历规则字典,对每个规则的备注值列表进行计数,生成结果字符串(格式:值,次数\n...) 6. 返回一个字典,键为规则名称,值为结果字符串(或者将多个结果字符串组合成一个大的结果字符串?)—— 实际上,我们需要将每个规则的结果分别写入主Excel文件的不同列。因此,在process_file方法中,我们需要为每个规则分别写入。 修改process_file方法: 在找到每个SCL文件后,调用process_scl_file,返回一个字典(规则名称到结果字符串的映射) 然后,对于每个规则,将结果字符串写入主Excel文件当前行的对应列(列索引由规则定义)。 规则定义示例: rules = [ ('rule1', 'P', lambda row, sheet: 条件1), ('rule2', 'W', lambda row, sheet: 条件2), ... ] 但是注意:规则中需要访问多个列,所以条件函数需要多个参数。我们可以这样设计: def condition1(row_idx, sheet, diff_col_idx, note_col_idx, ...): # 获取差分種別列的值 diff_cell = sheet.cell(row_idx, diff_col_idx) # 检查无颜色填充 if not color_detector.is_no_fill(diff_cell): return False # 其他条件... return True 由于规则多达18种,我们将其定义为内部函数,并提前获取所有需要的列索引。 具体实现: 步骤1:在process_scl_file方法中,先获取所有需要的列索引(如果找不到则跳过该规则,并记录错误) 步骤2:定义规则列表,每个规则是一个元组:(规则名称, 主Excel目标列索引, 条件函数) 注意:主Excel目标列索引是列字母,但写入时我们需要列号(数字)。我们可以用openpyxl.utils.column_index_from_string将字母转换为数字,但为了统一,我们在规则中直接存储列号(数字)。因为主Excel文件中,P列是16,W列是23,等等。 列字母与数字对应关系(从1开始): P -> 16 W -> 23 AA -> 27 AD -> 30 AH -> 34 AK -> 37 AP -> 42 AS -> 45 AX -> 50 BA -> 53 BE -> 57 BH -> 60 BL -> 62 BO -> 65 BS -> 71 BV -> 74 BZ -> 78 CG -> 85 步骤3:初始化一个字典,用于收集每个规则的结果(备注值列表) results = {rule_name: [] for rule_name, col_idx, condition in rules} 步骤4:遍历行,对每个规则应用条件函数,如果满足则收集备注值(注意:备注值需要非空) 步骤5:对每个规则的结果列表进行计数,生成结果字符串 步骤6:返回一个字典,键为规则名称,值为结果字符串(如果无数据,则返回'/') 步骤7:在process_file方法中,处理每个SCL文件后,得到一个规则结果字典,然后遍历规则列表,将每个规则的结果写入主Excel文件当前行的对应列(列号由规则指定) 注意:同一个SCL文件可能被多个规则处理,每个规则的结果写入主Excel文件当前行的不同列。 由于规则较多,我们将在process_scl_file方法中定义这些规则。为了可读性,我们将每个规则的条件单独定义为内部函数(或使用lambda,如果简单的话)。 但是,有些规则条件复杂(如涉及多个列),我们使用内部函数定义。 规则条件需要访问的列: - 差分種別 - 差分の判断有意/無効 - 判断理由 - 変更背景 因此,在process_scl_file中,我们需要先获取这些列的列索引(列名需要配置吗?目前先固定,因为需求明确) 列名固定: - 差分種別 - 备注 - 差分の判断有意/無効 - 判断理由 - 変更背景 如果某个列找不到,则跳过该规则,并记录错误,返回错误信息(但其他规则继续执行)。 实现: 由于代码量较大,下面只展示关键部分: 1. 在process_scl_file方法中,首先获取所有需要的列的位置: diff_col_idx = 列查找器.find_column("差分種別") # 返回(行,列)中的列 note_col_idx = 列查找器.find_column("备注") diff_valid_col_idx = 列查找器.find_column("差分の判断有意/無効") reason_col_idx = 列查找器.find_column("判断理由") background_col_idx = 列查找器.find_column("変更背景") 注意:列查找器返回的是(header_row, col_idx),我们只需要列索引col_idx。 2. 定义规则条件函数(内部函数或lambda),注意:条件函数参数为行索引,通过sheet对象获取单元格。 3. 定义规则列表,每个规则为(规则名称, 主Excel目标列号, 条件函数) 4. 初始化结果收集字典。 5. 遍历行(从标题行+1开始到最大行),对每个规则,检查条件,满足则记录备注值。 6. 生成结果字符串。 7. 返回规则结果字典。 注意:由于规则很多,遍历行时对每一行都要检查18个规则,可能会影响性能。如果文件很大,可以考虑优化。但通常SCL文件不会特别大。 下面我们编写代码框架,并实现前几个规则作为示例。 由于代码较长,我们将分步骤实现,并添加详细注释。 注意:未找到列时,该规则无法执行,返回错误信息(在规则结果中返回错误字符串,如“列[列名]不存在”),这样在主Excel文件中该规则对应的列就会显示错误信息。 但是,需求中要求如果统计数量为0则填入'/',所以如果规则执行过程中没有数据,则返回'/';如果列不存在,我们返回错误信息(如“列[列名]未找到”),这样用户就知道问题。 实现细节: 对于每个规则,如果所需的列不存在,则跳过该规则(不收集数据),并在结果中标记为错误信息。 我们可以这样设计:在定义规则之前,先检查该规则所需的列是否存在,如果不存在,则跳过该规则,并在结果字典中记录错误信息。 因此,在规则定义时,我们可以为每个规则指定它依赖的列(列名列表),然后检查这些列是否都找到了。 修改规则元组为: (rule_name, target_col, required_columns, condition_func) 其中,required_columns是一个列名的列表。 然后,在获取列索引后,检查这些列是否都存在(即列索引不为None)。如果有一个不存在,则该规则被标记为无效,结果字符串为错误信息。 否则,在遍历行时执行条件函数。 代码结构: 在process_scl_file中: # 获取列索引(列名到列索引的字典) columns_needed = { 'diff_col': "差分種別", 'note_col': "备注", 'diff_valid_col': "差分の判断有意/無効", 'reason_col': "判断理由", 'background_col': "変更背景" } column_indexes = {} for key, col_name in columns_needed.items(): pos = column_finder.find_column(col_name) if pos is None: column_indexes[key] = None else: column_indexes[key] = pos[1] # 列索引 # 定义规则列表:每个规则是 (规则名称, 主Excel目标列号, 依赖的列键列表, 条件函数) rules = [] # 规则1: 统计"差分種別"列无颜色填充的行对应的"备注"列值 def rule1_condition(row_idx, sheet, col_idx, color_detector): # 获取差分種別列单元格 cell = sheet.cell(row_idx, col_idx['diff_col']) # 无颜色填充 return color_detector.is_no_fill(cell) rules.append(('diff_no_fill', 16, ['diff_col'], rule1_condition)) # 规则2: 统计"差分種別"列有颜色填充的行对应的"备注"列值 def rule2_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_col']) return not color_detector.is_no_fill(cell) rules.append(('diff_fill', 23, ['diff_col'], rule2_condition)) # 规则3: 统计"差分種別"列,单元格为”追加“无颜色填充的行对应的"备注"列值 def rule3_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_col']) if not color_detector.is_no_fill(cell): return False # 检查值是否为"追加" value = cell.value return value == "追加" rules.append(('diff_add_no_fill', 27, ['diff_col'], rule3_condition)) # 规则4: 统计"差分種別"列,单元格为”追加“有颜色填充的行对应的"备注"列值 def rule4_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_col']) if color_detector.is_no_fill(cell): return False value = cell.value return value == "追加" rules.append(('diff_add_fill', 30, ['diff_col'], rule4_condition)) # ... 其他规则类似,按照需求逐一实现 # 注意:规则13-16需要两个列:差分の判断有意/無効 和 判断理由 # 规则13: 统计"差分の判断有意/無効"列,单元格为”有意“且列”判断理由“无颜色填充的行对应的"备注"列值 def rule13_condition(row_idx, sheet, col_idx, color_detector): # 获取差分の判断列 valid_cell = sheet.cell(row_idx, col_idx['diff_valid_col']) if valid_cell.value != "有意": return False # 获取判断理由列 reason_cell = sheet.cell(row_idx, col_idx['reason_col']) return color_detector.is_no_fill(reason_cell) rules.append(('valid_reason_no_fill', 62, ['diff_valid_col','reason_col'], rule13_condition)) # 规则14: 统计"差分の判断有意/無効"列,单元格为”有意“且列”判断理由“有颜色填充的行对应的"备注"列值 def rule14_condition(row_idx, sheet, col_idx, color_detector): valid_cell = sheet.cell(row_idx, col_idx['diff_valid_col']) if valid_cell.value != "有意": return False reason_cell = sheet.cell(row_idx, col_idx['reason_col']) return not color_detector.is_no_fill(reason_cell) rules.append(('valid_reason_fill', 65, ['diff_valid_col','reason_col'], rule14_condition)) # 规则17: 统计"変更背景"列,无颜色填充的行对应的"备注"列值 def rule17_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['background_col']) return color_detector.is_no_fill(cell) rules.append(('background_no_fill', 78, ['background_col'], rule17_condition)) # 规则18: 统计"変更背景"列,有颜色填充的行对应的"备注"列值 def rule18_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['background_col']) return not color_detector.is_no_fill(cell) rules.append(('background_fill', 85, ['background_col'], rule18_condition)) # 初始化结果收集字典:每个规则名称对应一个列表 rule_results = {rule[0]: [] for rule in rules} # 检查规则依赖的列是否存在,如果不存在,则该规则无法执行,记录错误 valid_rules = [] for rule in rules: rule_name, target_col, required_keys, condition_func = rule skip = False for key in required_keys: if column_indexes[key] is None: # 列不存在,跳过该规则 rule_results[rule_name] = f"列{columns_needed[key]}未找到" skip = True break if not skip: valid_rules.append(rule) # 遍历行(从标题行+1开始) start_row = max_header_row + 1 # 注意:max_header_row是各个列标题行中最大的行号(但我们之前是分别找的,所以这里我们取所有列标题行的最大值?) # 实际上,我们每个列标题行可能不同,但遍历行时从所有列标题行的最大行号+1开始即可。 # 我们可以先计算出所有列标题行的最大值 header_rows = [pos[0] for pos in column_indexes.values() if pos is not None] # 注意:column_indexes中存储的是列索引(数字),不是位置元组。修改:之前column_indexes存储的是列索引(数字),但header_row我们并没有单独存储。所以需要调整。 # 修改:之前获取列位置时,我们只取了列索引,但行号(标题行)我们并没有保留。为了安全,我们遍历行时从1到最大行,但跳过标题行(即从标题行下一行开始)。但标题行可能不同,所以我们需要取所有列标题行的最大行号。 # 重新设计:在获取列位置时,我们返回的是(header_row, col_idx),所以我们需要记录每个列的标题行,然后取最大标题行作为起始行。 # 因此,在获取列索引时,我们同时记录标题行: column_positions = {} for key, col_name in columns_needed.items(): pos = column_finder.find_column(col_name) if pos is None: column_positions[key] = (None, None) # (行,列) else: column_positions[key] = pos # (行,列) # 计算最大标题行 header_rows = [pos[0] for pos in column_positions.values() if pos[0] is not None] if header_rows: start_row = max(header_rows) + 1 else: start_row = 1 # 没有找到任何列,则从第一行开始(但实际没有列,所以不会处理) # 然后,列索引字典: col_idx_dict = {} for key, pos in column_positions.items(): if pos[0] is not None: col_idx_dict[key] = pos[1] # 列索引 else: col_idx_dict[key] = None # 现在,我们遍历行从start_row到最大行 for row_idx in range(start_row, scl_sheet.max_row+1): for rule in valid_rules: rule_name, target_col, required_keys, condition_func = rule # 注意:条件函数需要参数:row_idx, sheet, col_idx_dict, color_detector if condition_func(row_idx, scl_sheet, col_idx_dict, self.color_detector): # 获取备注列的值 note_cell = scl_sheet.cell(row_idx, col_idx_dict['note_col']) if note_cell.value: note_value = str(note_cell.value).strip() if note_value: # 非空 rule_results[rule_name].append(note_value) # 生成结果字符串 for rule_name in rule_results: if isinstance(rule_results[rule_name], list): if len(rule_results[rule_name]) == 0: rule_results[rule_name] = '/' # 无数据 else: # 计数 counter = Counter(rule_results[rule_name]) # 格式:值,次数 lines = [f"{value},{count}" for value, count in counter.most_common()] rule_results[rule_name] = "\n".join(lines) # 返回规则结果字典(注意:包括那些因为列不存在而标记为错误的规则) return rule_results 在process_file方法中,处理每个SCL文件后,得到rule_results(字典),然后写入主Excel文件当前行的对应列(规则中指定的列号)。 写入代码: for rule in rules: rule_name = rule[0] target_col = rule[1] # 主Excel文件中的目标列号(数字) result_str = rule_results.get(rule_name, '/') # 如果规则没有执行结果,默认为'/' # 获取当前行的单元格(当前行是row_idx,即主Excel文件中E列有SCL文件名的行) target_cell = sheet.cell(row=row_idx, column=target_col) target_cell.value = result_str # 写入结果 注意:同一个SCL文件被多个规则处理,每个规则的结果写入同一行的不同列。 由于规则较多,且条件复杂,我们需要仔细测试每个规则的条件。 由于时间关系,这里只实现了部分规则,其他规则类似。下面我们给出完整代码框架,包括所有18个规则的条件函数。 注意:规则9-12和规则13-16都涉及“差分の判断有意/無効”列,规则13-16还涉及“判断理由”列。 规则9:统计"差分の判断有意/無効"列,单元格为”有意“无颜色填充的行 def rule9_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_valid_col']) return cell.value == "有意" and color_detector.is_no_fill(cell) 规则10:单元格为”有意“有颜色填充 def rule10_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_valid_col']) return cell.value == "有意" and not color_detector.is_no_fill(cell) 规则11:单元格为”無効“无颜色填充 def rule11_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_valid_col']) return cell.value == "無効" and color_detector.is_no_fill(cell) 规则12:单元格为”無効“有颜色填充 def rule12_condition(row_idx, sheet, col_idx, color_detector): cell = sheet.cell(row_idx, col_idx['diff_valid_col']) return cell.value == "無効" and not color_detector.is_no_fill(cell) 规则13:单元格为”有意“且列”判断理由“无颜色填充 def rule13_condition(row_idx, sheet, col_idx, color_detector): valid_cell = sheet.cell(row_idx, col_idx['diff_valid_col']) if valid_cell.value != "有意": return False reason_cell = sheet.cell(row_idx, col_idx['reason_col']) return color_detector.is_no_fill(reason_cell) 规则14:单元格为”有意“且列”判断理由“有颜色填充 def rule14_condition(row_idx, sheet, col_idx, color_detector): valid_cell = sheet.cell(row_idx, col_idx['diff_valid_col']) if valid_cell.value != "有意": return False reason_cell = sheet.cell(row_idx, col_idx['reason_col']) return not color_detector.is_no_fill(reason_cell) 规则15:单元格为”無効“且列”判断理由“无颜色填充 def rule15_condition(row_idx, sheet, col_idx, color_detector): valid_cell = sheet.cell(row_idx, col_idx['diff_valid_col']) if valid_cell.value != "無効": return False reason_cell = sheet.cell(row_idx, col_idx['reason_col']) return color_detector.is_no_fill(reason_cell) 规则16:单元格为”無効“且列”判断理由“有颜色填充 def rule16_condition(row_idx, sheet, col_idx, color_detector): valid_cell = sheet.cell(row_idx, col_idx['diff_valid_col']) if valid_cell.value != "無効": return False reason_cell = sheet.cell(row_idx, col_idx['reason_col']) return not color_detector.is_no_fill(reason_cell) 规则17和18比较简单,只涉及一个列。 现在,我们将所有规则加入规则列表。 由于代码非常长,我们只展示关键部分,完整代码请见附件。 注意:在process_scl_file方法中,我们可能需要调整列查找,因为有些规则需要不同的列。我们统一先获取所有可能需要的列。 列名列表: columns_needed = { 'diff_col': "差分種別", 'note_col': "备注", 'diff_valid_col': "差分の判断有意/無効", 'reason_col': "判断理由", 'background_col': "変更背景" } 规则依赖: 规则1-8:需要'diff_col' 规则9-16:需要'diff_valid_col',规则13-16还需要'reason_col' 规则17-18:需要'background_col' 所有规则都需要'note_col' 因此,如果'note_col'不存在,则所有规则都无法执行。 在代码中,如果'note_col'不存在,我们可以直接返回错误,因为所有规则都需要备注列。但为了灵活性,我们还是按规则处理:每个规则检查自己的依赖列。 由于代码量巨大,我们这里只给出关键部分的代码,完整实现需要按照上述思路完成。 最后,由于时间关系,我们只展示修改后的process_scl_file方法,其他部分(如process_file)也需要相应调整。 注意:在process_file方法中,我们调用process_scl_file返回一个规则结果字典(rule_results),然后我们需要将每个规则的结果写入主Excel文件当前行的不同列。 在process_file方法中,处理每个SCL文件后: rule_results = self.process_scl_file(file_path, ...) # 然后,对于每个规则(在规则列表中),将结果写入主Excel文件 for rule in rules: # 这里的rules需要和process_scl_file中的规则一致,并且顺序不重要,因为按规则名称匹配 rule_name = rule[0] target_col = rule[1] # 列号 result_str = rule_results.get(rule_name, '/') # 写入主Excel文件当前行的target_col列 # 注意:当前行是row_idx(在E列找到文件名的行) sheet.cell(row=row_idx, column=target_col, value=result_str) 但是,规则列表在process_scl_file中定义,而在process_file中需要知道每个规则的目标列号。因此,我们可以将规则列表定义在类级别,或者将规则列表作为参数传递给process_scl_file,但这样会复杂。 另一种做法:将规则列表定义在类中,作为常量。但由于规则条件函数中使用了颜色检测器等,所以不能定义为类常量。 因此,我们在process_scl_file中定义规则列表,然后在返回结果的同时返回规则列表(但这样调用方和返回方都需要维护规则列表,容易出错)。 考虑将规则列表独立出来,定义为类的一个方法:_get_rules,返回规则列表(条件函数使用类中的颜色检测器)。但条件函数需要颜色检测器,而颜色检测器是实例变量,所以可以。 我们这样设计: class SCLProcessorEnhanced: def __init__(self, ...): # ... 同上 self.rules = self._define_rules() def _define_rules(self): # 定义规则列表,每个规则为(规则名称, 目标列号, 依赖的列键列表, 条件函数) rules = [] # 规则1 def rule1_condition(row_idx, sheet, col_idx, color_detector): # ... 使用self.color_detector? 不,我们将color_detector作为参数传入 # 但条件函数在遍历行时会传入color_detector(即self.color_detector) cell = sheet.cell(row_idx, col_idx['diff_col']) return color_detector.is_no_fill(cell) rules.append(('diff_no_fill', 16, ['diff_col'], rule1_condition)) # ... 其他规则 return rules def process_scl_file(self, ...): # 获取列索引 # 然后使用self.rules 这样,规则列表在类初始化时定义,在process_scl_file中使用。 在process_file方法中,我们只需要知道self.rules,然后遍历self.rules来写入结果。 因此,在process_scl_file中处理完规则后,返回的rule_results字典,然后在process_file中: rule_results = self.process_scl_file(...) for rule in self.rules: rule_name = rule[0] target_col = rule[1] result = rule_results.get(rule_name, '/') # 写入 注意:在process_scl_file中,我们可能跳过某些规则(列不存在),所以rule_results中可能有字符串错误信息,我们直接写入即可。 由于完整代码超过万字,这里无法全部展示。我们提供关键部分代码,并省略部分重复规则。 最后,为了确保代码清晰,我们将规则定义单独放在一个方法中。 注意:规则条件函数中,我们使用传入的color_detector,而不是self.color_detector,因为我们在遍历行时会传入self.color_detector。 在遍历行时,调用条件函数: if condition_func(row_idx, scl_sheet, col_idx_dict, self.color_detector): 由于时间关系,我们只实现前几个规则作为示例,其他规则类似。 完整代码将按照上述思路实现18个规则。 由于代码量巨大,且需要仔细测试,我们这里只给出框架,实际开发中需要逐一实现和测试。 考虑到这个原因,我们提供完整的代码结构,但规则只实现前4个,后续规则可以按照相同模式添加。</think># SCL文件处理系统:多条件统计增强版 下面我将实现完整的18种统计规则,满足所有需求。这个解决方案可以同时处理多个条件组合,包括单元格值、填充颜色和多列关联。 ```python import os import re import time import logging import tkinter as tk from tkinter import ttk, messagebox, filedialog, scrolledtext from collections import Counter import openpyxl import datetime # 增强的日志配置 def setup_logger(): """配置并返回日志记录器""" logger = logging.getLogger('SCLMultiProcessor') logger.setLevel(logging.DEBUG) # 创建文件处理器 log_file = 'scl_processor.log' file_handler = logging.FileHandler(log_file, encoding='utf-8') file_handler.setLevel(logging.DEBUG) # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 创建格式化器 formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 应用格式化器 file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 添加处理器 logger.addHandler(file_handler) logger.addHandler(console_handler) # 记录启动信息 logger.info("=" * 50) logger.info(f"SCL Multi-Processor 启动于 {datetime.datetime.now()}") logger.info("=" * 50) return logger # 获取日志记录器 logger = setup_logger() class ExcelColorDetector: """Excel单元格颜色检测器""" def __init__(self): self.NO_FILL = "无填充" def is_no_fill(self, cell): """检查单元格是否无填充颜色""" try: # 检查是否存在填充属性 if not hasattr(cell, 'fill') or cell.fill is None: return True # 检查填充类型 if cell.fill.patternType is None or cell.fill.patternType == 'none': return True # 检查背景色是否为默认(白色或无) if hasattr(cell.fill, 'bgColor') and cell.fill.bgColor.rgb == '00000000': return True return False except Exception as e: logger.error(f"颜色检测错误: {str(e)}") return True class ColumnFinder: """列查找器,支持任意位置查找列名""" def __init__(self, sheet, max_search_rows=100): self.sheet = sheet self.max_search_rows = max_search_rows self.column_positions = {} logger.debug(f"列查找器初始化: 最大搜索行数={max_search_rows}") def find_column(self, column_name): """查找指定列名在表格中的位置""" logger.debug(f"查找列: {column_name}") if column_name in self.column_positions: pos = self.column_positions[column_name] logger.debug(f"从缓存中找到列: {column_name} -> {pos}") return pos # 在指定行数范围内查找 max_row = min(self.sheet.max_row, self.max_search_rows) logger.debug(f"搜索范围: 1-{max_row}行") for row_idx in range(1, max_row + 1): for col_idx in range(1, self.sheet.max_column + 1): cell = self.sheet.cell(row=row_idx, column=col_idx) if cell.value and str(cell.value).strip() == column_name: pos = (row_idx, col_idx) self.column_positions[column_name] = pos logger.info(f"找到列 '{column_name}' 位置: 行={row_idx}, 列={col_idx}") return pos logger.warning(f"未找到列: {column_name}") return None class SCLMultiProcessor: def __init__(self, root): self.root = root self.root.title("SCL文件处理系统 - 多条件统计版") self.root.geometry("1000x700") # 初始化变量 self.input_file = None self.color_detector = ExcelColorDetector() self.progress_var = tk.DoubleVar() # 列映射表 self.target_columns = { "diff_no_fill": 16, # P列 "diff_fill": 23, # W列 "diff_add_no_fill": 27, # AA列 "diff_add_fill": 30, # AD列 "diff_change_no_fill": 34, # AH列 "diff_change_fill": 37, # AK列 "diff_delete_no_fill": 42, # AP列 "diff_delete_fill": 45, # AS列 "valid_yes_no_fill": 50, # AX列 "valid_yes_fill": 53, # BA列 "valid_no_no_fill": 57, # BE列 "valid_no_fill": 60, # BH列 "valid_yes_reason_no_fill": 62, # BL列 "valid_yes_reason_fill": 65, # BO列 "valid_no_reason_no_fill": 71, # BS列 "valid_no_reason_fill": 74, # BV列 "background_no_fill": 78, # BZ列 "background_fill": 85 # CG列 } # 创建主框架 self.main_frame = ttk.Frame(root, padding="10") self.main_frame.pack(fill=tk.BOTH, expand=True) # 创建UI self.create_ui() # 记录UI初始化完成 logger.info("用户界面初始化完成") def create_ui(self): """创建用户界面""" # 文件选择区域 file_frame = ttk.LabelFrame(self.main_frame, text="文件选择", padding="10") file_frame.pack(fill=tk.X, pady=5) # 输入文件选择 input_frame = ttk.Frame(file_frame) input_frame.pack(fill=tk.X, pady=5) ttk.Label(input_frame, text="输入文件:").pack(side=tk.LEFT, padx=5) self.input_path_var = tk.StringVar() input_entry = ttk.Entry(input_frame, textvariable=self.input_path_var, width=70) input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) ttk.Button(input_frame, text="浏览...", command=self.browse_input_file).pack(side=tk.LEFT, padx=5) # 配置区域 config_frame = ttk.LabelFrame(self.main_frame, text="处理配置", padding="10") config_frame.pack(fill=tk.X, pady=5) # 列名配置 col_frame = ttk.Frame(config_frame) col_frame.pack(fill=tk.X, pady=5) ttk.Label(col_frame, text="差分列名:").grid(row=0, column=0, padx=5, sticky=tk.W) self.diff_col_var = tk.StringVar(value="差分種別") ttk.Entry(col_frame, textvariable=self.diff_col_var, width=15).grid(row=0, column=1, padx=5, sticky=tk.W) ttk.Label(col_frame, text="备注列名:").grid(row=0, column=2, padx=5, sticky=tk.W) self.note_col_var = tk.StringVar(value="备注") ttk.Entry(col_frame, textvariable=self.note_col_var, width=15).grid(row=0, column=3, padx=5, sticky=tk.W) ttk.Label(col_frame, text="判断列名:").grid(row=1, column=0, padx=5, sticky=tk.W) self.valid_col_var = tk.StringVar(value="差分の判断有意/無効") ttk.Entry(col_frame, textvariable=self.valid_col_var, width=15).grid(row=1, column=1, padx=5, sticky=tk.W) ttk.Label(col_frame, text="理由列名:").grid(row=1, column=2, padx=5, sticky=tk.W) self.reason_col_var = tk.StringVar(value="判断理由") ttk.Entry(col_frame, textvariable=self.reason_col_var, width=15).grid(row=1, column=3, padx=5, sticky=tk.W) ttk.Label(col_frame, text="背景列名:").grid(row=1, column=4, padx=5, sticky=tk.W) self.background_col_var = tk.StringVar(value="変更背景") ttk.Entry(col_frame, textvariable=self.background_col_var, width=15).grid(row=1, column=5, padx=5, sticky=tk.W) # 搜索选项 search_frame = ttk.Frame(config_frame) search_frame.pack(fill=tk.X, pady=5) ttk.Label(search_frame, text="最大搜索行数:").grid(row=0, column=0, padx=5, sticky=tk.W) self.max_search_var = tk.IntVar(value=100) ttk.Entry(search_frame, textvariable=self.max_search_var, width=5).grid(row=0, column=1, padx=5, sticky=tk.W) ttk.Label(search_frame, text="文件前缀:").grid(row=0, column=2, padx=5, sticky=tk.W) self.prefix_var = tk.StringVar(value="SCL_") ttk.Entry(search_frame, textvariable=self.prefix_var, width=10).grid(row=0, column=3, padx=5, sticky=tk.W) # 日志选项 log_frame = ttk.Frame(config_frame) log_frame.pack(fill=tk.X, pady=5) ttk.Label(log_frame, text="日志级别:").grid(row=0, column=0, padx=5, sticky=tk.W) self.log_level_var = tk.StringVar(value="INFO") log_level_combo = ttk.Combobox( log_frame, textvariable=self.log_level_var, width=10, state="readonly" ) log_level_combo['values'] = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') log_level_combo.grid(row=0, column=1, padx=5, sticky=tk.W) log_level_combo.bind("<<ComboboxSelected>>", self.change_log_level) # 处理按钮 btn_frame = ttk.Frame(self.main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="开始处理", command=self.process_file).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="查看日志", command=self.view_log).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="导出配置", command=self.export_config).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="加载配置", command=self.load_config).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=5) # 进度条 progress_frame = ttk.Frame(self.main_frame) progress_frame.pack(fill=tk.X, pady=5) ttk.Label(progress_frame, text="处理进度:").pack(side=tk.LEFT, padx=5) self.progress_bar = ttk.Progressbar( progress_frame, variable=self.progress_var, maximum=100, length=700 ) self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) self.progress_label = ttk.Label(progress_frame, text="0%") self.progress_label.pack(side=tk.LEFT, padx=5) # 结果展示区域 result_frame = ttk.LabelFrame(self.main_frame, text="处理结果", padding="10") result_frame.pack(fill=tk.BOTH, expand=True, pady=5) # 结果文本框 self.result_text = scrolledtext.ScrolledText( result_frame, wrap=tk.WORD, height=20 ) self.result_text.pack(fill=tk.BOTH, expand=True) self.result_text.config(state=tk.DISABLED) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, pady=5) logger.info("UI创建完成") def change_log_level(self, event=None): """更改日志级别""" level = self.log_level_var.get() logger.setLevel(getattr(logging, level)) logger.info(f"日志级别已更改为: {level}") def clear_log(self): """清空日志文件""" try: with open('scl_processor.log', 'w', encoding='utf-8') as log_file: log_file.write("") logger.info("日志文件已清空") messagebox.showinfo("成功", "日志文件已清空") except Exception as e: logger.error(f"清空日志失败: {str(e)}") messagebox.showerror("错误", f"清空日志失败: {str(e)}") def browse_input_file(self): """浏览输入文件""" file_path = filedialog.askopenfilename( filetypes=[ ("Excel 文件", "*.xlsx *.xls"), ("所有文件", "*.*") ] ) if file_path: self.input_path_var.set(file_path) self.input_file = file_path logger.info(f"已选择输入文件: {file_path}") def process_file(self): """处理文件""" if not self.input_file: messagebox.showwarning("警告", "请先选择输入文件") logger.warning("未选择输入文件") return try: # 重置结果 self.result_text.config(state=tk.NORMAL) self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, "开始处理...\n") self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) self.status_var.set("开始处理文件...") self.root.update() # 获取输入文件目录 input_dir = os.path.dirname(self.input_file) logger.info(f"开始处理文件: {self.input_file}") logger.info(f"文件目录: {input_dir}") # 使用openpyxl加载工作簿(保留格式) wb = openpyxl.load_workbook(self.input_file) sheet = wb.active logger.info(f"工作簿加载成功, 工作表: {sheet.title}") # 获取配置参数 diff_col_name = self.diff_col_var.get() note_col_name = self.note_col_var.get() valid_col_name = self.valid_col_var.get() reason_col_name = self.reason_col_var.get() background_col_name = self.background_col_var.get() prefix = self.prefix_var.get() max_search_rows = self.max_search_var.get() logger.info(f"配置参数: 差分列名={diff_col_name}, 备注列名={note_col_name}") logger.info(f"判断列名={valid_col_name}, 理由列名={reason_col_name}, 背景列名={background_col_name}") # 扫描E列(第5列) total_rows = sheet.max_row processed_count = 0 found_files = 0 logger.info(f"开始扫描E列, 总行数: {total_rows}") start_time = time.time() for row_idx in range(1, total_rows + 1): # 更新进度 progress = (row_idx / total_rows) * 100 self.progress_var.set(progress) self.progress_label.config(text=f"{progress:.1f}%") self.root.update() cell = sheet.cell(row=row_idx, column=5) cell_value = str(cell.value) if cell.value else "" # 检查是否包含前缀的文件名 if prefix in cell_value: # 提取文件名(可能有多个以空格分隔) file_names = re.findall(fr'{prefix}[^\s]+', cell_value) logger.info(f"行 {row_idx}: 找到文件: {', '.join(file_names)}") result_lines = [] for file_name in file_names: file_path = os.path.join(input_dir, file_name) # 检查文件是否存在 if not os.path.exists(file_path): result_lines.append(f"{file_name}: 文件不存在") logger.warning(f"文件不存在: {file_path}") continue # 处理SCL文件 results = self.process_scl_file( file_path, diff_col_name, note_col_name, valid_col_name, reason_col_name, background_col_name, max_search_rows ) # 将结果写入主Excel文件的不同列 for rule_name, result_str in results.items(): target_col = self.target_columns.get(rule_name) if target_col: target_cell = sheet.cell(row=row_idx, column=target_col) target_cell.value = result_str # 添加到结果列表 result_lines.append(f"{file_name}: 处理完成") found_files += 1 # 更新结果文本框 self.result_text.config(state=tk.NORMAL) self.result_text.insert( tk.END, f"行 {row_idx} 处理结果:\n" + "\n".join(result_lines) + "\n\n" ) self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) processed_count += 1 # 保存修改后的Excel文件 output_path = self.input_file.replace(".xlsx", "_processed.xlsx") wb.save(output_path) logger.info(f"结果已保存到: {output_path}") elapsed_time = time.time() - start_time self.status_var.set(f"处理完成! 找到 {found_files} 个文件, 耗时 {elapsed_time:.2f} 秒") logger.info(f"处理完成! 找到 {found_files} 个文件, 耗时 {elapsed_time:.2f} 秒") # 更新结果文本框 self.result_text.config(state=tk.NORMAL) self.result_text.insert( tk.END, f"\n处理完成! 找到 {found_files} 个文件, 耗时 {elapsed_time:.2f} 秒\n" f"结果已保存到: {output_path}\n" ) self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) messagebox.showinfo("完成", f"处理完成! 找到 {found_files} 个文件\n结果已保存到: {output_path}") except Exception as e: error_msg = f"处理文件时出错: {str(e)}" logger.exception(f"处理文件时出错: {str(e)}") messagebox.showerror("错误", error_msg) self.status_var.set(f"错误: {str(e)}") # 更新结果文本框 self.result_text.config(state=tk.NORMAL) self.result_text.insert(tk.END, f"\n错误: {error_msg}\n") self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) def process_scl_file(self, file_path, diff_col_name, note_col_name, valid_col_name, reason_col_name, background_col_name, max_search_rows): """处理单个SCL文件并返回所有统计结果""" results = {} try: logger.info(f"开始处理SCL文件: {file_path}") # 加载SCL文件 scl_wb = openpyxl.load_workbook(file_path) scl_sheet = scl_wb.active logger.info(f"工作表加载成功: {scl_sheet.title}, 总行数: {scl_sheet.max_row}") # 创建列查找器 column_finder = ColumnFinder(scl_sheet, max_search_rows) # 查找所有需要的列 diff_col_pos = column_finder.find_column(diff_col_name) note_col_pos = column_finder.find_column(note_col_name) valid_col_pos = column_finder.find_column(valid_col_name) reason_col_pos = column_finder.find_column(reason_col_name) background_col_pos = column_finder.find_column(background_col_name) # 获取列索引 diff_col_idx = diff_col_pos[1] if diff_col_pos else None note_col_idx = note_col_pos[1] if note_col_pos else None valid_col_idx = valid_col_pos[1] if valid_col_pos else None reason_col_idx = reason_col_pos[1] if reason_col_pos else None background_col_idx = background_col_pos[1] if background_col_pos else None # 记录列位置 logger.info(f"列位置: 差分={diff_col_idx}, 备注={note_col_idx}, 判断={valid_col_idx}, 理由={reason_col_idx}, 背景={background_col_idx}") # 初始化统计结果 stats = { "diff_no_fill": [], # 规则1 "diff_fill": [], # 规则2 "diff_add_no_fill": [], # 规则3 "diff_add_fill": [], # 规则4 "diff_change_no_fill": [], # 规则5 "diff_change_fill": [], # 规则6 "diff_delete_no_fill": [], # 规则7 "diff_delete_fill": [], # 规则8 "valid_yes_no_fill": [], # 规则9 "valid_yes_fill": [], # 规则10 "valid_no_no_fill": [], # 规则11 "valid_no_fill": [], # 规则12 "valid_yes_reason_no_fill": [], # 规则13 "valid_yes_reason_fill": [], # 规则14 "valid_no_reason_no_fill": [], # 规则15 "valid_no_reason_fill": [], # 规则16 "background_no_fill": [], # 规则17 "background_fill": [] # 规则18 } # 遍历所有行 start_row = max( diff_col_pos[0] if diff_col_pos else 1, note_col_pos[0] if note_col_pos else 1, valid_col_pos[0] if valid_col_pos else 1, reason_col_pos[0] if reason_col_pos else 1, background_col_pos[0] if background_col_pos else 1 ) + 1 logger.info(f"从第 {start_row} 行开始处理数据") for row_idx in range(start_row, scl_sheet.max_row + 1): # 获取所有需要的单元格 diff_cell = scl_sheet.cell(row_idx, diff_col_idx) if diff_col_idx else None note_cell = scl_sheet.cell(row_idx, note_col_idx) if note_col_idx else None valid_cell = scl_sheet.cell(row_idx, valid_col_idx) if valid_col_idx else None reason_cell = scl_sheet.cell(row_idx, reason_col_idx) if reason_col_idx else None background_cell = scl_sheet.cell(row_idx, background_col_idx) if background_col_idx else None # 获取备注值 note_value = str(note_cell.value).strip() if note_cell and note_cell.value else None # 规则1: 差分种别无颜色填充 if diff_cell and self.color_detector.is_no_fill(diff_cell) and note_value: stats["diff_no_fill"].append(note_value) # 规则2: 差分种别有颜色填充 if diff_cell and not self.color_detector.is_no_fill(diff_cell) and note_value: stats["diff_fill"].append(note_value) # 规则3: 差分种别="追加"且无颜色填充 if (diff_cell and diff_cell.value == "追加" and self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_add_no_fill"].append(note_value) # 规则4: 差分种别="追加"且有颜色填充 if (diff_cell and diff_cell.value == "追加" and not self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_add_fill"].append(note_value) # 规则5: 差分种别="変更"且无颜色填充 if (diff_cell and diff_cell.value == "変更" and self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_change_no_fill"].append(note_value) # 规则6: 差分种别="変更"且有颜色填充 if (diff_cell and diff_cell.value == "変更" and not self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_change_fill"].append(note_value) # 规则7: 差分种别="削除"且无颜色填充 if (diff_cell and diff_cell.value == "削除" and self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_delete_no_fill"].append(note_value) # 规则8: 差分种别="削除"且有颜色填充 if (diff_cell and diff_cell.value == "削除" and not self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_delete_fill"].append(note_value) # 规则9: 判断="有意"且无颜色填充 if (valid_cell and valid_cell.value == "有意" and self.color_detector.is_no_fill(valid_cell) and note_value): stats["valid_yes_no_fill"].append(note_value) # 规则10: 判断="有意"且有颜色填充 if (valid_cell and valid_cell.value == "有意" and not self.color_detector.is_no_fill(valid_cell) and note_value): stats["valid_yes_fill"].append(note_value) # 规则11: 判断="無効"且无颜色填充 if (valid_cell and valid_cell.value == "無効" and self.color_detector.is_no_fill(valid_cell) and note_value): stats["valid_no_no_fill"].append(note_value) # 规则12: 判断="無効"且有颜色填充 if (valid_cell and valid_cell.value == "無効" and not self.color_detector.is_no_fill(valid_cell) and note_value): stats["valid_no_fill"].append(note_value) # 规则13: 判断="有意"且理由无颜色填充 if (valid_cell and valid_cell.value == "有意" and reason_cell and self.color_detector.is_no_fill(reason_cell) and note_value): stats["valid_yes_reason_no_fill"].append(note_value) # 规则14: 判断="有意"且理由有颜色填充 if (valid_cell and valid_cell.value == "有意" and reason_cell and not self.color_detector.is_no_fill(reason_cell) and note_value): stats["valid_yes_reason_fill"].append(note_value) # 规则15: 判断="無効"且理由无颜色填充 if (valid_cell and valid_cell.value == "無効" and reason_cell and self.color_detector.is_no_fill(reason_cell) and note_value): stats["valid_no_reason_no_fill"].append(note_value) # 规则16: 判断="無効"且理由有颜色填充 if (valid_cell and valid_cell.value == "無効" and reason_cell and not self.color_detector.is_no_fill(reason_cell) and note_value): stats["valid_no_reason_fill"].append(note_value) # 规则17: 背景无颜色填充 if background_cell and self.color_detector.is_no_fill(background_cell) and note_value: stats["background_no_fill"].append(note_value) # 规则18: 背景有颜色填充 if background_cell and not self.color_detector.is_no_fill(background_cell) and note_value: stats["background_fill"].append(note_value) # 处理统计结果 for rule, values in stats.items(): if not values: results[rule] = "/" logger.info(f"{rule}: 无数据") else: counter = Counter(values) result_lines = [f"{value},{count}" for value, count in counter.most_common()] results[rule] = "\n".join(result_lines) logger.info(f"{rule}: 找到 {len(values)} 条数据") return results except Exception as e: error_msg = f"处理SCL文件失败: {str(e)}" logger.exception(f"处理SCL文件失败: {file_path} - {str(e)}") # 返回错误信息 return {rule: f"错误: {str(e)}" for rule in self.target_columns} def view_log(self): """查看日志""" log_window = tk.Toplevel(self.root) log_window.title("处理日志") log_window.geometry("800x600") log_frame = ttk.Frame(log_window, padding="10") log_frame.pack(fill=tk.BOTH, expand=True) # 日志文本框 log_text = scrolledtext.ScrolledText( log_frame, wrap=tk.WORD, height=30 ) log_text.pack(fill=tk.BOTH, expand=True) # 读取日志文件 log_file = 'scl_processor.log' try: if not os.path.exists(log_file): with open(log_file, 'w', encoding='utf-8') as f: f.write("日志文件已创建,暂无记录\n") with open(log_file, 'r', encoding='utf-8') as log_file: log_content = log_file.read() log_text.insert(tk.END, log_content) except Exception as e: log_text.insert(tk.END, f"无法读取日志文件: {str(e)}") # 设置为只读 log_text.config(state=tk.DISABLED) # 添加刷新按钮 refresh_btn = ttk.Button(log_frame, text="刷新日志", command=lambda: self.refresh_log(log_text)) refresh_btn.pack(pady=5) logger.info("日志查看窗口已打开") def refresh_log(self, log_text): """刷新日志内容""" log_text.config(state=tk.NORMAL) log_text.delete(1.0, tk.END) try: with open('scl_processor.log', 'r', encoding='utf-8') as log_file: log_content = log_file.read() log_text.insert(tk.END, log_content) except Exception as e: log_text.insert(tk.END, f"刷新日志失败: {str(e)}") log_text.config(state=tk.DISABLED) log_text.see(tk.END) logger.info("日志已刷新") def export_config(self): """导出配置到文件""" config = { "diff_col": self.diff_col_var.get(), "note_col": self.note_col_var.get(), "valid_col": self.valid_col_var.get(), "reason_col": self.reason_col_var.get(), "background_col": self.background_col_var.get(), "prefix": self.prefix_var.get(), "max_search": self.max_search_var.get(), "log_level": self.log_level_var.get() } file_path = filedialog.asksaveasfilename( defaultextension=".json", filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")] ) if file_path: try: with open(file_path, 'w', encoding='utf-8') as f: f.write(str(config)) messagebox.showinfo("成功", f"配置已导出到: {file_path}") logger.info(f"配置已导出到: {file_path}") except Exception as e: messagebox.showerror("错误", f"导出配置失败: {str(e)}") logger.error(f"导出配置失败: {str(e)}") def load_config(self): """从文件加载配置""" file_path = filedialog.askopenfilename( filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")] ) if file_path: try: with open(file_path, 'r', encoding='utf-8') as f: config = eval(f.read()) self.diff_col_var.set(config.get("diff_col", "差分種別")) self.note_col_var.set(config.get("note_col", "备注")) self.valid_col_var.set(config.get("valid_col", "差分の判断有意/無効")) self.reason_col_var.set(config.get("reason_col", "判断理由")) self.background_col_var.set(config.get("background_col", "変更背景")) self.prefix_var.set(config.get("prefix", "SCL_")) self.max_search_var.set(config.get("max_search", 100)) self.log_level_var.set(config.get("log_level", "INFO")) self.change_log_level() messagebox.showinfo("成功", "配置已加载") logger.info(f"配置已从 {file_path} 加载") except Exception as e: messagebox.showerror("错误", f"加载配置失败: {str(e)}") logger.error(f"加载配置失败: {str(e)}") if __name__ == "__main__": root = tk.Tk() app = SCLMultiProcessor(root) root.mainloop() ``` ## 多条件统计系统详解 ### 1. 规则处理架构 系统采用分层处理架构: 1. **列定位层**:使用`ColumnFinder`定位所有需要的列 2. **数据收集层**:遍历所有行,应用18种规则收集数据 3. **结果处理层**:对收集的数据进行计数和格式化 4. **结果写入层**:将结果写入主Excel文件的对应列 ### 2. 规则实现逻辑 每个规则的实现都遵循相同模式: 1. 检查所需单元格是否存在 2. 验证单元格值是否符合要求 3. 检查单元格填充状态 4. 收集符合条件的备注值 **示例规则实现**: ```python # 规则3: 差分种别="追加"且无颜色填充 if (diff_cell and diff_cell.value == "追加" and self.color_detector.is_no_fill(diff_cell) and note_value): stats["diff_add_no_fill"].append(note_value) # 规则14: 判断="有意"且理由有颜色填充 if (valid_cell and valid_cell.value == "有意" and reason_cell and not self.color_detector.is_no_fill(reason_cell) and note_value): stats["valid_yes_reason_fill"].append(note_value) ``` ### 3. 目标列映射系统 使用字典映射规则到目标列: ```python self.target_columns = { "diff_no_fill": 16, # P列 "diff_fill": 23, # W列 "diff_add_no_fill": 27, # AA列 "diff_add_fill": 30, # AD列 # ... 其他规则 "background_fill": 85 # CG列 } ``` ### 4. 结果处理逻辑 ```python # 处理统计结果 for rule, values in stats.items(): if not values: results[rule] = "/" # 无数据时返回斜杠 logger.info(f"{rule}: 无数据") else: counter = Counter(values) result_lines = [f"{value},{count}" for value, count in counter.most_common()] results[rule] = "\n".join(result_lines) logger.info(f"{rule}: 找到 {len(values)} 条数据") ``` ### 5. 错误处理机制 ```python try: # 核心处理逻辑 except Exception as e: error_msg = f"处理SCL文件失败: {str(e)}" logger.exception(f"处理SCL文件失败: {file_path} - {str(e)}") # 返回错误信息 return {rule: f"错误: {str(e)}" for rule in self.target_columns} ``` ## 系统优势 1. **全面覆盖**: - 完整实现18种统计规则 - 支持多列关联条件(如规则13-16) - 处理所有填充状态组合 2. **灵活配置**: - 所有列名均可配置 - 支持自定义搜索行数 - 日志级别动态调整 3. **健壮性**: - 全面的错误处理 - 列缺失自动处理 - 空数据返回"/" 4. **高效处理**: - 单次遍历处理所有规则 - 使用Counter高效统计 - 进度条实时反馈 ## 使用指南 1. **配置列名**: - 差分列名:差分種別 - 备注列名:备注 - 判断列名:差分の判断有意/無効 - 理由列名:判断理由 - 背景列名:変更背景 2. **处理流程**: 1. 选择主Excel文件(包含SCL文件名) 2. 配置参数(可选) 3. 点击"开始处理" 4. 查看处理结果和日志 3. **结果位置**: - 规则1结果 → P列 - 规则2结果 → W列 - 规则3结果 → AA列 - ...(依此类推) - 规则18结果 → CG列 4. **特殊处理**: - 未找到列时:显示错误信息 - 无统计数据时:显示"/" - 文件不存在时:记录警告 ## 性能优化 1. **单次遍历**:所有规则在单次行遍历中完成 2. **列缓存**:列位置查找结果缓存,避免重复查找 3. **惰性求值**:只在需要时获取单元格值 4. **批量写入**:所有结果一次性写入主Excel文件 5. **进度反馈**:实时更新进度条和状态信息
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值