@@ -0,0 +1,274 @@
""" Generate ppanel-server test case Excel file. """
import os
from openpyxl import Workbook
from openpyxl . styles import (
Font , PatternFill , Alignment , Border , Side
)
from openpyxl . utils import get_column_letter
OUTPUT_PATH = os . path . join ( os . path . dirname ( __file__ ) , " .. " , " tests " , " ppanel_test_cases.xlsx " )
# ── Color palette ──────────────────────────────────────────────────────────────
C_HEADER_BG = " 1F4E79 " # dark blue – header row
C_HEADER_FONT = " FFFFFF " # white
C_SHEET_TITLE = " 2E75B6 " # mid blue – sheet title row
C_P0_BG = " FFE2E2 " # light red – P0
C_P1_BG = " FFF2CC " # light yellow – P1
C_P2_BG = " E2EFDA " # light green – P2
C_BORDER = " BFBFBF "
def thin_border ( ) :
s = Side ( style = " thin " , color = C_BORDER )
return Border ( left = s , right = s , top = s , bottom = s )
def header_fill ( hex_color ) :
return PatternFill ( " solid " , fgColor = hex_color )
def row_fill ( hex_color ) :
return PatternFill ( " solid " , fgColor = hex_color )
# ── Column definitions ─────────────────────────────────────────────────────────
COLUMNS = [ " 用例ID " , " 模块 " , " 功能点 " , " 前置条件 " , " 测试步骤 " , " 预期结果 " ,
" 实际结果 " , " 测试状态 " , " 优先级 " , " 备注 " ]
COL_WIDTHS = [ 16 , 16 , 28 , 32 , 40 , 40 , 20 , 12 , 8 , 20 ]
# ── Test data ──────────────────────────────────────────────────────────────────
SHEET1_ORDER = {
" name " : " 订单核心流程 " ,
" rows " : [
# id, 模块, 功能点, 前置, 步骤, 预期, 优先
( " TC-ORDER-001 " , " 订单/预创建 " , " 正常预览订单价格 " , " 用户已登录,套餐存在且在售 " , " 传入有效 subscribe_id, quantity=1 " , " 返回 price/amount/discount 字段正确 " , " P0 " ) ,
( " TC-ORDER-002 " , " 订单/预创建 " , " 数量为0时自动修正为1 " , " 用户已登录 " , " quantity=0 " , " 自动设为1,正常返回价格 " , " P1 " ) ,
( " TC-ORDER-003 " , " 订单/预创建 " , " 套餐购买数量限制(Quota) " , " 用户已达到该套餐购买上限 " , " 再次预创建同套餐订单 " , " 返回 SubscribeQuotaLimit 错误 " , " P0 " ) ,
( " TC-ORDER-004 " , " 订单/预创建 " , " 新用户专属折扣(24h内注册) " , " 用户注册在24h内,套餐有 new_user_only 折扣 " , " 预创建订单 " , " 折扣生效,amount < price " , " P0 " ) ,
( " TC-ORDER-005 " , " 订单/预创建 " , " 老用户不享受新用户折扣 " , " 用户注册超过24h " , " 预创建有 new_user_only 折扣的套餐 " , " 返回 SubscribeNewUserOnly 错误 " , " P0 " ) ,
( " TC-ORDER-006 " , " 订单/预创建 " , " 新用户已购过不重复享受新用户折扣 " , " 用户24h内注册,但已购买过该套餐 " , " 预创建同套餐 " , " 折扣不生效,按原价计算 " , " P0 " ) ,
( " TC-ORDER-007 " , " 订单/预创建 " , " 优惠券不存在 " , " 用户已登录 " , " 传入不存在的 coupon code " , " 返回 CouponNotExist 错误 " , " P1 " ) ,
( " TC-ORDER-008 " , " 订单/预创建 " , " 优惠券已用完(count限制) " , " 优惠券 used_count >= count " , " 传入该优惠券 " , " 返回 CouponAlreadyUsed 错误 " , " P1 " ) ,
( " TC-ORDER-009 " , " 订单/预创建 " , " 优惠券个人使用次数超限 " , " 用户已使用该优惠券达 user_limit 次 " , " 再次使用 " , " 返回 CouponInsufficientUsage 错误 " , " P1 " ) ,
( " TC-ORDER-010 " , " 订单/预创建 " , " 优惠券不适用于该套餐 " , " 优惠券绑定了特定套餐,与当前套餐不符 " , " 传入该优惠券 " , " 返回 CouponNotApplicable 错误 " , " P1 " ) ,
( " TC-ORDER-011 " , " 订单/预创建 " , " 支付手续费计算 " , " 支付方式有手续费配置 " , " 传入 payment_id " , " feeAmount 正确,amount = 原金额 + 手续费 " , " P1 " ) ,
( " TC-ORDER-012 " , " 订单/预创建 " , " 礼品金额抵扣 " , " 用户 gift_amount > 0 " , " 预创建订单 " , " deduction_amount 正确,amount 减去礼品金额 " , " P1 " ) ,
( " TC-ORDER-013 " , " 订单/预创建 " , " 礼品金额全额抵扣(amount归零) " , " 用户 gift_amount >= 订单金额 " , " 预创建订单 " , " amount=0, deduction_amount = 原订单金额 " , " P1 " ) ,
( " TC-ORDER-014 " , " 订单/购买 " , " 正常购买订阅 " , " 用户已登录,套餐在售有库存 " , " 发起购买请求 " , " 订单创建成功,返回 order_no " , " P0 " ) ,
( " TC-ORDER-015 " , " 订单/购买 " , " 套餐库存为0不允许购买 " , " 套餐 inventory=0 " , " 发起购买 " , " 返回 SubscribeOutOfStock 错误 " , " P0 " ) ,
( " TC-ORDER-016 " , " 订单/购买 " , " 单订阅模式:已有 pending 订单自动关闭 " , " SingleModel=true,用户已有 pending 订单 " , " 对同套餐再次购买 " , " 旧 pending 订单关闭,新订单创建成功 " , " P0 " ) ,
( " TC-ORDER-017 " , " 订单/购买 " , " 单订阅模式:自动路由为续费 " , " SingleModel=true,用户已有有效订阅 " , " 购买相同套餐 " , " 订单类型=2(续费),parent_id 指向原订单 " , " P0 " ) ,
( " TC-ORDER-018 " , " 订单/购买 " , " 数量超过 MaxQuantity 限制 " , " — " , " quantity > MaxQuantity " , " 返回 InvalidParams 错误 " , " P1 " ) ,
( " TC-ORDER-019 " , " 订单/购买 " , " 金额超过 MaxOrderAmount " , " 套餐单价极高 " , " 购买 " , " 返回 InvalidParams 错误 " , " P1 " ) ,
( " TC-ORDER-020 " , " 订单/购买 " , " 15分钟后自动关闭未支付订单 " , " 订单已创建,未支付 " , " 等待15分钟后触发队列 " , " 订单状态变为 Close(3) " , " P0 " ) ,
( " TC-ORDER-021 " , " 订单/激活 " , " 订单激活(NewPurchase) " , " 订单状态=已支付(2),类型=1 " , " 触发激活队列 " , " 用户订阅创建,订单状态变为 Finished(5) " , " P0 " ) ,
( " TC-ORDER-022 " , " 订单/激活 " , " 订单激活(Renewal) " , " 订单状态=已支付,类型=2 " , " 触发激活 " , " 订阅到期时间延长 " , " P0 " ) ,
( " TC-ORDER-023 " , " 订单/激活 " , " 订单激活(ResetTraffic) " , " 订单状态=已支付,类型=3 " , " 触发激活 " , " 用户流量重置 " , " P0 " ) ,
( " TC-ORDER-024 " , " 订单/激活 " , " 订单激活(Recharge) " , " 订单状态=已支付,类型=4 " , " 触发激活 " , " 用户余额增加 " , " P0 " ) ,
( " TC-ORDER-025 " , " 订单/激活 " , " 订单激活(Redemption) " , " 订单状态=已支付,类型=5 " , " 触发激活 " , " 兑换码激活成功 " , " P0 " ) ,
( " TC-ORDER-026 " , " 订单/激活 " , " 幂等性:已完成订单不重复处理 " , " 订单状态=Finished(5) " , " 再次触发激活 " , " 直接跳过,不重复执行 " , " P0 " ) ,
( " TC-ORDER-027 " , " 订单/激活 " , " 非已支付状态订单不处理 " , " 订单状态=Pending(1) 或 Close(3) " , " 触发激活 " , " 跳过,返回 ErrInvalidOrderStatus " , " P0 " ) ,
]
}
SHEET2_USER = {
" name " : " 用户模块 " ,
" rows " : [
( " TC-USER-001 " , " 用户/注册 " , " 邮箱注册 " , " 邮箱未注册 " , " 提交有效邮箱+密码 " , " 用户创建成功,返回 token " , " P0 " ) ,
( " TC-USER-002 " , " 用户/登录 " , " 邮箱密码登录 " , " 用户已注册 " , " 提交正确邮箱+密码 " , " 返回 JWT token, session 写入 Redis " , " P0 " ) ,
( " TC-USER-003 " , " 用户/设备登录 " , " AES-CBC 加密设备登录 " , " 配置 security_secret " , " Body 使用正确密钥加密 " , " 登录成功 " , " P0 " ) ,
( " TC-USER-004 " , " 用户/设备登录 " , " 错误密钥设备登录 " , " — " , " Body 使用错误密钥加密 " , " 返回认证失败错误 " , " P0 " ) ,
( " TC-USER-005 " , " 用户/退出登录 " , " 解绑设备(退出家庭组) " , " 用户在家庭组中 " , " 调用 unbind_device " , " 用户从家庭组移除,device 记录不删除、不禁用 " , " P0 " ) ,
( " TC-USER-006 " , " 用户/注销账号 " , " 正常注销 " , " 用户已登录 " , " 调用 delete_account " , " 账号软删除,auth_methods 软删除,Redis 缓存清理 " , " P0 " ) ,
( " TC-USER-007 " , " 用户/注销账号 " , " 家主注销 → 解散家庭 " , " 用户是家庭组家主 " , " 注销账号 " , " 家庭所有成员 status=removed, family status=disabled " , " P0 " ) ,
( " TC-USER-008 " , " 用户/注销账号 " , " 成员注销 → 仅退出家庭 " , " 用户是家庭组成员 " , " 注销账号 " , " 仅该成员退出,家庭组继续存在 " , " P0 " ) ,
( " TC-USER-009 " , " 用户/注销 " , " 缓存清理(email key 残留问题) " , " 用户已注销,email 缓存可能残留 " , " 注销后检查 Redis " , " cache:user:email: {email} 已删除 " , " P0 " ) ,
( " TC-USER-010 " , " 用户/邀请 " , " 绑定邀请码 " , " 用户未绑定过邀请码 " , " 提交有效邀请码 " , " referer_id 写入,邀请关系建立 " , " P1 " ) ,
( " TC-USER-011 " , " 用户/邀请 " , " 重复绑定邀请码 " , " 用户已绑定邀请码 " , " 再次绑定 " , " 返回错误,不允许重复绑定 " , " P1 " ) ,
( " TC-USER-012 " , " 用户/佣金 " , " 首购返佣 " , " 用户通过邀请码注册,完成首次付款 " , " 订单激活 " , " 邀请人佣金增加 " , " P1 " ) ,
( " TC-USER-013 " , " 用户/佣金 " , " only_first_purchase=true 仅首购返佣 " , " 配置仅首购 " , " 被邀请人第二次购买 " , " 不再发佣金 " , " P1 " ) ,
( " TC-USER-014 " , " 用户/佣金 " , " 赠送天数(双方) " , " 邀请关系建立,被邀请人购买 " , " 订单激活 " , " 邀请人和被邀请人各获得赠送天数 " , " P1 " ) ,
( " TC-USER-015 " , " 用户/家庭组 " , " 踢出家庭成员 " , " 用户是家庭组家主 " , " 踢出某成员 " , " 该成员退出家庭组,设备记录不变 " , " P1 " ) ,
( " TC-USER-016 " , " 用户/订阅 " , " 查看订阅状态(含节点分组名和限速时间) " , " 用户有有效订阅 " , " 查询订阅状态 " , " 返回节点分组名、限速起止时间 " , " P1 " ) ,
]
}
SHEET3_SUB = {
" name " : " 订阅套餐 " ,
" rows " : [
( " TC-SUB-001 " , " 套餐/列表 " , " 获取可用套餐列表 " , " — " , " 调用套餐列表接口 " , " 返回所有在售套餐 " , " P1 " ) ,
( " TC-SUB-002 " , " 套餐/列表 " , " 老版本客户端裁剪套餐列表 " , " 请求头含 X-App-Id(老版本标识) " , " 调用套餐列表 " , " 每个套餐的 discount 列表去掉最后一项 " , " P1 " ) ,
( " TC-SUB-003 " , " 套餐/购买限制 " , " Quota 限制(每用户购买上限) " , " 套餐设置 quota=1 " , " 用户购买2次同套餐 " , " 第二次返回 SubscribeQuotaLimit " , " P0 " ) ,
( " TC-SUB-004 " , " 套餐/折扣 " , " 数量折扣梯度 " , " 套餐配置多级数量折扣 " , " 购买不同数量 " , " 对应折扣率正确应用 " , " P1 " ) ,
( " TC-SUB-005 " , " 套餐/库存 " , " 库存充足时正常购买 " , " inventory > 0 " , " 购买 " , " 成功,inventory -1 " , " P1 " ) ,
( " TC-SUB-006 " , " 套餐/库存 " , " 库存=-1(无限库存) " , " inventory=-1 " , " 多次购买 " , " 不减少库存,始终可购 " , " P1 " ) ,
]
}
SHEET4_PAY = {
" name " : " 支付与优惠券 " ,
" rows " : [
( " TC-PAY-001 " , " 支付/方式 " , " 获取可用支付方式列表 " , " — " , " 调用支付方式接口 " , " 返回当前配置的支付方式 " , " P1 " ) ,
( " TC-PAY-002 " , " 支付/手续费 " , " 固定手续费计算 " , " 支付方式配置固定手续费 " , " 下单 " , " feeAmount = 配置值 " , " P1 " ) ,
( " TC-PAY-003 " , " 支付/手续费 " , " 百分比手续费计算 " , " 支付方式配置百分比手续费 " , " 下单 " , " feeAmount = amount × 百分比 " , " P1 " ) ,
( " TC-PAY-004 " , " 支付/手续费 " , " amount=0 时不计算手续费 " , " 礼品金额全额抵扣后 amount=0 " , " 下单 " , " feeAmount=0 " , " P1 " ) ,
( " TC-CPN-001 " , " 优惠券/固定减免 " , " 固定金额优惠券 " , " 优惠券类型=固定,value=100 " , " 使用优惠券 " , " 订单减免100 " , " P1 " ) ,
( " TC-CPN-002 " , " 优惠券/百分比 " , " 百分比优惠券 " , " 优惠券类型=百分比,value=0.8 " , " 使用优惠券 " , " 订单金额×0.8 " , " P1 " ) ,
( " TC-CPN-003 " , " 优惠券/过期 " , " 过期优惠券不可用 " , " 优惠券 expire_at < now " , " 使用 " , " 返回错误(CouponExpired) " , " P1 " ) ,
( " TC-CPN-004 " , " 优惠券/套餐绑定 " , " 仅限指定套餐使用 " , " 优惠券绑定套餐A " , " 用于套餐B " , " 返回 CouponNotApplicable " , " P1 " ) ,
]
}
SHEET5_IAP = {
" name " : " IAP苹果内购 " ,
" rows " : [
( " TC-IAP-001 " , " IAP/绑定 " , " 绑定苹果内购 transaction " , " 苹果 transaction 有效 " , " 提交 transaction_id " , " 订单创建并激活,订阅开通 " , " P1 " ) ,
( " TC-IAP-002 " , " IAP/绑定 " , " 重复绑定同一 transaction " , " transaction 已绑定 " , " 再次提交 " , " 幂等处理,不重复创建订单 " , " P1 " ) ,
( " TC-IAP-003 " , " IAP/单订阅模式 " , " 内购续费路由 " , " SingleModel=true,用户已有订阅 " , " 提交续费 transaction " , " 路由为续费类型订单 " , " P1 " ) ,
( " TC-IAP-004 " , " IAP/对账 " , " 日对账任务 " , " 配置了 IAP 对账 " , " 触发日对账 " , " 检查并补处理漏掉的 transaction " , " P2 " ) ,
]
}
SHEET6_LOG = {
" name " : " 日志与缓存 " ,
" rows " : [
( " TC-LOG-001 " , " 日志/佣金 " , " 佣金记录写入 system_logs " , " 发生佣金发放 " , " 触发订单激活 " , " type=33 的记录写入,content.type 为 331 或 332 " , " P2 " ) ,
( " TC-LOG-002 " , " 日志/礼品金额 " , " 礼品金额扣除记录 " , " 用户有 gift_amount,下单扣除 " , " 购买 " , " GiftTypeReduce 记录写入 system_logs " , " P2 " ) ,
( " TC-CACHE-001 " , " 缓存/用户 " , " 注销后 user email 缓存清理 " , " 用户已注销 " , " 检查 Redis " , " cache:user:email: {email} 已删除 " , " P0 " ) ,
( " TC-CACHE-002 " , " 缓存/订阅 " , " 订阅 token 缓存有效 " , " 用户有订阅 " , " 查询订阅 " , " 从 cache:user:subscribe:token: {token} 命中 " , " P2 " ) ,
( " TC-CACHE-003 " , " 缓存/签名 " , " X-App-Id 签名验证 " , " AppSecrets 已配置 " , " 发送带签名请求 " , " 验签通过,正常处理 " , " P1 " ) ,
( " TC-CACHE-004 " , " 缓存/签名 " , " 无 X-App-Id 跳过签名 " , " — " , " 发送无签名请求 " , " 直接通过,不验签 " , " P1 " ) ,
]
}
SHEET7_QUEUE = {
" name " : " 队列任务 " ,
" rows " : [
( " TC-QUEUE-001 " , " 队列/订单关闭 " , " 超时自动关闭订单 " , " 未支付订单存在 " , " 等待15分钟 " , " 订单状态=Close " , " P0 " ) ,
( " TC-QUEUE-002 " , " 队列/订阅检查 " , " 定期检查订阅到期 " , " 用户订阅即将到期 " , " 触发 checkSubscription " , " 到期通知发送 " , " P2 " ) ,
( " TC-QUEUE-003 " , " 队列/流量统计 " , " 服务器流量统计写入 " , " 有流量数据上报 " , " 触发 trafficStat " , " 流量数据正确写入 DB " , " P2 " ) ,
( " TC-QUEUE-004 " , " 队列/邮件 " , " 批量发送邮件任务 " , " 已创建批量邮件任务 " , " 触发队列 " , " 邮件发送成功,任务状态更新 " , " P2 " ) ,
( " TC-QUEUE-005 " , " 队列/流量重置 " , " 定期重置用户流量 " , " 配置了流量重置周期 " , " 触发 resetTraffic " , " 用户流量归零 " , " P2 " ) ,
]
}
ALL_SHEETS = [ SHEET1_ORDER , SHEET2_USER , SHEET3_SUB , SHEET4_PAY , SHEET5_IAP , SHEET6_LOG , SHEET7_QUEUE ]
PRIORITY_FILL = {
" P0 " : row_fill ( C_P0_BG ) ,
" P1 " : row_fill ( C_P1_BG ) ,
" P2 " : row_fill ( C_P2_BG ) ,
}
def write_sheet ( wb : Workbook , sheet_def : dict ) :
ws = wb . create_sheet ( title = sheet_def [ " name " ] )
rows = sheet_def [ " rows " ]
# ── Title row ──────────────────────────────────────────────────────────────
ws . merge_cells ( start_row = 1 , start_column = 1 , end_row = 1 , end_column = len ( COLUMNS ) )
title_cell = ws . cell ( row = 1 , column = 1 , value = f " ppanel-server 测试用例 — { sheet_def [ ' name ' ] } " )
title_cell . font = Font ( name = " 微软雅黑 " , bold = True , size = 13 , color = C_HEADER_FONT )
title_cell . fill = header_fill ( C_SHEET_TITLE )
title_cell . alignment = Alignment ( horizontal = " center " , vertical = " center " )
ws . row_dimensions [ 1 ] . height = 28
# ── Header row ─────────────────────────────────────────────────────────────
for col_idx , col_name in enumerate ( COLUMNS , start = 1 ) :
cell = ws . cell ( row = 2 , column = col_idx , value = col_name )
cell . font = Font ( name = " 微软雅黑 " , bold = True , size = 10 , color = C_HEADER_FONT )
cell . fill = header_fill ( C_HEADER_BG )
cell . alignment = Alignment ( horizontal = " center " , vertical = " center " , wrap_text = True )
cell . border = thin_border ( )
ws . row_dimensions [ 2 ] . height = 22
# ── Data rows ──────────────────────────────────────────────────────────────
for r_idx , row in enumerate ( rows , start = 3 ) :
tc_id , module , feature , precond , steps , expected , priority = row
values = [ tc_id , module , feature , precond , steps , expected , " " , " " , priority , " " ]
fill = PRIORITY_FILL . get ( priority , None )
for c_idx , val in enumerate ( values , start = 1 ) :
cell = ws . cell ( row = r_idx , column = c_idx , value = val )
cell . font = Font ( name = " 微软雅黑 " , size = 9 )
cell . alignment = Alignment ( horizontal = " left " , vertical = " center " , wrap_text = True )
cell . border = thin_border ( )
if fill :
cell . fill = fill
ws . row_dimensions [ r_idx ] . height = 45
# ── Column widths ──────────────────────────────────────────────────────────
for col_idx , width in enumerate ( COL_WIDTHS , start = 1 ) :
ws . column_dimensions [ get_column_letter ( col_idx ) ] . width = width
# ── Freeze panes ──────────────────────────────────────────────────────────
ws . freeze_panes = " A3 "
# ── Auto filter ───────────────────────────────────────────────────────────
ws . auto_filter . ref = f " A2: { get_column_letter ( len ( COLUMNS ) ) } 2 "
def write_legend_sheet ( wb : Workbook ) :
ws = wb . create_sheet ( title = " 说明 " , index = 0 )
ws . column_dimensions [ " A " ] . width = 18
ws . column_dimensions [ " B " ] . width = 50
title = ws . cell ( row = 1 , column = 1 , value = " ppanel-server 测试用例说明 " )
ws . merge_cells ( " A1:B1 " )
title . font = Font ( name = " 微软雅黑 " , bold = True , size = 13 , color = C_HEADER_FONT )
title . fill = header_fill ( C_SHEET_TITLE )
title . alignment = Alignment ( horizontal = " center " , vertical = " center " )
ws . row_dimensions [ 1 ] . height = 28
legend_data = [
( " 项目 " , " 说明 " ) ,
( " 测试框架 " , " ppanel-server — go-zero + Gin " ) ,
( " 数据库 " , " 本地 MySQL(真实),禁止 SQLite " ) ,
( " Redis " , " 本地 Redis 或 miniredis " ) ,
( " 时间戳规范 " , " 后端统一返回秒级 Unix(),前端 ×1000 " ) ,
( " " , " " ) ,
( " 优先级 " , " 含义 " ) ,
( " P0(红色) " , " 核心业务,必须通过。订单/认证/缓存清理等 " ) ,
( " P1(黄色) " , " 重要功能,强烈建议测试。折扣/优惠券/邀请等 " ) ,
( " P2(绿色) " , " 辅助功能,建议测试。日志/队列/IAP 等 " ) ,
( " " , " " ) ,
( " 测试状态 " , " 填写规范 " ) ,
( " Pass " , " 用例通过 " ) ,
( " Fail " , " 用例失败,需记录实际结果 " ) ,
( " Block " , " 用例被阻塞(依赖功能未就绪) " ) ,
( " Skip " , " 本轮跳过 " ) ,
( " " , " " ) ,
( " Sheet 说明 " , " " ) ,
( " Sheet1 订单核心流程 " , " 27 条:预创建/购买/激活全流程 " ) ,
( " Sheet2 用户模块 " , " 16 条:注册/登录/注销/邀请/家庭组 " ) ,
( " Sheet3 订阅套餐 " , " 6 条:库存/折扣/限额 " ) ,
( " Sheet4 支付与优惠券 " , " 8 条:手续费/优惠券各类型 " ) ,
( " Sheet5 IAP苹果内购 " , " 4 条:内购/对账 " ) ,
( " Sheet6 日志与缓存 " , " 6 条:日志写入/缓存清理 " ) ,
( " Sheet7 队列任务 " , " 5 条:队列任务验证 " ) ,
]
for r_idx , ( key , val ) in enumerate ( legend_data , start = 2 ) :
c1 = ws . cell ( row = r_idx , column = 1 , value = key )
c2 = ws . cell ( row = r_idx , column = 2 , value = val )
for c in ( c1 , c2 ) :
c . font = Font ( name = " 微软雅黑 " , size = 9 )
c . alignment = Alignment ( vertical = " center " , wrap_text = True )
c . border = thin_border ( )
if key in ( " 项目 " , " 优先级 " , " 测试状态 " , " Sheet 说明 " ) :
for c in ( c1 , c2 ) :
c . font = Font ( name = " 微软雅黑 " , bold = True , size = 9 , color = C_HEADER_FONT )
c . fill = header_fill ( C_HEADER_BG )
ws . row_dimensions [ r_idx ] . height = 18
def main ( ) :
os . makedirs ( os . path . dirname ( os . path . abspath ( OUTPUT_PATH ) ) , exist_ok = True )
wb = Workbook ( )
wb . remove ( wb . active ) # remove default sheet
write_legend_sheet ( wb )
for sheet_def in ALL_SHEETS :
write_sheet ( wb , sheet_def )
wb . save ( OUTPUT_PATH )
print ( f " Excel saved: { os . path . abspath ( OUTPUT_PATH ) } " )
if __name__ == " __main__ " :
main ( )