输入111后一直显示逗号分隔的长度为1,是否走错分支了 # 导出功能
def export_data(self):
def async_export():
try:
self.progress.start()
# 获取用户自定义文件名
filename = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx")],
initialfile="redmine.xlsx"
)
if not filename:
return
# 初始化Excel
wb = op.Workbook()
ws = wb.active
#ws.title = "Function"
total_rows = 0
# 添加表头(带错误处理)
try:
head1 = ['任务id','ISP名称','区域','机型','跟踪','主题','描述','状态','优先级','指派给','功能类别','目标版本','父任务','开始日期','计划完成日期','预期时间','%完成','源头团队','需求组评估工作量','项目组评估工作量','实际执行工作量','源头状态','源头机型','业务模块','任务类别','备注','文件','跟踪者','是否推送平台','验收平台版本']
head2 = ['(必填)','(必填)','(必填)','(必填)','(必填)','(必填)','(必填)','(默认值)','(必填)','(不填)','(必填)','(不填)','(必填)','(不填)','(不填)','(不填)','(不填)','(默认值)','(选填)','(必填)','(选值)','(不填)','(不填)','(不填)','(默认值)','(不填)','(不填)','(不填)','(必填)','(必填)']
ws.append(head1)
ws.append(head2)
except Exception as e:
self.append_output(f"表头创建失败:{str(e)}")
return
# 获取项目数据(带进度更新)
try:
project = self.controller.redmine.project.get(defaultProjectName)
self.append_output(f"正在获取项目数据:{project.name}")
issues = self.controller.redmine.issue.filter(project_id=project.id)
except Exception as e:
self.append_output(f"获取项目数据失败:{str(e)}")
return
# 获取用户输入(支持单个ID、范围ID和逗号分隔的ID)
input_str = self.id_entry.get().strip()
selected_ids = set()
# 空输入则导出所有任务
if not input_str:
self.append_output(f"空输入则导出所有任务")
issues_to_export = list(issues)
# 范围格式 {start..end}
elif re.match(r'^\s*\{\s*(\d+)\s*\.\.\s*(\d+)\s*\}\s*$', input_str):
match = re.match(r'^\s*\{\s*(\d+)\s*\.\.\s*(\d+)\s*\}\s*$', input_str)
start = int(match.group(1))
end = int(match.group(2))
# 确保范围有效
if start > end:
start, end = end, start
selected_ids = set(range(start, end + 1))
# 获取范围内的任务
issues_to_export = [issue for issue in issues if issue.id in selected_ids]
self.append_output(f"获取范围内的任务长度:{len(issues_to_export)}")
# 逗号分隔的ID列表
elif re.match(r'^[\d,\s]+$', input_str):
id_list = [int(id_str.strip()) for id_str in input_str.split(',')]
selected_ids = set(id_list)
# 获取匹配的任务
issues_to_export = [issue for issue in issues if issue.id in selected_ids]
self.append_output(f"逗号分隔的ID列表长度:{len(issues_to_export)}")
# 单个ID
else:
try:
root_id = int(input_str)
selected_ids = {root_id}
root_issue = self.controller.redmine.issue.get(root_id, include=['children'])
issues_to_export = [root_issue]
except Exception as e:
self.append_output(f"无效的任务ID格式:{input_str}")
return
# 获取所有需要导出的任务(包括子任务)
all_issues_to_export = []
# 递归获取子任务(修改后)
def get_children(issue):
children = []
try:
# 重新获取该任务,包含children
full_issue = self.controller.redmine.issue.get(issue.id, include=['children'])
except Exception as e:
# 输出错误,但继续
self.append_output(f"获取任务{issue.id}的子任务失败:{str(e)}")
return children
if hasattr(full_issue, 'children') and full_issue.children:
for child in full_issue.children:
children.append(child)
# 递归获取孙子任务
children.extend(get_children(child))
return children
# 收集所有需要导出的任务
for issue in issues_to_export:
all_issues_to_export.append(issue)
all_issues_to_export.extend(get_children(issue))
# 优化主题分割函数 - 保留原始方括号
def extract_subject_parts(full_subject):
"""
从完整主题中提取各部分,保留主题中的方括号
格式:【ISP】【区域】【机型】主题内容
"""
# 定义正则表达式匹配模式
pattern = r'【([^】]*)】【([^】]*)】【([^】]*)】(.*)'
match = re.match(pattern, full_subject)
if match:
# 提取前三个部分和剩余主题
return [
match.group(1), # ISP名称
match.group(2), # 区域
match.group(3), # 机型
match.group(4).strip() # 主题(保留原始方括号)
]
else:
# 匹配失败时返回默认值
self.append_output(f"警告:无法解析主题格式:{full_subject}")
return ['', '', '', full_subject]
# 新增getd函数定义
def getd(issue1):
try:
# 确保获取完整的issue对象(包括自定义字段)
issue = self.controller.redmine.issue.get(issue1.id, include=['children'])
#issue = self.controller.redmine.issue.get(issue_id, include=['children'])#预加载子任务
#res = split_subject(issue.subject)
res = extract_subject_parts(issue.subject)
# 获取并补充缺失的字段
req_workload = issue.custom_fields.get(31).value if issue.custom_fields.get(31) else None
proj_workload = issue.custom_fields.get(32).value if issue.custom_fields.get(32) else None
act_workload = issue.custom_fields.get(33).value if issue.custom_fields.get(33) else None
push_platform_workload = issue.custom_fields.get(23).value if issue.custom_fields.get(23) else None
platform_pass_workload = issue.custom_fields.get(37).value if issue.custom_fields.get(37) else None
return [
issue.id,
res[0], # ISP名称
res[1], # 区域
res[2], # 机型
issue.tracker.name, # 跟踪类型
res[3], # 主题
issue.description or '', # 空值处理
issue.status.name, # 状态
issue.priority.name, # 优先级
issue.author.name,
issue.custom_fields.get(24).value if issue.custom_fields.get(24) else '', # 功能类别
None, # 目标版本
issue.parent.id if issue.parent else '',
issue.start_date.strftime('%Y-%m-%d') if issue.start_date else '',
None, # 计划完成日期
None, # 预期时间
issue.done_ratio,
issue.custom_fields.get(8).value, # 源头团队
req_workload, # 需求组评估工作量
proj_workload, # 项目组评估工作量
act_workload, # 实际执行工作量
None, # 源头状态
None, # 源头机型
None, # 业务模块
None, # 任务类别
None, # 备注
None, # 文件
None, # 跟踪者
push_platform_workload,
platform_pass_workload
]
except Exception as e:
self.append_output(f"任务{issue_id}数据处理异常:{str(e)}")
return []
# 处理数据(带进度显示)
total = len(all_issues_to_export)
# 导出所有选中的任务
for i, issue in enumerate(all_issues_to_export):
try:
# 更新进度
self.after(0, lambda: self.progress.configure(value=(i+1)/total*100))
# 获取任务数据并写入Excel
row_data = getd(issue)
if row_data: # 确保有数据
ws.append(row_data)
total_rows += 1
except (ResourceNotFoundError, ResourceAttrError) as e:
self.append_output(f"处理任务{issue.id}时出错:{str(e)}")
except Exception as e:
self.append_output(f"未知错误:{str(e)}")
# 在循环外输出统计信息(避免每次循环都输出)
self.append_output(f"成功写入{total_rows}行数据")
# 保存文件
try:
wb.save(filename)
self.append_output(f"文件已保存至:{filename}")
except Exception as e:
self.append_output(f"保存文件失败:{str(e)}")
# Excel导出时及时释放资源
wb.close()
del wb
finally:
self.progress.stop()
self.progress["value"] = 0
# 在子线程中执行导出操作
Thread(target=async_export).start()