本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交 (5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。 一、docs 结构整改(整改 #14) 根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入, 随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录 树并存于 docs/,共 791 文件、双分类体系冲突。 修复动作: - b2 同名异主题文件改名迁移保全 9 个 - C 类 39 个孤立文件按主题正确归类 - A/B1 类 222 个重复文件删除(新结构已有内容副本) - 9 个旧独有空目录删除 - 270 处内部引用按 verified 映射改写 - 整改记录 #14 登记于 04-运维文档/部署运维 结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。 残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。 二、compose 双目录对齐(消除踩坑 A) - docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist 改为 src/frontend-*/dist(h5 / agent / admin / terminal) - docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/ - 效果:本地 docker compose up 不再把根目录 stale dist 挂回, 与线上一致,分叉隐患消除(已 docker compose config 校验通过) 防复发铁律: - 重构须提交;仓库修复须 git stash -u 或先 commit - 新结构须 git add 并提交,避免再次 untracked 复活 - H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
24 KiB
系统设计文档 — 代办事项真实数据源集成
项目:企微IT智能服务台 — 代办面板去mock改造 版本:v1.0 日期:2026-07-11
Part A: 系统设计
1. 实现方案
1.1 核心技术挑战
| 挑战 | 方案 |
|---|---|
| 两个异构数据源(企微审批 + ITSM 工单)聚合为统一列表 | 引入 TodoAggregatorService,通过抽象接口 TodoSourceService 统一两个数据源,asyncio.gather 并行查询,return_exceptions=True 容错 |
| 企微审批需 getapprovaldata → getapprovaldetail 二次过滤当前审批人,性能开销大 | 先用 getapprovaldata 按 sp_status=1 + 18 个模板批量获取 sp_no_list,再 asyncio.gather 并发调用 getapprovaldetail,最后用 _extract_current_approver 过滤 |
| ITSM 列表 API 尚未获取,签名认证方式特殊 | 设计 ITSMService 为可插拔实现,签名计算抽为独立工具 itsm_signer.py,列表方法暂返回空列表 + 日志告警,待 API 到位后填充 |
| Redis 缓存需区分不同坐席 | 缓存 key 设计:todo:cache:{agent_userid}:{type_filter},TTL 45 秒 |
| 彻底去 mock(后端 MOCK_TODO_ITEMS + 前端 mockTodoListData + device 类型) | 全量删除 mock 数据,schema 中 VALID_TODO_TYPES 移除 device,前端移除 DeviceDetail 引用 |
1.2 框架与库选择
| 库 | 用途 | 说明 |
|---|---|---|
httpx |
异步 HTTP 客户端 | 已用于 approval.py,复用 |
redis.asyncio |
Redis 异步缓存 | 已用于 token 管理,复用 |
asyncio |
并发查询两个数据源 | Python 标准库,asyncio.gather(return_exceptions=True) |
hashlib |
ITSM SHA1 签名 | Python 标准库 |
urllib.parse.quote_plus |
ITSM 签名 URL 编码 | Python 标准库 |
结论:无需新增任何第三方依赖。
1.3 架构模式
采用 Service Layer + 策略模式:
┌─────────────────────────────────────────────────────────┐
│ API Layer (todo_items.py) │
│ GET /api/todo-items │
│ GET /api/todo-items/{id} │
└──────────────────────┬──────────────────────────────────┘
│ 调用
┌──────────────────────▼──────────────────────────────────┐
│ TodoAggregatorService │
│ ┌─ Redis 缓存检查 ─────────────────────────────────┐ │
│ │ 命中 → 直接返回 │ │
│ │ 未命中 → asyncio.gather 并行查询 │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌──────────────┬──────────────┐ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌────▼─────────┐ │ │
│ │ApprovalTodo │ │ ITSMService │ │ │
│ │ Service │ │ (abstract) │ │ │
│ └──────┬──────┘ └────┬─────────┘ │ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌────▼─────────┐ │ │
│ │ 企微审批API │ │ ITSM API │ │ │
│ │ getapproval │ │ (待实现) │ │ │
│ │ data/detail│ │ │ │ │
│ └─────────────┘ └──────────────┘ │ │
└──────────────────────────────────────┘
1.4 后端改造方案
-
新增 Service 层(3 个文件):
TodoSourceService(抽象基类)+ApprovalTodoService(企微审批实现)ITSMService(ITSM 工单实现,列表方法待 API 到位)TodoAggregatorService(聚合服务,缓存 + 并行查询 + 容错)
-
新增 ITSM 签名工具:
itsm_signer.py,独立封装 SHA1 签名计算 -
重写
todo_items.py:删除MOCK_TODO_ITEMS,调用TodoAggregatorService获取真实数据 -
补充
approval.py:新增get_approval_data()函数调用企微getapprovaldataAPI -
更新
config.py:新增itsm_app_id、itsm_app_secret、itsm_base_url配置项 -
更新
schemas/todo_item.py:VALID_TODO_TYPES移除device
1.5 前端改造方案
api/todo.ts:类型定义移除device,新增type查询参数stores/todo.ts:删除mockTodoListDataimport 和 catch 块 mock fallbackmock/data.ts:删除mockTodoListData导出TodoPanel.vue:新增类型筛选 Tab(全部/审批/工单)+ 手动刷新按钮 + 移除 device 类型样式TaskDetailView.vue:移除 DeviceDetail 引用DeviceDetail.vue:标记为废弃(保留文件但不再引用)
2. 文件列表
后端
| 文件路径 | 操作 | 说明 |
|---|---|---|
backend/app/config.py |
修改 | 新增 ITSM 配置项 |
backend/app/schemas/todo_item.py |
修改 | VALID_TODO_TYPES 移除 device |
backend/app/models/todo_item.py |
修改 | type 注释移除 device |
backend/app/services/todo_source_service.py |
新建 | 抽象基类 + ApprovalTodoService |
backend/app/services/itsm_service.py |
新建 | ITSM Service(签名认证 + 可插拔列表) |
backend/app/services/todo_aggregator_service.py |
新建 | 聚合服务(缓存 + 并行 + 容错) |
backend/app/utils/itsm_signer.py |
新建 | ITSM SHA1 签名工具 |
backend/app/api/todo_items.py |
修改 | 删除 mock,重写为真实数据聚合 |
backend/app/api/approval.py |
修改 | 新增 get_approval_data() |
前端
| 文件路径 | 操作 | 说明 |
|---|---|---|
frontend-agent/src/api/todo.ts |
修改 | 移除 device 类型,新增 type 参数 |
frontend-agent/src/stores/todo.ts |
修改 | 移除 mock fallback |
frontend-agent/src/mock/data.ts |
修改 | 删除 mockTodoListData |
frontend-agent/src/components/conversation/TodoPanel.vue |
修改 | 新增 Tab + 刷新 + 移除 device |
frontend-agent/src/components/chat/TaskDetailView.vue |
修改 | 移除 DeviceDetail 引用 |
frontend-agent/src/components/chat/task/DeviceDetail.vue |
修改 | 废弃标记 |
frontend-agent/src/components/chat/task/TicketDetail.vue |
修改 | 适配真实 ITSM 数据结构 |
frontend-agent/src/components/chat/task/ApprovalDetail.vue |
修改 | 适配真实企微审批数据结构 |
3. 数据结构和接口(类图)
classDiagram
class TodoSourceService {
<<abstract>>
+agent_userid: str
+redis: aioredis.Redis
+get_todo_list() List~TodoItemData~*
+get_todo_detail(item_id: str) TodoItemData*
}
class ApprovalTodoService {
-redis: aioredis.Redis
-agent_userid: str
+get_todo_list() List~TodoItemData~
+get_todo_detail(sp_no: str) TodoItemData
-_fetch_approval_sp_no_list() List~str~
-_fetch_approval_details(sp_no_list: List~str~) List~dict~
-_filter_by_current_approver(details: List~dict~) List~dict~
-_map_to_todo_item(detail: dict) TodoItemData
}
class ITSMService {
-base_url: str
-app_id: str
-app_secret: str
-redis: aioredis.Redis
-agent_userid: str
+get_todo_list() List~TodoItemData~
+get_todo_detail(workitem_id: str) TodoItemData
-_do_post(url: str, body: dict) dict
-_get_workitem_detail(process_instance_id: int, executor: str) dict
}
class ITSMSigner {
+compute_signature(app_id: str, timestamp: str, app_secret: str, biz_data: dict) str
-_get_signature_compatible(params: dict) str
}
class TodoAggregatorService {
-redis: aioredis.Redis
-cache_ttl: int
+get_todo_list(agent_userid: str, todo_type: Optional~str~) dict
+get_todo_detail(agent_userid: str, item_id: str, todo_type: str) dict
-_get_from_cache(agent_userid: str, todo_type: Optional~str~) Optional~dict~
-_set_to_cache(agent_userid: str, todo_type: Optional~str~, data: dict) void
-_invalidate_cache(agent_userid: str) void
}
class TodoItemData {
+id: str
+type: str
+title: str
+priority: str
+description: dict
+status: str
+assigned_agent_id: Optional~str~
+corp_id: str
+created_at: str
+updated_at: str
}
TodoSourceService <|-- ApprovalTodoService
TodoSourceService <|-- ITSMService
TodoAggregatorService o-- ApprovalTodoService : creates
TodoAggregatorService o-- ITSMService : creates
ITSMService --> ITSMSigner : uses
TodoSourceService ..> TodoItemData : returns
4. 程序调用流程(时序图)
4.1 代办列表查询流程
sequenceDiagram
participant FE as 前端 TodoPanel
participant API as GET /api/todo-items
participant Agg as TodoAggregatorService
participant Cache as Redis Cache
participant Appr as ApprovalTodoService
participant Wecom as 企微审批API
participant ITSM as ITSMService
participant ITSM_API as ITSM OpenAPI
FE->>API: GET /api/todo-items?type=approval
API->>Agg: get_todo_list(agent_userid, type="approval")
Agg->>Cache: GET todo:cache:{userid}:approval
alt 缓存命中
Cache-->>Agg: cached_data
Agg-->>API: {items, total, cached:true}
else 缓存未命中
Agg->>Agg: asyncio.gather(approval_svc, itsm_svc, return_exceptions=True)
par 并行查询审批
Agg->>Appr: get_todo_list()
Appr->>Wecom: getapprovaldata(sp_status=1, templates=18)
Wecom-->>Appr: sp_no_list
Appr->>Appr: asyncio.gather(getapprovaldetail × N)
loop 每个sp_no并发获取详情
Appr->>Wecom: getapprovaldetail(sp_no)
Wecom-->>Appr: approval_detail
end
Appr->>Appr: _filter_by_current_approver(details)
Appr->>Appr: _map_to_todo_item(detail)
Appr-->>Agg: List[TodoItemData]
and 并行查询工单
Agg->>ITSM: get_todo_list()
Note over ITSM: ITSM列表API待实现<br/>当前返回空列表+日志告警
ITSM-->>Agg: List[TodoItemData] (空)
end
Agg->>Agg: merge + sort by priority
Agg->>Cache: SET todo:cache:{userid}:approval TTL=45s
Agg-->>API: {items, total, cached:false}
end
API-->>FE: {code:0, data:{items, total}}
4.2 代办详情查询流程
sequenceDiagram
participant FE as 前端 TaskDetailView
participant API as GET /api/todo-items/{id}
participant Agg as TodoAggregatorService
participant Appr as ApprovalTodoService
participant ITSM as ITSMService
participant Wecom as 企微审批API
participant ITSM_API as ITSM OpenAPI
FE->>API: GET /api/todo-items/approval:{sp_no}
API->>Agg: get_todo_detail(agent_userid, item_id, todo_type="approval")
alt type == "approval"
Agg->>Appr: get_todo_detail(sp_no)
Appr->>Wecom: getapprovaldetail(sp_no)
Wecom-->>Appr: approval_detail
Appr->>Appr: _map_to_todo_item(detail)
Appr-->>Agg: TodoItemData
else type == "ticket"
Agg->>ITSM: get_todo_detail(workitem_id)
ITSM->>ITSM_API: POST /openapi/v1/process/workitem/detail
ITSM_API-->>ITSM: {code:20000, data:{workitem_detail}}
ITSM->>ITSM: _map_to_todo_item(detail)
ITSM-->>Agg: TodoItemData
end
Agg-->>API: TodoItemData
API-->>FE: {code:0, data:{item}}
4.3 ITSM 签名认证流程
sequenceDiagram
participant Svc as ITSMService
participant Signer as ITSMSigner
participant API as ITSM OpenAPI
Svc->>Svc: timestamp = str(int(time.time()*1000))
Svc->>Signer: compute_signature(app_id, timestamp, app_secret, biz_data)
Signer->>Signer: sign_params = {appId, timestamp, appSecret, bizData}
Signer->>Signer: sort by key ASC
Signer->>Signer: concat all values → canonicalized_str
Signer->>Signer: quote_plus(canonicalized_str)
Signer->>Signer: sha1(q2).hexdigest().upper()
Signer-->>Svc: sign_str
Svc->>API: POST url, headers={appId, timestamp, sign}, json=body
API-->>Svc: {code:20000, data:{...}}
5. 待明确事项
| # | 问题 | 当前假设 | 影响 |
|---|---|---|---|
| 1 | ITSM 代办列表 API 端点和请求/响应格式 | 设计为抽象接口,列表方法暂返回空列表 | 待用户抓包获取后实现,不影响架构 |
| 2 | ITSM app_id 和 app_secret | 需在 config.py 中新增配置项占位 | 部署时通过环境变量注入 |
| 3 | 企微 getapprovaldata 单次查询上限(size 参数) | 假设每页100条,循环 cursor 分页 | 如上限更小需增加分页逻辑 |
| 4 | 审批详情并发查询数量较多时的限流 | 使用 asyncio.Semaphore 限制并发数(默认10) | 防止企微 API 限流 |
| 5 | 坐席身份标识(agent_userid)从哪里获取 | 假设从请求 header 或 JWT token 中提取 | 需确认认证中间件传递方式 |
| 6 | ITSM 工单的优先级映射规则 | 假设 ITSM 有自己的优先级字段,需映射到 urgent/high/normal | 待 API 确认后调整映射逻辑 |
Part B: 任务分解
6. 依赖包列表
无新增第三方依赖。 所有所需库已在项目中使用:
httpx— 已用于 approval.py 的企微 API 调用redis.asyncio— 已用于 token 缓存管理hashlib— Python 标准库(ITSM SHA1 签名)urllib.parse.quote_plus— Python 标准库(ITSM 签名 URL 编码)asyncio— Python 标准库(并行查询)
7. 任务列表
T01: 后端基础设施与数据模型
依赖:无 优先级:P0 文件:
backend/app/config.py(修改)backend/app/schemas/todo_item.py(修改)backend/app/models/todo_item.py(修改)
描述:
- config.py 新增 ITSM 配置项:
itsm_app_id: str = ""itsm_app_secret: str = ""itsm_base_url: str = "https://devops.dc.servyou-it.com/itsm"(生产)itsm_test_base_url: str = "https://test-devops.dc.servyou-it.com/itsm"(测试)
- schemas/todo_item.py:
VALID_TODO_TYPES从{"ticket", "approval", "device"}改为{"ticket", "approval"}- 更新所有字段描述中的类型注释
- models/todo_item.py:
- type 字段注释从
ticket/approval/device改为ticket/approval
- type 字段注释从
T02: 后端 Service 层 + ITSM 签名工具
依赖:T01 优先级:P0 文件:
backend/app/services/todo_source_service.py(新建)backend/app/services/itsm_service.py(新建)backend/app/services/todo_aggregator_service.py(新建)backend/app/utils/itsm_signer.py(新建)
描述:
todo_source_service.py:- 定义抽象基类
TodoSourceService,含get_todo_list()和get_todo_detail()抽象方法 - 实现
ApprovalTodoService:get_todo_list():调getapprovaldata(sp_status=1, 18模板)→ 并发getapprovaldetail→_extract_current_approver过滤 →_map_to_todo_item映射get_todo_detail(sp_no):调getapprovaldetail→_map_to_todo_item_map_to_todo_item():企微审批详情 → TodoItemData 格式映射- 使用
asyncio.Semaphore(10)限制并发详情查询
- 定义抽象基类
itsm_signer.py:ITSMSigner.compute_signature(app_id, timestamp, app_secret, biz_data)静态方法- 实现 PRD 中的签名算法:sort → concat → quote_plus → sha1 → upper
itsm_service.py:- 继承
TodoSourceService _do_post(url, body):签名 + 发送请求(复用 ITSMSigner)get_workitem_detail(process_instance_id, executor):调已知详情 APIget_todo_list():待 API 到位,当前返回空列表 +logger.warningget_todo_detail(workitem_id):调get_workitem_detail→ 映射
- 继承
todo_aggregator_service.py:get_todo_list(agent_userid, todo_type):Redis 缓存 →asyncio.gather(return_exceptions=True)→ 合并排序 → 写缓存get_todo_detail(agent_userid, item_id, todo_type):按类型路由到对应 Service- 缓存 key:
todo:cache:{agent_userid}:{todo_type or "all"},TTL 45s
T03: 后端 API 改造
依赖:T02 优先级:P0 文件:
backend/app/api/todo_items.py(修改)backend/app/api/approval.py(修改)backend/app/api/router.py(确认,无需修改路由注册)
描述:
todo_items.py:- 删除
MOCK_TODO_ITEMS(全部20条硬编码数据) - 删除
TodoItemResponse、TodoItemListResponse(移至 schemas,复用已有) list_todo_items():新增type查询参数,调用TodoAggregatorService.get_todo_list()get_todo_item():从item_id中解析类型前缀(如approval:{sp_no}/ticket:{workitem_id}),调用TodoAggregatorService.get_todo_detail()update_todo_item_status():保留但标记为"仅展示,不支持在服务台内操作"(按用户决策,交互方式为跳转原系统)- 从请求中获取
agent_userid(header 或 JWT)
- 删除
approval.py:- 新增
get_approval_data(access_token, starttime, endtime, filters)异步函数 - 调用企微
POST /cgi-bin/oa/getapprovaldataAPI - 支持 cursor 分页循环
- 新增
T04: 前端数据层与 API 改造
依赖:T03 优先级:P0 文件:
frontend-agent/src/api/todo.ts(修改)frontend-agent/src/stores/todo.ts(修改)frontend-agent/src/mock/data.ts(修改)
描述:
api/todo.ts:TodoItemData.type注释移除 devicegetTodoItems()新增type参数:type?: 'ticket' | 'approval'- 新增
refreshTodoItems()函数(带_force=1参数跳过缓存)
stores/todo.ts:- 删除
import { mockTodoListData } from '@/mock/data' fetchTodoList()catch 块:删除 mock fallback,改为todoList.value = []+ 错误日志- 新增
activeTyperef:'all' | 'approval' | 'ticket',传入 API type 参数 - 新增
refreshList()方法:强制刷新(调refreshTodoItems)
- 删除
mock/data.ts:- 删除
mockTodoListData常量和导出 - 删除
mockTodos数组(5条前端 mock 数据)
- 删除
T05: 前端组件层改造
依赖:T04 优先级:P0(移除 device)/ P1(Tab+详情+刷新)/ P2(定时刷新+跳转) 文件:
frontend-agent/src/components/conversation/TodoPanel.vue(修改)frontend-agent/src/components/chat/TaskDetailView.vue(修改)frontend-agent/src/components/chat/task/DeviceDetail.vue(修改/废弃)frontend-agent/src/components/chat/task/TicketDetail.vue(修改)frontend-agent/src/components/chat/task/ApprovalDetail.vue(修改)
描述:
TodoPanel.vue(P0+P1+P2):typeLabel移除device: '设备'- 新增类型筛选 Tab(全部/审批/工单),切换时更新
todoStore.activeType+ 刷新列表 - 新增刷新按钮(🔄 图标),点击调
todoStore.refreshList() - 新增定时刷新(P2):
setInterval(fetchTodoList, 60000),组件onUnmounted时清除 - 新增跳转按钮(P2):每条待办条目增加"在原系统中打开"链接(审批跳企微审批URL / 工单跳ITSM URL)
- 移除
.todo-type-tag.type-deviceCSS 样式
TaskDetailView.vue(P0):- 移除
import DeviceDetail from './task/DeviceDetail.vue' - 移除
v-else-if="todoItem.type === 'device'"条件渲染块 typeLabelMap移除device: '🖥 设备异常'- 移除
.tdv-type-deviceCSS 样式
- 移除
DeviceDetail.vue(P0):- 文件头注释标记为"已废弃 — v1.0 移除 device 类型"
- 保留文件但不再被任何组件引用
TicketDetail.vue(P1):- 适配真实 ITSM 工单数据结构(description 字段映射调整)
- 底部操作按钮改为"在 ITSM 中打开"跳转链接(P2)
ApprovalDetail.vue(P1):- 适配真实企微审批数据结构(description 字段映射调整)
- 底部操作按钮改为"在企微审批中打开"跳转链接(P2)
8. 共享知识(跨文件约定)
8.1 统一数据映射规则
企微审批 → TodoItemData 映射:
{
"id": f"approval:{sp_no}", # 前缀类型 + 原始ID
"type": "approval",
"title": sp_name, # 审批单名称
"priority": "high", # 审批默认 high(企微无优先级概念)
"description": {
"sp_no": sp_no,
"template_name": template_name,
"applicant": applyer_userid,
"apply_time": apply_time,
"sp_status": sp_status,
"current_approver": current_approver,
"template_id": template_id,
},
"status": "pending", # 审批中统一映射为 pending
"assigned_agent_id": current_approver,
"corp_id": corp_id,
"created_at": apply_time_iso, # apply_time 时间戳转 ISO
"updated_at": apply_time_iso,
}
ITSM 工单 → TodoItemData 映射:
{
"id": f"ticket:{process_instance_id}", # 前缀类型 + 原始ID
"type": "ticket",
"title": title,
"priority": itsm_priority_to_todo(priority), # ITSM 优先级 → urgent/high/normal
"description": {
"process_instance_id": process_instance_id,
"executor": executor,
"status": itsm_status,
"creator": creator,
# ... 其他 ITSM 字段
},
"status": "pending",
"assigned_agent_id": agent_userid,
"corp_id": "",
"created_at": created_at_iso,
"updated_at": updated_at_iso,
}
8.2 ID 格式约定
所有 TodoItem 的 id 字段使用 {type}:{原始ID} 格式:
- 审批:
approval:{sp_no}(如approval:202607110001) - 工单:
ticket:{process_instance_id}(如ticket:12345)
解析规则:item_id.split(":", 1) → [type, original_id]
8.3 缓存 Key 设计
todo:cache:{agent_userid}:{todo_type}
agent_userid:坐席企微 useridtodo_type:all/approval/ticket- TTL:45 秒
- 强制刷新:删除 key 后重新查询
8.4 API 响应格式
统一使用 {code: 0, data: {...}, message: "success"} 格式(复用 success_response)。
8.5 并发控制
- 企微审批详情并发查询:
asyncio.Semaphore(10)限制 - 两个数据源并行查询:
asyncio.gather(return_exceptions=True) - 任一数据源失败不影响另一个:
isinstance(result, Exception)检查后跳过
8.6 前端类型约定
type字段:仅'ticket'|'approval'(移除'device')- 前缀格式:前端通过
id.includes('approval:')或id.includes('ticket:')判断类型
9. 任务依赖图
graph TD
T01[T01: 后端基础设施与数据模型<br/>config.py + schemas + models]
T02[T02: 后端Service层 + ITSM签名<br/>4个新文件]
T03[T03: 后端API改造<br/>todo_items.py + approval.py]
T04[T04: 前端数据层与API<br/>api/todo.ts + store + mock]
T05[T05: 前端组件层<br/>TodoPanel + TaskDetailView + 3个子视图]
T01 --> T02
T02 --> T03
T03 --> T04
T04 --> T05
style T01 fill:#4CAF50,color:#fff
style T02 fill:#2196F3,color:#fff
style T03 fill:#2196F3,color:#fff
style T04 fill:#FF9800,color:#fff
style T05 fill:#FF9800,color:#fff