OAFRONT6 压缩长编号展示并修正视觉探针
这个提交包含在:
@@ -31,7 +31,7 @@ Read only what is relevant:
|
|||||||
- OALAUNCH1 is the current final-enable gate after OAOPS1/ASSET1D/OSMOKE1: do not treat ASSET1D completion or `PASS_OA_REAL_USE_DAILY_READY` as final launch approval. Use `scripts/qa/run-oa-final-launch-readiness.sh` to aggregate evidence only; it must not create business data, execute payments, write WeCom, reset/restore the production DB, print secrets, or mark final launch ready without real business closeout, 7-day stabilization, permission matrix acceptance, backup/restore drill evidence, data-governance/training acceptance, and Day 7 Go/No-Go. Valid conclusions are `PASS_OA_LAUNCH_READY`, `PASS_WITH_FINDINGS`, `PAUSED_MANUAL_CONFIRMATION`, and `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`. Permission or backup/restore fixes require read-only reviewer review before launch acceptance.
|
- OALAUNCH1 is the current final-enable gate after OAOPS1/ASSET1D/OSMOKE1: do not treat ASSET1D completion or `PASS_OA_REAL_USE_DAILY_READY` as final launch approval. Use `scripts/qa/run-oa-final-launch-readiness.sh` to aggregate evidence only; it must not create business data, execute payments, write WeCom, reset/restore the production DB, print secrets, or mark final launch ready without real business closeout, 7-day stabilization, permission matrix acceptance, backup/restore drill evidence, data-governance/training acceptance, and Day 7 Go/No-Go. Valid conclusions are `PASS_OA_LAUNCH_READY`, `PASS_WITH_FINDINGS`, `PAUSED_MANUAL_CONFIRMATION`, and `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`. Permission or backup/restore fixes require read-only reviewer review before launch acceptance.
|
||||||
- OPOLICY1 is the current policy-system alignment lane after OMAT1/OALAUNCH1 review: external company policies under `/home/shunli/Projects/faculty-daily-assistant/公司/制度体系建设_20260524` are adjusted to a small-team V1口径 first, then hyb-oa facts are aligned. Treat policy parameters, data-retention boundaries, seal-use ledger, approval-exception SOP, policy signoff, and P2 freeze rules as OALAUNCH1 data-governance gates. Do not assume budget hard control, complex procurement/tendering, e-signature, full e-archive, ERP/bank deep integration, or AI rule writes are pre-launch requirements. For V1, contract registry summary/archive/offline archive fields and existing attachment audit can carry seal-use traceability; announcements/manual evidence can carry policy notices/signoff until a later Issue proves dedicated schema is required. Never copy sensitive policy text, thresholds, confidential contract bodies, credentials, or full local paths into repo docs.
|
- OPOLICY1 is the current policy-system alignment lane after OMAT1/OALAUNCH1 review: external company policies under `/home/shunli/Projects/faculty-daily-assistant/公司/制度体系建设_20260524` are adjusted to a small-team V1口径 first, then hyb-oa facts are aligned. Treat policy parameters, data-retention boundaries, seal-use ledger, approval-exception SOP, policy signoff, and P2 freeze rules as OALAUNCH1 data-governance gates. Do not assume budget hard control, complex procurement/tendering, e-signature, full e-archive, ERP/bank deep integration, or AI rule writes are pre-launch requirements. For V1, contract registry summary/archive/offline archive fields and existing attachment audit can carry seal-use traceability; announcements/manual evidence can carry policy notices/signoff until a later Issue proves dedicated schema is required. Never copy sensitive policy text, thresholds, confidential contract bodies, credentials, or full local paths into repo docs.
|
||||||
- OAFLOW1 is the current OA reimbursement approval hardening lane: `wangshunli/HYD000006` as `ceo + admin` must not be assigned a fake direct manager, must not self-approve, and `admin` must not be treated as business approval authority. Reimbursement `approval_rules` now support `ownerSelfFallbackRole`; OAFLOW1 V1 only allows `finance_mgr` or null, and existing enabled rules default to `finance_mgr`. For CEO owner self-applications with no direct manager, build and persist a new workflow snapshot by replacing the first `manager` node with `finance_mgr`, removing the owner's own `ceo` node and duplicate fallback role. Precheck, submit, and approval advancement must use the same snapshot; public deployment and low-risk owner reimbursement verification require separate approval and must not execute real payment.
|
- OAFLOW1 is the current OA reimbursement approval hardening lane: `wangshunli/HYD000006` as `ceo + admin` must not be assigned a fake direct manager, must not self-approve, and `admin` must not be treated as business approval authority. Reimbursement `approval_rules` now support `ownerSelfFallbackRole`; OAFLOW1 V1 only allows `finance_mgr` or null, and existing enabled rules default to `finance_mgr`. For CEO owner self-applications with no direct manager, build and persist a new workflow snapshot by replacing the first `manager` node with `finance_mgr`, removing the owner's own `ceo` node and duplicate fallback role. Precheck, submit, and approval advancement must use the same snapshot; public deployment and low-risk owner reimbursement verification require separate approval and must not execute real payment.
|
||||||
- OAFRONT6 is the current frontend maturity small-step lane after OAFRONT5: use `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` for scoped design follow-ups only. Route-title cleanup, asset registry sectioning, mobile visual-matrix stability, and contract registry form grouping are implemented; long-number display compaction and Portal observation remain later TODO/observation items. Do not treat OAFRONT6 as approval for a broad redesign, business logic changes, public writes, deployment, or final launch readiness; further implementation still needs `docs/产品设计规则.md`, focused Playwright/visual evidence, and OALAUNCH1 gates.
|
- OAFRONT6 is the current frontend maturity small-step lane after OAFRONT5: use `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` for scoped design follow-ups only. Route-title cleanup, asset registry sectioning, mobile visual-matrix stability, and contract registry form grouping are implemented and deployed; long-number display compaction is implemented locally and still needs deployment plus public PC readonly visual rerun before closure; Portal observation remains later feedback-driven work. Do not treat OAFRONT6 as approval for a broad redesign, business logic changes, public writes, deployment, or final launch readiness; further implementation still needs `docs/产品设计规则.md`, focused Playwright/visual evidence, and OALAUNCH1 gates.
|
||||||
- OMAT1 is the current maturity-gap review goal after OALAUNCH1: use `issues/2026-05-23_17-20-00-oa-maturity-gap-review-v1.csv` as the reviewable ledger for every problem or suggestion. It includes domestic benchmark sources, a domain capability comparison matrix, manual decision Q&A checklist, review decision packet, status audit, and MAGI round reviews in the manual trial-run report, the small-team rule that maturity means controlled operation before feature breadth, explicit adopt/defer/reject decision回填, and a need for a concise evidence view. Follow the MAGI cycle wording as audit structure only: review/evaluate first, record or solve bounded evidence gaps second, then prioritize direction. Do not implement OMAT1 suggestions until the user chooses adopt/defer/reject and a proper Issue CSV exists for the adopted work.
|
- OMAT1 is the current maturity-gap review goal after OALAUNCH1: use `issues/2026-05-23_17-20-00-oa-maturity-gap-review-v1.csv` as the reviewable ledger for every problem or suggestion. It includes domestic benchmark sources, a domain capability comparison matrix, manual decision Q&A checklist, review decision packet, status audit, and MAGI round reviews in the manual trial-run report, the small-team rule that maturity means controlled operation before feature breadth, explicit adopt/defer/reject decision回填, and a need for a concise evidence view. Follow the MAGI cycle wording as audit structure only: review/evaluate first, record or solve bounded evidence gaps second, then prioritize direction. Do not implement OMAT1 suggestions until the user chooses adopt/defer/reject and a proper Issue CSV exists for the adopted work.
|
||||||
- WEGOV1 changes the current WeCom route to "Enterprise WeChat owns mature HR/attendance flows; OA stores self-owned history, audit, entry, messages, and emergency fallback." Treat four domains explicitly: WeCom-owned domain is check-in, leave, missed punch, outing, overtime, leave balance, and HR assistant; OA-owned domain remains reimbursement, procurement, contracts, projects, payments, invoices, bank receipts, sensitive attachments, and operating audit; OA archive domain stores WeCom business history, full external keys, and necessary snapshots; OA emergency domain is manually enabled only when WeCom is unavailable. Do not assume a full isolated WeCom sandbox; use the controlled test domain `SMOKE002 -> SMOKE101`, `OA-POC` templates, low-risk approvals, synthetic tests, public live runners, and limited real-device confirmation. WARCH1 now supplies the minimum leave/missed-punch archive implementation, WARCH2 supplies the formal read-only history query surface, and WROAD2 supplies controlled ops daily checks. Do not start deep mirror until WROAD2-CLOSEOUT, WROAD2-BIZ1, WAPPR1, WRULE1, WAUTO1/WEXT1LD, WFULLSMOKE1, WTCLOSE2, WEXT1-FORMAL, WREAL1 question gates or explicit approval, and the mirror stage's own contract are complete.
|
- WEGOV1 changes the current WeCom route to "Enterprise WeChat owns mature HR/attendance flows; OA stores self-owned history, audit, entry, messages, and emergency fallback." Treat four domains explicitly: WeCom-owned domain is check-in, leave, missed punch, outing, overtime, leave balance, and HR assistant; OA-owned domain remains reimbursement, procurement, contracts, projects, payments, invoices, bank receipts, sensitive attachments, and operating audit; OA archive domain stores WeCom business history, full external keys, and necessary snapshots; OA emergency domain is manually enabled only when WeCom is unavailable. Do not assume a full isolated WeCom sandbox; use the controlled test domain `SMOKE002 -> SMOKE101`, `OA-POC` templates, low-risk approvals, synthetic tests, public live runners, and limited real-device confirmation. WARCH1 now supplies the minimum leave/missed-punch archive implementation, WARCH2 supplies the formal read-only history query surface, and WROAD2 supplies controlled ops daily checks. Do not start deep mirror until WROAD2-CLOSEOUT, WROAD2-BIZ1, WAPPR1, WRULE1, WAUTO1/WEXT1LD, WFULLSMOKE1, WTCLOSE2, WEXT1-FORMAL, WREAL1 question gates or explicit approval, and the mirror stage's own contract are complete.
|
||||||
- For reimbursement, invoice, OCR, duplicate invoice, or attachment work, read `docs/报销发票附件规则.md` and `references/reimbursement.md`.
|
- For reimbursement, invoice, OCR, duplicate invoice, or attachment work, read `docs/报销发票附件规则.md` and `references/reimbursement.md`.
|
||||||
|
|||||||
@@ -30,3 +30,35 @@ export function formatDisplayDateTime(value: string | null | undefined) {
|
|||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
}).format(timestamp);
|
}).format(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatCompactIdentifier(
|
||||||
|
value: string | number | null | undefined,
|
||||||
|
options: { head?: number; tail?: number; minLength?: number } = {},
|
||||||
|
) {
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
if (!text) return "-";
|
||||||
|
|
||||||
|
const head = options.head ?? 10;
|
||||||
|
const tail = options.tail ?? 4;
|
||||||
|
const minLength = options.minLength ?? 18;
|
||||||
|
|
||||||
|
if (text.length <= minLength || text.length <= head + tail + 1) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${text.slice(0, head)}…${text.slice(-tail)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCompactedIdentifier(value: string | number | null | undefined, displayValue: string) {
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
return Boolean(text) && text !== displayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTextWithCompactLongNumbers(value: string | number | null | undefined) {
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
if (!text) return "-";
|
||||||
|
|
||||||
|
return text.replace(/\b20[0-9]{12,}\b/g, (match) =>
|
||||||
|
formatCompactIdentifier(match, { head: 10, tail: 4, minLength: 14 }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { SectionPanel } from "../components/patterns/SectionPanel";
|
|||||||
import { Button } from "../components/ui/button";
|
import { Button } from "../components/ui/button";
|
||||||
import { Input } from "../components/ui/input";
|
import { Input } from "../components/ui/input";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
|
||||||
import { formatDisplayDateTime } from "../lib/displayFormat";
|
import { formatCompactIdentifier, formatDisplayDateTime, formatTextWithCompactLongNumbers, isCompactedIdentifier } from "../lib/displayFormat";
|
||||||
|
|
||||||
function resolveError(error: unknown) {
|
function resolveError(error: unknown) {
|
||||||
if (isApiError(error)) {
|
if (isApiError(error)) {
|
||||||
@@ -156,6 +156,41 @@ function typeHint(assetType: AssetType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CompactAssetNo({ value, copyable = true }: { value: string; copyable?: boolean }) {
|
||||||
|
const displayValue = formatCompactIdentifier(value, { head: 14, tail: 6, minLength: 24 });
|
||||||
|
const compacted = isCompactedIdentifier(value, displayValue);
|
||||||
|
|
||||||
|
async function copyAssetNo() {
|
||||||
|
if (!compacted) return;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
toast.success("已复制完整资产编号");
|
||||||
|
} catch {
|
||||||
|
toast.error("复制失败,可在资产详情中查看完整编号");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="inline-flex min-w-0 max-w-full items-center gap-1.5">
|
||||||
|
<span className="truncate" title={compacted ? value : undefined}>
|
||||||
|
{displayValue}
|
||||||
|
</span>
|
||||||
|
{compacted && copyable ? (
|
||||||
|
<button
|
||||||
|
className="shrink-0 rounded border border-zinc-200 px-1.5 py-0.5 text-[11px] font-medium text-zinc-500 hover:border-zinc-300 hover:text-zinc-700"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
void copyAssetNo();
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function unsafeAssetTextReason(value: string) {
|
function unsafeAssetTextReason(value: string) {
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
return "";
|
return "";
|
||||||
@@ -796,7 +831,7 @@ export function AssetRegistryPage() {
|
|||||||
<option value="">使用系统默认</option>
|
<option value="">使用系统默认</option>
|
||||||
{(documentCategoriesQuery.data?.items ?? []).map((item) => (
|
{(documentCategoriesQuery.data?.items ?? []).map((item) => (
|
||||||
<option key={item.id} value={item.id}>
|
<option key={item.id} value={item.id}>
|
||||||
{item.name} ({item.code})
|
{formatTextWithCompactLongNumbers(item.name)} ({formatTextWithCompactLongNumbers(item.code)})
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -829,8 +864,12 @@ export function AssetRegistryPage() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="truncate text-sm font-semibold text-zinc-900">{item.name}</div>
|
<div className="truncate text-sm font-semibold text-zinc-900" title={item.name}>
|
||||||
<div className="mt-1 text-xs text-zinc-500">{item.code}</div>
|
{formatTextWithCompactLongNumbers(item.name)}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-xs text-zinc-500" title={item.code}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.code)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="shrink-0 rounded-full bg-zinc-100 px-2 py-1 text-xs text-zinc-600">
|
<span className="shrink-0 rounded-full bg-zinc-100 px-2 py-1 text-xs text-zinc-600">
|
||||||
{item.enabled ? "启用" : "停用"}
|
{item.enabled ? "启用" : "停用"}
|
||||||
@@ -855,9 +894,11 @@ export function AssetRegistryPage() {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{categoryItems.map((item) => (
|
{categoryItems.map((item) => (
|
||||||
<TableRow key={item.id} onClick={() => resetCategoryEditor(item)} className="cursor-pointer">
|
<TableRow key={item.id} onClick={() => resetCategoryEditor(item)} className="cursor-pointer">
|
||||||
<TableCell className="font-medium text-zinc-900">{item.code}</TableCell>
|
<TableCell className="font-medium text-zinc-900" title={item.code}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.code)}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div>{item.name}</div>
|
<div title={item.name}>{formatTextWithCompactLongNumbers(item.name)}</div>
|
||||||
<div className="text-xs text-zinc-500">{item.documentCategory?.name ?? "系统默认"}</div>
|
<div className="text-xs text-zinc-500">{item.documentCategory?.name ?? "系统默认"}</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{item.enabled ? "启用" : "停用"}</TableCell>
|
<TableCell>{item.enabled ? "启用" : "停用"}</TableCell>
|
||||||
@@ -903,7 +944,7 @@ export function AssetRegistryPage() {
|
|||||||
<option value="">全部分类</option>
|
<option value="">全部分类</option>
|
||||||
{(categoriesQuery.data?.items ?? []).map((item) => (
|
{(categoriesQuery.data?.items ?? []).map((item) => (
|
||||||
<option key={item.id} value={item.id}>
|
<option key={item.id} value={item.id}>
|
||||||
{item.name}
|
{formatTextWithCompactLongNumbers(item.name)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -968,15 +1009,21 @@ export function AssetRegistryPage() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="truncate text-sm font-semibold text-zinc-900">{item.assetNo}</div>
|
<div className="truncate text-sm font-semibold text-zinc-900">
|
||||||
<div className="mt-1 truncate text-xs text-zinc-500">{item.name}</div>
|
<CompactAssetNo copyable={false} value={item.assetNo} />
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 truncate text-xs text-zinc-500" title={item.name}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.name)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusBadge status={item.status} />
|
<StatusBadge status={item.status} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 grid grid-cols-2 gap-2 text-xs text-zinc-600">
|
<div className="mt-3 grid grid-cols-2 gap-2 text-xs text-zinc-600">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-zinc-400">分类</div>
|
<div className="text-zinc-400">分类</div>
|
||||||
<div className="mt-0.5 truncate text-zinc-700">{item.category?.name ?? "-"}</div>
|
<div className="mt-0.5 truncate text-zinc-700" title={item.category?.name ?? undefined}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.category?.name ?? "-")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-zinc-400">类型</div>
|
<div className="text-zinc-400">类型</div>
|
||||||
@@ -1012,11 +1059,17 @@ export function AssetRegistryPage() {
|
|||||||
selectAsset(item.id);
|
selectAsset(item.id);
|
||||||
}} className={item.id === selectedAssetId && !isCreatingAsset ? "bg-zinc-50" : "cursor-pointer"}>
|
}} className={item.id === selectedAssetId && !isCreatingAsset ? "bg-zinc-50" : "cursor-pointer"}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="font-medium text-zinc-900">{item.assetNo}</div>
|
<div className="font-medium text-zinc-900">
|
||||||
<div className="text-xs text-zinc-500">{item.name}</div>
|
<CompactAssetNo value={item.assetNo} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-zinc-500" title={item.name}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.name)}
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{assetTypeLabel(item.assetType)}</TableCell>
|
<TableCell>{assetTypeLabel(item.assetType)}</TableCell>
|
||||||
<TableCell>{item.category?.name ?? "-"}</TableCell>
|
<TableCell title={item.category?.name ?? undefined}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.category?.name ?? "-")}
|
||||||
|
</TableCell>
|
||||||
<TableCell>{sensitivityLabel(item.sensitivityLevel)}</TableCell>
|
<TableCell>{sensitivityLabel(item.sensitivityLevel)}</TableCell>
|
||||||
<TableCell><StatusBadge status={item.status} /></TableCell>
|
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||||
<TableCell>{item.owner?.name ?? item.assignee?.name ?? "-"}</TableCell>
|
<TableCell>{item.owner?.name ?? item.assignee?.name ?? "-"}</TableCell>
|
||||||
@@ -1052,8 +1105,12 @@ export function AssetRegistryPage() {
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-zinc-900">{isCreatingAsset ? "新建资产卡片" : currentAsset?.assetNo ?? "资产卡片"}</h3>
|
<h3 className="text-lg font-semibold text-zinc-900">
|
||||||
<p className="text-sm text-zinc-500">{isCreatingAsset ? "创建新的资产台账卡片。" : currentAsset?.name}</p>
|
{isCreatingAsset ? "新建资产卡片" : currentAsset ? <CompactAssetNo value={currentAsset.assetNo} /> : "资产卡片"}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-zinc-500" title={currentAsset?.name ?? undefined}>
|
||||||
|
{isCreatingAsset ? "创建新的资产台账卡片。" : formatTextWithCompactLongNumbers(currentAsset?.name)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!isCreatingAsset && currentAsset ? <StatusBadge status={currentAsset.status} /> : null}
|
{!isCreatingAsset && currentAsset ? <StatusBadge status={currentAsset.status} /> : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -1379,9 +1436,9 @@ export function AssetRegistryPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2 rounded-lg bg-zinc-50 px-3 py-2 text-xs text-zinc-600 md:grid-cols-2">
|
<div className="grid gap-2 rounded-lg bg-zinc-50 px-3 py-2 text-xs text-zinc-600 md:grid-cols-2">
|
||||||
<div>资产编号:{assetErpTrack?.asset.assetNo ?? currentAsset?.assetNo ?? "-"}</div>
|
<div>资产编号:{formatTextWithCompactLongNumbers(assetErpTrack?.asset.assetNo ?? currentAsset?.assetNo ?? "-")}</div>
|
||||||
<div>最近变更:{assetErpChangeTypeLabel(assetErpStage.preview.changeType)}</div>
|
<div>最近变更:{assetErpChangeTypeLabel(assetErpStage.preview.changeType)}</div>
|
||||||
<div>分类:{assetErpStage.preview.categoryCode ?? "-"}</div>
|
<div>分类:{formatTextWithCompactLongNumbers(assetErpStage.preview.categoryCode ?? "-")}</div>
|
||||||
<div>资产状态:{assetStatusLabel(assetErpStage.preview.status ?? assetErpTrack?.asset.status)}</div>
|
<div>资产状态:{assetStatusLabel(assetErpStage.preview.status ?? assetErpTrack?.asset.status)}</div>
|
||||||
<div>法体:{assetErpStage.preview.legalEntityCode ?? "-"}</div>
|
<div>法体:{assetErpStage.preview.legalEntityCode ?? "-"}</div>
|
||||||
<div>成本中心:{assetErpStage.preview.costCenterCode ?? "-"}</div>
|
<div>成本中心:{assetErpStage.preview.costCenterCode ?? "-"}</div>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ".
|
|||||||
import { Textarea } from "../components/ui/textarea";
|
import { Textarea } from "../components/ui/textarea";
|
||||||
import { FilterBar, ListWorkspace, PrimaryTable, WorkspaceHeader } from "../components/patterns/ListWorkspace";
|
import { FilterBar, ListWorkspace, PrimaryTable, WorkspaceHeader } from "../components/patterns/ListWorkspace";
|
||||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||||
import { formatDisplayDateTime } from "../lib/displayFormat";
|
import { formatCompactIdentifier, formatDisplayDateTime, formatTextWithCompactLongNumbers, isCompactedIdentifier } from "../lib/displayFormat";
|
||||||
import { buildPrecheckDialogMessage } from "../lib/precheck";
|
import { buildPrecheckDialogMessage } from "../lib/precheck";
|
||||||
import {
|
import {
|
||||||
formatApprovalHistoryTime,
|
formatApprovalHistoryTime,
|
||||||
@@ -112,6 +112,41 @@ function statusOptions(): Array<{ value: PurchaseRequestStatus | ""; label: stri
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CompactBusinessCode({ value, copyable = true }: { value: string; copyable?: boolean }) {
|
||||||
|
const displayValue = formatCompactIdentifier(value, { head: 12, tail: 4, minLength: 18 });
|
||||||
|
const compacted = isCompactedIdentifier(value, displayValue);
|
||||||
|
|
||||||
|
async function copyBusinessCode() {
|
||||||
|
if (!compacted) return;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
toast.success("已复制完整单据编号");
|
||||||
|
} catch {
|
||||||
|
toast.error("复制失败,可在详情页查看完整编号");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="inline-flex min-w-0 max-w-full items-center gap-1.5">
|
||||||
|
<span className="truncate" title={compacted ? value : undefined}>
|
||||||
|
{displayValue}
|
||||||
|
</span>
|
||||||
|
{compacted && copyable ? (
|
||||||
|
<button
|
||||||
|
className="shrink-0 rounded border border-zinc-200 px-1.5 py-0.5 text-[11px] font-medium text-zinc-500 hover:border-zinc-300 hover:text-zinc-700"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
void copyBusinessCode();
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type PurchaseErpStageKey = keyof PurchaseRequestErpTrack["stages"];
|
type PurchaseErpStageKey = keyof PurchaseRequestErpTrack["stages"];
|
||||||
|
|
||||||
const PURCHASE_ERP_STAGE_META: Record<
|
const PURCHASE_ERP_STAGE_META: Record<
|
||||||
@@ -933,9 +968,11 @@ export function PurchaseRequestsPage() {
|
|||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="truncate text-sm font-semibold text-zinc-900">
|
<div className="truncate text-sm font-semibold text-zinc-900">
|
||||||
{resolvePurchaseDisplayCode(item)}
|
<CompactBusinessCode copyable={false} value={resolvePurchaseDisplayCode(item)} />
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 truncate text-xs text-zinc-500" title={item.title || undefined}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.title || "未命名采购申请")}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 truncate text-xs text-zinc-500">{item.title || "未命名采购申请"}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<StatusBadge status={item.status} />
|
<StatusBadge status={item.status} />
|
||||||
</div>
|
</div>
|
||||||
@@ -951,7 +988,9 @@ export function PurchaseRequestsPage() {
|
|||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<div className="text-zinc-400">关联项目</div>
|
<div className="text-zinc-400">关联项目</div>
|
||||||
<div className="mt-0.5 truncate text-zinc-700">
|
<div className="mt-0.5 truncate text-zinc-700">
|
||||||
{item.project ? `${item.project.projectNo} · ${item.project.name}` : "未关联项目"}
|
{item.project
|
||||||
|
? `${formatTextWithCompactLongNumbers(item.project.projectNo)} · ${item.project.name}`
|
||||||
|
: "未关联项目"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -984,14 +1023,20 @@ export function PurchaseRequestsPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell className="cursor-pointer">
|
<TableCell className="cursor-pointer">
|
||||||
<div className="font-medium text-zinc-900">{resolvePurchaseDisplayCode(item)}</div>
|
<div className="font-medium text-zinc-900">
|
||||||
<div className="text-xs text-zinc-500">{item.title || "未命名采购申请"}</div>
|
<CompactBusinessCode value={resolvePurchaseDisplayCode(item)} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-zinc-500" title={item.title || undefined}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.title || "未命名采购申请")}
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{item.applicant?.name ?? "-"}</TableCell>
|
<TableCell>{item.applicant?.name ?? "-"}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{item.project ? (
|
{item.project ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-zinc-900">{item.project.projectNo}</div>
|
<div className="font-medium text-zinc-900" title={item.project.projectNo}>
|
||||||
|
{formatTextWithCompactLongNumbers(item.project.projectNo)}
|
||||||
|
</div>
|
||||||
<div className="text-xs text-zinc-500">{item.project.name}</div>
|
<div className="text-xs text-zinc-500">{item.project.name}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -1042,7 +1087,9 @@ export function PurchaseRequestsPage() {
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-zinc-900">{resolvePurchaseDisplayCode(detail)}</h3>
|
<h3 className="text-lg font-semibold text-zinc-900">
|
||||||
|
<CompactBusinessCode value={resolvePurchaseDisplayCode(detail)} />
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-zinc-500">申请人:{detail.applicant?.name ?? "-"}</p>
|
<p className="text-sm text-zinc-500">申请人:{detail.applicant?.name ?? "-"}</p>
|
||||||
<p className="mt-1 text-sm text-zinc-500" data-testid="purchase-status-hint">
|
<p className="mt-1 text-sm text-zinc-500" data-testid="purchase-status-hint">
|
||||||
{resolvePurchaseStatusHint(detail)}
|
{resolvePurchaseStatusHint(detail)}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
|||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { DateTextInput } from "../../components/common/DateTextInput";
|
import { DateTextInput } from "../../components/common/DateTextInput";
|
||||||
import { formatDisplayDate, formatDisplayDateTime } from "../../lib/displayFormat";
|
import { formatCompactIdentifier, formatDisplayDate, formatDisplayDateTime, isCompactedIdentifier } from "../../lib/displayFormat";
|
||||||
import { formatInvoiceOcrErrorMessage } from "../../lib/invoiceOcr";
|
import { formatInvoiceOcrErrorMessage } from "../../lib/invoiceOcr";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export type InvoiceDetailPanelProps = {
|
export type InvoiceDetailPanelProps = {
|
||||||
detail: InvoicePoolDetail | undefined;
|
detail: InvoicePoolDetail | undefined;
|
||||||
@@ -150,6 +151,8 @@ export function InvoiceDetailPanel({
|
|||||||
const invoiceNo = detail
|
const invoiceNo = detail
|
||||||
? [detail.invoiceCode, detail.invoiceNumber].filter(Boolean).join("-") || detail.originalName || "待识别"
|
? [detail.invoiceCode, detail.invoiceNumber].filter(Boolean).join("-") || detail.originalName || "待识别"
|
||||||
: "";
|
: "";
|
||||||
|
const invoiceNoDisplay = formatCompactIdentifier(invoiceNo, { head: 12, tail: 4, minLength: 18 });
|
||||||
|
const invoiceNoCompacted = isCompactedIdentifier(invoiceNo, invoiceNoDisplay);
|
||||||
const bindingStatus = detail?.linkedReimbursementId
|
const bindingStatus = detail?.linkedReimbursementId
|
||||||
? "已绑定报销单"
|
? "已绑定报销单"
|
||||||
: detail?.linkedTravelOrderId
|
: detail?.linkedTravelOrderId
|
||||||
@@ -168,6 +171,15 @@ export function InvoiceDetailPanel({
|
|||||||
setDuplicateOverrideRefFormNumber("");
|
setDuplicateOverrideRefFormNumber("");
|
||||||
setDuplicateOverrideReason("");
|
setDuplicateOverrideReason("");
|
||||||
};
|
};
|
||||||
|
const copyInvoiceNo = async () => {
|
||||||
|
if (!invoiceNoCompacted) return;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(invoiceNo);
|
||||||
|
toast.success("已复制完整发票号码");
|
||||||
|
} catch {
|
||||||
|
toast.error("复制失败,可在编辑表单中查看完整号码");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -178,7 +190,16 @@ export function InvoiceDetailPanel({
|
|||||||
<div className="grid grid-cols-2 gap-y-3 pt-2 text-sm text-zinc-700">
|
<div className="grid grid-cols-2 gap-y-3 pt-2 text-sm text-zinc-700">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<span className="text-zinc-500">发票号码:</span>
|
<span className="text-zinc-500">发票号码:</span>
|
||||||
{invoiceNo}
|
<span title={invoiceNoCompacted ? invoiceNo : undefined}>{invoiceNoDisplay}</span>
|
||||||
|
{invoiceNoCompacted ? (
|
||||||
|
<button
|
||||||
|
className="ml-2 rounded border border-zinc-200 px-1.5 py-0.5 text-[11px] font-medium text-zinc-500 hover:border-zinc-300 hover:text-zinc-700"
|
||||||
|
onClick={() => void copyInvoiceNo()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div><span className="text-zinc-500">状态:</span>{invoiceStatusLabel(detail.status)}</div>
|
<div><span className="text-zinc-500">状态:</span>{invoiceStatusLabel(detail.status)}</div>
|
||||||
<div><span className="text-zinc-500">绑定状态:</span>{bindingStatus}</div>
|
<div><span className="text-zinc-500">绑定状态:</span>{bindingStatus}</div>
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { TableSkeleton } from "../../components/common/TableSkeleton";
|
|||||||
import { Pagination } from "../../components/common/Pagination";
|
import { Pagination } from "../../components/common/Pagination";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../../components/ui/dropdown-menu";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { formatDisplayDate } from "../../lib/displayFormat";
|
import { formatCompactIdentifier, formatDisplayDate, isCompactedIdentifier } from "../../lib/displayFormat";
|
||||||
import { formatInvoiceOcrErrorMessage } from "../../lib/invoiceOcr";
|
import { formatInvoiceOcrErrorMessage } from "../../lib/invoiceOcr";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export type InvoiceListCardProps = {
|
export type InvoiceListCardProps = {
|
||||||
listQuery: UseQueryResult<InvoicePoolListResponse, Error>;
|
listQuery: UseQueryResult<InvoicePoolListResponse, Error>;
|
||||||
@@ -32,6 +33,41 @@ function invoiceNumberDisplay(item: InvoicePoolListResponse["items"][number]) {
|
|||||||
return [item.invoiceCode, item.invoiceNumber].filter(Boolean).join("-") || "-";
|
return [item.invoiceCode, item.invoiceNumber].filter(Boolean).join("-") || "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CompactInvoiceNumber({ value }: { value: string }) {
|
||||||
|
const displayValue = formatCompactIdentifier(value, { head: 10, tail: 4, minLength: 16 });
|
||||||
|
const compacted = isCompactedIdentifier(value, displayValue);
|
||||||
|
|
||||||
|
async function copyInvoiceNumber() {
|
||||||
|
if (!compacted) return;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
toast.success("已复制完整发票号码");
|
||||||
|
} catch {
|
||||||
|
toast.error("复制失败,可在票据详情中查看完整号码");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="inline-flex min-w-0 max-w-full items-center gap-1.5">
|
||||||
|
<span className="truncate" title={compacted ? value : undefined}>
|
||||||
|
{displayValue}
|
||||||
|
</span>
|
||||||
|
{compacted ? (
|
||||||
|
<button
|
||||||
|
className="shrink-0 rounded border border-zinc-200 px-1.5 py-0.5 text-[11px] font-medium text-zinc-500 hover:border-zinc-300 hover:text-zinc-700"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
void copyInvoiceNumber();
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function InvoiceListCard({
|
export function InvoiceListCard({
|
||||||
listQuery,
|
listQuery,
|
||||||
status,
|
status,
|
||||||
@@ -100,7 +136,7 @@ export function InvoiceListCard({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-zinc-500">发票号码</div>
|
<div className="text-xs text-zinc-500">发票号码</div>
|
||||||
<div className="truncate font-medium text-zinc-900">
|
<div className="truncate font-medium text-zinc-900">
|
||||||
{invoiceNumberDisplay(item)}
|
<CompactInvoiceNumber value={invoiceNumberDisplay(item)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -169,7 +205,7 @@ export function InvoiceListCard({
|
|||||||
</td>
|
</td>
|
||||||
<td className="table-td"><StatusBadge status={item.status} /></td>
|
<td className="table-td"><StatusBadge status={item.status} /></td>
|
||||||
<td className="table-td font-medium text-zinc-900">
|
<td className="table-td font-medium text-zinc-900">
|
||||||
{invoiceNumberDisplay(item)}
|
<CompactInvoiceNumber value={invoiceNumberDisplay(item)} />
|
||||||
</td>
|
</td>
|
||||||
<td className="table-td">
|
<td className="table-td">
|
||||||
{item.status === "pending_ocr" ? (
|
{item.status === "pending_ocr" ? (
|
||||||
|
|||||||
@@ -228,6 +228,11 @@ async function readMetrics(page: Page) {
|
|||||||
text: (element.textContent ?? element.getAttribute("aria-label") ?? element.getAttribute("placeholder") ?? "").trim().slice(0, 40),
|
text: (element.textContent ?? element.getAttribute("aria-label") ?? element.getAttribute("placeholder") ?? "").trim().slice(0, 40),
|
||||||
}));
|
}));
|
||||||
const englishFound = englishTerms.filter((term) => text.includes(term));
|
const englishFound = englishTerms.filter((term) => text.includes(term));
|
||||||
|
const forbiddenPage =
|
||||||
|
window.location.pathname === "/403" ||
|
||||||
|
headings.some((heading) => /^(403|无权访问|权限不足)$/.test(heading)) ||
|
||||||
|
Array.from(document.querySelectorAll("[role='alert'],[data-testid='forbidden-page']"))
|
||||||
|
.some((element) => /^(403|无权访问|权限不足)/.test((element.textContent ?? "").trim()));
|
||||||
return {
|
return {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
width,
|
width,
|
||||||
@@ -243,7 +248,7 @@ async function readMetrics(page: Page) {
|
|||||||
englishFound,
|
englishFound,
|
||||||
datePlaceholderCount: inputsWithDatePlaceholders,
|
datePlaceholderCount: inputsWithDatePlaceholders,
|
||||||
clippedControls,
|
clippedControls,
|
||||||
forbiddenLike: text.includes("无权访问") || text.includes("403") || text.includes("权限不足"),
|
forbiddenLike: forbiddenPage,
|
||||||
appErrorLike: text.includes("页面加载失败") || text.includes("加载失败") || text.includes("出错了"),
|
appErrorLike: text.includes("页面加载失败") || text.includes("加载失败") || text.includes("出错了"),
|
||||||
navPortalCopy: text.includes("Portal"),
|
navPortalCopy: text.includes("Portal"),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -745,3 +745,17 @@ git show 5da05eb:docs/trialrun/人工试运行报告.md
|
|||||||
| 公网验证 | 仍需单独批准部署后再由负责人本人提交 1 条低风险报销验证;本节不代表已执行公网写入 |
|
| 公网验证 | 仍需单独批准部署后再由负责人本人提交 1 条低风险报销验证;本节不代表已执行公网写入 |
|
||||||
|
|
||||||
预期链路:小额 `finance_mgr -> finance`,中额 `finance_mgr`,大额 `finance_mgr`。若 `finance_mgr` 没有 active 非本人持有人,或申请人本人也持有兜底角色,系统继续阻断。
|
预期链路:小额 `finance_mgr -> finance`,中额 `finance_mgr`,大额 `finance_mgr`。若 `finance_mgr` 没有 active 非本人持有人,或申请人本人也持有兜底角色,系统继续阻断。
|
||||||
|
|
||||||
|
## OAFRONT6 前端成熟化小步收口
|
||||||
|
|
||||||
|
当前口径:OAFRONT6 只处理上线前低风险展示成熟化,不替代 OALAUNCH1 的真实业务闭环、权限矩阵、备份恢复、7 天稳定化和 Go/No-Go。
|
||||||
|
|
||||||
|
| 项目 | 当前状态 |
|
||||||
|
| --- | --- |
|
||||||
|
| 合同 | `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` |
|
||||||
|
| 已部署项 | 审批规则路由标题、资产台账详情分区、移动视觉矩阵稳定等待、合同台账表单分组 |
|
||||||
|
| 本地新增项 | 票据中心、采购申请、资产台账的长编号主展示压缩;完整值通过 hover/title 或复制动作保留 |
|
||||||
|
| QA 修正 | PC 只读视觉矩阵不再把审计日志正文里的“权限不足”记录误判为 403 页面 |
|
||||||
|
| 边界 | 不改后端编号、不改权限模型、不写公网业务数据;部署后仍需公网 PC 只读视觉矩阵复测 |
|
||||||
|
|
||||||
|
Portal 首屏工作台化继续保留为 7 天真实使用反馈后的观察项,不能凭审美偏好在启用前大重排。
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ DG9 样板规则:
|
|||||||
- 卡片固定槽位:第一行左侧为业务类型加业务编号或摘要,第一行右侧为状态;第二行放申请人、金额/天数、日期;第三行可选当前节点或更新时间。
|
- 卡片固定槽位:第一行左侧为业务类型加业务编号或摘要,第一行右侧为状态;第二行放申请人、金额/天数、日期;第三行可选当前节点或更新时间。
|
||||||
- PC 高密度表格允许保留独立“单号/编号”列,但同一行的标题或摘要不得再拼接同一编号;确需复制编号时放在单号列或详情页摘要区。
|
- PC 高密度表格允许保留独立“单号/编号”列,但同一行的标题或摘要不得再拼接同一编号;确需复制编号时放在单号列或详情页摘要区。
|
||||||
- UUID、taskId、entityId、内部状态码和调试字段不进入用户主展示;审计页、管理员排障页和打印留痕页按权限例外处理。
|
- UUID、taskId、entityId、内部状态码和调试字段不进入用户主展示;审计页、管理员排障页和打印留痕页按权限例外处理。
|
||||||
|
- 发票号码、采购测试编号、资产分类标识等长编号在列表和摘要区默认使用短形态展示;完整值只通过 hover/title、详情编辑字段或“复制”动作提供,避免长数字压缩 PC 表格扫读。
|
||||||
|
|
||||||
### MIA7 移动工作台、进度、通知和详情标准
|
### MIA7 移动工作台、进度、通知和详情标准
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -115,7 +115,7 @@
|
|||||||
| `OMAT1` | `issues/2026-05-23_17-20-00-oa-maturity-gap-review-v1.csv` | `CURRENT` | 当前成熟度缺口审查台账:按 MAGI 审视/执行/提升轮次调查 hyb-oa 是否成熟,并对标国内主流 OA/协同/低代码/财务/资产系统;已补充国内对标来源、小团队取舍原则、用户决策回填和成熟度证据视图;所有问题和建议先记录为待用户采纳、暂缓或否决,不直接改业务代码 |
|
| `OMAT1` | `issues/2026-05-23_17-20-00-oa-maturity-gap-review-v1.csv` | `CURRENT` | 当前成熟度缺口审查台账:按 MAGI 审视/执行/提升轮次调查 hyb-oa 是否成熟,并对标国内主流 OA/协同/低代码/财务/资产系统;已补充国内对标来源、小团队取舍原则、用户决策回填和成熟度证据视图;所有问题和建议先记录为待用户采纳、暂缓或否决,不直接改业务代码 |
|
||||||
| `OPOLICY1` | `issues/2026-05-24_01-20-52-oa-policy-system-alignment-v1.csv` | `CURRENT` | 当前制度与 OA 对齐合同:先调整外部制度包为小团队 V1,再回写 OA 事实源和启用门禁;制度参数、资料留存、用印台账、审批异常 SOP、制度签收和 P2 冻结进入 OALAUNCH1 数据治理门禁;现有合同台账、资产台账和公告能力可承载 V1,不立即做 schema 或业务代码改造 |
|
| `OPOLICY1` | `issues/2026-05-24_01-20-52-oa-policy-system-alignment-v1.csv` | `CURRENT` | 当前制度与 OA 对齐合同:先调整外部制度包为小团队 V1,再回写 OA 事实源和启用门禁;制度参数、资料留存、用印台账、审批异常 SOP、制度签收和 P2 冻结进入 OALAUNCH1 数据治理门禁;现有合同台账、资产台账和公告能力可承载 V1,不立即做 schema 或业务代码改造 |
|
||||||
| `OAFLOW1` | `issues/2026-05-24_19-03-07-oa-owner-application-fallback-approval-v1.csv` | `CURRENT` | 当前负责人本人申请兜底审批合同:`wangshunli/HYD000006` 本人报销不配置假直属主管、不允许自审、不把 `admin` 当业务审批权;报销审批规则新增 `ownerSelfFallbackRole`,首版只允许 `finance_mgr` 或置空,现有启用规则默认 `finance_mgr`,提交时生成新单审批快照并移除本人 `ceo` 节点和重复兜底角色;公网低风险验证需另行批准 |
|
| `OAFLOW1` | `issues/2026-05-24_19-03-07-oa-owner-application-fallback-approval-v1.csv` | `CURRENT` | 当前负责人本人申请兜底审批合同:`wangshunli/HYD000006` 本人报销不配置假直属主管、不允许自审、不把 `admin` 当业务审批权;报销审批规则新增 `ownerSelfFallbackRole`,首版只允许 `finance_mgr` 或置空,现有启用规则默认 `finance_mgr`,提交时生成新单审批快照并移除本人 `ceo` 节点和重复兜底角色;公网低风险验证需另行批准 |
|
||||||
| `OAFRONT6` | `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` | `CURRENT` | 当前前端成熟化小步收口合同:承接 OAFRONT5 部署后 PC 硬问题清零和设计审计;路由标题修复、资产台账详情分区、移动视觉矩阵稳定等待和合同台账表单分组已实施并部署;长编号压缩和 Portal 观察继续后置;本合同不写公网业务数据,不替代 OALAUNCH1 真实业务、备份恢复、7 天稳定化或 Go/No-Go |
|
| `OAFRONT6` | `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` | `CURRENT` | 当前前端成熟化小步收口合同:承接 OAFRONT5 部署后 PC 硬问题清零和设计审计;路由标题修复、资产台账详情分区、移动视觉矩阵稳定等待和合同台账表单分组已实施并部署;长编号压缩已本地实施并待部署复测;Portal 观察继续后置;本合同不写公网业务数据,不替代 OALAUNCH1 真实业务、备份恢复、7 天稳定化或 Go/No-Go |
|
||||||
| `WREAL1-PREP` | `issues/2026-05-20_14-35-42-wecom-real-trial-prep-v1.csv` | `CURRENT` | 真实小范围试用准备合同:只固化候选员工、非自审审批人、回滚联系人、允许流程、暂停条件和验收标准;当前不启用真实员工,真实试用必须等请假/补卡/外出/加班四类测试号证据、WAPPR1 审批规则门禁和 WFULLSMOKE1 全域主路径可解释通过后再问 |
|
| `WREAL1-PREP` | `issues/2026-05-20_14-35-42-wecom-real-trial-prep-v1.csv` | `CURRENT` | 真实小范围试用准备合同:只固化候选员工、非自审审批人、回滚联系人、允许流程、暂停条件和验收标准;当前不启用真实员工,真实试用必须等请假/补卡/外出/加班四类测试号证据、WAPPR1 审批规则门禁和 WFULLSMOKE1 全域主路径可解释通过后再问 |
|
||||||
|
|
||||||
旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。
|
旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。
|
||||||
|
|||||||
+2
-2
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
## 最近事实源链
|
## 最近事实源链
|
||||||
|
|
||||||
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH -> CASHIER-VOUCHER-V1 -> LAUNCH-DEPLOY-FINANCE -> FIN-PROD-ENABLE -> ACCOUNTING-HANDOFF-V1 -> SYSTEM-COMPLETE-GATE -> LAUNCH-OPS-V1 -> REQ-COMPLETE-AUDIT -> ATTV1 -> LAUNCH-AUTO-OPS-V1 -> AUTO-REMAINING-CLOSEOUT -> HR-WECOM-DIRECTORY-V1 -> HRWD-CONTACT-LIVE-TEST -> WCFB -> WFOC -> DBWFOC -> WFDG -> WDRG -> WCP1 -> WCPC -> WHMS -> WHMSLIVE -> WOUA -> WAPI -> WRRA -> WOUA-FIX -> WRV1 -> WRA1 -> WMSG1 -> WAPC1 -> WAPC2 -> WPROC1 -> WPROC2 -> WAPC2F dry-run -> WAPC2FLIVE -> WCBK1 -> WEGOV1 -> WACP3/WACP3R -> WREAD1 自动汇总 -> WREAD1F 新样例 -> WREAD1 真机确认 -> WADM1 自建留存模型设计 -> WARCH1 最小审批历史留存 -> WARCH2 正式只读历史查询 -> WROAD2+OPS 受控上线与每日巡检 -> WARCH3 归档稳定化自动巡检 -> WCHECK1 打卡摘要留存 -> WEXT1 外出/加班 dry-run -> WOPS1 巡检告警降噪 -> WROAD2-CLOSEOUT 三天验收 -> WROAD2-BIZ1 三天业务冒烟 -> WAPPR1 审批规则规范化 -> WRULE1 审批链校验器 POC -> WAUTO1 自动证据汇总 -> WEXT1LD live 前 dry-run -> WFULLSMOKE1 三天全域最终目标测试 -> WROUTE2 测试号收口到最小镜像路线锁定 -> WTCLOSE1 小团队自动审批规则与测试号全域收口 dry-run -> WTCLOSE2 测试号最终收口与证据复核 -> WTCLOSE2F 远端可信 IP 执行口径修正 -> WREMOTE1 远端可信 IP 收口 -> WFULLSMOKE2 全域 smoke -> WEXT1-FORMAL/WEXT1F 正式模板外出/加班测试 -> OAOPS1 小团队全员真实使用稳定化 -> ASSET1 电子资产与固定资产统一台账 -> OSMOKE1 OA-only smoke 账号治理与公网自动化恢复 -> OALAUNCH1 最终启用前收口与 Go/No-Go 门禁 -> OMAT1 成熟度缺口审查 -> OPOLICY1 制度与 OA 对齐 -> OAFRONT6 前端成熟化小步收口(标题/资产/移动/合同已实施,当前 goal)-> 后续再按需问答 WREAL1/最小镜像。
|
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH -> CASHIER-VOUCHER-V1 -> LAUNCH-DEPLOY-FINANCE -> FIN-PROD-ENABLE -> ACCOUNTING-HANDOFF-V1 -> SYSTEM-COMPLETE-GATE -> LAUNCH-OPS-V1 -> REQ-COMPLETE-AUDIT -> ATTV1 -> LAUNCH-AUTO-OPS-V1 -> AUTO-REMAINING-CLOSEOUT -> HR-WECOM-DIRECTORY-V1 -> HRWD-CONTACT-LIVE-TEST -> WCFB -> WFOC -> DBWFOC -> WFDG -> WDRG -> WCP1 -> WCPC -> WHMS -> WHMSLIVE -> WOUA -> WAPI -> WRRA -> WOUA-FIX -> WRV1 -> WRA1 -> WMSG1 -> WAPC1 -> WAPC2 -> WPROC1 -> WPROC2 -> WAPC2F dry-run -> WAPC2FLIVE -> WCBK1 -> WEGOV1 -> WACP3/WACP3R -> WREAD1 自动汇总 -> WREAD1F 新样例 -> WREAD1 真机确认 -> WADM1 自建留存模型设计 -> WARCH1 最小审批历史留存 -> WARCH2 正式只读历史查询 -> WROAD2+OPS 受控上线与每日巡检 -> WARCH3 归档稳定化自动巡检 -> WCHECK1 打卡摘要留存 -> WEXT1 外出/加班 dry-run -> WOPS1 巡检告警降噪 -> WROAD2-CLOSEOUT 三天验收 -> WROAD2-BIZ1 三天业务冒烟 -> WAPPR1 审批规则规范化 -> WRULE1 审批链校验器 POC -> WAUTO1 自动证据汇总 -> WEXT1LD live 前 dry-run -> WFULLSMOKE1 三天全域最终目标测试 -> WROUTE2 测试号收口到最小镜像路线锁定 -> WTCLOSE1 小团队自动审批规则与测试号全域收口 dry-run -> WTCLOSE2 测试号最终收口与证据复核 -> WTCLOSE2F 远端可信 IP 执行口径修正 -> WREMOTE1 远端可信 IP 收口 -> WFULLSMOKE2 全域 smoke -> WEXT1-FORMAL/WEXT1F 正式模板外出/加班测试 -> OAOPS1 小团队全员真实使用稳定化 -> ASSET1 电子资产与固定资产统一台账 -> OSMOKE1 OA-only smoke 账号治理与公网自动化恢复 -> OALAUNCH1 最终启用前收口与 Go/No-Go 门禁 -> OMAT1 成熟度缺口审查 -> OPOLICY1 制度与 OA 对齐 -> OAFRONT6 前端成熟化小步收口(标题/资产/移动/合同已部署,长编号本地已实施待部署复测)-> 后续再按需问答 WREAL1/最小镜像。
|
||||||
|
|
||||||
- R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
|
- R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
|
||||||
- ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。
|
- ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。
|
||||||
@@ -175,7 +175,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
|
|||||||
| `OALAUNCH1` | `issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv` | 当前最终启用前收口与 Go/No-Go 门禁:在 OAOPS1/ASSET1D 之上增加业务闭环、权限、备份恢复、数据治理、培训和 Day 7 决策 | 新增 `scripts/qa/run-oa-final-launch-readiness.sh`,默认只读聚合 OAOPS1、ASSET1D、权限矩阵、备份恢复演练和人工 Go/No-Go 证据,输出 `PASS_OA_LAUNCH_READY` / `PASS_WITH_FINDINGS` / `PAUSED_MANUAL_CONFIRMATION` / `SKIP/BLOCKED_BY_EXTERNAL_CONFIG` | 不创建业务数据、不执行付款、不写企业微信、不恢复数据库;未达到 `PASS_OA_LAUNCH_READY` 前不得宣布最终正式启用完成 |
|
| `OALAUNCH1` | `issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv` | 当前最终启用前收口与 Go/No-Go 门禁:在 OAOPS1/ASSET1D 之上增加业务闭环、权限、备份恢复、数据治理、培训和 Day 7 决策 | 新增 `scripts/qa/run-oa-final-launch-readiness.sh`,默认只读聚合 OAOPS1、ASSET1D、权限矩阵、备份恢复演练和人工 Go/No-Go 证据,输出 `PASS_OA_LAUNCH_READY` / `PASS_WITH_FINDINGS` / `PAUSED_MANUAL_CONFIRMATION` / `SKIP/BLOCKED_BY_EXTERNAL_CONFIG` | 不创建业务数据、不执行付款、不写企业微信、不恢复数据库;未达到 `PASS_OA_LAUNCH_READY` 前不得宣布最终正式启用完成 |
|
||||||
| `OMAT1` | `issues/2026-05-23_17-20-00-oa-maturity-gap-review-v1.csv` | 当前成熟度缺口审查台账:按 MAGI 审视/执行/提升轮次,对标国内主流 OA、协同、低代码、财务和资产系统,记录每个问题或建议供用户逐项采纳、暂缓或否决 | 已登记最终启用、真实业务闭环、权限、备份恢复、运营支持、流程配置、报表、财务月结、合同、采购、资产、HR 镜像、移动体验、数据治理、SLO、集成、知识档案、主数据、UX、AI 边界、国内对标来源、小团队取舍原则、用户决策回填、成熟度证据视图和最低非功能基线等候选项 | 本台账只做审查和决策,不直接触发业务代码实现;采纳项必须另建或延续对应 Issue CSV,并满足 OALAUNCH1 门禁 |
|
| `OMAT1` | `issues/2026-05-23_17-20-00-oa-maturity-gap-review-v1.csv` | 当前成熟度缺口审查台账:按 MAGI 审视/执行/提升轮次,对标国内主流 OA、协同、低代码、财务和资产系统,记录每个问题或建议供用户逐项采纳、暂缓或否决 | 已登记最终启用、真实业务闭环、权限、备份恢复、运营支持、流程配置、报表、财务月结、合同、采购、资产、HR 镜像、移动体验、数据治理、SLO、集成、知识档案、主数据、UX、AI 边界、国内对标来源、小团队取舍原则、用户决策回填、成熟度证据视图和最低非功能基线等候选项 | 本台账只做审查和决策,不直接触发业务代码实现;采纳项必须另建或延续对应 Issue CSV,并满足 OALAUNCH1 门禁 |
|
||||||
| `OPOLICY1` | `issues/2026-05-24_01-20-52-oa-policy-system-alignment-v1.csv` | 当前制度与 OA 对齐合同:先把外部制度包裁剪为小团队 V1,再回写 OA 事实源和启用门禁 | 已固化制度参数、资料留存、用印台账、审批异常 SOP、制度签收和 P2 冻结边界;预算硬控、复杂招采、电子签章、完整电子档案系统、ERP/银行深集成和 AI 自动改规则不作为正式启用前置条件 | 本合同优先制度和事实源调整;现有合同台账/资产台账/公告能力可承载 V1,不立即做 schema 或业务代码改造 |
|
| `OPOLICY1` | `issues/2026-05-24_01-20-52-oa-policy-system-alignment-v1.csv` | 当前制度与 OA 对齐合同:先把外部制度包裁剪为小团队 V1,再回写 OA 事实源和启用门禁 | 已固化制度参数、资料留存、用印台账、审批异常 SOP、制度签收和 P2 冻结边界;预算硬控、复杂招采、电子签章、完整电子档案系统、ERP/银行深集成和 AI 自动改规则不作为正式启用前置条件 | 本合同优先制度和事实源调整;现有合同台账/资产台账/公告能力可承载 V1,不立即做 schema 或业务代码改造 |
|
||||||
| `OAFRONT6` | `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` | 当前前端成熟化小步收口:承接 OAFRONT5 部署后 PC 硬问题清零和设计审计;审批规则路由标题、资产台账详情分区、移动视觉矩阵稳定等待和合同台账表单分组已实施并部署,长编号和 Portal 继续后置观察 | 页面改动继续读取 `docs/产品设计规则.md` 和 OALAUNCH1 启用门禁;后续长编号/Portal 仍需按用户选择推进 | 不写公网业务数据;不替代真实业务闭环、备份恢复、7 天稳定化或 Day 7 Go/No-Go |
|
| `OAFRONT6` | `issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv` | 当前前端成熟化小步收口:承接 OAFRONT5 部署后 PC 硬问题清零和设计审计;审批规则路由标题、资产台账详情分区、移动视觉矩阵稳定等待和合同台账表单分组已实施并部署;长编号压缩已本地实施,待部署后跑公网 PC 只读视觉矩阵复测;Portal 继续后置观察 | 页面改动继续读取 `docs/产品设计规则.md` 和 OALAUNCH1 启用门禁;后续 Portal 仍需按真实使用反馈推进 | 不写公网业务数据;不替代真实业务闭环、备份恢复、7 天稳定化或 Day 7 Go/No-Go |
|
||||||
| `WREAL1-PREP` | `issues/2026-05-20_14-35-42-wecom-real-trial-prep-v1.csv` | 当前真实试用准备:不启用真实员工 | 固化候选员工、非自审审批人、回滚联系人、允许流程、暂停条件和验收标准;真实试用必须等请假、补卡、外出、加班四类测试号证据、WAPPR1 和 WFULLSMOKE1 通过后再问 | 当前不创建外出/加班 live 样件;真实员工名单不写入 Issue 或文档,后续通过问答确认 |
|
| `WREAL1-PREP` | `issues/2026-05-20_14-35-42-wecom-real-trial-prep-v1.csv` | 当前真实试用准备:不启用真实员工 | 固化候选员工、非自审审批人、回滚联系人、允许流程、暂停条件和验收标准;真实试用必须等请假、补卡、外出、加班四类测试号证据、WAPPR1 和 WFULLSMOKE1 通过后再问 | 当前不创建外出/加班 live 样件;真实员工名单不写入 Issue 或文档,后续通过问答确认 |
|
||||||
|
|
||||||
旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。
|
旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"OAFRONT6-2","资产台账详情分区与扩展 JSON 默认折叠","资产台账已完成 ASSET1D 公网试录,但管理员资产页仍把分类维护、资产列表、详情长表单、扩展元数据 JSON、流转和 ERP 摘要放在较长页面中;真实维护 FA/EA-SYS/EA-REPO/EA-BKP/EA-DOC 时可读性偏重。规划小步前端分区,不改变资产模型或导入写入开关。","资产详情按基础信息、责任与归档、电子资产安全字段、流转、ERP 摘要分区;扩展元数据 JSON 默认折叠,仅管理员可展开;新建资产主动作仍清楚;不展示密码、token、Secret、源码包、完整路径或涉密正文。","受影响流程:管理员登录 -> 资产台账 -> 搜索/筛选 -> 查看/编辑资产 -> 保存资产卡片;验证步骤:client typecheck/build;check-ui-design-rules;admin /assets PC 截图;ASSET1D 资产试录路径不回归;普通员工权限负例不变。","typescript|playwright|manual","DONE","DONE","DONE","client/src/pages/AssetRegistryPage.tsx;docs/需求-资产管理规约.md","[""OAFRONT6-0""]","已实施:资产详情按基础信息、责任与归档、电子资产安全字段分区;扩展元数据 JSON 默认折叠,保存 payload 不变。验证:client typecheck/build、check-ui-design-rules、check-minimal 均通过。回滚路径:还原 AssetRegistryPage 展示提交;公网资产数据无需清理。"
|
"OAFRONT6-2","资产台账详情分区与扩展 JSON 默认折叠","资产台账已完成 ASSET1D 公网试录,但管理员资产页仍把分类维护、资产列表、详情长表单、扩展元数据 JSON、流转和 ERP 摘要放在较长页面中;真实维护 FA/EA-SYS/EA-REPO/EA-BKP/EA-DOC 时可读性偏重。规划小步前端分区,不改变资产模型或导入写入开关。","资产详情按基础信息、责任与归档、电子资产安全字段、流转、ERP 摘要分区;扩展元数据 JSON 默认折叠,仅管理员可展开;新建资产主动作仍清楚;不展示密码、token、Secret、源码包、完整路径或涉密正文。","受影响流程:管理员登录 -> 资产台账 -> 搜索/筛选 -> 查看/编辑资产 -> 保存资产卡片;验证步骤:client typecheck/build;check-ui-design-rules;admin /assets PC 截图;ASSET1D 资产试录路径不回归;普通员工权限负例不变。","typescript|playwright|manual","DONE","DONE","DONE","client/src/pages/AssetRegistryPage.tsx;docs/需求-资产管理规约.md","[""OAFRONT6-0""]","已实施:资产详情按基础信息、责任与归档、电子资产安全字段分区;扩展元数据 JSON 默认折叠,保存 payload 不变。验证:client typecheck/build、check-ui-design-rules、check-minimal 均通过。回滚路径:还原 AssetRegistryPage 展示提交;公网资产数据无需清理。"
|
||||||
"OAFRONT6-3","移动视觉矩阵截图稳定等待与我的页对比度复核","上一轮移动截图中部分页面疑似截到淡入动画中间态,导致页面整体偏淡;但“我的”页也确实存在入口和个人信息可点击性偏弱的观察项。规划先修 QA 证据稳定性,再决定是否做移动 token 小调。","移动视觉矩阵在截图前等待页面稳定或禁用测试动效;新截图不再因 opacity 动画出现整页半透明;如最终态仍低对比,再记录移动我的页 token 小修任务。","受影响流程:企业微信/手机进入 OA -> 首页/审批/报销/我的 -> 视觉矩阵截图 -> 设计判断;验证步骤:VISUAL_MATRIX_SCOPE=mobile bash scripts/qa/run-visual-matrix.sh live;检查 320/360/375/390/393/414/430 宽度截图;无横向溢出、无桌面表格。","playwright|bash|manual","DONE","DONE","DONE","client/tests/e2e/r6-mobile-unified-design-rollout.spec.ts;scripts/qa/run-visual-matrix.sh;client/src/styles/mobile-touch.css;client/src/pages/MobileMePage.tsx","[""OAFRONT6-0""]","已实施:移动视觉矩阵在截图前注入测试态禁用动画,并等待移动壳、字体和 opacity 稳定。验证:本地启动 Vite 后,r6-mobile-unified-design-rollout.spec.ts 7 个宽度样本全部通过;截图证据输出到 output/playwright/r6-mobile-unified-design-rollout。回滚路径:还原测试等待辅助函数;不影响业务运行。"
|
"OAFRONT6-3","移动视觉矩阵截图稳定等待与我的页对比度复核","上一轮移动截图中部分页面疑似截到淡入动画中间态,导致页面整体偏淡;但“我的”页也确实存在入口和个人信息可点击性偏弱的观察项。规划先修 QA 证据稳定性,再决定是否做移动 token 小调。","移动视觉矩阵在截图前等待页面稳定或禁用测试动效;新截图不再因 opacity 动画出现整页半透明;如最终态仍低对比,再记录移动我的页 token 小修任务。","受影响流程:企业微信/手机进入 OA -> 首页/审批/报销/我的 -> 视觉矩阵截图 -> 设计判断;验证步骤:VISUAL_MATRIX_SCOPE=mobile bash scripts/qa/run-visual-matrix.sh live;检查 320/360/375/390/393/414/430 宽度截图;无横向溢出、无桌面表格。","playwright|bash|manual","DONE","DONE","DONE","client/tests/e2e/r6-mobile-unified-design-rollout.spec.ts;scripts/qa/run-visual-matrix.sh;client/src/styles/mobile-touch.css;client/src/pages/MobileMePage.tsx","[""OAFRONT6-0""]","已实施:移动视觉矩阵在截图前注入测试态禁用动画,并等待移动壳、字体和 opacity 稳定。验证:本地启动 Vite 后,r6-mobile-unified-design-rollout.spec.ts 7 个宽度样本全部通过;截图证据输出到 output/playwright/r6-mobile-unified-design-rollout。回滚路径:还原测试等待辅助函数;不影响业务运行。"
|
||||||
"OAFRONT6-4","合同台账录入表单分组规划","合同台账是 OALAUNCH1 真实启用门禁之一,目前 PC 页可用但新建表单较长,列表与表单并排时录入路径偏重。规划把合同基础信息、归档责任、到期续约、关联项目四类字段分组,保持涉密合同正文不上公网 OA 的边界。","合同台账页面在 PC 上清楚区分新建合同、筛选列表、详情/归档位置;日期和状态文案保持中文业务语义;敏感合同正文、敏感报价和成本细节不上传,只登记摘要、归档位置、责任人和关联项目。","受影响流程:管理员/负责人录入合同台账 -> 关联项目 -> 记录归档位置 -> 查询到期预警;验证步骤:client typecheck/build;admin /contracts-registry PC 截图;人工创建 1 条 TEST/SMOKE 合同台账或复用 OALAUNCH1 真实合同门禁步骤;权限负例不变。","typescript|playwright|manual","DONE","DONE","DONE","client/src/pages/ContractsRegistryPage.tsx;docs/需求-合同附件归档规约.md","[""OAFRONT6-0""]","已实施并部署:合同台账新建/编辑表单按基础信息、项目关联、日期与续约、归档与责任分组;保存 payload、导出、续约、归档动作不变;继续不上传涉密合同正文。验证:client typecheck/build、check-ui-design-rules、check-minimal、部署后 live smoke 3/3、公网 PC 只读视觉矩阵通过;管理员 /contracts-registry 路由 PASS。公网视觉报告 artifacts/qa-flow/oafront3-pc-readonly-visual-20260525-172935/summary.json。回滚路径:还原 ContractsRegistryPage 展示提交;不删除已录入合同台账。"
|
"OAFRONT6-4","合同台账录入表单分组规划","合同台账是 OALAUNCH1 真实启用门禁之一,目前 PC 页可用但新建表单较长,列表与表单并排时录入路径偏重。规划把合同基础信息、归档责任、到期续约、关联项目四类字段分组,保持涉密合同正文不上公网 OA 的边界。","合同台账页面在 PC 上清楚区分新建合同、筛选列表、详情/归档位置;日期和状态文案保持中文业务语义;敏感合同正文、敏感报价和成本细节不上传,只登记摘要、归档位置、责任人和关联项目。","受影响流程:管理员/负责人录入合同台账 -> 关联项目 -> 记录归档位置 -> 查询到期预警;验证步骤:client typecheck/build;admin /contracts-registry PC 截图;人工创建 1 条 TEST/SMOKE 合同台账或复用 OALAUNCH1 真实合同门禁步骤;权限负例不变。","typescript|playwright|manual","DONE","DONE","DONE","client/src/pages/ContractsRegistryPage.tsx;docs/需求-合同附件归档规约.md","[""OAFRONT6-0""]","已实施并部署:合同台账新建/编辑表单按基础信息、项目关联、日期与续约、归档与责任分组;保存 payload、导出、续约、归档动作不变;继续不上传涉密合同正文。验证:client typecheck/build、check-ui-design-rules、check-minimal、部署后 live smoke 3/3、公网 PC 只读视觉矩阵通过;管理员 /contracts-registry 路由 PASS。公网视觉报告 artifacts/qa-flow/oafront3-pc-readonly-visual-20260525-172935/summary.json。回滚路径:还原 ContractsRegistryPage 展示提交;不删除已录入合同台账。"
|
||||||
"OAFRONT6-5","长编号展示压缩与复制 affordance","最新 PC 只读矩阵仍有 3 个 P2:票据中心、采购申请、资产台账出现长数字或测试业务标识。它们不是 UUID/P1 阻断,但会降低列表扫读效率。规划统一长编号展示策略:主展示短摘要,完整值放详情、hover/title 或复制按钮。","A/B 类页面列表不被长数字压垮;发票号、采购测试业务编号、资产分类标识等长值在主列中截断或摘要化,并提供复制完整值的可理解入口;隐私报告仍不输出真实完整票据、手机号、邮箱、Secret 或原始 payload。","受影响流程:员工/财务查看票据中心 -> 采购列表 -> 管理员资产台账;验证步骤:PC 只读视觉矩阵;check-ui-design-rules;隐私扫描;手工确认复制入口不会暴露到无权限角色。","typescript|playwright|node|manual","TODO","TODO","TODO","client/src/pages/InvoicePoolPage.tsx;client/src/pages/PurchaseRequestsPage.tsx;client/src/pages/AssetRegistryPage.tsx;client/src/lib/displayFormat.ts","[""OAFRONT6-0""]","优先级 P2。公网 PC 只读视觉矩阵 20260525-172935 仍确认员工票据中心、员工采购申请、管理员资产台账存在长数字展示 finding;建议等 OALAUNCH1 真实业务数据样本出现后再修,避免只为测试数据过拟合;若用户觉得列表难读,可提前小步实现。回滚路径:还原展示 helper;不修改后端编号和业务数据。"
|
"OAFRONT6-5","长编号展示压缩与复制 affordance","最新 PC 只读矩阵仍有 3 个 P2:票据中心、采购申请、资产台账出现长数字或测试业务标识。它们不是 UUID/P1 阻断,但会降低列表扫读效率。规划统一长编号展示策略:主展示短摘要,完整值放详情、hover/title 或复制按钮。","A/B 类页面列表不被长数字压垮;发票号、采购测试业务编号、资产分类标识等长值在主列中截断或摘要化,并提供复制完整值的可理解入口;隐私报告仍不输出真实完整票据、手机号、邮箱、Secret 或原始 payload。","受影响流程:员工/财务查看票据中心 -> 采购列表 -> 管理员资产台账;验证步骤:PC 只读视觉矩阵;check-ui-design-rules;隐私扫描;手工确认复制入口不会暴露到无权限角色。","typescript|playwright|node|manual","DONE","DONE","TODO","client/src/pages/invoice-pool/InvoiceListCard.tsx;client/src/pages/invoice-pool/InvoiceDetailPanel.tsx;client/src/pages/PurchaseRequestsPage.tsx;client/src/pages/AssetRegistryPage.tsx;client/src/lib/displayFormat.ts;client/tests/e2e/oafront3-pc-readonly-visual.spec.ts","[""OAFRONT6-0""]","本地已实施:长发票号、采购编号/长数字、资产编号/分类长数字主展示压缩,完整值通过 hover/title 或复制动作保留;PC 只读视觉矩阵 forbidden 判定改为真正 403 页面/错误页标题,避免审计日志正文误判。验证:client typecheck/build、check-ui-design-rules、process-contract baseline/general、audit-docs、quick_validate、git diff --check、scripts/check-minimal.sh 通过。待部署后复跑公网 PC 只读视觉矩阵再置 Regression DONE。回滚路径:还原 displayFormat/helper、相关页面展示和视觉矩阵 forbidden 判定;不修改后端编号、权限模型或业务数据。"
|
||||||
"OAFRONT6-6","Portal 首屏工作台化继续观察门禁","PC 工作台当前可用,但 hero 和装饰仍偏展示型;是否重排必须基于 7 天真实使用反馈,不凭主观审美大改。规划观察门禁:只在用户反馈找不到待办、报销、财务、合同、资产入口时启动小步重排。","7 天稳定化期间收集角色反馈:员工能否 5 秒内找到新建报销/待处理/我的申请;财务能否找到待复核/待付款/AP;管理员能否找到合同/资产/巡检入口。无明确痛点则不改 Portal 布局。","受影响流程:PC 登录 -> 工作台 -> 进入报销/审批/付款/AP/合同/资产;验证步骤:OALAUNCH1 Day 1-7 人工反馈;PC Portal 截图;必要时再建实施 Issue。","manual|playwright","TODO","TODO","TODO","client/src/pages/PortalHomePage.tsx;docs/trialrun/人工试运行报告.md","[""OAFRONT6-0""]","优先级 P2/观察。当前不建议上线前大重排;若采纳后改动,必须保留 Hyinda 品牌蓝、浅色工作台和 DG9 工作台骨架。回滚路径:还原 PortalHomePage 展示提交。"
|
"OAFRONT6-6","Portal 首屏工作台化继续观察门禁","PC 工作台当前可用,但 hero 和装饰仍偏展示型;是否重排必须基于 7 天真实使用反馈,不凭主观审美大改。规划观察门禁:只在用户反馈找不到待办、报销、财务、合同、资产入口时启动小步重排。","7 天稳定化期间收集角色反馈:员工能否 5 秒内找到新建报销/待处理/我的申请;财务能否找到待复核/待付款/AP;管理员能否找到合同/资产/巡检入口。无明确痛点则不改 Portal 布局。","受影响流程:PC 登录 -> 工作台 -> 进入报销/审批/付款/AP/合同/资产;验证步骤:OALAUNCH1 Day 1-7 人工反馈;PC Portal 截图;必要时再建实施 Issue。","manual|playwright","TODO","TODO","TODO","client/src/pages/PortalHomePage.tsx;docs/trialrun/人工试运行报告.md","[""OAFRONT6-0""]","优先级 P2/观察。当前不建议上线前大重排;若采纳后改动,必须保留 Hyinda 品牌蓝、浅色工作台和 DG9 工作台骨架。回滚路径:还原 PortalHomePage 展示提交。"
|
||||||
"OAFRONT6-7","前端成熟化文档回写","若用户采纳 OAFRONT6 任一开发项,实施前后需要把前端成熟化边界回写到产品设计规则、人工试运行报告、文档索引、事实源矩阵和项目 skill;本规划阶段只登记 OAFRONT6 合同,不写大段设计新文档。","文档能说明:OAFRONT6 是展示层小步成熟化,不替代 OALAUNCH1;资产/合同/移动/长编号/Portal 的采纳状态可追溯;不新建孤立设计文档。","受影响流程:规划 -> 实施 -> 文档事实源 -> 后续审查;验证步骤:rg -n ""OAFRONT6|前端成熟化|Portal 首屏|长编号|资产台账|合同台账"" docs .agents/skills/hyb-oa-delivery/SKILL.md;audit-docs;quick_validate。","rg|node|python3|manual","DONE","DONE","DONE","docs/产品设计规则.md;docs/trialrun/人工试运行报告.md;docs/文档索引.md;docs/文档事实源状态矩阵.md;.agents/skills/hyb-oa-delivery/SKILL.md","[""OAFRONT6-0""]","本轮已登记 docs/文档索引.md、docs/文档事实源状态矩阵.md 和 .agents/skills/hyb-oa-delivery/SKILL.md;实施阶段已回填 OAFRONT6-1/2/3/4 状态。audit-docs errors=0 warnings=1(既有未登记文档 24 个),quick_validate 通过。回滚路径:还原文档回写提交。"
|
"OAFRONT6-7","前端成熟化文档回写","若用户采纳 OAFRONT6 任一开发项,实施前后需要把前端成熟化边界回写到产品设计规则、人工试运行报告、文档索引、事实源矩阵和项目 skill;本规划阶段只登记 OAFRONT6 合同,不写大段设计新文档。","文档能说明:OAFRONT6 是展示层小步成熟化,不替代 OALAUNCH1;资产/合同/移动/长编号/Portal 的采纳状态可追溯;不新建孤立设计文档。","受影响流程:规划 -> 实施 -> 文档事实源 -> 后续审查;验证步骤:rg -n ""OAFRONT6|前端成熟化|Portal 首屏|长编号|资产台账|合同台账"" docs .agents/skills/hyb-oa-delivery/SKILL.md;audit-docs;quick_validate。","rg|node|python3|manual","DONE","DONE","DONE","docs/产品设计规则.md;docs/trialrun/人工试运行报告.md;docs/文档索引.md;docs/文档事实源状态矩阵.md;.agents/skills/hyb-oa-delivery/SKILL.md","[""OAFRONT6-0""]","本轮已登记 docs/文档索引.md、docs/文档事实源状态矩阵.md 和 .agents/skills/hyb-oa-delivery/SKILL.md;实施阶段已回填 OAFRONT6-1/2/3/4 状态。audit-docs errors=0 warnings=1(既有未登记文档 24 个),quick_validate 通过。回滚路径:还原文档回写提交。"
|
||||||
"OAFRONT6-8","用户采纳问答门禁","OAFRONT6 包含多个可独立采纳的小步项,进入开发前需要用户逐项选择,避免审美重排影响最终启用主线。","用户至少对 5 组给出 A/B/C:1) 路由标题修复;2) 资产台账分区;3) 移动截图稳定与我的页对比度;4) 合同台账分组;5) 长编号与 Portal 观察。未采纳项不得进入开发。","受影响流程:OAFRONT6 规划 -> 用户选择 -> 实施合同/暂缓/否决;验证步骤:人工回填选择并 validate CSV。","manual|python3","DONE","DONE","DONE","issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv","[""OAFRONT6-0""]","用户已回复“采纳建议,开始实施”,本轮按推荐采纳并完成 1=A、2=A、3=A、4=A;5 长编号和 Portal 观察继续保留 TODO,等待真实使用或后续明确指令。"
|
"OAFRONT6-8","用户采纳问答门禁","OAFRONT6 包含多个可独立采纳的小步项,进入开发前需要用户逐项选择,避免审美重排影响最终启用主线。","用户至少对 5 组给出 A/B/C:1) 路由标题修复;2) 资产台账分区;3) 移动截图稳定与我的页对比度;4) 合同台账分组;5) 长编号与 Portal 观察。未采纳项不得进入开发。","受影响流程:OAFRONT6 规划 -> 用户选择 -> 实施合同/暂缓/否决;验证步骤:人工回填选择并 validate CSV。","manual|python3","DONE","DONE","DONE","issues/2026-05-25_12-29-03-oa-frontend-maturity-small-step-v1.csv","[""OAFRONT6-0""]","用户已回复“采纳建议,开始实施”,本轮按推荐采纳并完成 1=A、2=A、3=A、4=A;5 长编号和 Portal 观察继续保留 TODO,等待真实使用或后续明确指令。"
|
||||||
|
|||||||
|
在新工单中引用
屏蔽一个用户