improve BA skill

This commit is contained in:
Leonard-ThindPad-P50
2026-09-09 06:34:57 +07:00
parent 119792967c
commit 2c7bcde741
42 changed files with 5429 additions and 54 deletions

View File

@@ -6,7 +6,9 @@
> **Tiêu chí G3 riêng của biến thể này** *(thay cho dòng "bảng field" và "wireframe" ở
> [workflow.md §2](../../../ba-lifecycle/references/workflow.md))*:
> mỗi màn hình có bảng thành phần đủ 8 cột · mỗi field có bảng field đủ 10 cột · hai trạng
> thái rỗng khác nhau · mọi thành phần có điều kiện ẩn/khoá/read-only rõ ràng.
> thái rỗng khác nhau · mọi thành phần có điều kiện ẩn/khoá/read-only rõ ràng · **có `WF_<US>`
> (md + html) phủ mọi `SCR`, tập `C-id` khớp hai chiều, bảng lệch prototype ↔ SRS không còn
> dòng Tồn tại/Hành vi chưa quyết** · quy ước chung tham chiếu `UICONV_<PROJECT>`.
---
@@ -47,7 +49,8 @@ quay lại có giữ trạng thái danh sách không, huỷ giữa chừng thì
## 2.3 SCR-01 — <Tên màn hình>
**Wireframe:** `WF_SCR-01.png` *(bố cục — hành vi xem bảng bên dưới)*
**Bố cục:** `WF_<US>_v1.0.md` §3.x · `WF_<US>_v1.0.html#SCR-01` · prototype: *(frame/trang,
hoặc "✏️ BA tự dựng")*. Hành vi xem bảng bên dưới, không suy từ hình (W11).
### 2.3.1 Bảng thành phần
@@ -147,9 +150,12 @@ lỗi trải nghiệm nghiêm trọng và rất hay xảy ra khi spec không nó
### 2.3.6 Trạng thái rỗng, đang tải, lỗi
*Cách trình bày (skeleton/spinner/banner) theo `UICONV` §6 — ở đây chỉ ghi **text riêng** cho
đối tượng của màn hình này và nút hành động. Trông thế nào, ở vùng nào: `WF` §3.x.3.*
| Trạng thái | Hiển thị gì | Text nguyên văn | Nút hành động |
|---|---|---|---|
| Đang tải | | | |
| Đang tải | theo UICONV §6 | | |
| **Chưa có dữ liệu nào** | | "Chưa có bản ghi nào. Tạo bản ghi đầu tiên?" | ✅ Tạo mới |
| **Bộ lọc không khớp** | | "Không tìm thấy kết quả phù hợp với bộ lọc." | ✅ Xoá lọc |
| Lỗi tải dữ liệu | | | ✅ Thử lại |

View File

@@ -6,8 +6,8 @@
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification) |
| **Status** | 🟡 Draft |
| **Approved by** | PO: — · Tech Lead: — · QA: — |
| **Source** | BACKLOG_… v1.0 · BR_… v1.0 · RBAC_… v1.0 · IMPACT_… v1.0 |
| **Approved by** | PO: — · Tech Lead: — · QA: — · Designer: — *(chỉ khi PRODUCT = screen)* |
| **Source** | BACKLOG_… v1.0 · BR_… v1.0 · RBAC_… v1.0 · IMPACT_… v1.0 · UICONV_… v1.0 *(screen)* |
| **Scope** | US-0nn |
| **Profile** | `screen · brownfield · standard` *(PRODUCT · LIFECYCLE · RIGOR)* |
| **Biến thể PART 2** | `srs-part2/screen.md` |
@@ -21,8 +21,10 @@
> **Quy ước đọc tài liệu này** *(quy tắc W11 — chỉ áp dụng khi `PRODUCT = screen`)*
> Bảng thành phần quyết định **một phần tử có tồn tại hay không** và **nó hành xử thế nào**.
> Wireframe quyết định **nó nằm ở đâu, to bằng nào**. Khi hai thứ mâu thuẫn: bảng thắng về
> sự tồn tại và hành vi, hình thắng về bố cục — và mâu thuẫn đó phải được báo cho BA.
> `WF_<US>.md` + `.html` (và prototype nó tham chiếu) quyết định **nó nằm ở đâu, to bằng nào,
> trông ra sao ở từng trạng thái**. `UICONV_<PROJECT>.md` quyết định **quy ước chung** (phân
> trang, toast, định dạng) — tài liệu này chỉ ghi phần khác. Khi mâu thuẫn: bảng thắng về sự
> tồn tại và hành vi, WF thắng về bố cục — và mâu thuẫn đó phải có dòng ở `WF` §5.
---
@@ -199,6 +201,8 @@ nội dung cảnh báo vận hành** — đặc tả ở PART 2 của biến th
| 8 | Không còn OQ mở ảnh hưởng hành vi | | |
| 9 | Không còn `TBD` trong bảng đặc tả PART 2 và bảng mã lỗi | | |
| 10 | Đã áp đúng bảng "Bớt ở light" / "Thêm ở strict" theo `RIGOR` | | |
| 11 | *(screen)* `WF` có cho mọi `SCR`, tập `C-id` khớp hai chiều, §5 không còn lệch Tồn tại/Hành vi chưa quyết | | |
| 12 | *(screen)* Quy ước chung tham chiếu `UICONV`; chỗ khác có dòng ở `UICONV` §12 | | |
**Quét bắt buộc trước khi nộp:**

View File

