From 17c92304105ef7c2a368c00279987f2bf74b0fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=91=E5=A9=B7=E9=99=88?= Date: Tue, 28 Jul 2026 01:19:53 +0800 Subject: [PATCH] =?UTF-8?q?V0.1=E5=85=A8=E6=A8=A1=E5=9D=97=E5=AE=8C?= =?UTF-8?q?=E6=88=90:=20=E4=BB=93=E5=BA=93=E5=86=99=E5=85=A5(POST)+?= =?UTF-8?q?=E5=9B=9E=E6=89=A7=E5=B1=95=E5=BC=80+=E6=90=9C=E7=B4=A2+?= =?UTF-8?q?=E8=8D=89=E7=A8=BF=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 6 +-- src/lib/api.ts | 49 +++++++++++++++++++ src/lib/types.ts | 2 +- src/pages/HomePage.tsx | 2 +- src/pages/ReceiptPage.tsx | 96 +++++++++++++++++++++++++++++-------- src/pages/WorkOrderPage.tsx | 36 +++++++++++++- 6 files changed, 165 insertions(+), 26 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ec1e214..6e347e9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,8 +7,8 @@ import { ReceiptPage } from "./pages/ReceiptPage"; import { SettingsPage } from "./pages/SettingsPage"; const NAV: { id: Page; label: string; icon: string }[] = [ - { id: "home", label: "任务首页", icon: "📋" }, - { id: "plan", label: "工单面板", icon: "📐" }, + { id: "home", label: "频道首页", icon: "🌊" }, + { id: "plan", label: "工单面板", icon: "📋" }, { id: "authorize", label: "授权管理", icon: "🔑" }, { id: "receipt", label: "回执记录", icon: "📝" }, { id: "settings", label: "系统设置", icon: "⚙️" }, @@ -31,7 +31,7 @@ export function App() { {page === "home" && } {page === "plan" && } {page === "authorize" && } - {page === "receipt" && } + {page === "receipt" && } {page === "settings" && } diff --git a/src/lib/api.ts b/src/lib/api.ts index 5c631dd..4512b07 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -158,4 +158,53 @@ export function createDraftWorkOrder(summary: string, scope: string[]): WorkOrde operator: "妈妈 · ICE-GL∞", risk: "low", scope, source: "draft", }; +} + +// 写入 HLDP 回执到 yeye/receipts/ — 通过 Vite 代理走 Gitea API PUT +const WRITE_BASE = "/gitea-api/repos/bingshuo/yeye/contents"; + +export async function writeReceiptToRepo(receipt: { + receipt_id: string; operation: string; operator: string; + timestamp: string; result: string; status: string; scope?: string[]; +}): Promise { + const date = receipt.timestamp.split("T")[0]; + const filename = `receipts/${date}-${receipt.receipt_id.toLowerCase().replace(/\s+/g, "-")}.yml`; + const scopeList = receipt.scope?.map(s => ` - "${s}"`).join("\n") || " - \"全部\""; + const yml = `receipt_id: "${receipt.receipt_id}" +operation: "${receipt.operation}" +operator: "${receipt.operator}" +timestamp: "${receipt.timestamp}" +result: "${receipt.result}" +status: "${receipt.status}" +scope: +${scopeList} +`; + + try { + const body = { + content: btoa(unescape(encodeURIComponent(yml))), + message: `HoloLake V0.1: ${receipt.operation}`, + branch: "main", + }; + let res = await fetch(`${WRITE_BASE}/${filename}`, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (res.status === 409) { + const existing = await fetchJson(`${WRITE_BASE}/${filename}?ref=main`); + res = await fetch(`${WRITE_BASE}/${filename}`, { + method: "PUT", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...body, sha: existing.sha }), + }); + } + if (!res.ok) { + const errText = await res.text(); + console.error("写入失败:", res.status, errText); + return false; + } + return true; + } catch (e) { + console.error("写入异常:", e); + return false; + } } \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts index 6a721e7..cbe4ef8 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,3 +1,3 @@ export interface WorkOrder { id: string; summary: string; target: string; scope: string[]; risk: "low"|"medium"|"high"|"critical"; status: "draft"|"planned"|"authorized"|"executing"|"completed"|"closed"; acceptanceCriteria: string[]; createdBy: string; createdAt: string; updatedAt: string; } export interface Receipt { id: string; workOrderId: string; actions: string[]; executor: string; evidence: string[]; result: "success"|"partial"|"failed"; limitations: string[]; completedAt: string; } -export type Page = "home"|"plan"|"authorize"|"receipt"|"settings"; +export type Page = "home"|"plan"|"authorize"|"receipt"|"module"|"settings"; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 7122b3a..ab75ec7 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -51,7 +51,7 @@ export function HomePage({ onNavigate }: Props) {
工单面板
创建 · 追踪 · 闭环
-
onNavigate("settings")}> +
onNavigate("module")}>
📦
模块商店
浏览 · 安装 · 管理
diff --git a/src/pages/ReceiptPage.tsx b/src/pages/ReceiptPage.tsx index 6e4e62c..64dc50a 100644 --- a/src/pages/ReceiptPage.tsx +++ b/src/pages/ReceiptPage.tsx @@ -1,29 +1,87 @@ import React, { useEffect, useState } from "react"; import { fetchReceipts, ApiReceipt } from "../lib/api"; +import { Page } from "../lib/types"; -export function ReceiptPage() { +interface Props { onNavigate?: (page: Page) => void; } + +export function ReceiptPage({ onNavigate }: Props) { const [receipts, setReceipts] = useState([]); const [loading, setLoading] = useState(true); + const [expanded, setExpanded] = useState(null); + const [search, setSearch] = useState(""); - useEffect(() => { - fetchReceipts().then(data => { setReceipts(data); setLoading(false); }).catch(() => setLoading(false)); - }, []); + async function load() { + setLoading(true); + try { const data = await fetchReceipts(); setReceipts(data); } catch {} + setLoading(false); + } + useEffect(() => { load(); }, []); + + const filtered = search + ? receipts.filter(r => r.id.toLowerCase().includes(search.toLowerCase()) || r.operation.toLowerCase().includes(search.toLowerCase())) + : receipts; + + const counts = { all: receipts.length, success: receipts.filter(r => r.result === "success").length, failed: receipts.filter(r => r.result !== "success").length }; + + return
+

📝 回执记录

+ +
+
{counts.all}
全部
+
{counts.success}
已成功
+
{counts.failed}
待处理
+
+ +
+ setSearch(e.target.value)} /> +
- return

📝 回执记录

{loading &&

加载中...

} - {!loading && receipts.length === 0 &&

暂无回执记录

} - {receipts.map(rc =>
-
-
{rc.id}
{rc.operation}
- {rc.result==="success"?"✅":"⚠️"} -
- - - -
执行者{rc.operator}
时间{rc.timestamp}
-
)} -
- 数据源:guanghutansuohe.com/code/bingshuo/yeye/receipts/ + {!loading && filtered.length === 0 &&

{search ? "无匹配回执" : "暂无回执记录"}

} + + {filtered.map(rc => { + const isExpanded = expanded === rc.id; + return
setExpanded(isExpanded ? null : rc.id)}> +
+
+
{rc.id}
+
{rc.operation}
+
+ + {rc.result==="success"?"✅ 已成功":"⚠️ 待验证"} + +
+ + {isExpanded &&
+ + + + + + +
执行者{rc.operator}
完成时间{rc.timestamp}
状态{rc.result === "success" ? "成功闭环 ✅" : "需跟进 ⚠️"}
关联工单 + {onNavigate && } + {rc.id.replace("RC-","WO-")} +
证据链 + yeye 仓库 receipts/ 目录 + Git 历史可追溯 +
+ +
+ HLDP 回执链 · 工单 → 执行 → 结果 → 证据 · 不可静默覆盖 +
+
} + +
+ {isExpanded ? "▲ 收起" : "▼ 展开详情"} · {rc.timestamp?.split("T")[0] || "—"} +
+
; + })} + +
+ 数据源:guanghutansuohe.com/code/bingshuo/yeye/receipts/ · 最后同步 {new Date().toISOString().split("T")[0]}
; -} +} \ No newline at end of file diff --git a/src/pages/WorkOrderPage.tsx b/src/pages/WorkOrderPage.tsx index 1d244ea..e689a3d 100644 --- a/src/pages/WorkOrderPage.tsx +++ b/src/pages/WorkOrderPage.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { WorkOrder } from "../lib/types"; -import { fetchWorkOrders, loadDrafts, saveDrafts, createDraftWorkOrder } from "../lib/api"; +import { fetchWorkOrders, loadDrafts, saveDrafts, createDraftWorkOrder, writeReceiptToRepo } from "../lib/api"; export function WorkOrderPage() { const [orders, setOrders] = useState([]); @@ -30,8 +30,34 @@ export function WorkOrderPage() { await load(); } - function handleAuthorize(id: string, decision: "approved" | "rejected") { + async function handleAuthorize(id: string, decision: "approved" | "rejected") { setAuthorizations(prev => ({ ...prev, [id]: decision })); + const order = orders.find(o => o.id === id); + if (decision === "approved" && order) { + const success = await writeReceiptToRepo({ + receipt_id: order.id, operation: order.summary, + operator: "小线 · PER-YE-LN-001", timestamp: new Date().toISOString(), + result: "success", status: "completed", scope: order.scope, + }); + if (success) { + const drafts = loadDrafts().filter(d => d.id !== id); + saveDrafts(drafts); + await load(); + } + } + } + + function handleDeleteDraft(id: string) { + if (!confirm("确认删除这条本地草稿?")) return; + const drafts = loadDrafts().filter(d => d.id !== id); + saveDrafts(drafts); + load(); + } + + function handleClearAllDrafts() { + if (!confirm("确认清空所有本地草稿?")) return; + saveDrafts([]); + load(); } const filtered = filter === "all" ? orders : orders.filter(o => o.status === filter); @@ -73,6 +99,7 @@ export function WorkOrderPage() { {f==="all"?"全部":f==="draft"?"草稿":f==="executing"?"执行中":"已完成"} ({counts[f]}) ))} + {counts.draft > 0 && }
{loading &&

加载中...

} @@ -125,6 +152,11 @@ export function WorkOrderPage() { {needsAuth && authState !== "pending" &&
{authState==="approved" ? "✅ 已批准 · 单次执行 · 有效期 24 小时" : "⛔ 已拒绝"}
} + + {o.source === "draft" &&
e.stopPropagation()}> + +
}
}