diff --git a/.gitignore b/.gitignore index 4c9265c..6661c48 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,10 @@ __pycache__/ # Node 依赖与 Office 锁文件不入库 output/node_modules/ ~$* + +# 本地导出产物与测试缓存 +output/excel_reports/ +.pytest_cache/ + +# 运政网批量查询工作数据(截图与结果约 1.1G,不入库) +xst/ diff --git a/output/xmind_reports/安徽运八需求.xmind b/output/xmind_reports/安徽运八需求.xmind index a0ff630..809009e 100644 Binary files a/output/xmind_reports/安徽运八需求.xmind and b/output/xmind_reports/安徽运八需求.xmind differ diff --git a/output/xmind_reports/物流公司管理需求.xmind b/output/xmind_reports/物流公司管理需求.xmind new file mode 100644 index 0000000..270ee64 Binary files /dev/null and b/output/xmind_reports/物流公司管理需求.xmind differ diff --git a/output/xmind_reports/运单资料文件包下载需求.xmind b/output/xmind_reports/运单资料文件包下载需求.xmind index 72f2989..b3b9c14 100644 Binary files a/output/xmind_reports/运单资料文件包下载需求.xmind and b/output/xmind_reports/运单资料文件包下载需求.xmind differ diff --git a/scripts/export_XMind.py b/scripts/export_XMind.py index f38d323..cbf424e 100644 --- a/scripts/export_XMind.py +++ b/scripts/export_XMind.py @@ -26,6 +26,22 @@ MANIFEST_NS = "urn:xmind:xmap:xmlns:manifest:1.0" SEPARATOR_RE = re.compile(r"^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$") +# XMind 最外层根主题:系统名称(默认取项目画像,取不到则用默认值) +DEFAULT_SYSTEM_NAME = "运八网络货运平台" +PROJECT_PROFILE = REPO_ROOT / "knowledge_base" / "00_project" / "project_profile.md" + + +def resolve_system_name() -> str: + """解析系统名称:优先取项目画像的「项目名称」,失败则回退默认值。""" + try: + text = PROJECT_PROFILE.read_text(encoding="utf-8") + except OSError: + return DEFAULT_SYSTEM_NAME + match = re.search(r"项目名称\*{0,2}[::]\s*([^((\n]+)", text) + if not match: + return DEFAULT_SYSTEM_NAME + return match.group(1).strip() or DEFAULT_SYSTEM_NAME + def _new_id(): return str(uuid.uuid4()) @@ -99,7 +115,8 @@ def normalize_text(value: str) -> str: return re.sub(r"", "\n", value or "").replace("\\n", "\n").strip() -def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: str) -> bytes: +def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: str, + system_name: str = "") -> bytes: ts = _now_ms() nsmap = {None: XMAP_NS, "fo": FO_NS, "svg": SVG_NS, "xhtml": XHTML_NS, "xlink": XLINK_NS} @@ -115,10 +132,16 @@ def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: s root_topic = _el("topic", parent=sheet, id=_new_id(), timestamp=ts, **{"structure-class": "org.xmind.ui.logic.right"}) - _el("title", parent=root_topic).text = topic_name + _el("title", parent=root_topic).text = system_name or DEFAULT_SYSTEM_NAME rc = _el("children", parent=root_topic) rt = _el("topics", parent=rc, type="attached") + # 第二层:需求名称 + need_topic = _el("topic", parent=rt, id=_new_id(), timestamp=ts) + _el("title", parent=need_topic).text = topic_name + nc = _el("children", parent=need_topic) + nt = _el("topics", parent=nc, type="attached") + # 按模块路径分组 grouped: dict[str, list[dict[str, str]]] = {} for row in rows: @@ -127,7 +150,7 @@ def build_xmind_content(headers: list[str], rows: list[list[str]], topic_name: s grouped.setdefault(module, []).append(case) for module_name, cases in grouped.items(): - mtopic = _el("topic", parent=rt, id=_new_id(), timestamp=ts) + mtopic = _el("topic", parent=nt, id=_new_id(), timestamp=ts) _el("title", parent=mtopic).text = module_name mc = _el("children", parent=mtopic) mtopics = _el("topics", parent=mc, type="attached") @@ -207,15 +230,18 @@ def build_meta() -> bytes: return etree.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=True) -def export_markdown_to_xmind(source_path: Path, output_dir: Path, topic_name: str = "") -> Path: +def export_markdown_to_xmind(source_path: Path, output_dir: Path, topic_name: str = "", + system_name: str = "") -> Path: headers, rows = load_markdown_table(source_path) if not topic_name: topic_name = source_path.stem.replace("_测试用例", "").replace("_分析", "") + if not system_name: + system_name = resolve_system_name() output_dir.mkdir(parents=True, exist_ok=True) output_path = output_dir / f"{topic_name}.xmind" - content_xml = build_xmind_content(headers, rows, topic_name) + content_xml = build_xmind_content(headers, rows, topic_name, system_name) manifest_xml = build_manifest() meta_xml = build_meta() @@ -240,7 +266,9 @@ def main() -> None: parser = argparse.ArgumentParser(description="将 Markdown 测试用例导出为 XMind 思维导图。") parser.add_argument("--source-file", help="测试用例 Markdown 文件路径") parser.add_argument("--base-name", help="按 BASE_NAME 查找 output/test_cases/{BASE_NAME}_测试用例.md") - parser.add_argument("--topic-name", help="XMind 根主题名称(默认从文件名推断)", default="") + parser.add_argument("--topic-name", help="需求主题名称(默认从文件名推断)", default="") + parser.add_argument("--system-name", default="", + help=f"XMind 最外层根主题:系统名称(默认取项目画像,回退 {DEFAULT_SYSTEM_NAME})") parser.add_argument("--input-dir", default=str(DEFAULT_INPUT_DIR), help="Markdown 所在目录") parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="XMind 输出目录") args = parser.parse_args() @@ -259,11 +287,12 @@ def main() -> None: if not source_path.exists(): raise FileNotFoundError(f"测试用例文件不存在:{source_path}") - output_path = export_markdown_to_xmind(source_path, output_dir, args.topic_name) + topic_name = args.topic_name or source_path.stem.replace("_测试用例", "").replace("_分析", "") + output_path = export_markdown_to_xmind(source_path, output_dir, args.topic_name, args.system_name) headers, rows = load_markdown_table(source_path) print(f"来源文件: {source_path}") print(f"用例数量: {len(rows)}") - print(f"根主题: {args.topic_name or source_path.stem}") + print(f"根主题: {args.system_name or resolve_system_name()} → {topic_name}") print(f"XMind 已生成: {output_path}")