Files
Leonard-ThindPad-P50 2c7bcde741 improve BA skill
2026-09-09 06:34:57 +07:00

207 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
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>