docs(spec): 自由职业者/IT运维插件增强设计规格
三层增强方案:智能业务引擎 + 仪表盘重构 + 专业输出 纯插件层实现,无需修改平台代码
This commit is contained in:
@@ -0,0 +1,609 @@
|
|||||||
|
# freelance + itops 插件增强设计规格
|
||||||
|
|
||||||
|
> 日期: 2026-04-20
|
||||||
|
> 来源: 多专家头脑风暴(UX专家 + 业务顾问 + 运维专家 + 财务专家)
|
||||||
|
> 状态: Draft
|
||||||
|
> 前置: `docs/superpowers/specs/2026-04-19-shantou-zhijie-it-services-plugins-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与动机
|
||||||
|
|
||||||
|
当前插件是「数据录入系统」,不是「赚钱工具」。一人 IT 服务公司的核心痛点:
|
||||||
|
|
||||||
|
1. **钱从哪里来?** — 商机跟进靠人记,没有自动提醒、没有漏斗分析
|
||||||
|
2. **项目做到哪了?** — 任务状态和工时手动填,跟合同金额/应收款脱节
|
||||||
|
3. **钱收回来了吗?** — 报价→合同→开票→收款割裂,没有串联
|
||||||
|
4. **运维服务会不会忘?** — 巡检计划写了没人催,SLA 超时了才知道
|
||||||
|
5. **税和利润算不清?** — 收支分散在不同表里,月底做账要手动汇总
|
||||||
|
|
||||||
|
**问题根因:** 平台已有 trigger_events、settings、templates、cascade_from、visible_when、validation 六大能力,但两个插件完全没有使用。
|
||||||
|
|
||||||
|
**改进目标:** 纯插件层增强,三层递进:
|
||||||
|
- Layer 1: 智能业务引擎 — 让系统主动驱动用户做事
|
||||||
|
- Layer 2: 仪表盘重构 — 一个页面掌控全局
|
||||||
|
- Layer 3: 专业输出 — 一键生成报价单/发票/合同 PDF
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Layer 1: 智能业务引擎 — freelance 插件
|
||||||
|
|
||||||
|
### 2.1 Settings(插件配置页)
|
||||||
|
|
||||||
|
一次性配置公司信息和业务偏好,后续自动生效:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[settings]
|
||||||
|
|
||||||
|
# ── 基本信息 ──
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "company_name"
|
||||||
|
display_name = "公司名称"
|
||||||
|
field_type = "text"
|
||||||
|
required = true
|
||||||
|
group = "基本信息"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "currency_symbol"
|
||||||
|
display_name = "货币符号"
|
||||||
|
field_type = "text"
|
||||||
|
default_value = "¥"
|
||||||
|
group = "基本信息"
|
||||||
|
|
||||||
|
# ── 财务 ──
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "default_tax_rate"
|
||||||
|
display_name = "默认税率(%)"
|
||||||
|
field_type = "number"
|
||||||
|
default_value = 6
|
||||||
|
range = [0.0, 100.0]
|
||||||
|
group = "财务"
|
||||||
|
|
||||||
|
# ── 提醒 ──
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "payment_reminder_days"
|
||||||
|
display_name = "收款提前提醒(天)"
|
||||||
|
field_type = "number"
|
||||||
|
default_value = 3
|
||||||
|
range = [1.0, 30.0]
|
||||||
|
group = "提醒"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "notify_contract_expiring"
|
||||||
|
display_name = "合同到期提醒"
|
||||||
|
field_type = "boolean"
|
||||||
|
default_value = true
|
||||||
|
group = "提醒"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "notify_payment_overdue"
|
||||||
|
display_name = "逾期收款提醒"
|
||||||
|
field_type = "boolean"
|
||||||
|
default_value = true
|
||||||
|
group = "提醒"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "notify_opportunity_followup"
|
||||||
|
display_name = "商机跟进提醒"
|
||||||
|
field_type = "boolean"
|
||||||
|
default_value = true
|
||||||
|
group = "提醒"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Trigger Events(自动事件驱动)
|
||||||
|
|
||||||
|
关键操作时自动发通知,把"人找事"变"事找人":
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "opportunity_stage_changed"
|
||||||
|
display_name = "商机阶段变更"
|
||||||
|
description = "商机阶段发生变化时通知,特别是成交或失败"
|
||||||
|
entity = "opportunity"
|
||||||
|
on = "update"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "contract_status_changed"
|
||||||
|
display_name = "合同状态变更"
|
||||||
|
description = "合同状态变化时检查到期预警"
|
||||||
|
entity = "contract"
|
||||||
|
on = "update"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "invoice_status_changed"
|
||||||
|
display_name = "发票状态变更"
|
||||||
|
description = "发票状态变化时检查逾期收款"
|
||||||
|
entity = "invoice"
|
||||||
|
on = "update"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "task_status_changed"
|
||||||
|
display_name = "任务状态变更"
|
||||||
|
description = "任务完成或取消时通知"
|
||||||
|
entity = "task"
|
||||||
|
on = "update"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "expense_created"
|
||||||
|
display_name = "新支出记录"
|
||||||
|
description = "记录新支出时通知"
|
||||||
|
entity = "expense"
|
||||||
|
on = "create"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Cascade(智能联动下拉)
|
||||||
|
|
||||||
|
选客户后自动过滤其关联数据:
|
||||||
|
|
||||||
|
**contract 实体新增:**
|
||||||
|
```toml
|
||||||
|
# client_id 选择后,opportunity_id 自动过滤只显示该客户的商机
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "opportunity_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "关联商机"
|
||||||
|
ref_entity = "opportunity"
|
||||||
|
cascade_from = "client_id"
|
||||||
|
cascade_filter = "client_id"
|
||||||
|
|
||||||
|
# client_id 选择后,quote_id 自动过滤只显示该客户的报价
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "quote_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "关联报价"
|
||||||
|
ref_entity = "quote"
|
||||||
|
cascade_from = "client_id"
|
||||||
|
cascade_filter = "client_id"
|
||||||
|
```
|
||||||
|
|
||||||
|
**invoice 实体新增:**
|
||||||
|
```toml
|
||||||
|
# client_id 选择后,project_id 自动过滤
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "project_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "关联项目"
|
||||||
|
ref_entity = "project"
|
||||||
|
cascade_from = "client_id"
|
||||||
|
cascade_filter = "client_id"
|
||||||
|
|
||||||
|
# client_id 选择后,contract_id 自动过滤
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "contract_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "关联合同"
|
||||||
|
ref_entity = "contract"
|
||||||
|
cascade_from = "client_id"
|
||||||
|
cascade_filter = "client_id"
|
||||||
|
```
|
||||||
|
|
||||||
|
**time_entry 实体新增:**
|
||||||
|
```toml
|
||||||
|
# project_id 选择后,task_id 自动过滤只显示该项目的任务
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "task_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "关联任务"
|
||||||
|
ui_widget = "entity_select"
|
||||||
|
ref_entity = "task"
|
||||||
|
ref_label_field = "title"
|
||||||
|
ref_search_fields = ["title"]
|
||||||
|
cascade_from = "project_id"
|
||||||
|
cascade_filter = "project_id"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Visible When(条件显示)
|
||||||
|
|
||||||
|
只在有意义时才显示字段:
|
||||||
|
|
||||||
|
| 实体 | 字段 | 条件 |
|
||||||
|
|------|------|------|
|
||||||
|
| invoice | payment_date | `status == 'paid' \|\| status == 'partial'` |
|
||||||
|
| contract | paid_amount | `status != 'drafting'` |
|
||||||
|
| task | actual_hours | `status != 'todo'` |
|
||||||
|
| quote | total_amount | `status != 'draft'` |
|
||||||
|
|
||||||
|
### 2.5 Validation(字段校验)
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# client 实体 - email 字段增加校验
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "email"
|
||||||
|
field_type = "string"
|
||||||
|
display_name = "邮箱"
|
||||||
|
validation = { pattern = "^[^@]+@[^@]+\\.[^@]+$", message = "请输入有效的邮箱地址" }
|
||||||
|
|
||||||
|
# client 实体 - phone 字段增加校验
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "phone"
|
||||||
|
field_type = "string"
|
||||||
|
display_name = "电话"
|
||||||
|
validation = { pattern = "^1[3-9]\\d{9}$", message = "请输入有效的手机号" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Layer 2: 仪表盘重构 — freelance 插件
|
||||||
|
|
||||||
|
将占位符仪表盘升级为真正的指挥中心。通过 `widgets` 声明告诉平台该展示什么:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[ui.pages]]
|
||||||
|
type = "dashboard"
|
||||||
|
label = "工作台"
|
||||||
|
icon = "DashboardOutlined"
|
||||||
|
|
||||||
|
# ── 财务概览卡片 ──
|
||||||
|
[[ui.pages.widgets]]
|
||||||
|
type = "stat_cards"
|
||||||
|
label = "财务概览"
|
||||||
|
cards = [
|
||||||
|
{ entity = "invoice", aggregate = "sum", field = "amount", filter = "type == 'payment' && status != 'overdue'", label = "本月收入", icon = "rise", color = "green" },
|
||||||
|
{ entity = "expense", aggregate = "sum", field = "amount", label = "本月支出", icon = "fall", color = "red" },
|
||||||
|
{ entity = "invoice", aggregate = "sum", field = "amount", filter = "status == 'overdue' || status == 'pending'", label = "应收总额", icon = "dollar", color = "orange" },
|
||||||
|
{ entity = "invoice", aggregate = "count", filter = "status == 'overdue'", label = "逾期笔数", icon = "warning", color = "red" }
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── 紧急待办 ──
|
||||||
|
[[ui.pages.widgets]]
|
||||||
|
type = "action_list"
|
||||||
|
label = "紧急待办"
|
||||||
|
max_items = 5
|
||||||
|
queries = [
|
||||||
|
{ entity = "invoice", filter = "status == 'overdue'", label_field = "invoice_number", subtitle_field = "amount", action = "查看", icon = "warning" },
|
||||||
|
{ entity = "task", filter = "status != 'done' && status != 'cancelled'", sort = "due_date asc", label_field = "title", subtitle_field = "due_date", action = "处理", icon = "clock" },
|
||||||
|
{ entity = "contract", filter = "status == 'active'", sort = "end_date asc", label_field = "title", subtitle_field = "end_date", action = "续约", icon = "file-text" },
|
||||||
|
{ entity = "opportunity", filter = "next_follow_up <= today", label_field = "title", subtitle_field = "next_follow_up", action = "跟进", icon = "phone" }
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── 商机漏斗 ──
|
||||||
|
[[ui.pages.widgets]]
|
||||||
|
type = "funnel"
|
||||||
|
label = "商机漏斗"
|
||||||
|
entity = "opportunity"
|
||||||
|
lane_field = "stage"
|
||||||
|
value_field = "estimated_amount"
|
||||||
|
lane_order = ["visit", "requirement", "quote", "negotiation", "won", "lost"]
|
||||||
|
|
||||||
|
# ── 活跃项目卡片 ──
|
||||||
|
[[ui.pages.widgets]]
|
||||||
|
type = "card_list"
|
||||||
|
label = "活跃项目"
|
||||||
|
entity = "project"
|
||||||
|
filter = "status == 'in_progress'"
|
||||||
|
max_items = 4
|
||||||
|
title_field = "name"
|
||||||
|
subtitle_field = "contract_amount"
|
||||||
|
tags = ["business_type", "status"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**依赖:** 仪表盘 widgets 渲染需要前端配合解析 `widgets` 声明。数据源来自平台已有的聚合 API(`/count`、`/aggregate`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Layer 3: 专业输出 — freelance 插件
|
||||||
|
|
||||||
|
一键生成专业 PDF,替代手动排 Word:
|
||||||
|
|
||||||
|
### 4.1 报价单模板
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[templates]]
|
||||||
|
name = "quote_pdf"
|
||||||
|
display_name = "报价单"
|
||||||
|
entity = "quote"
|
||||||
|
format = "pdf"
|
||||||
|
template_html = """
|
||||||
|
<html>
|
||||||
|
<head><style>
|
||||||
|
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
|
||||||
|
h1 { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||||
|
th { background-color: #f5f5f5; }
|
||||||
|
.total { text-align: right; font-size: 18px; font-weight: bold; }
|
||||||
|
.footer { margin-top: 40px; color: #666; font-size: 12px; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>报价单 {{quote_number}}</h1>
|
||||||
|
<p>客户:{{client.name}} | 有效期至:{{valid_until}}</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>项目</th><th>描述</th><th>数量</th><th>单价</th><th>金额</th></tr>
|
||||||
|
{{#each lines}}
|
||||||
|
<tr><td>{{item_name}}</td><td>{{description}}</td><td>{{quantity}}</td><td>{{unit_price}}</td><td>{{amount}}</td></tr>
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
<p class="total">小计:{{subtotal}} | 税率:{{tax_rate}}% | 总计:{{total_amount}}</p>
|
||||||
|
<div class="footer">备注:{{notes}}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 发票模板
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[templates]]
|
||||||
|
name = "invoice_pdf"
|
||||||
|
display_name = "发票"
|
||||||
|
entity = "invoice"
|
||||||
|
format = "pdf"
|
||||||
|
template_html = """
|
||||||
|
<html>
|
||||||
|
<head><style>
|
||||||
|
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
|
||||||
|
h1 { text-align: center; color: #1890ff; border-bottom: 2px solid #1890ff; padding-bottom: 10px; }
|
||||||
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 20px 0; }
|
||||||
|
.info-item { padding: 8px; background: #fafafa; }
|
||||||
|
.amount { font-size: 24px; font-weight: bold; text-align: center; color: #f5222d; margin: 20px 0; }
|
||||||
|
.status-badge { display: inline-block; padding: 4px 12px; border-radius: 4px; background: #f0f0f0; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>发票 {{invoice_number}}</h1>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">客户:{{client.name}}</div>
|
||||||
|
<div class="info-item">类型:{{type}}</div>
|
||||||
|
<div class="info-item">开票日期:{{issue_date}}</div>
|
||||||
|
<div class="info-item">到期日:{{due_date}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="amount">¥{{amount}}</div>
|
||||||
|
<p>状态:<span class="status-badge">{{status}}</span></p>
|
||||||
|
<p>备注:{{notes}}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 合同模板
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[templates]]
|
||||||
|
name = "contract_pdf"
|
||||||
|
display_name = "合同"
|
||||||
|
entity = "contract"
|
||||||
|
format = "pdf"
|
||||||
|
template_html = """
|
||||||
|
<html>
|
||||||
|
<head><style>
|
||||||
|
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
|
||||||
|
h1 { text-align: center; border-bottom: 3px double #333; padding-bottom: 10px; }
|
||||||
|
.parties { margin: 20px 0; padding: 15px; background: #fafafa; border-left: 4px solid #1890ff; }
|
||||||
|
.signature { margin-top: 60px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
|
||||||
|
.sig-box { border-top: 1px solid #333; padding-top: 10px; text-align: center; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<p>合同编号:{{contract_number}}</p>
|
||||||
|
<div class="parties">
|
||||||
|
<p>甲方:{{client.name}}</p>
|
||||||
|
<p>合同金额:¥{{amount}} | 已付:¥{{paid_amount}}</p>
|
||||||
|
<p>期限:{{start_date}} 至 {{end_date}}</p>
|
||||||
|
<p>付款条款:{{payment_terms}}</p>
|
||||||
|
</div>
|
||||||
|
<p>备注:{{notes}}</p>
|
||||||
|
<div class="signature">
|
||||||
|
<div class="sig-box">甲方签章</div>
|
||||||
|
<div class="sig-box">乙方签章</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. itops 插件增强
|
||||||
|
|
||||||
|
### 5.1 Settings
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[settings]
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "default_sla_response"
|
||||||
|
display_name = "默认SLA响应时间(小时)"
|
||||||
|
field_type = "number"
|
||||||
|
default_value = 8
|
||||||
|
range = [1.0, 72.0]
|
||||||
|
group = "SLA"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "default_sla_resolve"
|
||||||
|
display_name = "默认SLA解决时间(小时)"
|
||||||
|
field_type = "number"
|
||||||
|
default_value = 48
|
||||||
|
range = [1.0, 168.0]
|
||||||
|
group = "SLA"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "notify_sla_breach"
|
||||||
|
display_name = "SLA超标提醒"
|
||||||
|
field_type = "boolean"
|
||||||
|
default_value = true
|
||||||
|
group = "提醒"
|
||||||
|
|
||||||
|
[[settings.fields]]
|
||||||
|
name = "notify_check_due"
|
||||||
|
display_name = "巡检到期提醒"
|
||||||
|
field_type = "boolean"
|
||||||
|
default_value = true
|
||||||
|
group = "提醒"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Trigger Events
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "ticket_created"
|
||||||
|
display_name = "新工单"
|
||||||
|
description = "创建工单时开始SLA计时并通知"
|
||||||
|
entity = "ticket"
|
||||||
|
on = "create"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "ticket_status_changed"
|
||||||
|
display_name = "工单状态变更"
|
||||||
|
description = "工单状态变化时检查SLA是否达标"
|
||||||
|
entity = "ticket"
|
||||||
|
on = "update"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "contract_status_changed"
|
||||||
|
display_name = "维保合同状态变更"
|
||||||
|
description = "合同状态变化时检查到期预警"
|
||||||
|
entity = "service_contract"
|
||||||
|
on = "update"
|
||||||
|
|
||||||
|
[[trigger_events]]
|
||||||
|
name = "check_plan_updated"
|
||||||
|
display_name = "巡检计划更新"
|
||||||
|
description = "巡检计划更新时检查下次巡检日期"
|
||||||
|
entity = "check_plan"
|
||||||
|
on = "update"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Cascade
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# ticket 实体:选客户后过滤其维保合同
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "contract_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "维保合同"
|
||||||
|
ui_widget = "entity_select"
|
||||||
|
ref_entity = "service_contract"
|
||||||
|
ref_label_field = "name"
|
||||||
|
ref_search_fields = ["name"]
|
||||||
|
cascade_from = "client_id"
|
||||||
|
cascade_filter = "client_id"
|
||||||
|
|
||||||
|
# check_record 实体:选巡检计划后过滤合同
|
||||||
|
[[schema.entities.fields]]
|
||||||
|
name = "contract_id"
|
||||||
|
field_type = "uuid"
|
||||||
|
display_name = "维保合同"
|
||||||
|
ref_entity = "service_contract"
|
||||||
|
cascade_from = "plan_id"
|
||||||
|
cascade_filter = "contract_id"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Visible When
|
||||||
|
|
||||||
|
| 实体 | 字段 | 条件 |
|
||||||
|
|------|------|------|
|
||||||
|
| ticket | resolution | `status == 'resolved' \|\| status == 'closed'` |
|
||||||
|
| ticket | responded_at | `status != 'open'` |
|
||||||
|
| ticket | resolved_at | `status == 'resolved' \|\| status == 'closed'` |
|
||||||
|
| ticket | closed_at | `status == 'closed'` |
|
||||||
|
| check_record | issues_found | `result == 'abnormal'` |
|
||||||
|
| check_record | actions_taken | `result == 'abnormal'` |
|
||||||
|
|
||||||
|
### 5.5 Validation
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# service_contract - contract_number 格式校验
|
||||||
|
validation = { pattern = "^SC-\\d{4}-\\d{4}$", message = "格式:SC-YYYY-NNNN" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.6 Dashboard
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[ui.pages]]
|
||||||
|
type = "dashboard"
|
||||||
|
label = "运维概览"
|
||||||
|
icon = "DashboardOutlined"
|
||||||
|
|
||||||
|
[[ui.pages.widgets]]
|
||||||
|
type = "stat_cards"
|
||||||
|
label = "运维概览"
|
||||||
|
cards = [
|
||||||
|
{ entity = "service_contract", aggregate = "count", filter = "status == 'active'", label = "活跃合同", icon = "file-text", color = "blue" },
|
||||||
|
{ entity = "ticket", aggregate = "count", filter = "status == 'open' || status == 'in_progress'", label = "待处理工单", icon = "tool", color = "orange" },
|
||||||
|
{ entity = "ticket", aggregate = "count", filter = "status == 'resolved'", label = "已解决工单", icon = "check-circle", color = "green" },
|
||||||
|
{ entity = "check_plan", aggregate = "count", filter = "status == 'active'", label = "活跃巡检", icon = "schedule", color = "blue" }
|
||||||
|
]
|
||||||
|
|
||||||
|
[[ui.pages.widgets]]
|
||||||
|
type = "action_list"
|
||||||
|
label = "紧急待办"
|
||||||
|
max_items = 5
|
||||||
|
queries = [
|
||||||
|
{ entity = "ticket", filter = "status == 'open'", sort = "priority asc", label_field = "title", subtitle_field = "type", action = "处理", icon = "warning" },
|
||||||
|
{ entity = "service_contract", filter = "status == 'active'", sort = "end_date asc", label_field = "name", subtitle_field = "end_date", action = "续约", icon = "file-text" },
|
||||||
|
{ entity = "check_plan", filter = "status == 'active'", sort = "next_check_date asc", label_field = "name", subtitle_field = "next_check_date", action = "巡检", icon = "schedule" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.7 Template(维保合同 PDF)
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[templates]]
|
||||||
|
name = "service_contract_pdf"
|
||||||
|
display_name = "维保合同"
|
||||||
|
entity = "service_contract"
|
||||||
|
format = "pdf"
|
||||||
|
template_html = """
|
||||||
|
<html>
|
||||||
|
<head><style>
|
||||||
|
body { font-family: 'Microsoft YaHei', sans-serif; margin: 40px; }
|
||||||
|
h1 { text-align: center; border-bottom: 3px double #1890ff; padding-bottom: 10px; color: #1890ff; }
|
||||||
|
.sla-box { margin: 20px 0; padding: 15px; background: #e6f7ff; border: 1px solid #91d5ff; border-radius: 4px; }
|
||||||
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 20px 0; }
|
||||||
|
.info-item { padding: 8px; background: #fafafa; }
|
||||||
|
.signature { margin-top: 60px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
|
||||||
|
.sig-box { border-top: 1px solid #333; padding-top: 10px; text-align: center; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<h1>{{name}}</h1>
|
||||||
|
<p>合同编号:{{contract_number}}</p>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">客户:{{client.name}}</div>
|
||||||
|
<div class="info-item">合同金额:¥{{amount}}</div>
|
||||||
|
<div class="info-item">期限:{{start_date}} 至 {{end_date}}</div>
|
||||||
|
<div class="info-item">状态:{{status}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="sla-box">
|
||||||
|
<strong>SLA 承诺:</strong>响应 {{sla_response_hours}} 小时内 / 解决 {{sla_resolve_hours}} 小时内
|
||||||
|
</div>
|
||||||
|
<p>服务范围:{{service_scope}}</p>
|
||||||
|
<p>付款条款:{{payment_terms}}</p>
|
||||||
|
<p>备注:{{notes}}</p>
|
||||||
|
<div class="signature">
|
||||||
|
<div class="sig-box">甲方签章</div>
|
||||||
|
<div class="sig-box">乙方签章</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 改进汇总
|
||||||
|
|
||||||
|
| 层次 | 能力 | freelance | itops |
|
||||||
|
|------|------|-----------|-------|
|
||||||
|
| Layer 1 | settings | 7 个配置项(公司名/税率/提醒偏好) | 4 个配置项(SLA默认值/提醒偏好) |
|
||||||
|
| Layer 1 | trigger_events | 5 个事件(商机/合同/发票/任务/支出) | 4 个事件(工单/合同/巡检) |
|
||||||
|
| Layer 1 | cascade | 4 处联动(合同/发票/工时表单) | 2 处联动(工单/巡检记录) |
|
||||||
|
| Layer 1 | visible_when | 4 个条件字段 | 6 个条件字段 |
|
||||||
|
| Layer 1 | validation | 2 个校验(邮箱/手机) | 1 个校验(合同编号格式) |
|
||||||
|
| Layer 2 | dashboard widgets | 财务卡片+紧急待办+商机漏斗+项目卡片 | 运维卡片+紧急待办 |
|
||||||
|
| Layer 3 | templates | 3 个 PDF(报价单/发票/合同) | 1 个 PDF(维保合同) |
|
||||||
|
|
||||||
|
**总计:** 2 个插件 × 3 层增强,从「数据录入」升级为「赚钱工具」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 实施优先级
|
||||||
|
|
||||||
|
```
|
||||||
|
P1: freelance Layer 1(settings + trigger_events + cascade + visible_when + validation)
|
||||||
|
P2: itops Layer 1(settings + trigger_events + cascade + visible_when + validation)
|
||||||
|
P3: freelance Layer 3(3 个 PDF 模板)
|
||||||
|
P4: itops Layer 3(维保合同 PDF 模板)
|
||||||
|
P5: freelance Layer 2(仪表盘 widgets)
|
||||||
|
P6: itops Layer 2(仪表盘 widgets)
|
||||||
|
```
|
||||||
|
|
||||||
|
P1-P4 是纯 plugin.toml 改动,可立即实施。P5-P6 的仪表盘 widgets 需要前端配合解析 widgets 声明。
|
||||||
Reference in New Issue
Block a user