@@ -0,0 +1,211 @@
# UICONV — Quy ước giao diện — <PROJECT>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification, activity `uiconv`) |
| **Status** | 🟡 Draft |
| **Approved by** | Designer: — · PO: — |
| **Source** | <prototype / design system / SAD §7 / GLOBAL_UI_CONVENTION của dự án / DEC-nn> |
| **Scope** | Toàn project — mọi `SRS` và `WF` có `PRODUCT = screen` |
| **Profile** | `screen · … · …` |
## Change Log
| Version | Date | Người sửa | Thay đổi | CR |
|---|---|---|---|---|
| 1.0 | | | Bản đầu | — |
> **Vai trò của tài liệu này.** Một project có nhiều `SRS`, mỗi lần chạy tối đa 3 US. Không có
> tài liệu này thì mỗi `SRS` tự đặt một kiểu phân trang, một vị trí toast, một định dạng ngày.
> `SRS` và `WF` **tham chiếu** mục ở đây thay vì định nghĩa lại; muốn lệch phải ghi vào §12.
>
> **Thứ tự ưu tiên khi lệch:** Design system của dự án (nếu có) > Prototype đã duyệt > `UICONV`
> > `SRS` từng US. `SRS` chỉ được ghi khác `UICONV` khi có dòng ở §12.
---
## 0. Nguồn
| | |
|---|---|
| **Design system** | *(tên + version + link, hoặc "Chưa có — dev chọn theo NFR, quy ước ở đây là ràng buộc tối thiểu")* |
| **Prototype tham chiếu** | *(đường dẫn / URL / mục SAD, phiên bản, ngày)* |
| **Guideline sẵn có của dự án** | *(vd: GLOBAL_UI_CONVENTION.md — nếu có thì file này chỉ bổ khuyết, không chép lại)* |
| **Ai sở hữu quy ước** | BA đề xuất · **Designer duyệt** · PO duyệt phần text/giọng văn |
| **Nền tảng** | Web desktop / web responsive / app di động — *(theo BRIEF)* |
## 1. Khung layout
| | |
|---|---|
| **Grid** | *(số cột · gutter px · max-width px)* |
| **Header cố định** | Có / Không — chứa: *(logo, tìm kiếm toàn cục, ngôn ngữ, tài khoản…)* |
| **Sidebar / menu** | Trái cố định / thu gọn được / không có |
| **Footer** | Có / Không — chứa: |
| Breakpoint | Khoảng (px) | Sidebar | Bảng dữ liệu | Form |
|---|---|---|---|---|
| Desktop | ≥ 1024 | Hiện | Bảng | 2 cột |
| Tablet | 768–1023 | Thu gọn icon | Bảng cuộn ngang | 1 cột |
| Mobile | < 768 | Ẩn sau nút menu | **Card** | 1 cột |
## 2. Điều hướng
| Câu hỏi | Quy ước |
|---|---|
| Quay lại từ màn hình chi tiết/form | Nút Back **và** breadcrumb — cả hai |
| Quay lại có giữ trạng thái danh sách (trang, bộ lọc, sắp xếp)? | ✅ Giữ, qua query string |
| Vào bằng URL trực tiếp với id không tồn tại | Trang trạng thái "Không tìm thấy" + nút về danh sách |
| Vào bằng URL trực tiếp không có quyền | Trang "Không có quyền" + nút về trang chủ; **không** lộ tên bản ghi |
| Rời màn hình khi form đang dở | Modal xác nhận "Rời trang? Thay đổi chưa lưu sẽ mất." |
| Menu với chức năng không có quyền | ❌ Ẩn (W7) |
| Mở chi tiết | Cùng tab, đổi URL |
## 3. Danh sách
| | |
|---|---|
| **Kiểu phân trang** | Offset (`page`, `size`) / Cursor / Cuộn vô hạn |
| **Số dòng mỗi trang** | Mặc định … · tuỳ chọn … |
| **Sắp xếp mặc định** | *(vd: ngày tạo giảm dần)* |
| **Bấm vào dòng** | Mở chi tiết / Chỉ nút "Chi tiết" mới mở |
| **Cột hành động** | Cột cuối, icon + tooltip / Menu ⋯ |
| **Chọn nhiều dòng** | Checkbox cột đầu, thanh hành động hàng loạt xuất hiện phía trên |
| **Tìm kiếm** | Gõ rồi Enter / Gợi ý sau khi ngừng gõ … ms |
| **Bộ lọc** | Thanh ngang trên bảng (desktop) · nút "Lọc" mở bottom-sheet (mobile) |
| **Cột trên mobile** | Bảng → card; card hiện tối đa … trường: *(liệt kê thứ tự ưu tiên chung)* |
## 4. Form
| | |
|---|---|
| **Vị trí nút** | Lưu (nút chính) **bên phải**, Huỷ (nút phụ) bên trái nút Lưu |
| **Đánh dấu bắt buộc** | Dấu `*` màu cảnh báo sau nhãn |
| **Thời điểm validate** | Khi rời field (blur) **và** khi bấm Lưu |
| **Vị trí message lỗi** | Ngay dưới field, thay chỗ hint; field viền cảnh báo |
| **Lỗi nhiều field** | Focus vào field lỗi đầu tiên, cuộn tới nó |
| **Lỗi từ server không gắn field** | Banner đầu form |
| **Sau lỗi mạng/5xx** | 🔴 **Giữ nguyên dữ liệu đã nhập**, mở lại nút Lưu |
| **Đang gửi** | Khoá toàn form, spinner trong nút Lưu, nhãn đổi "Đang lưu…" |
| **Huỷ khi có thay đổi** | Modal xác nhận |
| **Nhãn field** | Trên field (không đặt cạnh trái) |
| **Nút Lưu khi form chưa hợp lệ** | Bấm được, hiện lỗi / 🔒 disable — *(chọn một)* |
## 5. Phản hồi hệ thống
| Loại | Dùng khi | Vị trí | Thời lượng | Có nút đóng |
|---|---|---|---|---|
| **Toast thành công** | Lưu/xoá thành công | Góc trên phải (desktop) · trên cùng (mobile) | 3 s tự đóng | ✅ |
| **Toast lỗi** | Lỗi không gắn field, có thể thử lại | Như trên | Không tự đóng | ✅ |
| **Modal xác nhận** | Hành động không hoàn tác được (xoá, gửi duyệt) | Giữa màn | — | Nút chính bên **phải**; hành động nguy hiểm dùng nút nguy hiểm |
| **Inline dưới field** | Lỗi validation | Dưới field | Tới khi sửa | — |
| **Banner trong vùng** | Lỗi tải một vùng dữ liệu | Trong vùng đó | Tới khi thử lại | Nút Thử lại |
| **Trang trạng thái** | 404 / 403 / lỗi toàn trang | Toàn trang | — | Nút về danh sách/trang chủ |
## 6. Trạng thái chuẩn
| Trạng thái | Hiển thị | Text mặc định (VI) | Text (EN) | Nút |
|---|---|---|---|---|
| Đang tải danh sách | Skeleton … dòng | — | — | — |
| Đang tải chi tiết/form | Skeleton theo bố cục | — | — | — |
| Đang tải cục bộ (dropdown, tính lại) | Spinner nhỏ tại chỗ | — | — | — |
| **Rỗng — chưa có dữ liệu nào** | Minh hoạ + câu dẫn + nút chính | "Chưa có <đối tượng> nào." | | Tạo mới |
| **Rỗng — bộ lọc không khớp** | Câu dẫn + nút phụ *(không minh hoạ, để khác loại trên)* | "Không tìm thấy kết quả phù hợp với bộ lọc." | | Xoá lọc |
| Lỗi tải | Banner trong vùng | "Không tải được dữ liệu." | | Thử lại |
| Không có quyền | Trang trạng thái | "Bạn không có quyền truy cập nội dung này." | | Về trang chủ |
| Không tìm thấy | Trang trạng thái | "Không tìm thấy nội dung yêu cầu." | | Về danh sách |
`SRS` §2.3.6 chỉ ghi **phần khác** với bảng này (vd: câu dẫn riêng cho đối tượng), còn lại tham chiếu.
## 7. Ẩn · khoá · read-only *(quy tắc W7)*
| Trạng thái | Trình bày | Bắt buộc kèm |
|---|---|---|
| `❌ ẩn` | Không render | — |
| `🔒 disable` | Mờ 45%, con trỏ mặc định | **Tooltip nêu lý do** khi hover/focus |
| `👁 read-only` | Giá trị dạng text, không viền ô nhập, nhãn giữ nguyên | — |
## 8. Định dạng hiển thị
*`SRS` §4.3 tham chiếu bảng này; chỉ ghi khác khi có dòng ở §12.*
| Loại | Định dạng | Ví dụ | Ghi chú |
|---|---|---|---|
| Ngày | `dd/MM/yyyy` | 30/08/2026 | |
| Ngày giờ | `dd/MM/yyyy HH:mm` | 30/08/2026 14:05 | **Múi giờ hiển thị:** … *(theo người dùng / cố định UTC+7)* |
| Lưu trữ thời gian | ISO-8601 UTC | | Khớp `API` §1 |
| Số | `#,##0` | 1.234.567 | Dấu phân cách: … |
| Tiền | `#,##0 ₫` | 1.234.567 ₫ | Làm tròn: … · đa tiền tệ: … |
| Phần trăm | `0,0 %` | 12,5 % | |
| Rỗng / null | `—` | | Thống nhất toàn hệ thống |
| Số lớn (id) | Hiển thị nguyên văn chuỗi | | Không format |
## 9. Ngôn ngữ và giọng văn
| | |
|---|---|
| **Ngôn ngữ hỗ trợ** | VI (mặc định) · EN · … — thiếu bản dịch ⇒ hiện **khoá** hay **VI**? *(chọn một)* |
| **Xưng hô** | "bạn" / không xưng hô |
| **Thông điệp lỗi** | Nói **vì sao** và **làm gì tiếp**; không mã kỹ thuật; kết thúc bằng dấu chấm |
| **Nhãn nút** | Động từ, viết hoa chữ đầu: "Tạo mới", "Lưu", "Huỷ" |
| **Tiêu đề màn hình** | Danh từ: "Danh sách cửa hàng" |
| **Độ dài nhãn tối đa** | … ký tự — để không vỡ ở ngôn ngữ dài (KO/DE) |
| **Khoá i18n** | `<scr>.<thành phần>.<thuộc tính>` — vd `scr01.search.ph` |
## 10. Dãy mã lỗi
| Dãy | Module / phạm vi | Số kế tiếp chưa dùng | Ai cấp |
|---|---|---|---|
| `E-<DOMAIN>-` | | 0001 | BA |
## 11. Từ vựng thành phần
*`WF` §3.x.2 cột "Kiểu trình bày" chỉ dùng từ ở cột đầu. Cột thứ hai map sang design system khi có.*
| Kiểu trình bày | Component trong design system | Dùng khi |
|---|---|---|
| Nút chính | | Một hành động chính mỗi màn hình |
| Nút phụ | | Huỷ, hành động thứ cấp |
| Nút nguy hiểm | | Xoá, huỷ đơn — luôn kèm modal xác nhận |
| Liên kết | | Điều hướng trong text |
| Ô nhập | | |
| Ô nhập số | | |
| Chọn 1 | | ≤ 7 lựa chọn: radio · > 7: dropdown có tìm |
| Chọn nhiều | | |
| Chọn ngày / khoảng ngày | | |
| Công tắc | | Bật/tắt có hiệu lực ngay |
| Bảng | | Desktop |
| Card | | Mobile, hoặc dữ liệu ít cột |
| Badge trạng thái | | Trạng thái vòng đời (BR §3) |
| Tab | | Nhóm nội dung cùng cấp trong một màn hình |
| Modal | | Form ngắn ≤ 6 field, xác nhận |
| Drawer | | Form dài, chi tiết nhanh |
| Bottom-sheet | | Bộ lọc trên mobile |
| Toast | | §5 |
| Skeleton | | §6 |
## 12. Ngoại lệ đã duyệt
*`SRS`/`WF` lệch quy ước ⇒ phải có dòng ở đây. Không có dòng ⇒ lệch là lỗi, không phải quyết định.*
| # | Tài liệu | Mục lệch | Quy ước | Thực tế | Vì sao | Ai duyệt | Ngày |
|---|---|---|---|---|---|---|---|
## 13. Open Questions
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì |
|---|---|---|---|---|
---
## Tự chấm
| # | Tiêu chí | ☐/✅ |
|---|---|---|
| 1 | Mọi ô "chọn một" đã chọn, không còn dấu `/` giữa các phương án | |
| 2 | §6 có hai trạng thái rỗng **trông khác nhau** | |
| 3 | §8 có múi giờ hiển thị và quy tắc làm tròn tiền | |
| 4 | §11 mọi kiểu trình bày dùng trong `WF` hiện có đều có dòng | |
| 5 | Có chữ ký Designer (hoặc ghi rõ dự án không có Designer ⇒ PO ký thay, `DEC-nn`) | |

View File

@@ -0,0 +1,206 @@
<!--
WF_<US-id>_v1.0.html — wireframe low-fi tự chứa, sinh bởi skill ba-3-specification (activity wf).
QUY TẮC SINH FILE NÀY:
· Mỗi màn hình SCR-xx → một <section class="screen" id="SCR-xx">, theo đúng bảng WF §3.x.1 (vùng) và §3.x.2 (thành phần).
· Mỗi vùng Zn → <div class="zone" data-zone="Zn"> có nhãn vùng.
· Mỗi thành phần C-id → một phần tử có data-c="Cnn" và class theo kiểu trình bày (.btn-primary, .btn-secondary, .input, .select, .table, .card, .badge, .link, .label).
Tập data-c phải KHỚP HAI CHIỀU với bảng thành phần SRS §2.3.1 — đây là thứ ba-traceability kiểm.
· Trạng thái (WF §3.x.3) → các khối data-state="default|loading|empty-none|empty-filter|error|submitting" trong cùng vùng; thanh công cụ ở đầu file bật/tắt.
· Thành phần CHỈ CÓ Ở PROTOTYPE, chưa có trong SRS (WF §5 loại Tồn tại) → vẽ đúng chỗ prototype đặt nó, bọc <span class="proto-only" data-ref="WF §5 #n">…</span>,
KHÔNG gán data-c. Khung đỏ đứt để PO thấy ngay đang thiếu quyết định gì; PO quyết xong ⇒ hoặc gán C-id (SRS bổ sung) hoặc xoá khỏi WF.
· Bố cục nhiều cột (vd danh sách + sidebar tổng kết) → bọc các vùng trong <div class="cols">; ở khung nhìn mobile lớp này tự xếp chồng 1 cột (đúng WF §3.x.4). Không tự viết grid riêng.
· Không tô màu thương hiệu, không font riêng — đây là bố cục, không phải visual. Màu duy nhất được dùng: thang xám + một màu nhấn cho nút chính.
· Text hiển thị lấy nguyên văn từ SRS §4.2 (khoá i18n ghi ở data-key).
· Sửa nội dung mẫu bên dưới; giữ nguyên <style> và <script> để mọi WF trong dự án trông và thao tác giống nhau.
-->
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WF — <US-id> — <Tên US></title>
<style>
:root{--ink:#222;--mute:#777;--line:#bbb;--fill:#eee;--fill2:#f7f7f7;--accent:#3b6ea5;--danger:#b23a3a;--bg:#fafafa}
*{box-sizing:border-box}
body{margin:0;font:14px/1.45 system-ui,Segoe UI,Roboto,sans-serif;color:var(--ink);background:var(--bg)}
header.toolbar{position:sticky;top:0;z-index:9;background:#fff;border-bottom:1px solid var(--line);padding:8px 16px;display:flex;gap:16px;align-items:center;flex-wrap:wrap}
header.toolbar h1{font-size:15px;margin:0 16px 0 0}
header.toolbar label{font-size:12px;color:var(--mute)}
header.toolbar select{font:inherit}
.legend{font-size:12px;color:var(--mute);margin-left:auto}
main{padding:24px 16px;display:grid;gap:40px;justify-items:center}
.screen{width:100%;max-width:1200px;background:#fff;border:1px solid var(--line);border-radius:6px;overflow:hidden}
.screen>h2{margin:0;padding:8px 12px;font-size:13px;font-weight:600;background:var(--fill2);border-bottom:1px dashed var(--line);color:var(--mute)}
.screen>h2 small{font-weight:400;margin-left:8px}
.body{padding:12px;display:grid;gap:12px}
.zone{border:1px dashed var(--line);border-radius:4px;padding:10px;position:relative;background:#fff;min-height:44px}
.zone::before{content:attr(data-zone) " · " attr(data-name);position:absolute;top:-8px;left:8px;background:#fff;padding:0 4px;font-size:10px;color:var(--mute);letter-spacing:.02em}
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.row.right{justify-content:flex-end}
.row.between{justify-content:space-between}
[data-c]{position:relative}
[data-c]::after{content:attr(data-c);position:absolute;top:-9px;right:-4px;font-size:9px;background:var(--ink);color:#fff;padding:0 4px;border-radius:3px;line-height:14px;opacity:.85}
.label{font-weight:600;font-size:16px}
.btn{display:inline-block;padding:6px 14px;border:1px solid var(--ink);border-radius:4px;background:#fff;cursor:default;white-space:nowrap}
.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
.btn-secondary{background:#fff}
.btn-danger{border-color:var(--danger);color:var(--danger)}
.btn[aria-disabled="true"]{opacity:.45;border-style:dashed}
.link{color:var(--accent);text-decoration:underline}
.input,.select{display:inline-block;min-width:160px;padding:6px 8px;border:1px solid var(--line);border-radius:4px;background:#fff;color:var(--mute)}
.select::after{content:" ▾";float:right}
.field{display:grid;gap:4px}
.field>span{font-size:12px}
.field>span .req{color:var(--danger)}
.hint{font-size:12px;color:var(--mute)}
.table{width:100%;border-collapse:collapse}
.table th,.table td{border:1px solid var(--line);padding:6px 8px;text-align:left;font-size:13px}
.table th{background:var(--fill2)}
.card{border:1px solid var(--line);border-radius:4px;padding:10px;background:#fff}
.badge{display:inline-block;padding:0 8px;border:1px solid var(--line);border-radius:10px;font-size:12px;background:var(--fill2)}
.img{background:repeating-linear-gradient(45deg,var(--fill),var(--fill) 6px,var(--fill2) 6px,var(--fill2) 12px);border:1px solid var(--line);min-height:80px;display:grid;place-items:center;color:var(--mute);font-size:12px}
.skeleton{height:14px;background:linear-gradient(90deg,var(--fill),var(--fill2),var(--fill));border-radius:3px;margin:8px 0}
.state{padding:24px;text-align:center;color:var(--mute);border:1px dashed var(--line);border-radius:4px;display:grid;gap:10px;justify-items:center}
.state.error{border-color:var(--danger);color:var(--danger)}
.state .illus{width:80px;height:60px}
.spinner{display:inline-block;width:14px;height:14px;border:2px solid #fff;border-top-color:transparent;border-radius:50%;vertical-align:-2px;margin-right:6px}
.proto-only{display:inline-block;border:1.5px dashed var(--danger);border-radius:4px;padding:4px 8px;background:#fdf3f3;color:var(--danger);position:relative}
.proto-only::before{content:"chỉ ở prototype — " attr(data-ref);font-size:10px;border:1px solid var(--danger);border-radius:3px;padding:0 4px;margin-right:6px;vertical-align:1px}
.modal{max-width:520px;margin:0 auto;border:1px solid var(--ink);border-radius:6px;background:#fff;box-shadow:0 0 0 2000px rgba(0,0,0,.25)}
.modal .body{padding:16px}
[data-state]{display:none}
[data-state="default"]{display:block}
body[data-view="mobile"] .screen{max-width:390px}
body[data-view="mobile"] .m-hide{display:none!important}
body[data-view="mobile"] .body,body[data-view="mobile"] .cols{grid-template-columns:1fr!important;display:grid}
.cols{display:grid;grid-template-columns:2fr 1fr;gap:12px;align-items:start}
body[data-view="mobile"] .m-stack{flex-direction:column;align-items:stretch}
body[data-view="desktop"] .d-hide{display:none!important}
body[data-view="mobile"] .table{display:none}
body[data-view="mobile"] .cards{display:grid!important;gap:8px}
.cards{display:none}
footer.note{padding:16px;font-size:12px;color:var(--mute);text-align:center}
</style>
</head>
<body data-view="desktop">
<header class="toolbar">
<h1>WF — <US-id> · <Tên US></h1>
<label>Khung nhìn
<select id="view"><option value="desktop">Desktop ≥1024</option><option value="mobile">Mobile &lt;768</option></select>
</label>
<label>Trạng thái
<select id="state">
<option value="default">Mặc định (có dữ liệu)</option>
<option value="loading">Đang tải</option>
<option value="empty-none">Rỗng — chưa có dữ liệu nào</option>
<option value="empty-filter">Rỗng — bộ lọc không khớp</option>
<option value="error">Lỗi tải dữ liệu</option>
<option value="submitting">Đang gửi</option>
</select>
</label>
<span class="legend">Nhãn đen = C-id trong SRS §2.3.1 · khung đứt xám = vùng Zn trong WF §3.x.1 · <b style="color:var(--danger)">khung đứt đỏ</b> = chỉ có ở prototype, chưa có trong SRS (WF §5) · hành vi xem SRS, không suy từ hình</span>
</header>
<main>
<!-- ============================ SCR-01 (mẫu: màn hình danh sách) ============================ -->
<section class="screen" id="SCR-01">
<h2>SCR-01 · <Tên màn hình> <small>Danh sách · vào từ Menu · vai trò: ROLE-01, ROLE-02</small></h2>
<div class="body">
<div class="zone" data-zone="Z1" data-name="Header màn hình">
<div class="row between">
<span class="label" data-c="C01" data-key="scr01.title">Tiêu đề màn hình</span>
<span class="btn btn-primary" data-c="C02" data-key="btn.create">Tạo mới</span>
</div>
</div>
<div class="zone" data-zone="Z2" data-name="Thanh lọc">
<div class="row m-stack">
<span class="input" data-c="C03" data-key="scr01.search.ph">Nhập mã hoặc tên…</span>
<span class="select m-hide" data-c="C04" data-key="scr01.filter.status">Trạng thái: Tất cả</span>
<span class="btn btn-secondary d-hide" data-key="btn.filter">Lọc</span>
</div>
</div>
<div class="zone" data-zone="Z3" data-name="Dữ liệu">
<div data-state="default">
<table class="table" data-c="C05">
<thead><tr><th>Mã</th><th>Tên</th><th>Trạng thái</th><th>Ngày tạo</th><th></th></tr></thead>
<tbody>
<tr><td>STR-001</td><td>Cửa hàng A</td><td><span class="badge">Nháp</span></td><td>30/08/2026</td><td><span class="link">Chi tiết</span></td></tr>
<tr><td>STR-002</td><td>Cửa hàng B</td><td><span class="badge">Đã duyệt</span></td><td>29/08/2026</td><td><span class="link">Chi tiết</span></td></tr>
</tbody>
</table>
<div class="cards" data-c="C05">
<div class="card"><div class="row between"><b>STR-001</b><span class="badge">Nháp</span></div><div>Cửa hàng A</div><div class="hint">30/08/2026</div></div>
<div class="card"><div class="row between"><b>STR-002</b><span class="badge">Đã duyệt</span></div><div>Cửa hàng B</div><div class="hint">29/08/2026</div></div>
</div>
</div>
<div data-state="loading"><div class="skeleton"></div><div class="skeleton" style="width:85%"></div><div class="skeleton" style="width:92%"></div></div>
<div data-state="empty-none"><div class="state"><div class="img illus">minh hoạ</div><span data-key="empty.none">Chưa có bản ghi nào. Tạo bản ghi đầu tiên?</span><span class="btn btn-primary" data-key="btn.create">Tạo mới</span></div></div>
<div data-state="empty-filter"><div class="state"><span data-key="empty.filter">Không tìm thấy kết quả phù hợp với bộ lọc.</span><span class="btn btn-secondary" data-key="btn.clearFilter">Xoá lọc</span></div></div>
<div data-state="error"><div class="state error"><span data-key="error.load">Không tải được dữ liệu.</span><span class="btn btn-secondary" data-key="btn.retry">Thử lại</span></div></div>
</div>
<div class="zone" data-zone="Z4" data-name="Phân trang">
<div class="row between">
<span class="proto-only" data-ref="WF §5 #1">Nút "Xuất Excel" — prototype có, BACKLOG không có US ⇒ PO quyết</span>
<span class="row" data-c="C06"><span class="btn btn-secondary">‹</span><span>1 / 7</span><span class="btn btn-secondary">›</span><span class="select m-hide">20 dòng</span></span>
</div>
</div>
</div>
</section>
<!-- ============================ SCR-03 (mẫu: form trong modal) ============================ -->
<section class="screen" id="SCR-03">
<h2>SCR-03 · <Tên form> <small>Modal · mở từ C02 · vai trò: ROLE-01, ROLE-02</small></h2>
<div class="body">
<div class="modal">
<div class="body">
<div class="zone" data-zone="Z1" data-name="Tiêu đề modal">
<div class="row between"><span class="label" data-c="C01" data-key="scr03.title">Tạo mới</span><span class="link" data-c="C08" data-key="btn.close">✕</span></div>
</div>
<div class="zone" data-zone="Z2" data-name="Form">
<div class="body">
<label class="field"><span data-key="f01.label">Mã <b class="req">*</b></span><span class="input" data-c="F01" data-key="f01.ph">3–20 ký tự, A–Z 0–9 -</span><span class="hint" data-key="f01.hint">Không trùng với mã đã có</span></label>
<label class="field"><span data-key="f02.label">Loại <b class="req">*</b></span><span class="select" data-c="F02">Thường</span></label>
<label class="field"><span data-key="f03.label">Tên</span><span class="input" data-c="F03" data-key="f03.ph">Tên hiển thị</span></label>
</div>
</div>
<div class="zone" data-zone="Z3" data-name="Hành động">
<div class="row right">
<span class="btn btn-secondary" data-c="C07" data-key="btn.cancel">Huỷ</span>
<span data-state="default" style="display:inline"><span class="btn btn-primary" data-c="C09" data-key="btn.save">Lưu</span></span>
<span data-state="submitting"><span class="btn btn-primary" data-c="C09" aria-disabled="true"><i class="spinner"></i>Đang lưu…</span></span>
</div>
</div>
</div>
</div>
<p class="hint">Field F01–F03 tham chiếu bảng field SRS §2.3.3. Thất bại ⇒ giữ nguyên dữ liệu đã nhập (AC-09), lỗi hiện dưới field theo UICONV §4.</p>
</div>
</section>
</main>
<footer class="note">Wireframe low-fi · bố cục và trạng thái · không phải thiết kế visual · nguồn hành vi: SRS_<US-id> · nguồn quy ước: UICONV_<PROJECT></footer>
<script>
(function(){
var view=document.getElementById('view'),state=document.getElementById('state');
function apply(){
document.body.setAttribute('data-view',view.value);
var s=state.value;
document.querySelectorAll('[data-state]').forEach(function(el){
var on=el.getAttribute('data-state')===s || (s==='submitting'&&el.getAttribute('data-state')==='default'&&!el.closest('.zone[data-name="Hành động"]'));
el.style.display=on?(el.tagName==='SPAN'?'inline':'block'):'none';
});
}
view.addEventListener('change',apply);state.addEventListener('change',apply);apply();
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,204 @@
# WF — Wireframe & bố cục — <US-id>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification, activity `wf`) |
| **Status** | 🟡 Draft |
| **Approved by** | Designer: — · PO: — |
| **Source** | SRS_… v1.0 · UICONV_<PROJECT> v1.0 · <prototype: đường dẫn/URL + phiên bản> |
| **Scope** | US-0nn · SCR-01, SCR-02, … |
| **Nguồn bố cục** | 🎨 **Prototype tham chiếu** `<path>` / ✏️ **BA tự dựng — chờ Designer duyệt** |
| **Tệp kèm** | `WF_<US-id>_v1.0.html` *(low-fi render, mở bằng trình duyệt)* |
## Change Log
| Version | Date | Người sửa | Thay đổi | CR |
|---|---|---|---|---|
| 1.0 | | | Bản đầu | — |
> **Quy ước đọc** *(quy tắc W11)*. Bảng thành phần trong `SRS` §2.3.1 quyết định **một phần tử
> có tồn tại không** và **hành xử thế nào**. Tài liệu này và tệp HTML kèm theo quyết định **nó
> nằm ở đâu, thứ tự nào, to bằng nào, trông ra sao ở từng trạng thái**. Mâu thuẫn ⇒ SRS thắng
> về tồn tại/hành vi, WF thắng về bố cục — và mâu thuẫn đó **phải được ghi ở §5**, không im lặng.
---
## 0. Nguồn bố cục và độ tin cậy
| | |
|---|---|
| **Prototype tham chiếu** | *(đường dẫn / URL Figma / thư mục ảnh / mục SAD §7 · phiên bản · ngày · ai cung cấp)* |
| **Định dạng** | Figma / HTML tương tác / ảnh tĩnh / mô tả văn bản / **không có** |
| **Mức phủ** | n / m màn hình của US này có trong prototype |
| **Design system** | *(tên + version, hoặc "chưa có — dùng `UICONV` §11")* |
| **Độ tin cậy** | 🎨 Prototype đã được PO/Design duyệt ⇒ WF là **bản ghi lại** · ✏️ Không có prototype ⇒ WF là **đề xuất của BA**, Designer phải duyệt trước G3 |
🔴 **Có prototype thì prototype là nguồn sự thật về bố cục**, WF chỉ ghi lại và đối chiếu với
SRS. Không có prototype thì WF là đề xuất, và dòng `Approved by: Designer` là **bắt buộc** khi
`PRODUCT = screen` ở mức `standard` trở lên.
## 1. Bản đồ prototype ↔ màn hình
*Một dòng cho mỗi `SCR` trong `SRS` §2.1. Màn hình không có trong prototype ⇒ ghi rõ, và mục
§3 tương ứng do BA tự dựng.*
| SCR | Tên màn hình | Có trong prototype | Frame / trang / mục trong prototype | Mức khớp với SRS | Ghi chú |
|---|---|---|---|---|---|
| SCR-01 | | ✅ / ❌ | *(vd: Figma frame "Cart / Desktop", hoặc SAD §7.1.1 SCR-04)* | ✅ khớp · 🟠 khác bố cục · 🔴 khác tồn tại/hành vi | → §5 nếu 🟠/🔴 |
## 2. Khung bố cục chung
*Lấy từ `UICONV` §1 nếu đã có. Chỉ ghi lại phần **khác** hoặc **bổ sung** cho US này.*
| | |
|---|---|
| **Grid** | *(số cột · gutter · max-width, đơn vị px — nguồn: UICONV / prototype)* |
| **Vùng cố định** | Header · Sidebar · Footer — *(có / không, nội dung, tham chiếu UICONV §1)* |
| Breakpoint | Khoảng (px) | Bố cục | Nguồn |
|---|---|---|---|
| Desktop | ≥ 1024 | | UICONV §1 |
| Tablet | 768–1023 | | |
| Mobile | < 768 | | |
---
## 3. Bố cục từng màn hình
*Lặp mục này cho **mọi** `SCR` của US, kể cả modal và trang lỗi.*
### 3.1 SCR-01 — <Tên màn hình>
| | |
|---|---|
| **Loại** | Danh sách / Chi tiết / Form / Modal / Trang trạng thái |
| **Prototype** | *(frame/trang cụ thể, hoặc "✏️ BA tự dựng")* |
| **HTML** | `WF_<US-id>_v1.0.html#SCR-01` |
| **Thành phần (SRS §2.3.1)** | C01 … C0n — *(số lượng phải khớp bảng thành phần)* |
#### 3.1.1 Bảng vùng
*Bảng này thay cho hình vẽ: mỗi vùng là một khối trên màn hình. Tệp HTML render đúng theo bảng này.*
| Vùng | Tên | Vị trí desktop | Vị trí mobile | Chứa thành phần | Chiều rộng | Ưu tiên hiển thị |
|---|---|---|---|---|---|---|
| Z1 | Header màn hình | Hàng 1, full | Hàng 1 | C01 (tiêu đề), C02 (nút Tạo mới) | 100% | 1 |
| Z2 | Thanh lọc | Hàng 2, full | Hàng 2, thu gọn thành nút "Lọc" | C03, C04 | 100% | 2 |
| Z3 | Bảng dữ liệu | Hàng 3, full | Hàng 3, dạng card | C05 | 100% | 1 |
| Z4 | Phân trang | Hàng 4, phải | Hàng 4, giữa | C06 | auto | 3 |
*Ưu tiên hiển thị: 1 = luôn hiện · 2 = có thể gộp/thu gọn ở mobile · 3 = có thể ẩn sau nút.*
#### 3.1.2 Vị trí thành phần
*Mọi `C-id` trong `SRS` §2.3.1 phải có ở đây. `C-id` có ở đây mà không có trong SRS ⇒ lỗi
(thành phần chưa được đặc tả hành vi).*
| C-id | Tên thành phần | Vùng | Thứ tự trong vùng | Kích thước / độ rộng | Kiểu trình bày | Phần tử trong prototype | Khớp |
|---|---|---|---|---|---|---|---|
| C01 | lbl_title | Z1 | 1 | auto | Tiêu đề cấp 1 | | ✅ |
| C02 | btn_create | Z1 | 2 (phải) | auto | **Nút chính** | | ✅ |
| C03 | txt_search | Z2 | 1 | 320px | Ô nhập có icon tìm | | 🟠 |
**Kiểu trình bày** dùng từ vựng của `UICONV` §11 (nút chính / nút phụ / nút nguy hiểm / liên
kết / ô nhập / chọn 1 / chọn nhiều / bảng / card / badge trạng thái …). Không tự đặt tên mới.
#### 3.1.3 Trạng thái hiển thị
*Hành vi và text lấy từ `SRS` §2.3.6 và §4.2. Ở đây chỉ nói **trông thế nào và ở vùng nào**.*
| Trạng thái | Vùng thay đổi | Hiển thị gì | Khoá text (SRS §4.2) | Nút hành động |
|---|---|---|---|---|
| Đang tải lần đầu | Z3 | Skeleton n dòng *(theo UICONV §6)* | — | — |
| **Chưa có dữ liệu nào** | Z3 | Minh hoạ + câu dẫn + nút chính | `empty.none` | C02 Tạo mới |
| **Bộ lọc không khớp** | Z3 | Câu dẫn + nút phụ | `empty.filter` | Xoá lọc |
| Lỗi tải dữ liệu | Z3 | Banner lỗi trong vùng + nút thử lại | `error.load` | Thử lại |
| Đang gửi (form) | Nút gửi | Spinner trong nút, khoá form | — | — |
🔴 Hai trạng thái rỗng **phải trông khác nhau**, không chỉ khác câu chữ.
#### 3.1.4 Responsive
| Breakpoint | Thay đổi so với desktop | Thành phần ẩn / gộp / đổi kiểu |
|---|---|---|
| Tablet | Z2 xuống 2 hàng | — |
| Mobile | Z3 đổi bảng → card; Z2 thu vào nút "Lọc" mở bottom-sheet | C04 gộp vào bottom-sheet |
#### 3.1.5 Tương tác trình bày
*Chỉ phần **trình bày** (hành vi nghiệp vụ đã ở SRS). Mọi con số phải có nguồn — không có ⇒ `OQ`.*
| Thành phần | Sự kiện | Phản hồi trình bày | Ngưỡng / thời lượng | Nguồn |
|---|---|---|---|---|
| C03 txt_search | Gõ | Gợi ý sau khi ngừng gõ | 300 ms | UICONV §4 |
| Toàn màn | Lưu thành công | Toast góc trên phải | 3 s, tự đóng | UICONV §5 |
| C05 bảng | Hover dòng | Đổi nền dòng | — | UICONV §11 |
#### 3.1.6 Thứ tự focus *(màn hình có nhập liệu)*
| Thứ tự | C-id | Ghi chú |
|---|---|---|
| 1 | C03 | Focus mặc định khi vào màn hình |
---
### 3.2 SCR-02 — <Tên màn hình>
*(lặp cấu trúc 3.1)*
---
## 4. Luồng màn hình theo prototype
*Chỉ khi prototype có tương tác. Đối chiếu với sơ đồ điều hướng `SRS` §2.2 — cạnh có trong
prototype mà SRS không có, hoặc ngược lại, ghi vào §5.*
| Từ | Hành động | Đến | Có trong SRS §2.2 | Có trong prototype |
|---|---|---|---|---|
| SCR-01 | Bấm dòng | SCR-02 | ✅ | ✅ |
| SCR-03 | Huỷ giữa chừng | SCR-01 | ✅ | ❌ *(prototype không vẽ nút Huỷ)* → §5 |
## 5. Lệch giữa prototype và SRS
*Bảng bắt buộc, kể cả khi rỗng (ghi "Không phát hiện lệch, đã đối chiếu n màn hình").*
| # | Màn hình | Prototype có | SRS nói | Loại lệch | Bên thắng theo W11 | Xử lý | Ai quyết | Trạng thái |
|---|---|---|---|---|---|---|---|---|
| 1 | SCR-03 | Không có nút Huỷ | C07 btn_cancel tồn tại | Tồn tại | **SRS** | Thêm vào WF, báo Design cập nhật prototype | Designer | ☐ |
| 2 | SCR-01 | Bộ lọc ở sidebar trái | Bảng vùng ghi hàng 2 | Bố cục | **Prototype** | Sửa WF theo prototype | — | ✅ |
| 3 | SCR-01 | Nút "Tạo" | Nhãn "Tạo mới" | Text | **SRS** (BA sở hữu text, W5) | Design đổi nhãn | Designer | ☐ |
Loại lệch: **Tồn tại** · **Hành vi** · **Bố cục** · **Text** · **Trạng thái thiếu**.
Lệch loại *Tồn tại* hoặc *Hành vi* chưa được quyết ⇒ 🔴 **chặn G3**.
## 6. Bàn giao
| | |
|---|---|
| **Designer còn phải làm** | *(visual: màu, typography, icon, minh hoạ trạng thái rỗng, motion… — hoặc "Không: prototype đã phủ toàn bộ")* |
| **Dev dùng WF để** | Dựng layout và chọn component theo §3.x.2; **không** suy ra hành vi từ WF |
| **Không được suy ra từ WF** | Khoảng cách/kích thước chính xác nếu chưa có design system; màu; font |
| **Khi prototype thay đổi** | Design báo BA ⇒ cập nhật §1, §5 và version WF; SRS chỉ đổi nếu lệch loại Tồn tại/Hành vi |
## 7. Open Questions
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì | 🔴 chặn G3? |
|---|---|---|---|---|---|
---
## Tự chấm
| # | Tiêu chí | ☐/✅ | Ghi chú |
|---|---|---|---|
| 1 | Mọi `SCR` trong `SRS` §2.1 có một mục §3 | | |
| 2 | Mọi `C-id` khớp **hai chiều** giữa `SRS` §2.3.1 và WF §3.x.2 | | *chạy: grep -o "C[0-9][0-9]" cả hai file, so tập* |
| 3 | Mỗi màn hình có §3.x.3 với **hai trạng thái rỗng trông khác nhau** | | |
| 4 | Mỗi màn hình web có responsive ≥ 2 breakpoint | | |
| 5 | Mọi con số ở §3.x.5 có nguồn (UICONV / prototype / OQ) | | |
| 6 | §5 đã điền, mọi lệch Tồn tại/Hành vi có người quyết | | |
| 7 | Tệp HTML mở được, mỗi `SCR` một `<section id>`, mỗi thành phần có `data-c` khớp C-id | | |
| 8 | Kiểu trình bày chỉ dùng từ vựng `UICONV` §11 | | |
| 9 | Có chữ ký Designer *(bắt buộc ở `standard`+ khi WF do BA tự dựng)* | | |